# CLI Setup

> Install the command-line tool for creating apps.

**URL:** https://inference.sh/docs/extend/cli-setup
**Last updated:** 2026-10-25

---

Install the command-line tool for creating apps.

---

## Prerequisites

### For Python Apps: uv

The CLI uses **uv** for Python environment management:

```bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

### For Node.js Apps: Node.js

Node.js v20 or later:

```bash
# macOS / Linux (via fnm)
curl -fsSL https://fnm.vercel.app/install | bash
fnm install 22

# Or via nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
nvm install 22
```

### Hardware

| App Type | Development Environment |
|----------|------------------------|
| CPU apps | Any machine |
| GPU apps | Requires NVIDIA CUDA GPU |

---

## Install CLI

```bash
curl -fsSL https://cli.inference.sh | sh
```

This covers macOS, Linux, and Git Bash on Windows. In Windows PowerShell:

```powershell
irm https://cli.inference.sh/install.ps1 | iex
```

Both install to `~/.local/bin` and add it to your PATH. Open a new terminal afterwards. On Windows, pass JSON input from a file (`belt app run <app> --input input.json`) because PowerShell strips the quotes from inline JSON.

Or via package managers:

```bash
brew install inference-sh/tap/belt       # macOS / Linux
npx @inferencesh/belt                    # Node.js
scoop bucket add inference https://github.com/inference-sh/scoop-bucket && scoop install belt  # Windows
```

---

## Command name

The same CLI is available as **`belt`**, **`infsh`**, and **`inferencesh`** (package installs may expose one or all of these names). Subcommands are identical:

```bash
belt version
infsh version    # same CLI
```

Examples in this documentation use `belt`. Either prefix works.

---

## Verify

```bash
belt version
```

---

## Update CLI

```bash
belt update    # or: infsh update
```

**Shell installer** (`curl … | sh`): `belt update` downloads and replaces the binary in place.

**Homebrew or Scoop**: the CLI detects package-manager installs and refuses to self-update over a managed binary. Run the manager's command instead:

```bash
brew upgrade belt    # Homebrew
scoop update belt    # Scoop
```

If you installed with `brew install inference-sh/tap/belt` or `scoop install belt`, `belt update` prints the matching command when an upgrade is available.

Auto-update runs in the background on most commands. It is skipped for `belt version`, `belt update`, and help flags. Set `INFSH_NO_AUTOUPDATE=1` to disable automatic upgrades.

**Dev builds** (version `dev`, typical of `go run` or local builds) skip self-update so a release binary does not overwrite your working tree. `belt update` prints `skipped: dev build`. Pass `--force` to check the manifest and upgrade anyway:

```bash
belt update --force
```

---

## Login

```bash
belt login
belt login --key-stdin < key.txt          # non-interactive: skip browser flow (CI, scripts)
printf %s "$KEY" | belt login --key-stdin
```

Opens a browser for [device authorization](/docs/api/rest/device-auth) in interactive terminals.

**Headless device auth:** in non-interactive shells (no TTY, `CI` set, `BELT_NON_INTERACTIVE=1`, or `--no-input`), `belt login` without `--key` prints an approval URL instead of opening a browser. After you approve, the browser page shows a 6-digit code. Run `belt login --code <code>` (`-c`) on the same machine to finish. The link and the code expire 5 minutes after `belt login` ran. The CLI keeps the PKCE verifiers of up to five recent unclaimed runs and tries them newest first, so a code from an earlier `belt login` on the same machine still works inside that window. If the claim fails, run `belt login` again for a fresh link.

For scripts and CI, pipe an API key with `--key-stdin` (or `--key -`) or set `INFERENCE_API_KEY` instead. `--key <key>` still works but warns: a key on the command line lands in shell history and the process list.

The CLI, the SDKs and curl examples all read `INFERENCE_API_KEY` (see [Authentication](/docs/api/authentication)). `INFSH_API_KEY` still works but is deprecated: belt prints a warning once per run, and when both are set and differ, `INFERENCE_API_KEY` wins.

Credentials are stored in `~/.inferencesh/config.json` with `0600` permissions (owner read/write only). Other local belt data (skill cache, command history, hook logs, remote machine keys, and similar) defaults to `~/.belt/` and paths under `~/.inferencesh/`. Set `BELT_CONFIG_DIR` to use **one directory for all of it** — config, cache, history, and remote identity — so separate agents or shells on the same machine never share login or cached skills. See [Environment Variables](#environment-variables).

A `belt login` **session token** slides on use like a browser session: regular CLI use extends server-side expiry (seven-day idle window) without changing the stored token, so you are not logged out a fixed week after login while you keep using belt. Short-lived sessions from device approval (custom `expires_in` under seven days) are not extended. See [Device authorization — Sliding lifetime](/docs/api/rest/device-auth#sliding-lifetime).

After a successful login, the CLI clears the skill cache and notice cooldowns under that layout (`~/.belt/cache` and `~/.belt/notices` by default), so cached API responses start fresh for the new account.

---

## Logout

```bash
belt logout
```

Clears the stored credentials in `~/.inferencesh/config.json`: API key, session token, anonymous session token, and any workspace override from `belt auth switch`. Also clears `~/.belt/cache` and `~/.belt/notices`.

Everything else under `~/.belt` and `~/.inferencesh` is kept, including command history (`~/.belt/history`), agent session ledgers (`~/.belt/sessions`), the hook log (`~/.belt/hooks.log`), and CLI logs.

`INFERENCE_API_KEY` in the environment is not cleared. Unset it separately.

Logout removes credentials from your machine only. To revoke a session on the server, use [settings → personal → sessions](https://app.inference.sh/settings/sessions) or `POST /oauth/revoke` (see [Device authorization: Revoke](/docs/api/rest/device-auth#revoke-a-credential-rfc-7009)). API keys stay valid until you revoke them with [`belt keys revoke`](/docs/extend/cli-setup#manage-api-keys) or delete them in [settings → workspace → api keys](https://app.inference.sh/settings/team/keys).

When no credential is stored, the command prints `not logged in` and exits `0`.

---

## Reset

```bash
belt reset
```

Clears all local belt state: the credentials in `~/.inferencesh/config.json`, `~/.belt/cache`, `~/.belt/notices`, `~/.belt/sessions`, `~/.belt/hooks.log`, the pending headless login (`~/.inferencesh/pending_auth.json`), and the log directories `~/.inferencesh/logs` and `~/.inferencesh/logs-staging`.

Command history (`~/.belt/history`) is kept.

Reset does not revoke server-side sessions or delete API keys. For credential-only cleanup, use [Logout](#logout).

---

## Check your account

```bash
belt me
belt me --json    # same payload as GET /me — for scripts and automation
```

Shows your username and workspace. When a workspace override is active, `belt me` prints a hint to use `belt auth switch`.

With `--json`, stdout is compact JSON for the `GET /me` response (`user` and `team` objects). Use `--pjson` for indented output. See [Workspaces API: get current user](/docs/api/rest/teams#get-current-user). On failure, stdout is a JSON error object (same as other `--json` / `--pjson` commands); stderr still has the formatted message.

**Logged in?** `belt auth status` (alias `whoami`) prints the account and workspace the stored credential resolves to, and exits non-zero (`auth-required`) when there is no credential or the server rejects it. With `--json`: `{"logged_in", "email", "team", "credential", "endpoint"}`.

```bash
belt auth status --json || printf %s "$KEY" | belt login --key-stdin
```

**Balance:** `belt balance` is a top-level alias for `belt me balance` (agents often try `belt balance` first). It prints your workspace's current balance and nudges you to top up when below $5. Use `--json` for `{"balance": <microcents>, "balance_dollars": <float>}`.

```bash
belt balance
belt me balance --json
```

Requires `billing:read` scope (included in standard API keys). See [Billing API](/docs/api/rest/billing#get-balance).

---

## Switch workspaces

If you belong to multiple workspaces, switch the active workspace for all CLI commands:

```bash
belt auth switch              # interactive team picker
belt auth switch my-org       # switch directly by team username
belt auth sw my-org           # alias
```

The CLI stores the override in `~/.inferencesh/config.json` and sends it as the `X-Team-ID` header on API requests. Switching back to your default workspace clears the override. Each successful switch also clears `~/.belt/cache` and `~/.belt/notices`, the same as [login](#login).

---

## Temporary token for scripts

```bash
belt auth token
```

Mints a short-lived API key from your login and prints it to stdout with no trailing newline — for subshells, `curl`, and shell scripts. The login credential itself stays in `~/.inferencesh/config.json`.

```bash
curl -H "Authorization: Bearer $(belt auth token)" https://api.inference.sh/me
export INFERENCE_API_KEY=$(belt auth token --ttl 8h)
```

The key is pinned to the active workspace (`belt auth switch`) and cannot act in any other workspace, even ones you belong to.

| Flag | Effect |
|---|---|
| `--ttl 1h` | Key lifetime, 1m to 24h. Default 15m, or `token_ttl` in `config.json`. |
| `--scope apps:read` | Restrict the key to a scope. Repeatable. Without it the key gets the same scopes as the login. |
| `--long-lived` | Print the stored login credential instead of minting a key. |
| `--json` | Output `token`, `expires_at`, and `scopes`. |

The key is a regular API key with a short expiry — see [temporary keys](/docs/api/rest/api-keys#temporary-keys). It cannot create other keys, so a leaked one cannot renew itself. While it is live it shows in your API keys list, where you can revoke it; once expired it is removed.

Works from a `belt login` session. Creating keys is held by your own sign-in, so an API key login cannot. When not logged in, errors go to stderr and stdout stays empty.

For a **long-lived** personal or workspace key, use [`belt keys`](/docs/extend/cli-setup#manage-api-keys) or the web app.

---

## Manage API keys

Create, list, and revoke API keys in the **active workspace** (`belt auth switch`). Requires `belt login` or `INFERENCE_API_KEY`, with the same [capabilities](/docs/org/api-keys#who-sees-and-manages-keys) as the REST API: every member manages their own personal keys; workspace owners and admins also manage workspace keys and every member's keys.

```bash
belt keys create laptop
belt keys create ci --workspace --scope apps:execute --ttl 90d
belt keys list
belt keys revoke <id> -y
```

| Command | Purpose |
|---------|---------|
| `belt keys create <name>` | Create a key and print the full secret once |
| `belt keys list` | List keys you may see (masked values, scope, creator, last use, expiry) |
| `belt keys revoke <id>` | Revoke by ID from `belt keys list` (destructive; confirm or `-y` / `--yes`) |

Personal keys (the default) act as **you** in the active workspace and stop working when you leave it. Workspace keys (`--as workspace` or `--workspace`) act as the **workspace** and survive their creator; only workspace owners and admins can create them. See [API keys](/docs/org/api-keys#two-kinds-of-key).

| Flag | Effect |
|------|--------|
| `--as user\|workspace` | Who the key acts as (`user` is default) |
| `--workspace` | Shorthand for `--as workspace` |
| `--scope <scope>` | Restrict permissions (repeatable). Without it the key gets the same scopes as your login |
| `--ttl 720h` or `90d` | Expiration; omit for a key that never expires |
| `--json` | JSON on `create` and `list` |

Use `belt auth token` for short-lived keys in scripts (24 hours at most, always personal). Use `belt keys create` for CI deploy keys, shared automation, or named keys you rotate yourself.

---

## Doctor commands

`belt auth doctor`, `belt app doctor`, and `belt plugin doctor` share the same automation contract:

- Without `--check`, each command is a diagnostic report and exits 0 even when it finds problems — useful for humans reading the output.
- With `--check`, the command exits non-zero when the report is unhealthy — for CI gates and agents.
- With `--json` (or `--pjson`), stdout is a JSON report with a top-level `ok` boolean.

```bash
belt auth doctor --check --json --no-input
belt app doctor --check --json
belt plugin doctor --check --json
```

### Authentication (`belt auth doctor`)

```bash
belt auth doctor
belt auth doctor --check --json --no-input
```

Config being present is not the same as credentials being accepted — a stored session expires, an API key is revoked, or the endpoint may be unreachable. `belt auth doctor` runs four checks:

| Check | What it verifies |
|-------|------------------|
| `config` | Whether a config file exists and was loaded |
| `credential` | Which credential type would be sent (session token or API key) — never prints the secret |
| `path` | Whether `belt`, `infsh`, and `inferencesh` on `PATH` resolve to this binary. Warns when another binary shadows the CLI, for example the launcher left by the old PyPI `inferencesh` package. |
| `api` | Whether the credential is accepted by the API (`GET /me`); skipped when no credential is configured |

With `--check`, any failed check sets a non-zero exit code. The exit code reflects the first failing check:

- Credential or auth failures → exit `4` (`auth_required`)
- Network or endpoint unreachable → exit `8` (`retryable`)
- API rate limit → exit `7` (`rate_limited`)

Use `--json` for machine-readable output:

```json
{
  "ok": false,
  "endpoint": "https://api.inference.sh",
  "checks": [
    {"name": "config", "status": "ok", "detail": "/home/user/.inferencesh/config.json"},
    {"name": "credential", "status": "fail", "detail": "none configured", "hint": "run 'belt login', or set INFERENCE_API_KEY"},
    {"name": "api", "status": "skipped", "detail": "no credential to test"}
  ]
}
```

Check statuses: `ok`, `warn`, `fail`, `skipped`. Failed checks include a `hint` with suggested next steps. The `path` check only warns, so it never fails `--check`. See [Troubleshooting: Stale CLI on PATH](/docs/extend/troubleshooting#stale-cli-on-path).

### App health (`belt app doctor`)

```bash
belt app doctor
belt app doctor fix
belt app doctor --check --json
```

Run from your app directory before deploy. The doctor checks `inf.yml`, required files (`inference.py` / `src/inference.js`, dependency manifests), Python package layout (`__init__.py`, relative imports), Node ESM config, GPU dependencies, stale schemas, and other common issues. Errors block `belt app deploy`; warnings are shown but do not stop the build.

`belt app doctor fix` auto-fixes issues marked fixable (relative imports, missing `__init__.py`, GPU `accelerate` dependency, ESM `type: module`, and similar).

With `--check`, only **errors** gate the exit code — warnings are reported but do not fail the run. When errors are found, `--check` exits `10` (`config`): local project setup is missing or invalid (for example no `inf.yml`, a bad import, or an uninitialised project). Warnings alone still exit `0`. With `--json`, stdout is:

```json
{
  "ok": false,
  "issues": [
    {
      "id": "missing-init",
      "message": "missing __init__.py — has sibling modules (utils.py) but no package init",
      "fixable": true,
      "severity": "error",
      "fix_description": "create __init__.py with `from .inference import App`"
    }
  ]
}
```

Issue fields: `id`, `message`, `severity` (`error` or `warning`), `fixable`, and optional `fix_description` / `extra`. → [Deploying](/docs/extend/deploying)

### Plugin health (`belt plugin doctor`)

```bash
belt plugin doctor
belt plugin doctor --fix
belt plugin doctor --check --json
```

Checks belt CLI availability, authentication, agent runtime installs, plugin cache and hook configuration (Claude Code / Codex), hook shim format, and recent activity in `~/.belt/hooks.log`. Use `--fix` to auto-repair stale caches, reinstall the belt plugin, and patch hook scripts that still contain embedded logic instead of the thin shim format.

With `--check`, any failed check exits non-zero. With `--json`, stdout is:

```json
{
  "ok": false,
  "checks": [
    {"name": "belt", "ok": true, "detail": "/usr/local/bin/belt"},
    {"name": "auth", "ok": false, "detail": "not logged in"},
    {"name": "plugin", "ok": false, "detail": "belt not found in installed plugins"}
  ]
}
```

Check names vary by detected agent runtime (for example `plugin`, `hooks`, `shims`, `cursor`, `pi-ext`). → [Coding agents](/docs/extend/coding-agents#claude-code-plugin) · [Troubleshooting](/docs/extend/troubleshooting#claude-code-belt-plugin-issues)

---

## What you can do

### Development

| Command | Purpose |
|---------|---------|
| `belt app init` | Create a new app |
| `belt app init <name> --template api` | Create a vendor API wrapper: one function per upstream operation, an httpx client with retries, a secret for the key, `test_requests/` per function, a `pricing.md` skeleton and a README with the finishing steps (needs engine 1.19+, belt 1.22+) |
| `belt app init --openapi <url-or-file>` | Generate vendor API apps from an OpenAPI 2.0/3.x document: a hand-editable `<vendor>/openapi.plan.yaml`, then one `api`-template app per group of operations with Pydantic models and operation descriptors in `_generated.py`, a thin `inference.py`, `inf.yml` declaring the vendor's credential, and `test_requests/` from the spec's examples. Rerun with the plan file to regenerate. `--vendor <slug>` overrides the slug, `--openapi-header "Name: value"` authenticates the download. Needs [uv](https://docs.astral.sh/uv/) for the model generator and belt 1.25+ (→ [Creating an app](/docs/extend/creating-app#from-an-openapi-spec)) |
| `belt app test` | Test locally |
| `belt app deploy` | Deploy to inference.sh (`--skip-validation` uploads the committed schema files without running the local engine, for machines where it cannot run) |

### Running Apps

| Command | Purpose |
|---------|---------|
| `belt app run` | Run an app in the cloud |
| `belt app estimate` | Predict run cost without dispatching a task |
| `belt app sample` | Generate sample input (file fields use `./your-file.jpg` placeholders) |

For [multi-function apps](/docs/extend/multi-function-apps), `belt app sample` defaults to the resolved default function (`run`, explicit `default_function`, or the sole function). Use `-f` / `--function` for other entry points. `belt app get` lists all functions with run/sample hints.

When an app is missing required secrets or credentials, `belt app run` (and `belt agent run`) prints categorized setup hints instead of a generic API error — for example `belt secrets set <KEY>` (prompts for the value) or `belt credentials connect <provider>`. MCP requirements suggest `belt mcp connect <slug>`. When a requirement names no provider, the hint is `connect at: https://app.inference.sh/vaults`. The same requirement shape is returned by `POST /run` as HTTP **412** — see [Check requirements](/docs/api/rest/credentials#check-requirements-pre-flight).

