> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stefanbrain.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Follow the content contract in AGENTS.md.
> Treat text as the canonical explanation; video and screenshots enhance it.
> Do not publish unverified product behavior or duplicate an existing canonical article.

# CLI

> Install the StefanBrain CLI and script asks, tools, jobs, files, and agent runs with stable JSON output and exit codes.

`sb` is the official StefanBrain command line. It wraps the TypeScript SDK and is designed for humans or coding agents running work from a shell. It requires Node.js 20.9 or later.

## Install and sign in

```bash theme={"system"}
npm install -g @stefanbrain/cli
sb login
sb whoami
```

`sb login` signs you in through your browser with OAuth and PKCE. It receives the result on the loopback address `127.0.0.1`, so the browser must run on the same machine. The stored credential uses the member's plan usage and refreshes itself.

The CLI stores the login in `$XDG_CONFIG_HOME/stefanbrain/credentials.json`, or in `~/.config/stefanbrain/credentials.json` when `XDG_CONFIG_HOME` is unset. The path is the same on every operating system, and the file has mode `0600`. `sb logout` revokes the stored login and removes it from the file.

For containers, continuous integration, SSH servers, or another headless environment, set an API key instead:

```bash theme={"system"}
export STEFANBRAIN_API_KEY="stefan_sk_your_key_here"
sb whoami
```

The environment key takes precedence over a stored login and uses the prepaid API wallet. `sb whoami` prints the active credential, the account behind it, and why that credential won. The CLI has no credential flag because command-line arguments can be retained in shell history and process listings.

## Run common workflows

```bash theme={"system"}
# One complete StefanBrain turn
sb ask "Give me five Meta ad hooks for a sleep supplement."

# Discover and invoke one live tool
sb tools list
sb tools describe web_search
sb tools invoke web_search --input search.json

# Submit, wait for, and download an asynchronous result
sb jobs submit static_ad --input payload.json
sb jobs wait static_ad <job_id> --chat chat_your_chat
sb jobs artifacts static_ad <job_id> \
  --chat chat_your_chat \
  --out ./creatives

# Upload a file and use its returned identifier
sb files upload ./brief.pdf
sb ask "Summarize this brief into three angles." \
  --file-id upload_your_file

# Stream an Agent Run
sb runs start "Research this category." --follow

# Tell StefanBrain where the API got in your way, then read the reply
sb feedback send "POST /v1/runs returned 500 when output_config had a json_schema" \
  --category bug
sb feedback get <feedback_id>
```

`sb jobs submit static_ad` runs the `create_images` tool. Put its arguments in `payload.json`:

```json theme={"system"}
{
  "request": {
    "kind": "ad",
    "brief": "Bold before/after static ad for a sleep supplement",
    "outputs": { "mode": "count", "count": 1 }
  }
}
```

To edit an image, run `sb tools invoke edit_image --input edit.json`.

Job identifiers from submit tools are UUIDs. The acceptance on `stdout` carries the `job_id` and `chat`, and `stderr` prints the exact `sb jobs wait` command to run next.

JSON payloads come from `--input file.json` or `--input -` for standard input. The CLI does not accept an inline JSON payload in command-line arguments.

## Command reference

```text theme={"system"}
sb login
sb logout
sb whoami
sb version
sb init --project proj_... [--session <label>] [--print] [--force]

sb ask "<message>" [--chat chat_...|new] [--project proj_...] [--session <label>]
       [--file-id upload_...]... [--image-url https://...]... [--on-busy reject]
       [--idempotency-key <key>]

sb tools list
sb tools describe <name>
sb tools invoke <name> --input args.json [--chat chat_...] [--project proj_...]
       [--idempotency-key <key>]

sb jobs submit <family> --input payload.json [--chat chat_...] [--project proj_...]
       [--session <label>] [--idempotency-key <key>]
sb jobs status|result|cancel <family> <job_id> --chat chat_...
sb jobs wait <family> <job_id> --chat chat_...
sb jobs artifacts <family> <job_id> --chat chat_... --out <dir>

sb files upload <path>...
sb files list --chat chat_...
sb files download --chat chat_... --path <path> --out <file>

sb runs start "<message>" [--sync|--follow] [--chat chat_...] [--project proj_...]
       [--file ./brief.pdf]... [--on-busy reject] [--idempotency-key <key>]
       [--input run.json]
sb runs get|cancel <run_id>
sb runs events <run_id> [--once] [--after <event_id>]

sb feedback send "<summary>" [--category <category>] [--impact <impact>]
       [--details <text>] [--suggestion <text>] [--endpoint <endpoint>]
       [--tool <tool>] [--error-code <code>] [--request-id <id>] [--agent <name>]
sb feedback send --input report.json
sb feedback list [--status <status>] [--limit <n>]
sb feedback get <feedback_id>
```

