Every subcommand of the roscoe binary, with full flag tables and worked
examples. Where source matters, apps/cli/src/commands/<name>.ts is the
canonical definition.
The compiled binary (bun run compile → apps/cli/dist/roscoe) and the
source-mode runner (bun apps/cli/src/index.ts) expose the same commands.
Examples below assume the binary is on PATH.
| Command | Purpose |
|---|---|
roscoe init |
Scaffold data dirs, run DB migrations, seed example workflows |
roscoe serve |
Start the HTTP server + embedded web UI |
roscoe stop |
Stop the running server for the current ROSCOE_HOME |
roscoe locate |
Print where a running server is, or exit 3 if none |
roscoe mcp |
Start the MCP stdio server (for Claude Code) |
roscoe run |
Run a workflow by id or YAML path |
roscoe list |
List available workflows from project + global sources |
roscoe resume |
Resume a paused run with a chosen transition |
roscoe monitor |
Print the current state of a run |
roscoe history |
List recent runs for the current project |
roscoe validate |
Validate a workflow YAML against the schema |
roscoe version |
Print the installed version + check for a newer release |
roscoe update |
Download + apply the latest release; auto-restart roscoe serve |
roscoe install-mcp |
Register the binary as an MCP server for Claude Code or Desktop |
roscoe register |
Register a project so it shows in the UI project switcher |
roscoe unregister |
Remove a project from the UI project switcher (deletes no files) |
roscoe init#
Scaffold the global data dir (~/.roscoe/) and (when inside a git repo) the
project data dir (<repo>/.roscoe/). Runs DB migrations, writes a
roscoe.yaml config + JSON Schema, wires .vscode/settings.json for YAML
autocomplete, drops a self-contained .gitignore inside <repo>/.roscoe/
(create-only — --force does not overwrite it), and seeds the starter
workflows into the global dir.
Whenever global init runs (anything but --repo-only), it also installs the
global /roscoe slash command at ~/.claude/commands/roscoe.md so Claude Code /
Cowork users can launch workflows with /roscoe <workflow-name>. The write is
best-effort and idempotent (always overwritten) — a failure (e.g. a read-only
home) is logged but never aborts init.
roscoe init [--global-only] [--repo-only] [--force] [--no-examples]| Flag | Type | Default | Behaviour |
|---|---|---|---|
--global-only |
boolean | false | Touch only ~/.roscoe/; skip repo scaffolding |
--repo-only |
boolean | false | Scaffold only <repo>/.roscoe/; skip global init and DB migrations |
--force |
boolean | false | Overwrite existing example workflow files and roscoe.yaml |
--no-examples |
boolean | false | Skip seeding example workflows (commander negation pattern) |
Example workflows seeded into ~/.roscoe/workflows/: demo-1-the-panel,
demo-2-week-of-dinners, demo-3-name-tournament, demo-4-second-draft,
demo-5-reality-check, demo-6-gift-shortlist, demo-7-fair-price,
demo-8-smell-test, demo-9-check-one-claim. The repo workflow dir is left
empty so a team can fill it with shared workflows.
Exit codes: 0 always (failures in individual steps are logged but never abort init — by design, you can re-run it any time).
# Default: scaffold both global and (if in a repo) project dirs
roscoe init
# I'm not in a repo and don't want the warning
roscoe init --global-only
# Refresh the roscoe.yaml schema and re-seed examples
roscoe init --forceroscoe serve#
Start the HTTP server with the embedded web UI on a single port. Available
only from the compiled binary (the source-mode dev server is bun run dev,
which runs API + Vite separately).
roscoe serve [-p <port>] [--no-open] [--no-register]| Flag | Type | Default | Behaviour |
|---|---|---|---|
-p, --port <port> |
number | see below | Port to listen on. 1..65535 |
--no-open |
boolean | opens | Skip auto-opening the browser once the server is up |
--no-register |
boolean | registers | Skip auto-registering the current git root as the active project |
By default, roscoe serve registers the detected git root (making it the
active project) and opens the URL in a browser. Auto-register failure is
soft: it's logged and the server still starts.
Port resolution order (first match wins):
--portflagROSCOE_PORTenv varports.serveinroscoe.yaml- Built-in fallback
7777
When the port comes from the fallback only, roscoe serve will auto-bump up
to 9 ports forward to find a free one (so 7777, 7778, … 7786). Any
explicitly set port is never silently bumped — if it's busy, roscoe serve
exits with [roscoe] Port <N> is in use.
Exit codes: 0 on clean shutdown (SIGINT/SIGTERM), 1 on bind failure or invalid port.
roscoe serve # listens on 7777 (or next free port)
roscoe serve -p 8000 # listens on 8000 only; fails if busy
ROSCOE_PORT=9001 roscoe serve # same effect via env varroscoe stop#
Stop the running roscoe serve for the current ROSCOE_HOME, and wait for it
to actually exit before returning. It triggers the server's own authenticated
self-shutdown (POST /api/serve/restart, which SIGTERMs its own process),
not a cross-process kill, so it's safe regardless of who started the server.
roscoe stopNo flags. Waits up to 10 seconds for the listener to disappear after requesting the stop.
The server aborts in-flight runs before it exits, so executor children of a running workflow are torn down rather than orphaned.
Exit codes: 0 if stopped (or nothing was running), 1 if a server is running but couldn't be stopped (missing auth token, or the request failed).
roscoe stop # e.g. before roscoe update installs a new binary manuallyroscoe locate#
Print where a running roscoe serve is for the current ROSCOE_HOME, without
starting one. This is the attach-logic primitive the desktop shell uses to
decide whether to reuse an existing server or spawn a new one; it's also
useful from a shell to find the port and auth token of a server you started
elsewhere.
roscoe locate [--json]| Flag | Behaviour |
|---|---|
--json |
Emit machine-readable output |
Exit codes: 0 if a server is found, 3 if none is running (the signal an attach caller uses to decide to spawn one), 1 on an unexpected error.
roscoe locate
roscoe locate --json | jq .baseUrlroscoe mcp#
Start the MCP stdio server. Reads protocol messages on stdin, writes them
on stdout — never use console.log from this command, or you'll corrupt
the protocol stream. Diagnostic output goes to stderr.
roscoe mcpNo flags. Auto-loads .env.dev from the current repo if present (so a dev
shell pointing at ~/.roscoe-dev keeps working without explicit env). Sets
ROSCOE_SWEEP_TEST_RUNS=1 to enable cleanup of orphaned test_workflow
rows.
Exit codes: 0 on SIGINT/SIGTERM. There is no explicit failure exit — a throw during tool registration or transport connect propagates and exits non-zero through Node's default handler (typically a corrupted DB — see Database recovery).
This is the entry point that Claude Code shells out to. Don't run it
directly unless you're debugging the MCP layer; use roscoe install-mcp to
register it with Claude Code instead.
roscoe run <workflow>#
Run a workflow by id (resolved from project or global sources) or by
explicit YAML path. Routes through a running roscoe serve (or the dev API
under bun run dev) — if neither is up, exits with a hint naming
roscoe serve and ROSCOE_PORT. Running from source, the hint names the dev
server too.
In a TTY, mounts a live Ink view with a node tree, status, and HITL
prompt for human nodes. Press q to detach (the run keeps executing
on the server), c to cancel, or o to open the run page in the
browser.
With --json, the run is polled to completion (up to 120 s) and the result is
printed as { runId, status, totalCostUsd, meteredCostUsd, subscriptionEquivCostUsd, billingMode, inputTokens, outputTokens }, plus
stopReason when one is set. Outside a TTY without --json, no JSON is
printed — just the run id and a monitor hint.
roscoe run [<workflow>] [-v key=value...] [-i name=value...] [-s global|repo]
[--json] [--no-tty] [--attach <runId>] [--run-here]
[--open cowork|claude_code] [--spending-cap <usd>]
[--spending-cap-tokens <n>]| Argument / flag | Type | Default | Behaviour |
|---|---|---|---|
<workflow> |
string | — | Workflow id (e.g. demo-1-the-panel) or path (e.g. examples/foo.workflow.yaml) |
-v, --var <pairs> |
string[] | — | Context variables as key=value, repeatable |
-i, --input <pairs> |
string[] | — | A declared workflow input as name=value, repeatable. JSON-parsed when valid, else used as a raw string. |
-s, --source |
enum | — | Force global or repo when both have the same id |
--attach <runId> |
string | — | Skip workflow start; mount the live view against an existing run. Requires an interactive TTY — exits 1 with --json, --no-tty, or piped output |
--json |
boolean | false | Emit a JSON result and exit (no live UI) |
--no-tty |
boolean | false | Force non-interactive mode even in a TTY |
--run-here |
boolean | false | Run server-side, skipping the subscription-handoff prompt. Metered only if ANTHROPIC_API_KEY is set; otherwise it still runs on your Claude subscription |
--open <cowork|claude_code> |
string | — | Hand the run off to that surface instead of running it here |
--spending-cap <usd> |
number | — | Cancel the run once its running cost exceeds this many USD |
--spending-cap-tokens <n> |
number | — | Cancel the run once its total input+output tokens exceed this count |
Workflow resolution — if the argument contains /, \, ends in .yaml
/ .yml, or names an existing file, it's treated as a path. Otherwise it's
a workflow id. When the same id exists in both sources, repo wins; pass
--source global to override. When repo shadows global, a # resolved from repo (shadows global) notice is printed. Path-based starts are
realpath-checked server-side; non-existent or unreadable paths return 400.
Where the run executes. --run-here and --open are explicit
overrides; absent either, the choice follows preferences.defaultRunLocation
in roscoe.yaml (here, cowork, claude_code, or the default ask).
ask prompts interactively in a TTY when Claude Desktop is installed, and
falls back to here otherwise (so CI never hangs). See
Running on your Claude subscription for what
runs where, and roscoe.yaml for the
preference.
Exit codes: 0 on successful start (or live-view detach), 1 on resolution / start / server-not-running failure.
roscoe run demo-1-the-panel
roscoe run demo-1-the-panel --source global
roscoe run examples/hello-world.workflow.yaml
roscoe run demo-4-second-draft --input goal="warm but firm"
roscoe run summarize --input document="my text" --input maxWords=50
roscoe run demo-1-the-panel --json # CI / agent
roscoe run --attach 01HXYZ... # reattach to a running run
roscoe run demo-1-the-panel --run-here # skip the handoff prompt
roscoe run demo-1-the-panel --spending-cap 2.50 # cancel past $2.50roscoe list#
List workflows from both project (<repo>/.roscoe/workflows/) and global
(~/.roscoe/workflows/) sources. The project section is omitted entirely
when there's no project source.
roscoe listNo flags. Shadowed entries (same id in both sources) are marked
[shadowed by repo] in the global section.
Exit codes: 0 always.
roscoe resume <run-id> [transition]#
Resume a paused run. With a transition (positional or --transition), runs
non-interactively (suitable for scripts and CI). In a TTY without one,
mounts the same interactive Select picker roscoe run uses for HITL prompts.
Like roscoe run, requires a running server.
roscoe resume <run-id> [transition] [--transition <name>] [--no-tty]| Argument / flag | Type | Behaviour |
|---|---|---|
<run-id> |
string | Run id from roscoe run / roscoe history |
<transition> |
string | Optional transition key; required outside a TTY |
--transition <name> |
string | Scriptable alternative to the positional transition; wins if both are given |
--no-tty |
boolean | Force non-interactive mode (a transition is then required) |
Exit codes: 0 on success, 1 if the run can't be resumed (not paused, unknown transition, run not found, server not running).
roscoe resume r-abc123 approve
roscoe resume r-abc123 reject
roscoe resume r-abc123 --transition approve
roscoe resume r-abc123 # interactive picker (TTY only)roscoe monitor <run-id>#
Inspect a run. In a TTY, mounts the same live Ink view as roscoe run
without starting a new run — useful for picking up an in-progress run from
another shell. With --json or when piped, prints a one-shot snapshot
(run + per-node trace) and exits without contacting the server.
roscoe monitor <run-id> [--json] [--no-tty]| Argument / flag | Type | Behaviour |
|---|---|---|
<run-id> |
string | Run id from roscoe run / roscoe history |
--json |
boolean | One-shot JSON snapshot (reads local DB, no server required) |
--no-tty |
boolean | One-shot text snapshot (reads local DB, no server required) |
Exit codes: 0 on success, 1 if the run isn't found or the live view can't reach the server.
roscoe history#
List recent runs for the current project, newest first.
roscoe history [-n <limit>] [--all]| Flag | Type | Default | Behaviour |
|---|---|---|---|
-n, --limit |
number | 20 | Max rows. Invalid values fall back to 20. |
--all |
boolean | false | Show runs from every project, not only the current git repo |
When run outside a git repo, defaults to --all semantics with a notice.
Output columns: STATUS · WORKFLOW · AGE · ID · DURATION. Paused rows print
a roscoe resume <id> hint underneath.
Exit codes: 0 always.
roscoe validate <file>#
Validate a workflow YAML file against the Zod schema, the integrity checker, and the cross-workflow graph check. Prints schema errors first, then integrity errors, then graph errors, stopping at the first failing stage. Paths are resolved from the current working directory.
roscoe validate <file>| Argument | Type | Behaviour |
|---|---|---|
<file> |
string | Path to a *.workflow.yaml file |
The graph check follows subworkflow nodes to their child workflow files
and reports missing children, unknown child input/output names, reference
cycles, and depth violations. It's a no-op for workflows with no
subworkflow nodes.
Exit codes: 0 if valid, 1 if any of the three checks fails.
roscoe validate examples/my-workflow.workflow.yamlWhen the workflow's version: field doesn't match the binary's supported
schema version, the error tells you which way the mismatch goes and what
to do — newer-than-binary asks you to run roscoe update, older-than-binary
asks you to update the file or downgrade.
roscoe version#
Print the installed version and check the release manifest for a newer one.
roscoe version [--json] [--check] [--no-fetch]| Flag | Behaviour |
|---|---|
--json |
Emit { current, latest, updateAvailable, pending, swapped, restartRequired, releasedAt, lastChecked, lastError } instead of a human-readable line. For scripts. |
--check |
Exit code 0 if up to date, 1 if a newer release is available. Useful in cron + CI gates. |
--no-fetch |
Never reach the network; report only current plus the cached poll state. Used by the desktop shell, which needs current and must not block startup on a fetch. |
# Human output
roscoe version
# Script-friendly
roscoe version --json | jq .updateAvailable
# CI gate — fail the build if the local binary is stale
roscoe version --check || echo "::warning::roscoe binary out of date"The manifest URL defaults to https://roscoe.run/v/manifest.json;
override with ROSCOE_MANIFEST_URL (used by tests and self-hosted mirrors).
roscoe update#
Download the latest release, verify its sha256 against the manifest, and
swap it over the installed binary at ~/.roscoe/bin/roscoe (or wherever
ROSCOE_INSTALL_DIR points). If a roscoe serve process is running on this
machine the update token from ~/.roscoe/serve.lock is used to ask it to
restart cleanly on the new version.
roscoe update [--check] [--force] [--rollback] [--no-restart]| Flag | Behaviour |
|---|---|
--check |
Report whether a newer release exists without installing. Exits 1 if an update is available, 0 if up to date. |
--force |
Reinstall the latest version even if it matches the running one (useful when a download was interrupted). |
--rollback |
Restore the previous binary from ~/.roscoe/updates/previous/roscoe. Reverts the most recent update. |
--no-restart |
Skip the auto-restart of the local roscoe serve. Useful when running under a supervisor. |
# Standard upgrade path
roscoe update
# Will an update do anything? (handy in a Makefile)
roscoe update --check
# Revert the last update
roscoe update --rollbackroscoe serve also pre-downloads new releases in the background, so when
you (or the Settings → Updates button) trigger an install the asset is
already on disk and the swap is near-instant. The download path
verifies sha256 before the rename, and on macOS the new binary is
xattr-stripped and ad-hoc codesigned so Apple Silicon doesn't refuse to
launch it. The previous binary stays on disk at
~/.roscoe/updates/previous/roscoe until the next update overwrites it.
roscoe update refuses to run when the active binary isn't at the
canonical install location (e.g. you copied roscoe to /usr/local/bin/).
In that case it prints the path it found and asks you to re-run
install.sh or use your package manager's update command.
roscoe install-mcp#
Register the roscoe entry so a Claude MCP client launches the compiled binary
as its MCP server. By default it targets Claude Code and writes the
user-global ~/.claude.json (top-level mcpServers), so the install applies to
every project; the machine-specific binary path stays out of any shared repo.
Pass --client claude-desktop to target the Claude desktop app instead.
Must be run from the compiled binary, not from source.
roscoe install-mcp [--client <name>] [--project] [--path <file>] [--command-path <path>]| Flag | Type | Default | Behaviour |
|---|---|---|---|
--client <name> |
string | claude-code |
Target MCP client: claude-code (~/.claude.json) or claude-desktop |
--project |
boolean | false |
Write a project-local ./.mcp.json instead of ~/.claude.json (Claude Code only) |
--path <file> |
string | (scope default) | Explicit config file path; overrides the scope default |
--command-path <path> |
string | this binary | Binary path to record as the MCP server command. The desktop app passes the canonical ~/.roscoe/bin/roscoe path so the entry stays valid across app updates and deletion. |
With --client claude-desktop the entry is written to the desktop app's config
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS,
~/.config/Claude/claude_desktop_config.json on Linux,
%APPDATA%\Claude\claude_desktop_config.json on Windows, or the Microsoft Store
build's per-package path under %LOCALAPPDATA%\Packages\Claude_*\), creating the directory
if needed. --project is not valid for Claude Desktop (it has no project-local
config).
All target files use the same top-level mcpServers shape. Writes:
{
"mcpServers": {
"roscoe": {
"command": "<path-to-roscoe-binary>",
"args": ["mcp"]
}
}
}Every other key in the file is preserved: ~/.claude.json's projects map
and any other MCP servers stay put, only mcpServers.roscoe is replaced.
The write is atomic (temp file + rename), so a populated ~/.claude.json
is never truncated. A missing target file is created. Refuses to run from
bun (source mode), since the resulting process.execPath would be the
bun interpreter, not the roscoe command.
/roscoe slash command. The default user-global Claude Code install also
writes ~/.claude/commands/roscoe.md (the same /roscoe command roscoe init
installs) and prints its path. This is deliberately skipped for --project
(a repo-local opt-in shouldn't create a machine-wide command file), for
--client claude-desktop (the desktop app has no slash commands), and for
--path targets. The write is best-effort — a failure is reported but never
fails the MCP registration.
Exit codes: 0 on success, 1 if run from source or if the target config file exists but is malformed JSON (in which case it is left untouched).
roscoe register#
Register a project in the local project registry so it appears in the web
UI's project switcher (the roscoe serve UI scopes itself to the active
registered project). Registration does not move or copy any files; it only
records the path.
roscoe register [path] [--activate] [--name <name>]| Argument / flag | Type | Default | Behaviour |
|---|---|---|---|
[path] |
string | enclosing git repo | Project path; defaults to the git root of the current directory |
--activate |
boolean | false | Set this project as the active one after registering |
--name <name> |
string | directory basename | Custom display name shown in the switcher |
Entries are keyed on the resolved (realpath) git root, so registering the
same repo twice is idempotent and sibling worktrees of one repo each get
their own entry. Exits 1 when path isn't inside a git repo.
roscoe register # register the current repo
roscoe register ~/dev/projectA --activate
roscoe register . --name "Billing service"roscoe unregister#
Remove a project from the registry — the inverse of roscoe register. This
only unregisters the project from the UI project switcher; it deletes no
files (workflows, roscoe.yaml, and run history all stay on disk).
roscoe unregister [path]| Argument | Type | Default | Behaviour |
|---|---|---|---|
[path] |
string | enclosing git repo | Project path; defaults to the git root of the current directory |
Removal is keyed on path, so it targets exactly one entry even when sibling
worktrees of the same repo share a registry id. It resolves the same candidate
paths registration uses (git root + realpath), so it still matches an entry for
a path whose git root can no longer be resolved (e.g. a removed worktree).
Exits 1 when no registered project matches the path.
roscoe unregister # unregister the current repo
roscoe unregister ~/dev/projectAEnvironment variables#
| Variable | Affects | Behaviour |
|---|---|---|
ROSCOE_HOME |
All | Override the global home dir (default ~/.roscoe) |
ROSCOE_WORKFLOWS_DIR |
Resolution | Override only the global workflows dir (back-compat) |
ROSCOE_REPO_HOME |
Resolution | Override the detected repo root (set automatically) |
ROSCOE_PORT |
roscoe serve |
Default port. Beaten only by --port; it in turn beats roscoe.yaml's ports.serve (see the precedence list above) |
ANTHROPIC_API_KEY |
AI nodes | If set, AI nodes use the Anthropic SDK directly |
ROSCOE_SWEEP_TEST_RUNS |
roscoe mcp |
When 1, sweeps orphaned test_workflow rows on startup |
ROSCOE_MANIFEST_URL |
roscoe version, roscoe update |
Override the release manifest URL (tests, self-hosted mirrors) |
ROSCOE_INSTALL_DIR |
roscoe update |
Where the installed binary lives (default <ROSCOE_HOME>/bin) |
For the dev server (bun run dev), .env.dev at the repo root sets
ROSCOE_API_PORT (default 8080), ROSCOE_WEB_PORT (default 3000), and
ROSCOE_HOME=~/.roscoe-dev so dev runs don't pollute the production DB.
Where to next#
- Running workflows from the CLI — the same commands as a walkthrough rather than a lookup table.
- MCP tools — what
roscoe mcpexposes once an agent is connected to it. - REST API — the HTTP surface
roscoe serveputs these commands on top of. - Environment variables — the full list, of which the table above covers only what the CLI reads.
- roscoe.yaml — the config file
initwrites andservereads for its port.