**JSON input** — `belt app run`, `belt mcp run`, and `belt flow run` resolve input from three sources, in priority order: `--input` / `-i` flag → positional argument → piped stdin (when stdin is not a TTY). Each source accepts inline JSON (`'{"prompt": "a cat"}'`), `@filename`, or a path to an existing JSON file.

```bash
belt app run namespace/my-app '{"prompt": "a cat"}'          # positional
echo '{"prompt": "a cat"}' | belt app run namespace/my-app   # stdin
belt mcp run linear:list_issues '{"project": "INF"}'         # positional
belt flow run abc123 --input '{"text":"hello"}' -f           # flag (unchanged)
```

Omit input entirely when the app, tool, or flow does not require it.

**Filter output (`--jq`)** — `belt app run` and `belt mcp run` accept `--jq` with a [jq](https://jqlang.org/) expression to filter JSON on stdout without piping to an external `jq` binary (the CLI embeds the same pure-Go engine GitHub CLI uses). String results are printed unquoted (like `jq -r`); other values are compact JSON, one per line.

```bash
belt mcp run linear:list_issues --jq '.issues[] | {id, title}'
belt app run okaris/flux -i '{"prompt": "a cat"}' --jq '.output.url'
belt app run okaris/flux -i '{"prompt": "a cat"}' --no-wait --jq '.id'   # task id only
```

`belt app run --jq` evaluates against the task object (`id`, `status`, `status_text`, `output`, `error`, `session_id`, `created_at`, `updated_at`). App outputs live under `.output` — for example `.output.url` for a generated file URL. `--jq` suppresses progress lines on stdout (same as `--json`). Use `--json` or `--pjson` when you need the full task or tool payload instead.

`belt app run` exits `1` (`error`) when the final task status is `failed` or `cancelled`. With `--json` or `--jq`, stdout still contains the task JSON, so check `$?` or the `status` field. See [Exit codes](#exit-codes).

**Output files** — the task result prints to stdout (progress and status lines go to stderr), so `belt app run … | jq` and `> out.json` work. With `--json`, `files` lists every file the app produced (`path` and `url`, found from the app's output schema). `--urls` prints only those URLs, one per line. `--download` (bare form: current directory) or `--download=DIR` saves each file as `<task>-<field>-<n>.<ext>` and adds `local_path` to the JSON entries.

```bash
belt app run okaris/flux -i '{"prompt": "a cat"}' --urls
belt app run okaris/flux -i '{"prompt": "a cat"}' --download=./out
belt app run okaris/flux -i '{"prompt": "a cat"}' --json --jq '.files[].url'
```

**Batch** — `--batch prompts.jsonl` (or `-` for stdin) runs one task per line and writes one JSONL result per input (`index`, `task_id`, `status`, `output`, `error`, `submit_error`, `attempts`). `--concurrency N` (default 4) bounds how many tasks are in flight; `--retry N` re-submits an input after a transient submit error (5xx, 429, network) or a provider-side task failure, with a 2s·attempt backoff.

```bash
belt app run pruna/p-image --batch prompts.jsonl --concurrency 8 --retry 1 > results.jsonl
```

**Cost estimation** — `belt app estimate` reports the predicted cost of a run without dispatching a task. `belt app run --estimate` (`-e`) does the same when you already have a run command written. Both call `POST /store/apps/{appId}/estimate` with the same input handling as `belt app run` (inline JSON, `@file`, positional, or stdin). Apps with fixed per-call pricing can be estimated with no input; input-dependent pricing needs the intended input JSON.

```bash
belt app estimate okaris/flux --input input.json
belt app estimate okaris/flux --input '{"prompt":"a cat"}'
belt app run okaris/flux --estimate --input '{"prompt":"a cat"}'
belt app estimate okaris/flux --input input.json --json --select microcents,confidence
```

Human-readable output shows `estimated cost` with a confidence level:

| `confidence` | Display |
|--------------|---------|
| `exact` | Single dollar amount (all fees are input-based) |
| `range` | Min–max dollar range (from the app's `estimate` CEL expression) |
| `unknown` | `unknown until the app runs` plus `depends on:` fields when pricing needs post-execution data |

With `--json`, stdout is the full `EstimateCostResponse` (`confidence`, `microcents`, `min`, `max`, `depends_on`, `pricing_description`, `estimate_error`). → [Estimate app cost](/docs/api/rest/store#estimate-app-cost) · [Usage-based pricing](/docs/extend/pricing)

### Managing Apps

| Command | Purpose |
|---------|---------|
| `belt app list` | List your deployed apps (`--search`, `--limit`, `--cursor`, `--json`; `belt app my` / `belt app list` aliases) |
| `belt app store` | Browse the public app store (`--featured`, `--category`, `--new`, `--limit`, `--cursor`, `--json`, `--save`) |
| `belt app search` | Search apps by name or description |
| `belt suggest <query>` | Unified discovery (apps, skills, knowledge, docs) |
| `belt app get` | Get app details |
| `belt app pull` | Download or sync an app |

**Programmatic listing:**

```bash
belt app list --json                         # your deployed apps (requires login)
belt app list --search "flux" --json
belt app list --limit 50 --cursor <next>     # cursor pagination (see hints in table output)
belt app store --json                        # public store with cursor pagination metadata
belt app store search "video" --category video --json
belt app store --save store-snapshot.json    # write JSON to file instead of stdout
```

`belt app list --json` prints the full cursor-list response from `POST /apps/list` (`items` as `AppDTO[]`, plus `next_cursor`, `has_next`, …). `belt app store --json` prints the same cursor-list shape from `POST /store/apps/list` (`items` as `PublicAppStoreDTO[]`) — see [List approved apps](/docs/api/rest/store#list-approved-apps). In table output, both commands print `next page:` / `prev page:` hints with the `--cursor` value when more results exist. `--save` writes the store payload to a file (always pretty-printed) and prints a confirmation line to stdout.

**`belt app pull`** syncs app code and metadata from inference.sh:

```bash
belt app pull              # sync current app (from inf.yml in cwd)
belt app pull --meta       # sync only the listing into inf.yml (no code)
belt app pull <app-id>     # download app by ID to namespace/name/
belt app pull --all        # sync every local app (finds inf.yml recursively)
belt app pull --all --meta # sync only the listing for every local app
belt app pull <app-id> --force   # overwrite existing directory without prompt
```

A pull merges into `inf.yml`; it does not rewrite the file (CLI 1.19.11 and later):

- The listing comes down from the platform: `title`, `description`, `category` and `images`. Use `--meta` after editing an app's images or description on its page.
- Everything else stays as you wrote it, including comments, key order and keys such as `default_function`, `secrets` and `credentials`. These reach the platform when you deploy.
- A value the platform does not have, such as an empty image or no title, leaves the local one in place.
- A full pull also takes the version's `metadata` and fills in keys the deployed `inf.yml` lacks.

An app pulled into a new directory gets `namespace` and `name` in its `inf.yml`, so deploy and run commands resolve the correct `namespace/name` ref.

If the name in `inf.yml` answers with a different app on the platform, because the app was renamed or redirected, the pull stops with an error and changes nothing.

When syncing an existing directory, the CLI removes old app files before extracting the new archive but preserves local environment and VCS paths: `.git`, `.env`, `.infsh`, `.venv`, `venv`, `node_modules`, and `__pycache__`.

**`belt app get`** shows schemas, pricing, and run/sample hints for an app ref (`namespace/name`, `namespace/name@version-id`, or app ID). With `--json`, stdout is `{"app": <AppDTO>, "pricing": ...}` from `GET /apps/{ref}`. When the ref (or a platform alias on it) pins a function, `app.resolved_function` names that entry point — see [Get App](/docs/api/rest/apps#get-app).

```bash
belt app get okaris/flux
belt app get okaris/flux@abc123 --versions   # version history
belt app get okaris/flux --json              # includes status fields for scripts
```

When [lifecycle status](/docs/api/rest/apps#app-lifecycle-status) is not `active`, human-readable output shows a banner after the description:

| Status | Display | Run hints |
|--------|---------|-----------|
| `deprecated` | `⚠ deprecated` (warning) plus optional `status_message` | Shown — app still accepts runs |
| `maintenance` | `⚙ under maintenance` (warning) plus optional `status_message` | Omitted — the banner already indicates runs are unavailable |
| `retired` | `✕ retired` (error) plus optional `status_message` | Omitted — the banner already indicates runs are unavailable |

`belt app run` against a `maintenance` or `retired` app returns API errors (`503 app_maintenance`, `410 app_retired`) — see [Tasks API](/docs/api/rest/tasks#errors). Use `belt app get` before scripting runs to check status and the owner's message (for example, a replacement app ref).

**`belt suggest`** calls the same discovery API as `GET /suggest`. Results include a suggested CLI command or URL for each hit:

```bash
belt suggest "flux image"
belt suggest "web search" --json     # JSON for agent hooks
belt suggest "deploy" --agent        # directive framing for agent hooks
```

With no arguments, `belt suggest` reads stdin (hook JSON or plain text). CLI args take precedence over stdin.

Human-readable output groups results by category (`app`, `skill`, `knowledge`, and so on). Each section header shows the category and a command template (for example `belt skill use <name>`). Result lines include an optional inline `[tag]`, the name, and description. After a cross-collection search, stderr may suggest scoped follow-ups such as `belt app suggest "..."`.

**Hook mode (`--json`):** Returns `hookSpecificOutput.additionalContext` with one line per match: `- [type] name — description`, then `run: <command>` on the next line. Results are ranked by score and deduped server-side. Hook stdin is JSON with a `prompt` field (or plain text). When `transcript_path` is present, belt extracts assistant text from the current turn (since the last user message, up to 1000 characters) and sends it as the `context` field to `POST /suggest` for semantic ranking — keyword matching still uses `prompt` only. Belt also sends **scope signals** from the current directory (`git:` remote, `lang:`, `module:`, and similar tags) on every suggest call so knowledge captured in the same repo or language ranks higher. API failures are silent in `--json` mode so hooks do not pollute agent stderr.

Use `--agent` to frame hook output as a directive to the agent. Descriptions in suggest results are always one line of at most 200 characters; load the resource to read the rest. Hook integrations installed via `belt plugin install <agent>` call `belt suggest --json` automatically.

`belt suggest` also sends `scope` tags from the current working directory (git remote, module name, language, path) so project-relevant knowledge ranks higher in results. Entries uploaded with `belt know upload` carry the same tags unless you used `--global`.

Use `belt app search` when you only need apps. Use `belt suggest` for cross-collection discovery (skills, knowledge, docs, and apps).

→ [Search concept](/docs/concepts/search) · [Search API](/docs/api/rest/search)

### Secrets

| Command | Purpose |
|---------|---------|
| `belt secrets list` | List all secrets (`--json`) |
| `belt secrets set <key>` | Create or update a secret. The value is read from stdin (or `--value-stdin`); on a terminal it is prompted without echo. A value argument still works but lands in shell history. Flags: `--provider`, `--provider-name`, `--provider-website`, `--as` / `--team` |
| `belt secrets attach <key> <provider>` | Link an existing secret to a provider's credential |
| `belt secrets detach <key>` | Turn a linked secret back into a plain secret |
| `belt secrets get <key>` | Get a secret (masked; `--json`) |
| `belt secrets delete <key>` | Delete a secret |

### Connectors (MCP)

| Command | Purpose |
|---------|---------|
| `belt mcp list` | Browse MCP connector servers (`--category`, `--featured`, `--json`) |
| `belt mcp search <query>` | Search servers by name or description (same filters as `list`) |
| `belt mcp get <slug>` | Show server details and connection status |
| `belt mcp connect <slug>` | Connect OAuth for a server (`--as`, `--team`, see [Credentials (CLI)](#credentials-cli)) |
| `belt mcp disconnect <slug>` | Disconnect an MCP server (confirms; `--yes` to skip) |
| `belt mcp refresh <slug>` | Re-run discovery and enrichment on a server |
| `belt mcp tools <slug>` | List tools on a connected server (`slug:tool` to show one tool's schema; `--json`) |
| `belt mcp run <slug>:<tool>` | Call a connector tool from the terminal (`<slug>` alone lists tools, same as `mcp tools`) |

`belt mcp run` and `belt mcp tools` use `slug:tool` format — the first colon splits the server slug from the tool name (for example `linear:list_issues`). Pass only the slug to `belt mcp run` to list available tools instead of calling one. If you mistype an MCP subcommand, the CLI prints this format hint. After `belt mcp list`, stderr may show tips for `belt mcp search`, `get`, and `connect`.

`belt mcp run <slug>:<tool> --help` fetches that tool's schema (description, `required` fields, JSON Schema, and a sample `belt mcp run … --input '…'` line) before the usual usage text — the same detail as `belt mcp tools <slug>:<tool>`. Tool input is `-i` / `--input`, a positional JSON argument, or stdin; mistyped flags such as `--args` print the parse error on stderr plus a reminder instead of exiting silently. When the server returns `is_error: true`, stderr repeats the server's message when present and suggests `belt mcp tools <slug>:<tool>` for the schema; stdout still holds the full JSON payload. When the tool result is only one page of a larger list (`next_cursor`, `has_more`, `pageInfo`, and similar), stderr also hints how to pass the cursor in `--input` for the next page — see [Using tools — Call a tool](/docs/connectors/using-tools#call-a-tool).

With `--json`, `belt mcp tools` prints the tool surface from the connected MCP server: an array of `name`, `description`, and `inputSchema` objects when you pass only the slug, or a single tool object when you pass `slug:tool`. Tool names, descriptions, and schemas are authored by the MCP server (third-party content), so combine `--wrap-untrusted` when piping this output to an LLM — see [Global automation flags](#global-automation-flags).

```bash
belt mcp tools linear --json
belt mcp tools linear:list_issues --json
belt mcp tools linear --json --select name,description   # projection for agents
```

Some MCP tools need extra input mid-call (Multi Round-Trip Request / MRTR). In an interactive terminal, `belt mcp run` prompts for missing fields (form mode) or opens a browser for OAuth-style steps (URL mode), then retries automatically. In non-interactive mode (`CI`, no TTY, or `BELT_NON_INTERACTIVE=1`), the CLI prints the `input_required` JSON envelope to stdout and exits with code **2** — scripts must parse `input_requests` and call the API again with `inputResponses`. See [Using tools — Interactive elicitation](/docs/connectors/using-tools#interactive-elicitation-mrtr).

→ [Connectors overview](/docs/connectors/overview) · [Browsing connectors](/docs/connectors/browsing) · [Using tools](/docs/connectors/using-tools)

### Credentials (CLI)

List, connect, and inspect OAuth, service account, and MCP credentials for your workspace:

| Command | Purpose |
|---------|---------|
| `belt credentials list` | List connected credentials (`--limit`, `--cursor`, `--json`; table output prints pagination hints). Table columns: `company`, `slug` (the provider / credential slug), `sign-in` (OAuth, API key, HTTP), `status`, `owner` (you, the workspace, the organization, or the platform), `keys` (an API key's linked secret keys). The table also lists providers whose OAuth app is set up but not connected yet; `--json --apps` adds them to the JSON as `apps` (`--json` alone stays a bare array) |
| `belt credentials get <provider>` | Show one provider slug and its saved credential (`--json` for scripts). Table mode merges `GET /credentials/configs` with `GET /credentials/{provider}`: a **provider** block (`name`, `company` when it differs from the provider name, `slug`, `sign-in`, `website`, `docs`, declared `keys`) and a **credential** block (`owner`, `status`, connection times, linked keys or OAuth accounts) |
| `belt credentials connect <provider>` | Connect OAuth, API-key, or service-account providers (`--as`, `--team`) |
| `belt credentials disconnect <provider>` | Remove a workspace connection |

```bash
belt credentials list
belt credentials list --limit 50 --cursor <next>   # when stderr hints show more pages
belt credentials get google
belt credentials get mcp --json   # includes credential id for agent tools
belt credentials connect google
belt credentials connect slack
belt credentials disconnect google
```

`belt creds` is an alias for the same command group.

`belt credentials connect` looks up the provider in `GET /credentials/configs`. **OAuth** providers open your browser and poll for up to two minutes. **API key** providers prompt for each declared secret field (sensitive fields are masked), save each as the provider's credential (the same as `belt secrets set <KEY> --provider <provider>`), and report connected when the server marks the credential connected. **Service accounts** connect directly when the provider supports it. If the provider is already connected, the CLI prints the account and suggests disconnecting first. Unknown providers list available options; for MCP servers, use `belt mcp connect <slug>` instead.

**Connection scope:** Pass `--as user|team|org|platform` (or `--team` as shorthand for `--as team`) to set who owns the credential. The flag sets `connection_scope` on the connect request. Omit both flags to use the provider default. Invalid `--as` values are rejected before any API call. To change who owns an existing connection without reconnecting, use `PUT /credentials/{provider}/scope` — see [Change connection scope](/docs/api/rest/credentials#change-connection-scope).

```bash
belt credentials connect google --as team      # team admin: shared team OAuth
belt mcp connect linear --as user              # personal MCP authorization (default for MCP)
```

Google OAuth and the managed [service account](/docs/credentials/google-service-account) are separate providers (`google` vs `google-sa`). Connect the service account from **vaults → credentials** (`google-sa` slug) or with `belt credentials connect google-sa`.

**Non-interactive OAuth (CI, scripts):** When stderr is not a TTY, `CI` is set, or `BELT_NON_INTERACTIVE=1`, `belt credentials connect` prints the authorization URL but does not open a browser or poll. Open the URL manually, then verify with `belt credentials get <provider>`. For **API key** providers in the same conditions, the CLI prints `belt secrets set <KEY> --value-stdin --provider <provider> < value-file` lines instead of prompting.

Provider keys match the REST API: `google`, `google-sa`, `gcp`, `slack`, `mcp`, and so on. MCP servers all sit under `mcp`; the server URL is the credential's account identifier, and `GET /credentials/mcp?identifier={server_url}` names one. Use `belt credentials get --json` when you need the credential `id` for [agent connector tools](/docs/api/agent/connector-tools).

→ [Credentials API](/docs/api/rest/credentials) · [Connectors overview](/docs/connectors/overview)

### Vaults (CLI)

Inspect workspace **vaults**, the containers that group credentials and secrets. In the web app they are under [vaults](https://app.inference.sh/vaults).

| Command | Purpose |
|---------|---------|
| `belt vaults list` | List workspace vaults (`ls` alias, `--json`) |
| `belt vaults show [vault-id]` | Show a vault's credentials and secrets |

`vault` is an alias for `vaults` (for example `belt vault show`). Both commands are read-only.

```bash
belt vaults list
belt vaults show                    # default vault
belt vaults show vault_abc123       # specific vault by ID
belt vaults show --json
```

`belt vaults show` with no argument resolves the workspace's default vault (`GET /vaults/default`). It prints two tables: **credentials** (provider, type, scope, status, display name) and **secrets** (key, value, description). Secret values are masked.

With `--json`:

- `belt vaults list` prints a `VaultDTO[]` array (`id`, `name`, `description`, `is_default`, `credential_count`, `secret_count`, ...)
- `belt vaults show` prints `{"vault_id", "name", "credentials": [...], "secrets": [...]}`

To connect credentials or set secrets, use `belt credentials connect`, `belt secrets set`, or the [Vault UI](https://app.inference.sh/vaults).

→ [Vaults API](/docs/api/rest/vaults) · [Credentials overview](/docs/credentials/overview) · [Environment secrets](/docs/secrets/environment)

### Flows (CLI)

Create, edit, run, and publish flows from the terminal — same APIs as [Flows](/docs/api/rest/flows) and [Flow Runs](/docs/api/rest/flow-runs).

**Flow refs** — Commands that target a saved flow accept `namespace/name` or a flow ID (full `flow_…` ID or short ID). Resolution matches `belt agent get` and `belt agent run`: namespaced refs call `GET /flows/{namespace}/{name}`; IDs call `GET /flows/{id}`.

| Command | Purpose |
|---------|---------|
| `belt flow create --name <name>` | Create an empty flow (`--json` for raw response) |
| `belt flow list` | List your flows (`ls` alias; `--json`) |
| `belt flow get <ref>` | Show flow metadata (`--json`) |
| `belt flow describe <ref>` | Show the full graph with node I/O fields and connections |
| `belt flow duplicate <ref>` | Copy a flow (`dup` / `cp` aliases) |
| `belt flow delete <ref>` | Delete a flow |
| `belt flow run <ref>` | Start a flow run (`--input` JSON, `-f` to stream progress) |
| `belt flow runs` | List recent flow runs (`--limit`, `--json`) |
| `belt flow run-get <run-id>` | Get run status and output (`-f` to follow until complete) |
| `belt flow cancel <run-id>` | Cancel a running flow |
| `belt flow publish <ref>` | Deploy the flow as a standalone app |

**Graph editing** — `belt flow edit` applies draft mutations via `POST /flows/{id}/actions`. The first argument is the same `<ref>` as `belt flow get`:

| Command | Purpose |
|---------|---------|
| `belt flow edit node add <ref> <name> --app <app-ref>` | Add an app node (resolves `namespace/name` to app ID) |
| `belt flow edit node rm <ref> <name>` | Remove a node |
| `belt flow edit node inspect <ref> <name>` | Show input/output fields with types and defaults |
| `belt flow edit node set-input <ref> <name> <key> [value]` | Set a static value or `--from node:key` connection |
| `belt flow edit node clear-input <ref> <name> <key>` | Clear an input value or connection |
| `belt flow edit edge add <ref> <source> <target>` | Add a structural edge between nodes |
| `belt flow edit edge rm <ref> <source> <target>` | Remove an edge |
| `belt flow edit output set <ref> <field> --from <node>:<key>` | Map a node output to the flow output |
| `belt flow edit output rm <ref> <field>` | Remove an output mapping |
| `belt flow edit set-schema <ref> --input '...' --output '...'` | Set flow-level input/output JSON Schema (`type` must be `"object"`; see [Flows API](/docs/api/rest/flows#flow-inputoutput-schema-validation)) |
| `belt flow edit undo <ref>` | Undo your most recent graph edit (server-side action log; see [Undo and redo](/docs/api/rest/flows#undo-and-redo)) |
| `belt flow edit redo <ref>` | Redo your most recently undone edit |
| `belt flow edit actions <ref> [json]` | Apply raw action JSON (escape hatch for uncovered mutations) |

```bash
belt flow get myteam/voice-pipeline         # by namespace/name
belt flow get abc123                        # by short ID
belt flow create --name voice-pipeline
belt flow edit node add myteam/voice-pipeline tts --app elevenlabs/tts
belt flow edit node set-input myteam/voice-pipeline tts text "hello world"
belt flow edit node set-input myteam/voice-pipeline voice-changer audio --from tts:audio
belt flow edit output set myteam/voice-pipeline audio --from voice-changer:audio
belt flow describe myteam/voice-pipeline
belt flow run myteam/voice-pipeline -f
belt flow publish myteam/voice-pipeline
```

Use `input` as the source node in `--from` to wire a node input to a flow-level input (for example `--from input:text`). `belt flow run -f` streams NDJSON progress events with polling fallback when SSE is unavailable.

→ [Flows guide](/docs/flows/overview) · [Creating a flow](/docs/flows/creating) · [Flows API](/docs/api/rest/flows) · [Flow Runs API](/docs/api/rest/flow-runs)

### Agent integration (CLI)

Wire belt into a local coding agent runtime (discovery hooks and core skills):

| Command | Purpose |
|---------|---------|
| `belt plugin install [agent...]` | Wire belt into coding agents: hooks, core skills, and a belt section in the agent's instruction file (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, steering, rules). No args detects installed agents and sets up all of them; `--all` covers every supported agent; `--scope project` writes into the current repo; `--rules=false` skips the instruction file. Claude Code and Codex go through their plugin marketplaces. |
| `belt plugin uninstall [agent...]` | Remove hooks and the belt section. No args cleans every agent that has belt installed. |
| `belt plugin prompts` | List, `show`, `export`, and `reset` the extraction prompts and the rules block. Overrides live in `<repo>/.beltsh/prompts/<name>.md` (team) or `~/.belt/prompts/<name>.md` (personal); `{{default}}` includes the embedded text. |
| `belt plugin status` | List each detected agent and whether belt hooks are installed at user scope, project scope, both, or not at all |
| `belt plugin info` | Show belt plugin capabilities and install status |
| `belt plugin doctor` | Check belt plugin health (`--fix` to auto-repair; `--check --json` for CI and agents) |
| `belt plugin history` | Import knowledge from Claude Code conversation history |
| `belt review --agent <name>` | Extract knowledge from agent sessions (hook command) |

**`belt review`** is called by Stop and PreCompact hooks installed via `belt plugin install <agent>`. Claude Code plugin hooks call `belt plugin review` instead. It reads hook JSON from stdin, evaluates the session transcript, and saves reusable knowledge to your workspace. Each candidate is deduped against similar entries already in your registry (search, then create / update / skip) — see [Knowledge API — extraction deduplication](/docs/api/rest/knowledge#extraction-deduplication).

```bash
belt review --agent cursor --trigger stop        # called by Cursor Stop hook
belt plugin review --trigger stop                # called by Claude Code Stop hook
belt review --agent claude-code --trigger stop   # same pipeline as belt plugin review
belt review --agent codex --dry-run                # preview without saving
belt review --agent cursor --force                 # skip 10-turn gating
```

| Flag | Purpose |
|------|---------|
| `--agent` | Runtime: `claude-code`, `codex`, `cursor`, `gemini`, or `windsurf` (defaults to `claude-code`) |
| `--trigger` | Hook that invoked review: `stop`, `precompact`, or `session-end` (default `stop`) |
| `--force` | Run immediately instead of every 10 user turns on Stop |
| `--dry-run` | Print extraction results without saving |

Claude Code uses fork-based extraction and can auto-publish skills. Other agents use transcript-based extraction through the inference.sh API. Requires `belt login`.

→ [Coding agents](/docs/extend/coding-agents) — skills directories, hook behavior, and Claude Code details

### Agents (CLI)

| Command | Purpose |
|---------|---------|
| `belt agent create <name> [description]` | Create an agent from flags (no YAML file) |
| `belt agent deploy <path>` | Create or update an agent from a YAML definition |
| `belt agent pull <ref>` | Export agent config as YAML (`--save` / `-o` to write a file) |
| `belt agent list` | List your agents |
| `belt agent get <ref>` | Show agent details (version, tools, skills, context) |
| `belt agent delete <ref>` | Delete an agent (`rm` alias) |
| `belt agent run <ref> "<message>"` | Run an agent from the terminal |
| `belt agent run <ref> "<message>" --chat <id>` | Continue an existing chat |
| `belt agent run <ref> "<message>" --no-wait` | Return chat ID immediately (use `belt chat` to follow up) |
| `belt agent run <ref> "<message>" --timeout <duration>` | Max wait for response (default `4m`; e.g. `10m`) |

**`belt agent create`** calls `POST /agents` with a first version. Use it for quick agents from the terminal; use `belt agent deploy` when you need skills, context fields, output schema, or other YAML-only options.

Redeploying the same agent **name** creates a new version instead of failing with a name conflict. This is the same upsert behavior as [`belt app deploy`](/docs/extend/deploying#update). The namespace is assigned from your active workspace server-side, so the `namespace` field in YAML is optional. You only need a stable `name` in the file to update an existing agent.

```bash
belt agent create my-helper "A helpful assistant"
belt agent create todo-bot --model openrouter/claude-sonnet-46 --prompt "You manage Todoist tasks"
belt agent create todo-bot --model openrouter/claude-sonnet-46 \
  --mcp 46e8a4bq...:find-projects \
  --mcp 46e8a4bq...:add-tasks
belt agent create my-bot --json    # raw AgentDTO on stdout
```

| Flag | Purpose |
|------|---------|
| `--model` | Core app ref (e.g. `openrouter/claude-sonnet-46`) |
| `--prompt` | System prompt |
| `--mcp` | MCP tool as `server:tool_name`, the server by its slug (repeatable); it runs as each person's own login. List tool names with `belt mcp tools <slug>` |
| `--json` | Output the created agent as JSON |

On success, the CLI prints `namespace/name@version-id` for use with `belt agent run`.

**`belt agent pull`** exports an agent version as YAML for local editing and redeploy. Ref format matches `belt agent get` and `belt agent run` (`namespace/name`, `namespace/name@version-id`, or agent ID). Print to stdout or save with `--save` / `-o`:

```bash
belt agent pull myteam/support-bot
belt agent pull myteam/support-bot@abc123 --save agent.yml
```

YAML from pull is intended to round-trip through `belt agent deploy` without losing configuration: `call` and `http` tool auth, headers, and JSON schemas; client tool `input_schema` / `output_schema`; skill `skill_id` and `version_id` pins; `preload` (system-prompt injection vs on-demand `skill_get`); inline skill `content` for text-defined skills; the `permissions` block (today `allow_all_tools`, copied into each new chat created in the agent's own workspace so unattended runs do not stall on tool approval); and lifecycle `hooks`, including the builtin learning hooks. Redeploying YAML that omits `hooks` clears hooks that were configured in the editor or on a prior version. See [Skills on agents](/docs/agents/skills), [Adding skills](/docs/skills/adding-to-agents), and [Approval settings](/docs/api/agent/approval).

Lifecycle hooks go under `hooks`. To turn on the learning hooks (the **suggest** and **learn** switches in the agent editor):

```yaml
hooks:
  - { event: agent.turn_start, type: builtin, handler: "belt:suggest" }
  - { event: agent.complete, type: builtin, handler: "belt:extract" }
  - { event: agent.pre_compact, type: builtin, handler: "belt:extract" }
```

Before belt v1.19.4, `belt agent deploy` ignored `hooks` and deployed a version without them. Update the CLI before redeploying an agent that has hooks. See [Lifecycle hooks](/docs/api/agent/lifecycle-hooks#builtin).

**`belt agent get`** accepts the same refs as `belt agent run` — `namespace/name`, `namespace/name@version-id`, or agent ID. With `--json`, stdout is the full `AgentDTO` from `GET /agents/{ref}`.

```bash
belt agent get myteam/support-bot
belt agent get myteam/support-bot@latest
belt agent get agent_abc123
```

Human-readable output uses lowercase labels and lists attached skills with their resolved ref and version pin:

```
  agent:  myteam/support-bot
  version: abc123
  description: Handles customer support
  system prompt: You are a helpful support agent...
  core app: openrouter/claude-sonnet-46

  tools:
    - search (call): Search the web

  skills:
    - myteam/pricing-rules@ver_xyz: Pricing guidelines

  context:
    - tenant_id (required): Tenant identifier
```

Each skill line shows `skill_id@version_id` when the agent pins a specific skill version; otherwise it falls back to the skill name from the agent definition. System prompts longer than 200 characters are truncated.

With `--no-wait`, the CLI returns as soon as the run is accepted. Without `--json`, it prints `Chat started: <chat-id>` with muted `belt chat get` / `belt chat pending` hints. With `--json --no-wait`, stdout is only `{"chat_id": "<id>"}` — use this in scripts that poll with `belt chat get` or `belt chat watch`.

Without `--no-wait`, `belt agent run` streams chat events until the agent finishes, needs approval, or hits `--timeout` (default `4m`). On timeout, the CLI prints the chat ID so you can follow up with `belt chat get` or `belt chat pending`. With `--json` (and no `--no-wait`), stdout is the full `POST /agents/run` response after streaming completes. Tool calls print as muted progress lines while they run:

```
→ calling search...
→ search completed
```

Failed tools print `→ {name} failed`. The final assistant message is printed when the run completes. After a successful run, the CLI prints a muted continuation hint with the chat ID — use `belt chat send <chat-id> "<message>"` for follow-ups without starting a new run.

### Chat (CLI)

Manage agent conversations and human-in-the-loop approvals from the terminal:

| Command | Purpose |
|---------|---------|
| `belt chat list` | List chats (`--status busy`, `awaiting_input`, `waiting`, `idle`, `completed`); prints a continuation hint below the table |
| `belt chat get <chat-id>` | Show transcript and pending tool invocations (`--last N`, `--all`, `--full`) |
| `belt chat send <chat-id> "<message>"` | Send a follow-up and stream the agent's response (same streaming as `belt agent run`) |
| `belt chat watch <chat-id>` | Re-attach to a running chat and stream until idle or another approval is needed |
| `belt chat pending` | List tool invocations awaiting approval |
| `belt chat approve <invocation-id>` | Approve HIL tool (`--watch` / `-w` to stream the response) |
| `belt chat reject <invocation-id>` | Reject (`--reason "..."`) |
| `belt chat stop <chat-id>` | Stop a running chat |

When `belt agent run` stops at `awaiting_input`, the CLI prints `belt chat approve` / `reject` commands for each pending invocation. For the unified interrupt model (widgets, auth gates, hook gates, and tool approvals), use `belt interrupt list` on the run ID from `belt chat get --json` — see [Interrupt (CLI)](#interrupt-cli).

**`belt chat get`** shows the last 3 messages by default. Use `--last N` for more, `--all` for the full transcript, or `--full` to disable truncation of message text and tool argument JSON (default truncates at 200 and 100 characters respectively). Pending tool invocations always show full args and an `approve:` hint.

**`belt chat send`** posts your message via `POST /chats/{id}/messages` and then streams chat events until the agent finishes, needs approval, or the stream times out (4 minutes) — the same SSE path as `belt agent run` without `--no-wait`. Tool calls print as muted progress lines (`→ calling {name}...` / `→ {name} completed` or `failed`). If the agent stops at `awaiting_input`, the CLI prints `belt chat approve` / `reject` commands. With `--json`, stdout is the created user message from `POST /chats/{id}/messages` immediately after the message is accepted, without waiting for the agent to finish — poll with `belt chat get --json` or run `belt chat watch` if you need the final transcript in automation.

Use **`belt chat watch`** when you did not just send a message — for example after `belt chat approve` without `--watch`, or to re-attach to a chat that is already `busy`.

### Interrupt (CLI)

List and resolve **interrupt gates** on agent runs. Interrupts are the unified gate model when a run pauses for tool approval, widget input, missing credentials, lifecycle hook gates, or other external input (`active_run.state` is `input_required` or `auth_required`). For tool-approval interrupts (`reason: tool_approval`), `belt interrupt resolve … allow` is equivalent to `belt chat approve`; `deny` is equivalent to `belt chat reject`.

| Command | Purpose |
|---------|---------|
| `belt interrupt list <run-id>` | List pending interrupts for an agent run (`GET /agent-runs/{id}/interrupts`) |
| `belt interrupt resolve <interrupt-id> <allow\|deny>` | Resolve a pending interrupt (`POST /interrupts/{id}/resolve`) |

```bash
# List pending interrupts for a run (run ID from belt chat get --json active_run.id)
belt interrupt list run_abc123

# Allow or deny a specific interrupt
belt interrupt resolve int_abc123 allow
belt interrupt resolve int_abc123 deny

# JSON output for scripting
belt interrupt list run_abc123 --json
belt interrupt resolve int_abc123 allow --json
```

With `--json`, `list` prints an array of `InterruptDTO` objects; `resolve` prints the updated interrupt. The table output shows interrupt ID, `reason`, `source`, and `resource` (resource ID).

→ [Agents REST API — Interrupt gates](/docs/api/rest/agents#interrupt-gates) · [Approval settings](/docs/api/agent/approval) · [Human-in-the-loop](/docs/runtime/human-in-the-loop)

### Skills (CLI)

Refs use `namespace/name` or `namespace/name@version-id` (same as the REST API). `belt skill use` without a version fetches the latest.

| Command | Purpose |
|---------|---------|
| `belt skill list` | List your uploaded skills (`--json`; table includes relative `updated` times) |
| `belt skill upload <path>` | Publish or update a skill from a directory or `SKILL.md` (`--name`, `--desc`, `--prune`) |
| `belt skill use <ref>` | Print a skill's instructions to stdout |
| `belt skill add <ref>` | Install a skill into your workspace |
| `belt skill files <ref>` | List supporting files (references, scripts, examples) |
| `belt skill view <ref> <path>` | View one supporting file |
| `belt skill versions <namespace>/<name>` | List version history (version ID, date, notes) |
| `belt skill supersede <old> <new>` | Mark an outdated skill as superseded by a replacement |
| `belt skill delete <ref>` | Soft-delete a skill (removed from search and suggest) |
| `belt skill store` | Browse the public skill store (`--featured`, `--category`, `--search`, `--page`) |
| `belt skill store search <query>` | Search approved store skills |

`belt skill list --json` prints a page-based list envelope: `items` as `SkillDTO[]`, plus `total`, `page`, `pages`, `has_next`, and `has_previous`. Pagination uses `--page` (20 per page). `--search` / `-q` filters by name or description. `--save` / `-o` writes the same shape to a file (pretty-printed). Entry fields match [List Knowledge](/docs/api/rest/knowledge#list-knowledge) `items`. Use `--results-only --select` to project fields from `items` — keys are stable snake_case in release builds.

`belt skill use` does not inline supplementary files. When a skill has extra files, stderr prints tips for `belt skill add` and `belt skill files`.

A skill version holds only the files in that upload. When updating an existing skill, `belt skill upload` refuses to publish if the upload omits supplementary files the current version has (for example after uploading a lone `SKILL.md` over a skill with `references/`). The error lists the paths that would be dropped. Upload the full skill directory, or pass `--prune` to publish without the missing files after you remove or rename them locally. New skills are not checked.

`belt know upload` applies the same guard to every upload of type `skill` (directory, file, or stdin), with the same `--prune` flag.

`belt skill versions` requires `belt login` — it resolves the skill by name, then calls `GET /knowledge/{id}/versions`. The table marks the current version at the bottom.

`belt skill supersede` requires `belt login`. Both arguments are `namespace/name` refs or skill IDs. The old skill is hidden from `belt suggest` and other discovery results; the replacement skill is unchanged. Use this when you publish a replacement under a new name instead of versioning in place.

`belt skill delete` requires `belt login`. Pass a skill ID or `namespace/name` ref. The CLI resolves refs to an ID and soft-deletes the entry (same backend path as `belt know delete`). `belt know delete` accepts the same ref forms for any knowledge type. The skill is removed from search indexing and no longer appears in suggest results. Destructive: prompts for confirmation unless you pass `--yes` / `-y`. To keep a replacement discoverable instead of removing the old skill, use `belt skill supersede`.

```bash
belt skill versions myteam/code-review
belt skill use myteam/code-review@abc123   # specific version
belt skill supersede myteam/old-review myteam/code-review
belt skill delete myteam/deprecated-skill --yes
```

→ [Skill registry](/docs/skills/registry) · [Delete Skill API](/docs/api/rest/skills#delete-skill) · [Supersede Skill API](/docs/api/rest/skills#supersede-skill) · [Search concept](/docs/concepts/search) · [Search API](/docs/api/rest/search) · [Knowledge API — versions](/docs/api/rest/knowledge#get-versions)

### Knowledge (CLI)

`belt know` is an alias for `belt knowledge`. Refs use `namespace/name` or `namespace/name@version-id` (same as the REST API). 

| Command | Purpose |
|---------|---------|
| `belt know list` | List entries (`--type`, `--search`, `--json`, pagination) |
| `belt know search <query>` | Search your knowledge entries |
| `belt know get <ref>` | Show metadata, file list, and instructions (up to 64KB) |
| `belt know upload <path>` | Upload a directory, file, or stdin (`--type`, `--name`, `--global`, `--prune` for skills) |
| `belt know delete <ref>` | Delete an entry (entry ID or `namespace/name`) |

`belt know list --json` uses the same page-based envelope as `belt skill list --json` (`items`, `total`, `page`, `pages`, `has_next`, `has_previous`). Filter with `--type` and `--search` / `-q`; paginate with `--page`. `--save` / `-o` writes the same JSON to a file (pretty-printed).

```bash
belt know list --type observation
belt know get acme/onboarding              # latest version (no login for public entries)
belt know get acme/onboarding@abc123       # specific version
belt know upload ./my-skill/               # directory with SKILL.md → skill
belt know upload ./notes.md --type reference
belt know upload ./notes.md --global       # skip project scope tags
```

By default, `belt know upload` attaches **project scope** tags from the working directory (git remote, module name, language, path) so `/suggest` can rank the entry higher in matching projects. Pass `--global` to omit scope for workspace-wide references that should surface everywhere.

When `belt know get` finds supplementary files (references, scripts, examples), the CLI prints tips for `belt skill add` and `belt skill files` so you can install or inspect them locally.

`belt know upload` auto-collects **scope signals** from the current directory (git remote, module name, language) and stores them on the new version — see [Knowledge — Project scope](/docs/api/rest/knowledge#project-scope).

→ [Knowledge API](/docs/api/rest/knowledge) · [Creating skills](/docs/skills/creating)

### Files (CLI)

`belt file` (alias `belt files`) lists, uploads, downloads, and deletes files in your workspace. Use it to save outputs from `belt app run` or `belt mcp run` without shelling out to `curl`.

| Command | Purpose |
|---------|---------|
| `belt file list` | List your files (`ls` alias; `--json`, `--limit`, `--cursor`, `--type`, `--search` / `-q`, `-l` / `--long`) |
| `belt file download <url-or-id>` | Download to cwd or `-o` / `--out` path (`-o -` pipes to stdout) |
| `belt file upload <path> [path...]` | Upload via presigned URL; returns permanent URI (`--name` for a single file only) |
| `belt file info <id>` | Show metadata including media dimensions, duration, and codec |
| `belt file delete <id>` | Delete a file (`rm` alias; `--force` / `-f` or `--yes` to skip confirmation) |

```bash
belt file list --type image --limit 20
belt file list --search photo
belt file list -q avatar --type image
belt file download https://files.inference.sh/abc/image.png
belt file download 01abc123def456 -o ./output.png
belt file upload ./photo.jpg --name avatar.jpg
belt file upload *.png
belt file info 01abc123def456 --json
belt file delete 01abc123def456 --force
```

`--type` filters by content category (`image`, `video`, `audio`, `text`) — the CLI matches MIME types like `image/%`. `--search` / `-q` filters by filename substring (case-insensitive). Combine both to narrow results (for example `-q avatar --type image`). In table output, the `type` column shows short labels (`image`, `video`, `audio`, or subtypes like `json`, `pdf`, `docx`, `csv`, `text`) instead of raw MIME strings. Downloads accept a file URL (for example from task output) or a file ID from `belt file list`. If the destination filename already exists, download auto-renames with `(1)`, `(2)` suffixes. Uploads and downloads show live progress (speed, bytes transferred, ETA); batch uploads print a `[1/N]` counter per file.

`belt app run` auto-uploads local file paths and `data:` URIs in `--input` JSON for schema fields with `format: file` — use `belt file upload` when you need a URI outside a task run. See [SDK files — automatic upload](/docs/api/sdk/files#automatic-upload).

→ [Files API](/docs/api/rest/files) · [SDK Files](/docs/api/sdk/files)

### Forms (CLI)

List, inspect, fill, and review submissions for schema-driven forms in your workspace (and public forms from other teams). Requires `belt login`.

| Command | Purpose |
|---------|---------|
| `belt form list` | List your team's forms (`ls` alias; `--all` includes other teams' public forms; pagination + `--json`) |
| `belt form get <namespace/name>` | Show title, status, submit policy, then each question in full (`--json` for the full `FormDTO`) |
| `belt form fill <namespace/name> [key=value ...]` | Answer and submit (`--data` / `--data-file` for JSON; interactive prompts for remaining questions) |
| `belt form submissions <namespace/name>` | List submissions for a form you own (`--json`, pagination) |

```bash
belt form list
belt form get infsh/use_case
belt form fill acme/feedback rating=5 role=dev langs=go,ts
belt form fill acme/feedback --data '{"rating":5}' --no-input
belt form fill acme/feedback --data-file answers.json --json
belt form submissions acme/feedback --limit 50
```

Refs use `namespace/name` (your workspace username is the namespace). `belt form fill` only submits when the form `status` is `open`.

**Inspect.** `belt form get` prints one block per question (never truncated): key, widget type, required/optional, full prompt text, description, choice labels, validation limits (length, numeric bounds, and similar), examples, and `only if` visibility rules. Use this before scripting `belt form fill` so agents see the same constraints the web app enforces.

**Answers.** Pass `key=value` pairs, `--data` (inline JSON or `@file`), or `--data-file`. Later sources override earlier ones; repeat a key to collect multiple values for multiple-choice questions. Values are coerced by question type (numbers for ratings, `yes`/`no` for yes-no, option labels or list indices for choices). In a terminal, belt prompts for any shown question still missing an answer; optional questions accept Enter to skip. Conditional questions appear and disappear as earlier answers change visibility (same rules as the web app).

**Automation.** With `--no-input` or without a TTY, every required visible question must be supplied up front or the command fails. Local checks (missing required answers, bad coercion) and API `422` field errors are reported together in question order. On stderr, each problem is one `key: message` line; when belt knows the accepted shape, the line adds `— expects …` (question kind, choice labels, and limits such as length or numeric bounds). A muted footer points at `belt form get <ref>` for every question with full prompts and limits. With `--json`, stdout is a single object `{ "error", "form"?, "missing"?, "fields"? }`; each entry in `missing` or `fields` includes `key`, optional `title`, `message`, and `expects` when applicable, so agents can fix every problem without reading the whole form first. Answers to hidden questions are dropped; non-empty answers to questions that are not shown produce a muted warning.

**Claims.** `belt form fill` records answers only — it does not print credit amounts or grant rewards. When a bounty program uses the form as proof (`proof_type: form`), claim credits with `belt bounty claim <program>` (see below). That command reuses an existing submission or fills the form first in one step.

→ [Forms API](/docs/api/rest/forms) · [Bounties (CLI)](#bounties-cli)

### Bounties (CLI)

List active credit programs from `GET /bounties` and claim them with a proof (form submission or deployed app). Requires `belt login`. Responses are cached locally for 6 hours (`~/.belt/cache/bounty-programs.json`).

| Command | Purpose |
|---------|---------|
| `belt bounty list` | Show each program's name, reward, and how to claim (`bounties` alias; `--json` for machine output) |
| `belt bounty claim <program> [key=value ...]` | Claim a **form** bounty — fills `proof_form` when you have not submitted yet (same `--data` / `--data-file` / prompts as `belt form fill`) |
| `belt bounty claim <program> <namespace/app>` | Claim an **app** bounty (for example `app-builder`) |

```bash
belt bounty list
belt bounty claim agent-survey
belt bounty claim agent-survey role=dev langs=go,ts --no-input
belt bounty claim agent-survey --data-file answers.json --json
belt bounty claim app-builder acme/my-app
```

`belt bounty list --json` returns `{name, description, amount_microcents, reward, proof_type, proof_form?, command?, claimed?}` per program. Form programs show `claimed: true` after a successful claim on this machine.

**Form programs.** Use `belt form get <proof_form>` to inspect every question before scripting. With `--no-input` or without a TTY, missing required answers exit **2** and print the same structured errors as `belt form fill` (`expects` hints and a `belt form get` footer). When a saved payment method is required, exit **9** includes `billing_url` and the exact `belt bounty claim …` command to rerun; your answers are kept.

**App programs.** The app must be deployed on inference.sh. `belt bounty claim app-builder <namespace/app>` is the same as `belt app promo submit <namespace/app>` (alias). Typical rules: useful app, not a duplicate of an existing listing, public after claim, daily limit per program — see `belt app promo` help.

`belt bounty claim --json` prints one object: `status` is `claimed`, `payment_method_required`, `already_claimed`, or `failed`; missing form answers use the fill object `{error, form, missing, fields}` on stdout.

→ [Bounties API](/docs/api/rest/teams#bounties)

### App bounties (CLI)

The **app-builder** program pays workspace credits for useful apps you deploy and make public. Reward amounts and claim limits come from `GET /bounties` and can change without a CLI release; `belt bounty list` shows the current offer (`belt app promo` points there instead of naming an amount).

| Command | Purpose |
|---------|---------|
| `belt bounty list` | List active programs with reward and the command that claims each one (`--json` for machine-readable rows) |
| `belt bounty claim app-builder <namespace/app>` | Claim the app-builder bounty for a deployed app you own |
| `belt app promo` | Show app-builder rules, examples, and how to claim |
| `belt app promo submit <namespace/app>` | Same as `belt bounty claim app-builder <namespace/app>` |

App-builder rules (also shown by `belt app promo`):

1. The app must be deployed (`belt app deploy`).
2. The app must be useful and not duplicate something already on the platform.
3. Python or JavaScript apps are fine — simple utilities are great candidates.
4. Submitting agrees to make the app public so other agents can reuse it.
5. **One claim per day** for this program (`max_per_day` on the API) — check `belt bounty list` for limits on other programs.

```bash
belt bounty list
belt skill use inference-sh/building-inferencesh-apps   # optional starter skill
belt app deploy
belt bounty claim app-builder myteam/my-utility
# same as: belt app promo submit myteam/my-utility
```

Claims call `POST /me/bounty` with `bounty_id` set to the program `name` and `proof_id` set to your app ID; the CLI accepts `namespace/name` and resolves the app. When the program sets `requires_payment_method` and the team has no saved payment method, the API returns **402** `payment_method_required` and records nothing; add a card under [team billing payments](https://app.inference.sh/settings/team/billing/payments) and run the claim again.

### Feedback (CLI)

Submit customer-discovery answers and open feedback. Responses are stored on your account via `POST /me/survey` (requires `belt login`).

| Command | Purpose |
|---------|---------|
| `belt feedback` | List pending survey questions and bounty-linked forms (✓ when already submitted) |
| `belt feedback <question-id> "<answer>"` | Answer a structured question |
| `belt feedback "<message>"` | Send open feedback (`question_id` = `open`) |

Structured question IDs: `use_case`, `discovery`, `need`, `testimonial` (legacy `feedback` is still accepted by the API). Which questions are active, and the exact wording and options, come from active bounty programs named `survey-<id>` and the matching platform form `infsh/<id>` (first question in the schema). When the API cannot be reached and nothing is cached, the CLI falls back to built-in wording for those four IDs.

```bash
belt feedback                                    # see what's pending
belt feedback discovery "found it on github"
belt feedback use_case "building AI agents for customer support"
belt feedback need "better template discovery"
belt feedback testimonial "fastest way to ship agents with real integrations"
belt feedback "love the speed, wish there were more templates"
```

Survey answers are stored with `POST /me/survey`. `belt feedback` does not claim credits; a `survey-<id>` bounty is claimed separately with `POST /me/bounty` (see [Bounties](/docs/api/rest/teams#bounties)). The API rejects placeholder template text and stores nothing.

**Bounty forms.** Programs with `proof_type: form` name the form in `proof_form` (for example `infsh/marketplace_waitlist`). With `belt login`, `belt feedback` lists those programs (excluding single-question `survey-<id>` entries). Each line shows the title, reward, and `belt bounty claim <program>`. Lines show `✓` when the program is already claimed on this machine. Use `belt bounty list` for the same programs in table or JSON form.

Before most commands, the CLI may show one due bounty or survey notice at the top of stderr (the notice block). Programs come from `GET /bounties` (cached 6 hours); the CLI walks them in `notice_priority` order and respects each program's `notice_cooldown_hours`. Survey programs (`survey-<id>`) use the same wording as `belt feedback` (from `infsh/<id>` when available). Form programs use `notice_text` or `earn $X — <description>`, ending with `belt bounty claim <program>`; they keep appearing until claimed on this machine. Single-question surveys use a 1-hour cooldown per question; when every active structured question is answered, an occasional freeform prompt (72-hour cooldown) suggests `belt feedback "..."`. Notices are skipped for `belt version`, `belt update`, `belt feedback`, `belt login`, `belt logout`, `belt bounty`, and `belt app promo` so scripts and agents parsing those commands get clean output. A logged-out session does not spend survey notice cadence.

Survey prompts are **not** injected into `belt suggest --json` hook output — only the stderr notice path above. Submissions include optional metadata: `agent` (detected runtime, e.g. `cursor`) and `context` (your five most recent belt commands, comma-separated).

The CLI records the last 10 commands you run in `~/.belt/history` (via the post-command tips hook; skipped for `belt feedback` itself and when tips are suppressed with `INFSH_NO_TIPS=1`, `--json`, `--pjson`, or `--jq`, or when stderr is not a terminal). When you submit feedback, the last five entries are sent as `context`.

Survey prompts and other stderr tips are silent when stderr is not a terminal or `CI` / `BELT_NON_INTERACTIVE` is set. Inside a coding agent, only **UserKnowledge** survey questions (worded for the operator to answer) and **bounty forms** (intended for the agent to complete with `belt bounty claim <program> … --no-input`) appear in the notice block; cash bounties and self-answerable surveys are hidden so the agent does not claim rewards on the operator's behalf. `INFSH_NO_TIPS=1` disables them everywhere.

`belt skill use` keeps a copy of each resolved skill under `~/.belt/cache/skills/` and serves the newest cached copy, with a stderr note, when the API is unreachable.

→ [Survey API](/docs/api/rest/teams#survey) · [Forms API](/docs/api/rest/forms) · [Bounties (CLI)](#bounties-cli) · [Coding agents](/docs/extend/coding-agents#other-agent-runtimes-belt-plugin-install)

### Remotes (CLI)

Connect a machine to inference as a remote and optionally run commands on it. See [Remotes](/docs/extend/remote) for the security model and workflow.

| Command | Purpose |
|---------|---------|
| `belt remote start` | Register this machine and connect the outbound daemon (`--name`, `--exec`, `--agents`, `--detach`) |
| `belt remote enable` / `disable` | Linux (systemd): install or remove a user service that starts the remote at boot (`enable` accepts the same flags as `start`) |
| `belt remote list` | List remotes registered to your team |
| `belt remote tag <id> [tag...]` | Show, add, or remove policy tags on a remote (`--rm` to remove; needs `manage_policy`) |
| `belt remote rm <id>...` | Remove remote rows from your account (`--yes` to skip confirm) |
| `belt remote status` / `logs` / `stop` | Inspect or stop the local daemon (foreground, detached, or service) |
| `belt remote exec <remote-id> -- <command> [args...]` | Run a command on a connected remote and stream output (`--cwd`, `--timeout`, `--pty`) |

```bash
belt login
belt remote start --name work-laptop --exec    # opt in to command execution
belt remote start --agents                     # opt in to harness agent sessions
belt remote exec remote_abc123 -- ls -la
belt remote exec remote_abc123 -- bash -c "cd /tmp && make test"
```

`belt remote start` requires login and the `remotes:write` scope. It creates a machine identity key in `~/.inferencesh/` (or `BELT_CONFIG_DIR` when set), probes for installed agent harnesses (from the agentprotocol registry), registers with `POST /remotes`, and keeps an authenticated WebSocket open to inference. Registration is scoped to your active team (`belt auth switch`); restart the daemon after switching teams. Press Ctrl-C to disconnect a foreground daemon.

Starting again with different `--exec` or `--agents` than the running daemon offers to restart it (agent sessions on that machine end). Decline or run without an interactive terminal and belt tells you to `belt remote stop` first. Flags belong on **`belt remote start`**, not bare `belt remote`. `belt remote list` shows account remotes; after seven days without a heartbeat it suggests `belt remote rm` for stale rows.

**Exec opt-in.** Command execution is off by default. Start the daemon with `--exec` so it accepts `remote_exec_start` frames. Without `--exec`, `belt remote exec` gets `403 exec_disabled` from the API.

**Agent sessions opt-in.** Harness sessions are off by default. Start the daemon with `--agents` so it accepts `remote_session_*` frames and registers `agents_enabled: true`. Without `--agents`, the daemon refuses session opens with `agent sessions are disabled on this remote (start it with --agents)`. This is separate from `--exec`. See [Agent harness sessions](/docs/extend/remote#agent-harness-sessions).

**`belt remote exec`** streams stdout/stderr as output arrives and exits with the remote command's exit code. It is marked **destructive** (confirmation prompt, `--yes` in non-interactive mode). Automation should call `POST /remotes/{id}/execs` and poll `GET /execs/{id}/output`. See [Remotes API](/docs/api/rest/remotes).

→ [Remotes guide](/docs/extend/remote) · [Remotes API](/docs/api/rest/remotes)

### Tasks (CLI)

| Command | Purpose |
|---------|---------|
| `belt task list` | List recent tasks (`--status`, `--limit`, `--cursor`, `--app`) |
| `belt task get <id>` | Get task status and output (`-f` / `--follow` to stream until complete) |
| `belt task logs <id>` | View execution logs (`--type` to filter; `-f` to tail live output) |
| `belt task cost <id>` | Show cost breakdown in microcents |
| `belt task timings <id>` | Show timing breakdown by phase |
| `belt task cancel <id>` | Cancel a running task |
| `belt task files <id>` | List input and output files with IDs (`--role input` or `output`) |
| `belt task delete-files <id>` | Delete a finished task's files and keep the task (`--role` to limit the side) |
| `belt task delete <id>` | Delete a finished task (alias `rm`; `--files` also deletes its input and output files) |

Cancel a running task before `delete` or `delete-files`. `belt task files` lists only files stored on inference.sh; external `https://` URLs in input are not listed.

```bash
belt task files task_abc
belt task files task_abc --role output --json
belt task delete-files task_abc --yes          # destructive — confirm in scripts
belt task delete task_abc --files --yes        # remove task and its platform files
```

For HTTP equivalents, see [Tasks API](/docs/api/rest/tasks) (list, logs, timings, telemetry, status, delete, and file endpoints). Resource telemetry is available via `GET /tasks/:id/telemetry` or `client.tasks.getTelemetry(id)` in the JavaScript SDK.

---

## Automation contract

Belt exposes a machine-readable contract for agents, CI, and scripts. stdout is data; prompts, progress, and diagnostics go to stderr.

### `belt schema`

Print the complete command tree, flags, and automation guarantees as JSON. The output describes the binary that is actually running, so it stays correct as commands change. Schema output is always indented JSON — `--json` is not required.

```bash
belt schema                              # full contract
belt schema app run                      # one subtree
belt schema --include-hidden             # hidden commands and flags
belt schema --include-inherited          # repeat global flags on every command node
belt schema | jq '.automation.exit_codes'   # exit-code table only
```

Top-level fields:

| Field | Purpose |
|-------|---------|
| `schema_version` | Bumped when the document shape changes in a breaking way |
| `build` | CLI version string |
| `automation.output_formats` | `text`, `json`, `pjson` |
| `automation.exit_codes` | Stable name → numeric code map (same table the process exits with) |
| `automation.global_flags` | Persistent flags available on every command |
| `command` | Recursive command tree (`name`, `path`, `aliases`, `flags`, `subcommands`, `runnable`, `effect`, …) |

Each command node includes an `effect` field that tells agents what running it does:

| `effect` | Meaning |
|----------|---------|
| `readonly` | Safe to call speculatively — only reads remote state |
| `destructive` | Removes or halts something; prompts for confirmation and refuses unattended runs without `--yes` |
| `unclassified` | Neither read-only nor destructive — treat as potentially mutating |

Pass a command path to scope to one subtree. An invalid path exits `2` (`usage`).

### Destructive commands

Commands that remove or halt remote resources prompt for confirmation before acting. Declining the prompt exits `130` (`cancelled`) without sending a request — the resource is unchanged. In non-interactive mode (`--no-input`, `CI`, or `BELT_NON_INTERACTIVE=1`), destructive commands refuse to run without `--yes` / `-y` and exit `2` (`usage`).

| Command | Action |
|---------|--------|
| `belt agent delete <ref>` | Delete an agent |
| `belt chat stop <chat-id>` | Stop a running chat |
| `belt file delete <id>` | Delete a file |
| `belt flow cancel <run-id>` | Cancel a flow run |
| `belt flow delete <id>` | Delete a flow |
| `belt credentials disconnect <provider>` | Remove a workspace credential |
| `belt know delete <ref>` | Delete a knowledge entry (ID or `namespace/name`) |
| `belt skill delete <ref>` | Delete a skill (ID or `namespace/name`) |
| `belt mcp disconnect <slug>` | Disconnect an MCP server |
| `belt remote exec <remote-id> -- <command>` | Run a command on a connected remote |
| `belt remote rm <id>...` | Remove remote registrations from your account |
| `belt secrets delete <key>` | Delete a secret |
| `belt task cancel <id>` | Cancel a task |
| `belt task delete <id>` | Delete a finished task |
| `belt task delete-files <id>` | Delete a finished task's files |

Pass `--yes` / `-y` to skip the confirmation prompt. `belt file delete` also accepts `--force` / `-f` (predates `--yes`; same effect).

```bash
belt agent delete myteam/old-bot --yes
belt file delete 01abc123 --force           # equivalent to --yes
belt task cancel task_abc --yes             # confirm unattended cancel
```

`--yes` is separate from domain flags like `belt task cancel --force`, which means hard kill instead of graceful drain — it does not skip the confirmation prompt.

### Exit codes

Belt exits with stable numeric codes so automation can branch without parsing stderr. Existing codes keep their meaning; unrecognized failures exit `1` (`error`).

| Code | Name | Typical cause |
|------|------|---------------|
| `0` | `ok` | Success |
| `1` | `error` | Generic or unclassified failure |
| `2` | `usage` | Invalid command, arguments, or flags; or a destructive command run without `--yes` when input is disabled |
| `4` | `auth_required` | Missing, expired, or rejected credentials |
| `5` | `not_found` | Requested resource does not exist |
| `6` | `permission_denied` | Authenticated but not allowed |
| `7` | `rate_limited` | API quota or rate limit |
| `8` | `retryable` | Transient network or server failure |
| `9` | `payment` | Insufficient balance or entitlement |
| `10` | `config` | Required local setup is missing or invalid (for example `belt app doctor --check` findings) |
| `130` | `cancelled` | Interrupted (Ctrl-C), declined confirmation prompt, or context cancellation |

Read the live table from `belt schema | jq '.automation.exit_codes'`. With `--json` or `--pjson` active, failures also write a JSON error object to stdout (see below).

### Compact help for agents (`BELT_HELP=agent`)

When `BELT_HELP=agent` is set, `belt help` (or running `belt` with no subcommand) prints a compact automation summary instead of the full grouped command tree — output formats, discovery commands, common reads, and the exit-code table. Individual `belt <command> --help` output is unchanged.

```bash
BELT_HELP=agent belt help
```

### Global automation flags

| Flag | Purpose |
|------|---------|
| `--no-input` | Never prompt; fail instead (for CI and agents). Also implied by `BELT_NON_INTERACTIVE=1` and `CI`. |
| `--wrap-untrusted` | Mark third-party content as untrusted for LLM consumers |

With `--wrap-untrusted`, text belt did not author — MCP tool listings and results, skill instructions, knowledge entries — is framed with `<<<EXTERNAL_UNTRUSTED_CONTENT>>>` markers and a security notice so an LLM can distinguish data from operator instructions. JSON output uses in-place neutralization plus an `externalContent` provenance object so the payload stays parseable. Applies to commands that fetch external content (for example `belt mcp tools`, `belt mcp run`, `belt skill use`, `belt know get`).

---

## JSON output (`--json` / `--pjson`)

Many commands accept `--json` (`-j`) for machine-readable output on stdout. As of CLI v1.15.13, `--json` writes **compact** single-line JSON so it pipes cleanly to `jq` and other tools. Use `--pjson` when you want **indented** JSON for human debugging in the terminal.

For `belt app run` and `belt mcp run`, `--jq '<expr>'` filters output inline (no external `jq` required) — see [Filter output (`--jq`)](#running-apps) above.

Both flags suppress contextual stderr tips (post-command tips only — hook `tip:` lines inside `belt suggest --json` output are unchanged). On `belt mcp run`, `--jq` suppresses those tips as well so piped or merged streams stay machine-readable. Tips are also skipped when stderr is not a terminal (for example when an agent captures stdout and stderr together). Commands that support `--save` (for example `belt app store --save`) always write pretty-printed JSON to the file, regardless of `--json` or `--pjson`.

### Output projection (`--results-only`, `--select`)

Global flags that shrink JSON output for agents and scripts. Both require `--json` or `--pjson` — using them without a JSON mode is a usage error (exit `2`).

| Flag | Purpose |
|------|---------|
| `--results-only` | Unwrap list envelopes — drop pagination fields (`has_next`, `next_cursor`, …) and emit only the payload array |
| `--select <fields>` | Keep only comma-separated fields (dot paths allowed). Applied per element when the result is an array |

Both are applied centrally on every JSON stdout path (including `belt mcp run`, which holds raw upstream payloads), so list commands support them without per-command opt-in.

```bash
belt know list --json --results-only --select id,name
belt app list --json --results-only --select id,namespace,name
belt skill store --json --results-only --select namespace,name
belt mcp run linear:list_issues --json --select id,title
belt mcp tools linear --json --select name,description
belt task list --json --select id,status
```

The payload array key is not always `items` — the skill store uses `skills`, MCP listings may use `servers`, and some APIs use `results`. `--results-only` unwraps whichever single non-envelope key holds an array. If the object has two payload arrays or a non-array sibling field, it is treated as a result object and left unchanged.

Key matching is normalized (`has_next`, `HasNext`, and `hasNext` are equivalent). `--select meta.owner` keeps nested fields using the leaf name (`owner`). Missing fields are omitted rather than emitted as `null`.

**Note:** `--results-only` drops pagination metadata. If you need `next_cursor`, `has_next`, or `page`, do not use `--results-only`. Page-based list commands (`belt know list`, `belt skill list`, `belt skill store`) expose `has_next` / `has_previous` and `page` / `pages` instead of cursor fields.

All list commands support JSON output: `belt agent list`, `belt chat list`, `belt task list`, `belt flow list`, `belt flow runs`, `belt credentials list`, `belt keys list`, `belt vaults list`, `belt app list`, `belt app store`, `belt skill list`, `belt know list`, `belt file list`, `belt mcp list`, `belt secrets list`, and others. Detail commands include `belt me`, `belt balance`, `belt task get`, `belt chat send` (`POST /chats/{id}/messages` user-message payload — does not wait for the agent to finish), `belt app get`, `belt agent get`, `belt agent create`, `belt file info`, `belt file download`, `belt file upload`, `belt file delete`, `belt mcp tools`, `belt secrets get`, `belt vaults show`, and `belt agent run` (`{"chat_id": "..."}` with `--no-wait`; full run response otherwise).

```bash
belt task list --json | jq '.[].id'          # compact — pipes cleanly
belt task get task_abc --pjson               # indented — easier to read
belt app store --json --save store.json      # stdout compact; file pretty-printed
```

On failure with `--json` or `--pjson` active, the CLI writes a JSON error object to **stdout** in addition to the human-readable message on stderr:

```json
{"error": "not logged in or session expired. run 'belt login' to authenticate"}
```

Parse stdout for automation; ignore stderr unless you need the formatted message. Successful JSON responses use the command-specific schema documented in each command's help (`belt task get --help`).

## Billing and plan limits

Billing and plan-limit errors include upgrade URLs in the error message when the API returns entitlement metadata with `upgrade_available: true` under the top-level `meta` of the problem+json response.

| Error kind | Typical message | What to do |
|------------|-----------------|------------|
| Insufficient balance | Billing URL in message | Add credits at [settings → workspace → billing](https://app.inference.sh/settings/team/billing) |
| Plan limit | Subscription URL in message | Upgrade at [settings → workspace → subscription](https://app.inference.sh/settings/team/billing/subscription) |
| Addon entitlement | Add-ons URL in message | Subscribe at [settings → workspace → add-ons](https://app.inference.sh/settings/team/billing/addons) |
| Not logged in | Session expired | Run `belt login` |

When `addon_plan_id` is present in entitlement metadata, the CLI links to the add-ons page instead of the subscription page.

Inspect usage programmatically with `GET /entitlements/usage` — see [Entitlements API](/docs/api/rest/entitlements#get-usage).

→ [Billing API](/docs/api/rest/billing) · [Subscription API](/docs/api/rest/subscription) · [Troubleshooting — Plan limits](/docs/extend/troubleshooting#plan-limits-and-billing-errors)

---

## Environment Variables

| Variable | Description |
|----------|-------------|
| `INFERENCE_API_KEY` | API key (overrides config file). Same variable the SDKs read |
| `INFSH_API_KEY` | Deprecated fallback for `INFERENCE_API_KEY`; prints a warning. `INFERENCE_API_KEY` wins when both are set |
| `BELT_NON_INTERACTIVE` | Force non-interactive mode (skip browser login, MCP OAuth polling, spinners, and stdin prompts). Also set automatically when `CI` is truthy. |
| `BELT_HELP` | Set to `agent` for compact automation help instead of the full command tree |
| `INFSH_NO_AUTOUPDATE` | Disable automatic CLI updates |
| `INFSH_NO_TIPS` | Disable stderr tips and survey prompts after commands |
| `INFSH_ENGINE_BIN` | Path to a custom private engine binary |
| `BELT_CONFIG_DIR` | Root directory for **all** local belt state instead of the default split (`~/.inferencesh` for credentials, pending login, remote keys, logs; `~/.belt` for cache, history, sessions, hooks). Point each agent or shell at its own directory so logins, skill cache, and remote identity never leak across accounts on one machine. |
| `BELT_HTTP_TIMEOUT` | Overall timeout per API request (Go duration, default `60s`). Streams and downloads have no deadline. |
| `BELT_HTTP_RETRIES` | Retries of `GET` requests after transport errors or 5xx (default `2`, `0` disables). Writes are never retried. |
| `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` | Honoured by the CLI, the installers and the npm package. |
| `SSL_CERT_FILE`, `SSL_CERT_DIR` | Extra CA certificates for TLS-intercepting proxies. |

---

## Shell Completions

```bash
belt completion bash  # or zsh, fish, powershell
```

---

## Next

→ [Coding Agents](/docs/extend/coding-agents) — Build with AI assistants

→ [Creating an App](/docs/extend/creating-app) — Manual setup