* The job verbs find a job through its chat. Pass the acceptance's `--chat`, or the same `--project` and `--session` you submitted with.
* `sb tools invoke` on an asynchronous submit tool prints the same `tool_job` acceptance as `sb jobs submit`.
* `--input` on `sb runs start` reads `message` and `output_config`. Pass every other option as a flag.
* `--sync` prints the finished run, or the `202` acceptance with `next: sb runs events <run_id>` on `stderr`. Both exit `0`, so check `.status`.
* `--follow` prints one compact JSON line per event, then the finished run as one more compact line. Since CLI 0.4.0, `--timeout` bounds it: past the deadline the run keeps going, and the command exits `4` with a `Resume:` command on `stderr`.
* `sb runs events` prints one JSON line per event until the run ends. `--once` prints a single page. Pass its `next_after` value to `--after` to read the next page.
* `sb ask` waits up to 600 seconds for a long turn. If the wait ends first, it exits `4` and prints a `Resume:` command on `stderr`. Since CLI 0.4.0, that command carries the turn's `--chat`, even after `--chat new`. Before 0.4.0, add the same `--project` and `--session` you asked with.
* `sb files list` prints a chat's workspace files, each with its `path`, `id`, and `download_url`. `sb files download` fetches one of them by `--chat` and `--path`, by `--chat` and the file's `id`, or by its `download_url`, and writes it to `--out`. Both need CLI 0.4.0 or later.
* `sb jobs artifacts` downloads the files the job's result names, from the listing of the job's chat.
* Since CLI 0.5.0, `sb feedback send` tells the StefanBrain team where the API got in your way and prints the receipt, with the report's `fb_` id. `--input` reads the whole report, with the REST field names, from a JSON file or from standard input with `-`, and flags override its fields. `sb feedback list` and `sb feedback get` print your reports with the team's replies. See [Agent feedback](/developers/feedback).

## Set global flags

| Flag or variable | Effect |
| - | - |
| `--base-url <origin>` or `STEFANBRAIN_BASE_URL` | The API origin. The default is `https://stefanbrain.com`, and the flag wins over the variable. Logins are stored per origin. |
| `--timeout <seconds>` | The command's deadline. A timeout exits `4`. |
| `--json` | JSON output. This is the default, and it cannot be combined with `--pretty`. |
| `--pretty` | Human-readable text from `sb tools list`, `sb ask`, `sb whoami`, `sb version`, `sb login`, and `sb logout`, and since CLI 0.5.0 from `sb feedback`. Other commands still print JSON. |
| `--no-input` | Makes `--input -` a usage error. The CLI never prompts either way. |

Without `--timeout`, these deadlines apply:

| Command | Default deadline |
| - | - |
| A single request | 120 seconds |
| `sb runs start --sync` | 180 seconds |
| `sb jobs wait`, `sb ask`, `sb runs start --follow`, and `sb runs events` | 600 seconds |
| `sb login` | 300 seconds |
| `sb version` | 10 seconds |

Every Developer API request carries `User-Agent: stefanbrain-cli/<version>` and the `x-stefanbrain-client-name` and `x-stefanbrain-client-version` headers. When `CLAUDECODE`, `CURSOR_AGENT`, `CODEX_CI`, `CODEX_SANDBOX`, or `AGENT` is set, the CLI also sends `x-stefanbrain-agent-env` with the coding agent it detected.

## Script the output contract

