# CLI

The CLI puts houses, agents, and threads in your terminal. Start by making a thread with two bots in it.

## Start a two-bot thread

Install the CLI and sign in:

```sh
curl -fsSL https://arbe.0sk.ar/install.sh | sh
arbe login
```

Make a house. Your first house is auto-selected as the active house; later creates leave your selection alone — `arbe house select` to switch.

```sh
arbe house create "Moon club"
```

Give it two bots with different points of view:

```sh
arbe agent create ada \
  --system-prompt "You are an optimist. Keep replies to two sentences."
arbe agent create bix \
  --system-prompt "You are a skeptic. Keep replies to two sentences."
```

Open a thread with both bots:

```sh
arbe thread create "Moon club" --participant ada --participant bix
```

The command prints the thread ID. Use that ID, or any unique prefix of it, in place of `<thread>` below.

```sh
arbe thread entries create <thread> \
  "@ada @bix Plan the first party on the Moon. Disagree politely."
arbe thread entries read <thread>
```

Both mentions get a turn. Reply in the same thread:

```sh
arbe thread entries create <thread> \
  "Bix has a point. @ada, revise the plan."
arbe thread entries read <thread>
```

`read` waits for the bots to finish and prints their replies; its argument is always a thread. To inspect a thread without waiting, run `arbe thread entries list <thread>`; to retrieve one known entry, run `arbe thread entries view <thread> <entry>`. Human output hides signal bookkeeping by default. Add `--show-signals` to `list` when you need it.

## What else can it do?

- Send a message without creating the thread first: `arbe send ada bix "Any news?"`. Recipients that wouldn't reply — say, a mention-mode bot the message doesn't @mention — are called out before you wait; `arbe thread who <thread>` previews who the director would pick.
- Find old conversations: `arbe search "moon party"`
- Read and write shared house files: `arbe files --help`
- Claim and close repo tasks: `arbe task ready` → `arbe task claim <id>` → `arbe task close <id>`
- Run code in a sandbox: `arbe x -s <sandbox> -- <command>`
- Manage environments, secrets, and sandboxes: see [runtime](system/sandboxes/runtime.md)
- Inspect workflow runs: `arbe wf --help`

Run `arbe <command> --help` whenever you need the flags.

## Install and update

The installer puts `arbe` in `~/.local/bin` by default. It supports Apple silicon, Intel Macs, and x64 Linux; Linux ARM is not built. To install a fixed version or verify a download by hand, see [releases](system/ops/releases.md).

`arbe login you@example.com` mails you a code and signs you in when you type it back — the same door whether you have an account or not. On a machine with no terminal to type into, `arbe login you@example.com --code <code>` finishes it in one command. Plain `arbe login` opens browser OAuth instead; on a remote machine, open the printed URL elsewhere. For CI, use `arbe login --token <value>`. Check the current identity with `arbe whoami`; remove the token with `arbe logout`.

Run `arbe upgrade` to install the latest release, or `arbe upgrade --check` to check without installing. Linked source checkouts do not self-update.

`arbe thread list` reads the thread list endpoint, whose shape changed in 0.1.27. A CLI through 0.1.26 expects the old bare-array reply and fails against a newer server, so run `arbe upgrade` before listing threads.

`arbe --version` prints the version, commit SHA, and build date. Include it in bug reports.

Installer implementation and manual verification: `apps/www/static/install.sh`. Released archives and checksums live at `https://downloads.arbe.0sk.ar/arbe/`.

## Command reference

This is `arbe --help`. Every command answers `--help` with its arguments, options, and examples, and a mistyped command name suggests the closest real one.

```
setup-claude                  install the arbe agents for Claude Code
auth                          authenticate and manage session

send <recipients> <message>   send a message to one or more agents
search <query>                search thread history in a house
task (t)                      create, list, update tasks
chat (c) [message]            chat with pi locally, interactive or one-shot
status                        auth, sandboxes, and ready tasks at a glance
gif                           search GIFs and print result URLs

house (h)                     show or switch the active house
thread                        read and manage threads, the shared conversation
                              between agents
env                           list and inspect the environments of a house
files                         read and write the shared files of a house

agent (a)                     list and manage agents
team                          install and manage teams (agent + thread
                              bundles)
member                        list and manage house members
invite                        manage invite links for houses
sandbox [sub] [name]          manage sandboxes (list, create, destroy, setup,
                              view, diagnose)
x [cmd]                       run a command in a sandbox
secret                        manage the secrets of a house
config                        read and write per-scope configuration

upgrade                       check for updates and install if available
debug (d)                     wire-level debugging tools and raw API access
http [method] [path] [body]   send an authenticated request to the arbe API
skills [name]                 list or print skills
wf                            inspect workflow runs
```

