CLI reference

TEXT
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.

Shell
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.

Shell
# 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:

TOML
[tasks.test]
run = "vitest"
args = ["--color"]
Shell
tsr test -- --watch    # → vitest --color --watch

tsr --list

Prints the tasks defined in tasks.toml, each with a one-line form descriptor.

TEXT
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, q quits. Every sub-screen returns here with Esc.

  • Add / Delegate — open the form for a new task. Delegate starts already on the delegate type so you land on the backend-bin field.

  • Edit / Delete — open a task picker (the list of defined tasks). ↑↓ move, edits (or deletes, with a y/n confirm) the selected task, g previews its graph, Esc goes back to the menu.

  • Form view — fields for the full task model: name, form (run / delegate / delegate(table) / auto-detect), dir or packages, deps, args, parallel, env, and env_file. Irrelevant fields are hidden based on the chosen form. ↑↓/Tab move, ←→/Space change a choice/toggle, saves the task, Esc cancels. 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 than Ctrl+S deliberately: editor and IDE terminals grab Ctrl+S for "save file", and it is XOFF on terminals with flow control on. Ctrl+S still 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 tsr would actually execute, resolved by the same precedence: delegaterun → 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. sequential deps are tagged, and undefined deps or cycles in a mid-edit config are flagged inline. ↑↓ scroll, a widen to all, Esc/g back 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.