# CLI command reference

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

```bash
# 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 --force
```

## `roscoe 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):

1. `--port` flag
2. `ROSCOE_PORT` env var
3. `ports.serve` in `roscoe.yaml`
4. 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.

```bash
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 var
```

## `roscoe 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 stop
```

No 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).

```bash
roscoe stop  # e.g. before roscoe update installs a new binary manually
```

## `roscoe 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.

```bash
roscoe locate
roscoe locate --json | jq .baseUrl
```

## `roscoe 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 mcp
```

No 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](/docs/troubleshooting/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](/docs/running/subscription) for what
runs where, and [roscoe.yaml](/docs/configuration/roscoe-yaml) for the
preference.

**Exit codes:** 0 on successful start (or live-view detach), 1 on
resolution / start / server-not-running failure.

```bash
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.50
```

## `roscoe 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 list
```

No 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).

```bash
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.

```bash
roscoe validate examples/my-workflow.workflow.yaml
```

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

```bash
# 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.                    |

```bash
# Standard upgrade path
roscoe update

# Will an update do anything? (handy in a Makefile)
roscoe update --check

# Revert the last update
roscoe update --rollback
```

`roscoe 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:

```json
{
  "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.

```bash
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.

```bash
roscoe unregister                     # unregister the current repo
roscoe unregister ~/dev/projectA
```

## Environment 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](/docs/running/cli) — the same commands as a
  walkthrough rather than a lookup table.
- [MCP tools](/docs/reference/mcp-tools) — what `roscoe mcp` exposes once an
  agent is connected to it.
- [REST API](/docs/reference/rest-api) — the HTTP surface `roscoe serve` puts
  these commands on top of.
- [Environment variables](/docs/configuration/environment) — the full list, of
  which the table above covers only what the CLI reads.
- [roscoe.yaml](/docs/configuration/roscoe-yaml) — the config file `init`
  writes and `serve` reads for its port.