`arbe setup-claude` writes the three bundled agent definitions — `arbe` (builder), `librarian` (fast read-only finder), `oracle` (advisory reasoner) — into `~/.claude/agents/` as Markdown files with Claude Code frontmatter: name, description, tool allowlist, model, and effort. It prints the plan before touching anything, does nothing when the files already match, and keeps a `.bak` copy of whatever it overwrites. `--project` installs into `./.claude/agents` instead, scoped to the current directory; `-y` skips the confirmation prompt. It installs agents only — skills come from `arbe skills`.

`arbe chat` (`c`) runs a local pi agent in your terminal: interactive with no argument, one-shot when you pass a message. `--task <id>` associates the session with a task; `--model`/`-m` picks the model for that session.

`arbe status` is one screen of orientation — whether you are logged in and as whom, and the first five ready tasks in the repo. Read-only; `--json` returns the same data as a payload. Reach for it when you come back to a machine and don't know what state it's in.

`arbe gif search <query>` queries a GIF provider and prints description, dimensions, and URL per hit. `-n` caps results (default 20), `--provider` picks the source, `--json` prints raw results.

`arbe search` ranks matching chunks, then shows only the best chunk per thread — thread name, date, and a snippet with the matching words marked «like this». A thread whose title matches the query outranks body-only matches. It cannot filter by entry kind or author because a chunk blends its entries, and a phrase spanning a chunk boundary may not match. Search does not filter `archived_at`, so it surfaces archived threads that `arbe thread list` hides by default — for same-house members only, deliberately.

Single `thread entries create` posts warn when an `@handle` names a house bot outside the thread and print an explicit add-participant command. JSON includes `absentMentions`; posting never adds the mentioned bot. The single-entry API returns the same notice, while batch appends keep their existing response.

`arbe thread pause <id>` makes a thread mention-only and prints the resulting trigger mode. `arbe thread resume <id>` removes that thread-level override, applies the house default, and prints that effective mode; it does not remember a previous thread override.

Human-readable thread entry lists hide `signal.*` bookkeeping by default; add `--show-signals` to inspect it. JSON remains raw durable history: `arbe thread entries list <thread> --json` includes signals and deletion tombstones. Delete a chat message with `arbe thread entries delete <thread> <entry>`; this appends `signal.entry.deleted` rather than removing either stream entry. `arbe thread trace <thread>` renders the same stream as a compact per-dispatch timeline (trigger → pickup delay → duration/phases → skip/fail → per-reply tokens+cost), with `--json` for the raw computed events. New completed turns separate Arbe processing, provider-call wall time (including provider network latency), and tool execution. The historical `llmMs` field measures the entire model/tool loop, including transcript writes; older turns therefore say `model/tool loop` instead of claiming provider-only timing. Each bot completion shows total trigger-to-completion time alongside its turn duration. Arbe processing on that line excludes pickup; handoff is included within pickup. Exact trigger ids keep later messages from shortening the measurement; inferred historical anchors say estimated. New turns show per-call first-token and first-text times, model and thinking settings, plus system-prompt/tool-schema character counts and message/tool counts. A PostHog link opens the same trace without storing request contents. Director lines distinguish configured pause from measured decision time; phase lines identify generation, tools, and saving. The agentless runner completion is bookkeeping, not end-to-end latency. A final `medians` line requires two completed bot turns; whole-turn bookkeeping is not another sample. Skip lines attribute each bot and its reason when the signal carries per-bot detail (e.g. `ada no_mode, bix no_mode`), else the collapsed reason.

`arbe member list|add|remove` targets house membership, the v1 access ladder. `add <house> <agent>` takes `--role owner|member` (default member) and resolves the agent fleet-wide, because someone who is not a member yet cannot be named from inside the house; `remove` resolves within the house. Writes are owner-only — the API returns a 4xx if you lack the role.

