CLI reference
tsr <task> [-- <args>...] run a task; args after -- are forwarded tsr --list list the tasks defined in tasks.toml tsr --config edit tasks.toml in an interactive TUI tsr --init create a starter tasks.toml here tsr --help | --version
The first argument is always a task name. Every builtin is a flag, so a task named list or init is never shadowed — tsr list runs your list task.
tsr <task>
Runs the named task. tsr finds the workspace root by walking up to the nearest tasks.toml, validates the config and the dependency subgraph, then executes.
tsr dev tsr build:prod tsr web#build # the 'build' task in package 'web'
No config? It still runs
tasks.toml is optional. With no config file, tsr <task> runs repo-aware by auto-detecting the ecosystem in the current directory (or a parent) and mapping the task to its native runner — so tsr dev becomes npm run dev, tsr build becomes cargo build, and so on, with -- passthrough working as usual.
# in a repo that only has a package.json — no tasks.toml tsr dev # → npm run dev tsr test -- --watch # → npm run test --watch
If neither a tasks.toml nor an ecosystem marker (package.json, Cargo.toml, go.mod, pyproject.toml) is found, tsr exits 64 and points you at tsr --init. A tasks.toml, when present, always wins — there's no fall-through from a defined config to auto-detection, so a mistyped task name stays an error. Package-qualified names (web#build) and the dependency graph need a tasks.toml.
Argument passthrough
Everything after -- is forwarded to the resolved command. If the task defines args, they are prepended before the passthrough:
[tasks.test] run = "vitest" args = ["--color"]
tsr test -- --watch # → vitest --color --watch
tsr --list
Prints the tasks defined in tasks.toml, each with a one-line form descriptor.
Available tasks: build delegate: turbo ci deps: lint, test, build · parallel dev run: vite · dir: apps/web test packages: apps/*
With no tasks.toml, there is nothing declared to list, so --list instead reports the detected package and reminds you that scripts run directly (e.g. tsr dev).
tsr --config
Opens an interactive terminal UI for editing tasks.toml — the intended way to author tasks with all their options instead of hand-editing TOML.
It autosaves: there is no separate write step, no unsaved state, and no "discard changes?" prompt. Applying a task in the form (or confirming a delete) validates the whole config and writes tasks.toml immediately — and since an invalid form is never committed, an autosave can never leave a broken file.
Menu (home) — the TUI opens on a menu of workflows rather than a blank list, so there is always an obvious next step: Add a task, Edit a task, Delegate a task, Delete a task, Preview graph, and Quit.
↑↓move,⏎opens the selected workflow,qquits. Every sub-screen returns here withEsc.Add / Delegate — open the form for a new task. Delegate starts already on the
delegatetype so you land on the backend-bin field.Edit / Delete — open a task picker (the list of defined tasks).
↑↓move,⏎edits (or deletes, with ay/nconfirm) the selected task,gpreviews its graph,Escgoes back to the menu.Form view — fields for the full task model: name, form (
run/delegate/delegate(table)/ auto-detect),dirorpackages,deps,args,parallel,env, andenv_file. Irrelevant fields are hidden based on the chosen form.↑↓/Tabmove,←→/Spacechange a choice/toggle,⏎saves the task,Esccancels. A validation error keeps the form open and shows the problem inline, so nothing is written until it is fixed.⏎is the save key rather thanCtrl+Sdeliberately: editor and IDE terminals grabCtrl+Sfor "save file", and it is XOFF on terminals with flow control on.Ctrl+Sstill works as an alias where the terminal lets it through.Graph view — a read-only, connected dependency tree with each task's dry-run command (what
tsrwould actually execute, resolved by the same precedence:delegate→run→ auto-detect; a deps-only task shows "runs its deps only"). It shows every task rooted at the tasks nothing depends on, or a single task's subtree when opened from a picker. Parallel vs. sequentialdepsare tagged, and undefined deps or cycles in a mid-edit config are flagged inline.↑↓scroll,awiden to all,Esc/gback to the menu.
Edits go through the same format-preserving parser used everywhere else, so comments and unknown keys survive each autosave, and every change is validated before it is written. If no tasks.toml exists yet, --config starts a new one in the current directory — the file appears on your first saved task.
tsr --init
Scaffolds a starter tasks.toml in the current directory: reference comments only, showcasing all three task forms, [workspace], [env] and the dependency graph, with a link back to these docs. It refuses to overwrite an existing file, so it is always safe to run.
It deliberately defines no tasks. Because a present tasks.toml always wins over auto-detection, a scaffolded placeholder task would shadow what your repo already does — tsr dev would stop running your real npm run dev. Uncomment the example you want, or run tsr --config to add tasks interactively. A bare [tasks.<name>] (form 3) re-enables auto-detection for that task.
tsr --help · tsr --version
Print usage and the binary version, respectively.
Exit status
tsr follows a precise exit-code contract: 0 on success, the failing child's exact code on task failure, or 64 for any runner-level error.