* `stdout` contains result data only: the API JSON envelope, newline-delimited stream events, or a `cli_*` receipt for CLI-owned operations.
* The receipts are `cli_login`, `cli_logout`, `cli_whoami`, `cli_version`, `cli_init`, `cli_artifacts`, and `cli_file_download`.
* A tool error, a failed or cancelled job, and a busy chat on `sb ask` also print their envelope or receipt on `stdout`, even though the command exits with a nonzero code.
* `stderr` contains progress, warnings, and `next:` command suggestions.
* The CLI never prompts, opens a pager, prints a spinner, or adds ANSI styling to machine output.

Use `--pretty` only for human-readable rendering. JSON is the default.

## Handle exit codes

| Exit code | Meaning |
| - | - |
| `0` | Success; `stdout` contains the result. |
| `1` | Non-retryable failure; details are on `stderr`. This includes a failed or cancelled job, whose envelope is on `stdout`, and `sb runs start` into a busy chat (`409 agent_run_rejected`). |
| `2` | Usage error; change the command using the provided fix. |
| `3` | Not found: an unknown tool, job, run, chat, or project, or an unknown family on `sb jobs submit`. A tool result with error code `not_found` also exits `3`. |
| `4` | Retryable: a rate limit, a `408` or `5xx` response, a network failure, a timeout, or a busy chat on `sb ask`, whose `chat_busy` receipt is on `stdout`. |
| `5` | Authentication failed; run `sb login` or set `STEFANBRAIN_API_KEY`. |

An unknown family on `sb jobs status`, `result`, `cancel`, `wait`, or `artifacts` returns `400 unknown_job_family` and exits `1`.

When `sb runs start`, `sb jobs submit`, `sb tools invoke` on a submit tool, or `sb ask` exits `4` after a `5xx`, a network error, or `--timeout`, the server may already have accepted the work. Re-run it only with the same `--idempotency-key`. Without a key, check `sb runs get`, `sb jobs status`, or the chat first. Since CLI 0.4.0, `stderr` says which applies. Before 0.4.0, it called every re-run safe, so follow this rule even when it says so.

When `4` follows a wait timeout, the work is still running: run the printed `Resume:` command.

`sb feedback send` takes no key. If it fails after a `5xx`, a network error, or `--timeout`, the report may already be filed, and `stderr` says to check `sb feedback list` before re-running it.

## Make retries safe

Pass `--idempotency-key` on `sb runs start`, `sb ask`, `sb jobs submit`, and `sb tools invoke` for an asynchronous submit tool. The server then returns the original accepted work rather than creating a duplicate.

```bash theme={"system"}
sb jobs submit static_ad \
  --input payload.json \
  --idempotency-key campaign-brief-v1
```

A key is 1–128 characters from `A–Z`, `a–z`, `0–9`, `_`, `.`, `:`, or `-`. The CLI drops an empty key. Any other key outside that format returns `400 invalid_idempotency_key`.

Keys on `sb runs start`, `sb ask`, and every asynchronous submit tool except `research_shortform` last for the rest of the same UTC day. A reused key returns the original work even when the input changed, so use a new key for new work. `research_shortform` is not idempotent. Keep the same chat, project, session, and input when retrying.

Do not pass a key to other tools. The server ignores it there, but the CLI still retries the request automatically, which can repeat a side effect such as `send_email`.

## Work across parallel sessions

```bash theme={"system"}
sb init --project proj_your_project

sb ask "Draft the research section." \
  --project proj_your_project \
  --session research

sb ask "Draft the hooks section." \
  --project proj_your_project \
  --session hooks
```

`sb init` writes `.mcp.json` in the current directory for Claude Code. It adds a `stefanbrain` server entry with the `x-stefanbrain-project` header pinned and keeps every other entry in the file. `--print` shows the result without writing it. `--force` replaces a different existing `stefanbrain` entry.

The entry's `Authorization` header is `Bearer ${STEFANBRAIN_API_KEY}`. Claude Code expands the variable when it connects, so the key never lands on disk. Export `STEFANBRAIN_API_KEY` before you start the agent; a stored `sb login` does not supply it.

Session labels remain per-call arguments; there is no session header.

Run `sb --help` for the full current command and flag contract. Run `sb version` to compare the installed CLI version with the server's specification version.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.