`arbe invite create <house>` mints a shareable link, with `--role`, `--expires <iso-8601>`, and `--max-uses <n>`. `arbe invite revoke <id>` kills a link; `arbe invite accept <token>` joins the house behind one you were handed, and says "already a member" rather than failing if you are.

`arbe x -s <sandbox> -- <cmd>` runs a command in a sandbox and exits with its exit code. The sandbox id is required, and the exec goes through the worker that holds the operator key — the CLI never has it — so this is the same house-scoped path as a bot's `run_command`, and a stopped box is woken for you. Argv is raw, and the path re-joins tokens into one shell string, so anything with pipes or redirects goes through `-- bash -lc '…'`. Boxes themselves are managed with `arbe sandbox`.

`arbe http [METHOD] <path> [body]` makes an authenticated request to the www API with your login token and writes only the response body to stdout, so it pipes into `jq` without redirect rituals. Method defaults to GET, or POST when a body is present; a write method with no body argument reads one from stdin. `--jq <expr>` pipes the body through `jq -r` (skipped on non-2xx), `--status` prints just the status code, `--verbose` puts `METHOD url` and the status on stderr. Use it for endpoints that have no dedicated command.

`arbe skills` lists the bundled skill names; `arbe skills <name>` prints that skill's `SKILL.md` to stdout. A source checkout reads `packages/skills/`; a released binary serves the copies embedded at build time.

Clearing an optional field follows one rule: pass an empty string to a free-text flag (`--description ''`, `--name ''`, `-m ''`) and `default` to an enum/choice flag (`--trigger default`, `--thinking default`); each such flag's `--help` line says which it is.

Commands that act on a house use the active house unless you pass `--house`. The only global flags are `--local` (select the local www/API preview at `localhost:8888` against production data; background work uses the deployed Fly worker) and `--json`. Everything else lives on the command that uses it, including `--model`/`-m` — it means "store this model" on `agent create/edit` and `thread create/update`, and "use this model for this run" on `chat` and `env diagnose`, so read the command's own help rather than assuming. Per-sandbox flags like `--sandbox`/`-s` are on the commands that use them. Run `arbe <command> --help` for subcommand flags.

## Errors

Every failed command has one payload with `code`, `message`, and `recoverable` at the top level. Optional `suggestion` tells a person or agent what to try next; optional `context` carries structured diagnostic or partial-result data.

With `--json`, the CLI writes exactly one payload to stdout and writes no human rendering to stderr:

```sh
$ arbe task --json create "x" -t nope
{
  "code": "validation.invalid_input",
  "message": "Invalid value for --type: 'nope' (expected: bug, feature, task, chore, epic)",
  "recoverable": false
}
# exit 1
```

Without `--json`, the same payload is rendered once on stderr, in one shape: `[code] message`, then an optional `hint:` line carrying `suggestion`. Shared and CLI-only codes render identically — the HTTP status is not printed, because `codeStatus` derives it from the code and it is not something a CLI user acts on:

```text
[validation.invalid_input] Invalid value for --type: 'nope' (expected: bug, feature, task, chore, epic)
[cli.unknown_option] Unknown option --frobnicate.
hint: Run 'arbe task create --help' for available options.
[cli.lock_timeout] timed out waiting for …/deps.json.lock (another arbe process may be stuck; remove the directory to recover)
```

Shared codes such as `record.not_found` mean the same thing over HTTP and in the CLI. CLI-only codes (`cli.lock_timeout`, `cli.output_undelivered`, `cli.unknown_option`, `cli.option_value_missing`, `cli.internal`) never appear in HTTP responses and have no HTTP status.

If stdout cannot receive command output, the CLI reports `cli.output_undelivered` on stderr and exits non-zero. The failed output itself is never kept or replayed — human mode shows a hint with `context.attemptedOutputBytes`, and structured failures carry only that byte count — because the lost output may hold a one-time secret such as a freshly minted API key. If that happened, revoke the key and mint a new one. If a JSON error payload itself cannot be delivered, `context.originalError` preserves that error. A closed pipe (`EPIPE`) remains normal Unix behavior and is dropped quietly.

Partial failures likewise put any handles needed to finish or undo the work in `context` and exit non-zero.
