Stackbone CLI
Command-line tool for scaffolding, developing, publishing and operating Stackbone agents.
A Stackbone project is a workspace: one or more deep agents (each a deep-agents/<name>/ folder whose index.ts default-exports defineDeepAgent(...) — they run in-process, served over the standard OpenAI/Anthropic chat endpoints) plus durable Workflow SDK workflows (each workflows/<name>.workflow.ts). The workspace is derived by convention from those files on disk. A stackbone.config.ts (default-exporting defineWorkspace({ agents: [], workflows })) can optionally override the workflow list only. The CLI scaffolds it (init, add), runs the whole stack locally (dev), packages it (publish), and operates a running install (runs, workflows, hitl, logs, db, …).
Critical: how to call the CLI
The binary is stackbone (installed into your project's node_modules/.bin/ by package.json after npm install / pnpm install, and globally available if you install @stackbone/cli yourself). Always invoke it explicitly:
stackbone <command> [--json] [--yes] [--verbose]
You can also run it one-off without installing: npx @stackbone/cli <command>. If stackbone --help fails, you have not run npm install in the project or have not installed the global CLI.
Session start — verify auth and project:
stackbone whoami # who am I, which org
stackbone current # is this directory linked to an agent?
If not authenticated: stackbone login (opens browser; falls back to device-flow code if no browser).
If no project yet: stackbone init (emit a workspace shell and link it — needs login) or stackbone link (attach an existing directory to an agent).
Global options
| Flag | Description |
|---|---|
--json |
Emit a versioned JSON envelope instead of human-formatted text. Success writes { schema_version, …payload } to stdout (the payload fields are spread at the top level, not nested under data); an error writes { schema_version, error: { code, message, suggestion? }, exit_code } to stderr. Also skips value-collection prompts — errors out if required params are missing. (STACKBONE_JSON=1 is equivalent.) |
--yes / -y |
Auto-accept Y/N confirmation prompts (deletes, overwrites). Does NOT skip value prompts — combine with --json for fully non-interactive runs. |
--verbose |
Stream every log line instead of spinner UI. Useful for CI / agent shells where TTY redraws break parsing. |
When an AI coding agent is the consumer, pass
--json --yesby default. The confirm prompts protect humans typing! stackbone …, not autonomous agents.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error (network, validation, backend 4xx/5xx) |
| 2 | Not authenticated |
| 3 | Project not linked (no .stackbone/project.json in this directory) |
| 4 | Resource not found |
| 5 | Permission denied (RBAC, capability, tier quota) |
Branching on exit codes lets the agent recover deterministically — e.g., exit 3 → run stackbone init or stackbone link first.
Environment variables
| Variable | Description |
|---|---|
STACKBONE_API_URL |
Override the control plane URL (dev / staging). Highest-priority source for apiUrl. |
STACKBONE_JSON |
1 forces JSON-envelope output, exactly like passing --json. |
STACKBONE_VERBOSE |
1 streams every log line, like --verbose. |
STACKBONE_LOG_LEVEL |
Pino log level for the CLI's own diagnostics (to stderr). |
The CLI does not read access-token / org-id / agent-template-id env overrides. Authentication comes only from
~/.stackbone/credentials.json(minted bystackbone login); the active org comes fromstackbone organization use; the linked agent from.stackbone/project.json.
Commands
Auth
stackbone login— RFC 8628 device flow. Browser opens; copy the code if it doesn't. See references/login.mdstackbone logout— clear stored credentials at~/.stackbone/credentials.jsonstackbone whoami— show the active user and organization
Project lifecycle
stackbone init [dir] [--with empty|agent|workflow|workflow-agent] [--name <ws>]— emit a workspace shell, optionally a first piece on top of it, and link the workspace to your org. Workspace-first: it writes the shell — adeep-agents/folder, aworkflows/folder,package.json(withdeepagents+@langchain/*+workflowpinned at the root),tsconfig.json, an.npmrc(hoistednode_modulesso the process resolves ONE copy of the SDK/LangChain),.gitignore, a README and the coding-agent skills (best-effort).--withpicks an optional first piece:empty(shell only),agent(a deep agent),workfloworworkflow-agent; with a TTY and no--with, it shows an interactive picker. For every--withkindinitregisters the workspace's identity in the control plane and writes.stackbone/project.json, so you must be signed in — there is no offlineinit.--namesets the workspace name (and the default first-piece name); the[dir]positional sets the target subdirectory. There is no--starter/--templateoninitanymore — passing one prints a migration message and exits non-zero. See references/init.mdstackbone add deep-agent <name>(alias:add agent <name>) — add one deep agent: a singledeep-agents/<name>/index.ts, plus the runtime dep pins merged into the rootpackage.json(never overwriting a version you pinned). Offline — the agent is a member of the workspaceinitalready linked, so no login and no network call.stackbone add workflow <name> [--template <t>] [--calls <agent>]— add one durable workflow file atworkflows/<name>.workflow.ts. Offline (workflows are dev-only today).--calls <agent>wires a step that delegates a turn to that agent viacallDeepAgent(the workflow → agent hybrid).stackbone add workflow-agent <name>— the composed template: scaffold a deep agent and a workflow already wired to call it (the qualify-lead → lead-qualifier pattern). Offline, likeadd deep-agent.stackbone link— attach the current directory to an existing agent (writes.stackbone/project.json)stackbone current— show the authenticated user and the linked agentstackbone list— list organizations the user owns or is a member of, with their agentsstackbone organization use [slug]— choose which organization the CLI acts as (the active-org contextinit,link,devand the agent-runtime surfaces resolve against). Pass the slug to switch directly; omit it to pick interactively from your memberships (the current one is marked). Non-interactive (--jsonor CI) requires the slug — discover slugs withstackbone list.--jsonemits{ organization: { id, slug, name } }. Exit4when the slug is not one of your memberships.stackbone metadata— machine-readable overview of the workspace (auth status, linked project, capabilities, current agent/template ids). Run this first with--jsonto discover state before building features.
addonly writes new files. It never edits your existing TypeScript and never editsstackbone.config.ts(the rootpackage.jsondep merge is the one exception — it adds missing pins, never edits existing ones). A name collision fails with a clear error (re-run with--forceto overwrite).addmust run inside a workspace; outside one it exits3(no_project). All kinds are offline — the pieces you add are members of the workspaceinitalready linked, soaddneeds no login and never exits2(auth).
The workspace registry is derived by convention — from the files on disk, not a hand-maintained list. Agents are every
deep-agents/<name>/folder containing anindex.ts(the folder basename is the agent name); workflows are everyworkflows/<name>.workflow.ts(the workflow name is the file basename without.workflow.ts, and its exported function is<camelCase(name)>Workflow— e.g.qualify-lead.workflow.tsexportsqualifyLeadWorkflow).stackbone devandstackbone publishboth read this same convention; astackbone.config.tscan only override the workflow list.
Connectors & connections — stackbone connectors
stackbone connectors— list the available connectors (the catalog: ids +authKind+ action/trigger ids) and the connections that exist for the account, nested under each connector with their id, name and health. This is the discovery step before you call a connection from agent or workflow code withstackbone.connection('<id or unique name>'): it gives you the id/name to pass. Read-only, authenticated. Listing connections needs the owner/adminconnections:managecapability; without it the command still prints the catalog and flagsconnections_unavailable. See references/connectors.md.
stackbone connectors # human table: connectors + nested connections
stackbone connectors --json # { connectors: [{ id, authKind, actions, triggers, connections: [{ id, name, healthStatus }] }] }
When an account has several connections of the same connector, address the one you mean by its id or unique name:
stackbone.connection('<id or unique name>'). Runstackbone connectors --jsonto get the id/name to pass. (Connector authoring + the@stackbone/sdk/connectsurface live in the stackbone skill.)
Local dev — stackbone dev
Boot the whole workspace locally with hot reload and Stackbone Studio. The single HTTP server (with Studio mounted) listens on http://127.0.0.1:4242 by default and binds to loopback only. It brings up the local stack — Postgres, Redis and MinIO — and runs the same contract the durable runtime serves in production, so there are no surprises at publish time. See references/dev.md.
stackbone dev # server + Studio on 127.0.0.1:4242
stackbone dev --port 4300 # custom port
stackbone dev --listen # bind 0.0.0.0 so it's reachable from your LAN
stackbone dev --verbose # stream every log line instead of the spinner UI
The deep agents run in-process inside that one server — no subprocesses, no per-agent ports. You chat with an agent over the standard wire (POST /anthropic/v1/messages or POST /openai/v1/chat/completions, selecting the agent with the model field; GET /openai/v1/models lists them) and start workflows through POST /api/workflows/:name/start. Saving deep-agents/<name>/** re-bundles only that agent and hot-swaps it live (well under 2s, no process restart).
Workflows — stackbone workflows
Inspect the durable Workflow SDK workflows the targeted installation exposes, the input/output JSON Schema each one declares (recovered from the sibling inputSchema / outputSchema Zod exports a workflow file ships), and start one by name. Targets the local-dev install by default; override with --agent <id>. See references/workflows.md.
stackbone workflows list --json # { items: [{ name, trigger, hasSchema }] } — ◆ = declares a schema
stackbone workflows schema onboarding --json # { schema: { input, output } } — each half null when undeclared
stackbone workflows start onboarding --input '{"userId":"u1"}' --json # start a run → { workflowName, runId }
list/schemaare read-only (no--yes);starttriggers a run by name — the name resolves server-side, so the workflow need not be bundled in the CLI, and input comes from--input <json>or--input-file <path>. From inside agent/workflow code the SDK equivalents arestackbone.workflows.start/stackbone.workflows.startAndWaiton the ambient client (see the stackbone skill). Dev-first: thestackbone devemulator serves these today; a cloud install's workflow routes may still 404 until the prod port lands.
Publish — stackbone publish
Package the current workspace. Detected by convention — the deep-agents/ + workflows/ folders (or a stackbone.config.ts) — publish esbuilds every deep agent (with @stackbone/sdk, deepagents and @langchain/* kept external, so the runtime image resolves ONE copy) + every workflow on this host and packs them into dist/stackbone/workspace-bundle.tar, writing a workspace-bundle.json pointer beside it with the digest, sizes and contents. Native dependencies (.node add-ons) are rejected up-front — only pure JS runs in the runtime image. See references/publish.md.
stackbone publish # esbuild every agent + workflow, pack the bundle tar
stackbone publish --json # emit a JSON envelope (digest, sizes, agents, workflows)
There is no
--version,--dry-runor--yesonpublish. The bundle is written locally and verifiable by digest; the upload to object storage + the build-pointer registration are the platform's provisioning job on deploy — the CLI produces the artifact, it does not push it.
Database — stackbone db
Manage Drizzle migrations and browse the agent's dedicated Neon read-only from the shell. See references/db.md.
Migration verbs (drizzle-native; run against STACKBONE_POSTGRES_URL, which stackbone dev exports):
stackbone db migrate create <name>— diffsrc/schema.tsagainst the journal and write a new SQL migration under.stackbone/migrations/stackbone db migrate up [--target <tag>]— apply every pending migration (advisory lock + journal table; safe to re-run)stackbone db migrate status— classify each migration as applied / pending / drifted
Read-only explorer verbs (HTTP, target one installation; default local-dev install, override with --agent <id>):
stackbone db query <sql>— run an ad-hoc single SELECT (SQL from the positional,--file <path>, or stdin); the backend rejects anything that isn't a read, and rows truncate at 1000stackbone db schemas— list the schemas and tables visible to the install, with row estimatesstackbone db table <schema> <table>— browse one table with cursor pagination (--limit --cursor --order)
Migrations run inside a backend-managed transaction. Do not put
BEGIN,COMMITorROLLBACKin your migration files; the journal lives in the agent's Neon. The RAG schema is platform-provisioned per install (nodb add-rag— never hand-write it). Schema changes go throughdb migrate create, notdb query(which is read-only).
Inline docs — stackbone docs
stackbone docs— list available topicsstackbone docs sdk— full@stackbone/sdkreferencestackbone docs cli— this command surface in long formstackbone docs agent-yaml— theagent.yamlmanifest referencestackbone docs <topic> --json— structured output for agent parsing
Prefer
stackbone docsover web search when the user asks "how does the SDK do X" — the inline docs ship with the installed CLI and match the installed SDK version.
Non-obvious behaviors
Authentication is device flow, not OAuth redirect. stackbone login opens the browser to a code-entry page. The CLI polls the platform until the code is approved. If the browser fails to open, the CLI prints the URL and code in plain text — copy-paste them into any browser on any machine.
Credentials are local-only, chmod 600. Stored at ~/.stackbone/credentials.json. Never committed, never shipped in containers. At runtime the agent uses the env vars the runtime injects (HMAC_SECRET, STACKBONE_SECRET_KEY, STACKBONE_INSTALLATION_ID, DATABASE_URL, …), not your personal session.
.stackbone/project.json is the link. Generated by init (for every --with kind) or link, contains { schemaVersion, organizationId, agentId, controlPlaneUrl, localDevInstallationId? }. Add .stackbone/ to .gitignore — init does this automatically.
stackbone init is workspace-first and always links. It emits the workspace shell and, for every --with kind, registers the workspace in the control plane and writes .stackbone/project.json — so it needs a signed-in session (there is no offline init). The [dir] positional sets the target subdirectory and --name sets the workspace name (slugified). A bare stackbone init writes into a subdirectory derived from the workspace name so it doesn't pollute the current directory; stackbone init my-thing writes into ./my-thing/.
stackbone dev boots its own local stack. Postgres, Redis and MinIO come up automatically; the deep agents run in-process behind the :4242 server. Restarting your shell leaves the data services running, so the next stackbone dev picks up where it left off.
stackbone publish rebuilds every time. A workspace (detected by convention — the deep-agents/ + workflows/ folders, or a stackbone.config.ts) re-esbuilds every agent + workflow on this host and re-packs the tar; the digest only changes when the inputs do, so an unchanged workspace yields a stable sha256: you can compare. There is no --cache flag.
stackbone db migrate up is idempotent. Re-running after partial failure picks up where it left off — the journal table records the last applied version. Never edit a migration file after it has been applied; create a new one instead.
Targeting a cloud agent vs the local-dev install. The agent-runtime command groups (workflows, runs, logs, db query/schemas/table, storage, rag, secrets, config, contract, hitl, prompts, openrouter) default to the local-dev installation if one is active (.stackbone/project.json.localDevInstallationId); to target a cloud agent, pass --agent <id> per invocation. When neither resolves they exit 3 (no_project) with a suggestion to pass --agent or run stackbone dev. There is no stackbone use <id> that persists a target — that hidden state was rejected because it's the exact foot-gun the safety design avoids. To discover the install ids/slugs to pass as --agent, run stackbone agents list --json (the agents group is the target selector, so it takes no --agent of its own).
Tier quota is enforced server-side. If the org's credit bundle is spent, mutating commands return exit code 5 (permission). The CLI does not emit an error.code of tier_quota_exceeded, and there is no nextActions field — the JSON error envelope carries { code, message, suggestion? }. (tier_quota_exceeded is a string on the control plane's HTTP 402 body, not a CLI/SDK error code — see the stackbone-debug skill.) Surface the message verbatim; do not retry.
Agent-runtime surfaces
These command groups operate a running agent installation (cloud or the local-dev install). Every verb accepts --json, targets the active install or a --agent <id> (except agents, which IS the target selector and takes no --agent), and any destructive verb requires --yes (without it the verb exits 5/permission before any network call). Pagination is uniform: paginated lists take --limit + --cursor and emit { items, nextCursor, prevCursor } plus any domain extras. Per-command flags, outputs and exit codes live in the references/ files linked in each row.
| Group | Verbs (positional args; ✱ = destructive, needs --yes) |
Reference |
|---|---|---|
stackbone agents |
list, get <agentSlug> — discover install targets; no --agent flag |
references/agents.md |
stackbone workflows |
list, schema <name>, start <name> [--input/--input-file] — inspect the durable workflows the install exposes (+ each one's input/output schema) and start one by name |
references/workflows.md |
stackbone runs |
list [--status --limit --cursor], get <runId>, retry <runId>✱, cancel <runId>✱ |
references/runs.md |
stackbone logs |
tail [--run --level --q --trace-id --since --until --follow --limit] — SSE stream |
references/logs.md |
stackbone storage |
buckets, list --bucket [--prefix --limit --cursor], get <key> --bucket [--out], put <key> --bucket --file, presign <key> --bucket, remove <key> --bucket✱ |
references/storage.md |
stackbone rag |
collections list/create <name>/remove <name>✱, list --collection, get <docId> --collection, ingest <path> --collection, query <text> --collection [--topk], remove <docId> --collection✱, jobs, retry <jobId>✱, cancel <jobId>✱ |
references/rag.md |
stackbone secrets |
list, set <name> [--value] (stdin if omitted), remove <name>✱ — values are never revealed |
references/secrets.md |
stackbone config |
get, set [--file] (stdin if omitted), versions, rollback --version✱, types (local codegen, no --agent) |
references/config.md |
stackbone contract |
show, capabilities, validate |
references/contract.md |
stackbone hitl |
list [--status], get <hitlId>, approve <hitlId> [--reason]✱, reject <hitlId> [--reason]✱ |
references/hitl.md |
stackbone prompts |
list, get <key> [--version], create <key> --name [--template/--file], update <key>, remove <key>✱, versions <key>, rollback <key> --version✱, preview <key> [--vars] |
references/prompts.md |
stackbone openrouter |
get, models — read-only; the bearer key value is never returned |
references/openrouter.md |
db query/db schemas/db tableare the same kind of read-only HTTP explorer against the targeted install — they live understackbone db(see references/db.md) alongside the drizzle-native migration verbs.
No
runs stepsverb. To trace one run, usestackbone runs get <runId>for the header andstackbone logs tail --run <runId>for the per-run log lines. (config typesis the one runtime-group verb that is local — it regenerates.stackbone/config.d.tsfrom this project'sconfig.schema.tsand takes no--agent.)
Secrets and OpenRouter plaintext are never revealed by the CLI by design.
secrets listonly prints masked previews and there is nosecrets get;openrouter getreturns mode/public-id/spend-cap but never the bearer value. Reading a plaintext secret is a human-only Studio action behind a re-auth challenge.
Common workflows
Per-command flags, outputs and exit codes live in the references/ files — these are just the cross-command happy paths.
Scaffold → iterate → ship:
stackbone login # init links the workspace, so sign in first
stackbone init my-workspace --with agent # emit the workspace shell + a first deep agent, and link the workspace
cd my-workspace && npm install
stackbone add workflow qualify-lead --calls lead-qualifier # add a durable workflow that delegates to the agent (offline)
stackbone dev # server + Studio on 127.0.0.1:4242 (already linked — boots straight away)
# edit deep-agents/<name>/index.ts and workflows/…, the dev session hot-swaps them, then:
stackbone publish # packs dist/stackbone/workspace-bundle.tar
stackbone init --with empty(the default) scaffolds and links a bare workspace, then you grow it withstackbone add deep-agent|workflow|workflow-agent— those are offline (the added pieces are members of the already-linked workspace).addonly writes new files — it never edits your existing TypeScript orstackbone.config.ts.
Database schema: stackbone db migrate create <name> → edit the generated SQL → stackbone db migrate up. You never migrate the RAG schema — it's platform-provisioned per install. See references/db.md.
Non-interactive (CI / agent shell) — login is device-flow only (no password, no token env var). A headless run must already have ~/.stackbone/credentials.json in place (run stackbone login once on a machine with a browser, then carry that file into CI). After that, everything else runs with --json (and --yes for destructive verbs):
stackbone organization use "$ORG_SLUG" --json # set the active org (discover slugs with `stackbone list`)
stackbone link --agent "$AGENT_SLUG" --force --json
stackbone db migrate up --json
stackbone publish --json
Discover state before building: stackbone metadata --json (auth + project), stackbone list --json (orgs + agents), stackbone docs sdk (SDK surface).
Project link — .stackbone/project.json
init (for every --with kind) and link write { schemaVersion, organizationId, agentId, controlPlaneUrl, localDevInstallationId? } to .stackbone/project.json. The localDevInstallationId is written at init / link time (the workspace's local-dev install); it survives shell restarts and is GC'd after ~7 days idle. Never commit .stackbone/ — init gitignores it automatically; if a clone is missing it, re-run stackbone link to mint a fresh ID for your machine.