Install the command-line tool for creating apps.
Prerequisites
For Python Apps: uv
The CLI uses uv for Python environment management:
1# macOS / Linux2curl -LsSf https://astral.sh/uv/install.sh | sh34# Windows5powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"For Node.js Apps: Node.js
Node.js v20 or later:
1# macOS / Linux (via fnm)2curl -fsSL https://fnm.vercel.app/install | bash3fnm install 2245# Or via nvm6curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash7nvm install 22Hardware
| App Type | Development Environment |
|---|---|
| CPU apps | Any machine |
| GPU apps | Requires NVIDIA CUDA GPU |
Install CLI
1curl -fsSL https://cli.inference.sh | shThis covers macOS, Linux, and Git Bash on Windows. In Windows PowerShell:
1irm https://cli.inference.sh/install.ps1 | iexBoth 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:
1brew install inference-sh/tap/belt # macOS / Linux2npx @inferencesh/belt # Node.js3scoop bucket add inference https://github.com/inference-sh/scoop-bucket && scoop install belt # WindowsCommand name
The same CLI is available as belt, infsh, and inferencesh (package installs may expose one or all of these names). Subcommands are identical:
1belt version2infsh version # same CLIExamples in this documentation use belt. Either prefix works.
Verify
1belt versionUpdate CLI
1belt update # or: infsh updateShell 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:
1brew upgrade belt # Homebrew2scoop update belt # ScoopIf 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:
1belt update --forceLogin
1belt login2belt login --key-stdin < key.txt # non-interactive: skip browser flow (CI, scripts)3printf %s "$KEY" | belt login --key-stdinOpens a browser for device authorization 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). 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.
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.
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
1belt logoutClears 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 or POST /oauth/revoke (see Device authorization: Revoke). API keys stay valid until you revoke them with belt keys revoke or delete them in settings → workspace → api keys.
When no credential is stored, the command prints not logged in and exits 0.
Reset
1belt resetClears 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.
Check your account
1belt me2belt me --json # same payload as GET /me — for scripts and automationShows 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. 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"}.
1belt auth status --json || printf %s "$KEY" | belt login --key-stdinBalance: 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>}.
1belt balance2belt me balance --jsonRequires billing:read scope (included in standard API keys). See Billing API.
Switch workspaces
If you belong to multiple workspaces, switch the active workspace for all CLI commands:
1belt auth switch # interactive team picker2belt auth switch my-org # switch directly by team username3belt auth sw my-org # aliasThe 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.
Temporary token for scripts
1belt auth tokenMints 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.
1curl -H "Authorization: Bearer $(belt auth token)" https://api.inference.sh/me2export 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. 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 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 as the REST API: every member manages their own personal keys; workspace owners and admins also manage workspace keys and every member's keys.
1belt keys create laptop2belt keys create ci --workspace --scope apps:execute --ttl 90d3belt keys list4belt 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.
| 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-levelokboolean.
1belt auth doctor --check --json --no-input2belt app doctor --check --json3belt plugin doctor --check --jsonAuthentication (belt auth doctor)
1belt auth doctor2belt auth doctor --check --json --no-inputConfig 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:
1{2 "ok": false,3 "endpoint": "https://api.inference.sh",4 "checks": [5 {"name": "config", "status": "ok", "detail": "/home/user/.inferencesh/config.json"},6 {"name": "credential", "status": "fail", "detail": "none configured", "hint": "run 'belt login', or set INFERENCE_API_KEY"},7 {"name": "api", "status": "skipped", "detail": "no credential to test"}8 ]9}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.
App health (belt app doctor)
1belt app doctor2belt app doctor fix3belt app doctor --check --jsonRun 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:
1{2 "ok": false,3 "issues": [4 {5 "id": "missing-init",6 "message": "missing __init__.py — has sibling modules (utils.py) but no package init",7 "fixable": true,8 "severity": "error",9 "fix_description": "create __init__.py with `from .inference import App`"10 }11 ]12}Issue fields: id, message, severity (error or warning), fixable, and optional fix_description / extra. → Deploying
Plugin health (belt plugin doctor)
1belt plugin doctor2belt plugin doctor --fix3belt plugin doctor --check --jsonChecks 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:
1{2 "ok": false,3 "checks": [4 {"name": "belt", "ok": true, "detail": "/usr/local/bin/belt"},5 {"name": "auth", "ok": false, "detail": "not logged in"},6 {"name": "plugin", "ok": false, "detail": "belt not found in installed plugins"}7 ]8}Check names vary by detected agent runtime (for example plugin, hooks, shims, cursor, pi-ext). → Coding agents · Troubleshooting
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 for the model generator and belt 1.25+ (→ Creating an app) |
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, 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.
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.
1belt app run namespace/my-app '{"prompt": "a cat"}' # positional2echo '{"prompt": "a cat"}' | belt app run namespace/my-app # stdin3belt mcp run linear:list_issues '{"project": "INF"}' # positional4belt 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 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.
1belt mcp run linear:list_issues --jq '.issues[] | {id, title}'2belt app run okaris/flux -i '{"prompt": "a cat"}' --jq '.output.url'3belt app run okaris/flux -i '{"prompt": "a cat"}' --no-wait --jq '.id' # task id onlybelt 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.
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.
1belt app run okaris/flux -i '{"prompt": "a cat"}' --urls2belt app run okaris/flux -i '{"prompt": "a cat"}' --download=./out3belt 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.
1belt app run pruna/p-image --batch prompts.jsonl --concurrency 8 --retry 1 > results.jsonlCost 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.
1belt app estimate okaris/flux --input input.json2belt app estimate okaris/flux --input '{"prompt":"a cat"}'3belt app run okaris/flux --estimate --input '{"prompt":"a cat"}'4belt app estimate okaris/flux --input input.json --json --select microcents,confidenceHuman-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 · Usage-based 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:
1belt app list --json # your deployed apps (requires login)2belt app list --search "flux" --json3belt app list --limit 50 --cursor <next> # cursor pagination (see hints in table output)4belt app store --json # public store with cursor pagination metadata5belt app store search "video" --category video --json6belt app store --save store-snapshot.json # write JSON to file instead of stdoutbelt 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. 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:
1belt app pull # sync current app (from inf.yml in cwd)2belt app pull --meta # sync only the listing into inf.yml (no code)3belt app pull <app-id> # download app by ID to namespace/name/4belt app pull --all # sync every local app (finds inf.yml recursively)5belt app pull --all --meta # sync only the listing for every local app6belt app pull <app-id> --force # overwrite existing directory without promptA 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,categoryandimages. Use--metaafter 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,secretsandcredentials. 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
metadataand fills in keys the deployedinf.ymllacks.
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.
1belt app get okaris/flux2belt app get okaris/flux@abc123 --versions # version history3belt app get okaris/flux --json # includes status fields for scriptsWhen 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. 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:
1belt suggest "flux image"2belt suggest "web search" --json # JSON for agent hooks3belt suggest "deploy" --agent # directive framing for agent hooksWith 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).
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)) |
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.
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.
1belt mcp tools linear --json2belt mcp tools linear:list_issues --json3belt mcp tools linear --json --select name,description # projection for agentsSome 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.
→ Connectors overview · Browsing 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 |
1belt credentials list2belt credentials list --limit 50 --cursor <next> # when stderr hints show more pages3belt credentials get google4belt credentials get mcp --json # includes credential id for agent tools5belt credentials connect google6belt credentials connect slack7belt credentials disconnect googlebelt 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.
1belt credentials connect google --as team # team admin: shared team OAuth2belt mcp connect linear --as user # personal MCP authorization (default for MCP)Google OAuth and the managed 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.
→ Credentials API · Connectors overview
Vaults (CLI)
Inspect workspace vaults, the containers that group credentials and secrets. In the web app they are under 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.
1belt vaults list2belt vaults show # default vault3belt vaults show vault_abc123 # specific vault by ID4belt vaults show --jsonbelt 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 listprints aVaultDTO[]array (id,name,description,is_default,credential_count,secret_count, ...)belt vaults showprints{"vault_id", "name", "credentials": [...], "secrets": [...]}
To connect credentials or set secrets, use belt credentials connect, belt secrets set, or the Vault UI.
→ Vaults API · Credentials overview · Environment secrets
Flows (CLI)
Create, edit, run, and publish flows from the terminal — same APIs as Flows and 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) |
belt flow edit undo <ref> | Undo your most recent graph edit (server-side action log; see 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) |
1belt flow get myteam/voice-pipeline # by namespace/name2belt flow get abc123 # by short ID3belt flow create --name voice-pipeline4belt flow edit node add myteam/voice-pipeline tts --app elevenlabs/tts5belt flow edit node set-input myteam/voice-pipeline tts text "hello world"6belt flow edit node set-input myteam/voice-pipeline voice-changer audio --from tts:audio7belt flow edit output set myteam/voice-pipeline audio --from voice-changer:audio8belt flow describe myteam/voice-pipeline9belt flow run myteam/voice-pipeline -f10belt flow publish myteam/voice-pipelineUse 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 · Creating a flow · Flows API · Flow Runs API
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.
1belt review --agent cursor --trigger stop # called by Cursor Stop hook2belt plugin review --trigger stop # called by Claude Code Stop hook3belt review --agent claude-code --trigger stop # same pipeline as belt plugin review4belt review --agent codex --dry-run # preview without saving5belt 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 — 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. 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.
1belt agent create my-helper "A helpful assistant"2belt agent create todo-bot --model openrouter/claude-sonnet-46 --prompt "You manage Todoist tasks"3belt agent create todo-bot --model openrouter/claude-sonnet-46 \4 --mcp 46e8a4bq...:find-projects \5 --mcp 46e8a4bq...:add-tasks6belt 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:
1belt agent pull myteam/support-bot2belt agent pull myteam/support-bot@abc123 --save agent.ymlYAML 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, Adding skills, and Approval settings.
Lifecycle hooks go under hooks. To turn on the learning hooks (the suggest and learn switches in the agent editor):
1hooks:2 - { event: agent.turn_start, type: builtin, handler: "belt:suggest" }3 - { event: agent.complete, type: builtin, handler: "belt:extract" }4 - { 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.
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}.
1belt agent get myteam/support-bot2belt agent get myteam/support-bot@latest3belt agent get agent_abc123Human-readable output uses lowercase labels and lists attached skills with their resolved ref and version pin:
1 agent: myteam/support-bot2 version: abc1233 description: Handles customer support4 system prompt: You are a helpful support agent...5 core app: openrouter/claude-sonnet-4667 tools:8 - search (call): Search the web910 skills:11 - myteam/pricing-rules@ver_xyz: Pricing guidelines1213 context:14 - tenant_id (required): Tenant identifierEach 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:
1→ calling search...2→ search completedFailed 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).
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) |
1# List pending interrupts for a run (run ID from belt chat get --json active_run.id)2belt interrupt list run_abc12334# Allow or deny a specific interrupt5belt interrupt resolve int_abc123 allow6belt interrupt resolve int_abc123 deny78# JSON output for scripting9belt interrupt list run_abc123 --json10belt interrupt resolve int_abc123 allow --jsonWith --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 · Approval settings · 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 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.
1belt skill versions myteam/code-review2belt skill use myteam/code-review@abc123 # specific version3belt skill supersede myteam/old-review myteam/code-review4belt skill delete myteam/deprecated-skill --yes→ Skill registry · Delete Skill API · Supersede Skill API · Search concept · Search API · Knowledge API — 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).
1belt know list --type observation2belt know get acme/onboarding # latest version (no login for public entries)3belt know get acme/onboarding@abc123 # specific version4belt know upload ./my-skill/ # directory with SKILL.md → skill5belt know upload ./notes.md --type reference6belt know upload ./notes.md --global # skip project scope tagsBy 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.
→ Knowledge API · Creating skills
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) |
1belt file list --type image --limit 202belt file list --search photo3belt file list -q avatar --type image4belt file download https://files.inference.sh/abc/image.png5belt file download 01abc123def456 -o ./output.png6belt file upload ./photo.jpg --name avatar.jpg7belt file upload *.png8belt file info 01abc123def456 --json9belt 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.
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) |
1belt form list2belt form get infsh/use_case3belt form fill acme/feedback rating=5 role=dev langs=go,ts4belt form fill acme/feedback --data '{"rating":5}' --no-input5belt form fill acme/feedback --data-file answers.json --json6belt form submissions acme/feedback --limit 50Refs 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.
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) |
1belt bounty list2belt bounty claim agent-survey3belt bounty claim agent-survey role=dev langs=go,ts --no-input4belt bounty claim agent-survey --data-file answers.json --json5belt bounty claim app-builder acme/my-appbelt 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.
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):
- The app must be deployed (
belt app deploy). - The app must be useful and not duplicate something already on the platform.
- Python or JavaScript apps are fine — simple utilities are great candidates.
- Submitting agrees to make the app public so other agents can reuse it.
- One claim per day for this program (
max_per_dayon the API) — checkbelt bounty listfor limits on other programs.
1belt bounty list2belt skill use inference-sh/building-inferencesh-apps # optional starter skill3belt app deploy4belt bounty claim app-builder myteam/my-utility5# same as: belt app promo submit myteam/my-utilityClaims 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 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.
1belt feedback # see what's pending2belt feedback discovery "found it on github"3belt feedback use_case "building AI agents for customer support"4belt feedback need "better template discovery"5belt feedback testimonial "fastest way to ship agents with real integrations"6belt 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). 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 · Forms API · Bounties (CLI) · Coding agents
Remotes (CLI)
Connect a machine to inference as a remote and optionally run commands on it. See Remotes 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) |
1belt login2belt remote start --name work-laptop --exec # opt in to command execution3belt remote start --agents # opt in to harness agent sessions4belt remote exec remote_abc123 -- ls -la5belt 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.
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.
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.
1belt task files task_abc2belt task files task_abc --role output --json3belt task delete-files task_abc --yes # destructive — confirm in scripts4belt task delete task_abc --files --yes # remove task and its platform filesFor HTTP equivalents, see Tasks API (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.
1belt schema # full contract2belt schema app run # one subtree3belt schema --include-hidden # hidden commands and flags4belt schema --include-inherited # repeat global flags on every command node5belt schema | jq '.automation.exit_codes' # exit-code table onlyTop-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).
1belt agent delete myteam/old-bot --yes2belt file delete 01abc123 --force # equivalent to --yes3belt 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.
1BELT_HELP=agent belt helpGlobal 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) 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.
1belt know list --json --results-only --select id,name2belt app list --json --results-only --select id,namespace,name3belt skill store --json --results-only --select namespace,name4belt mcp run linear:list_issues --json --select id,title5belt mcp tools linear --json --select name,description6belt task list --json --select id,statusThe 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).
1belt task list --json | jq '.[].id' # compact — pipes cleanly2belt task get task_abc --pjson # indented — easier to read3belt app store --json --save store.json # stdout compact; file pretty-printedOn failure with --json or --pjson active, the CLI writes a JSON error object to stdout in addition to the human-readable message on stderr:
1{"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 |
| Plan limit | Subscription URL in message | Upgrade at settings → workspace → subscription |
| Addon entitlement | Add-ons URL in message | Subscribe at settings → workspace → add-ons |
| 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.
→ Billing API · Subscription API · Troubleshooting — Plan limits
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
1belt completion bash # or zsh, fish, powershellNext
→ Coding Agents — Build with AI assistants
→ Creating an App — Manual setup