Global flags, environment variables, persistent state on disk, the
--jsonenvelope and the semantic exit codes. Everything you need to drive the CLI from a script — or from a coding agent inside an IDE — without parsing prose.
Global flags
Every command accepts these. Their effective values are read from
process.argv before subcommand parsing, so they propagate
transparently.
| Flag | Env equivalent | Default | Description |
|---|---|---|---|
--json |
STACKBONE_JSON=1 |
off | Emit machine-readable JSON wrapped in { "schema_version": 1, ... }. Errors get error + exit_code keys. |
-y, --yes |
(none) | off | Skip every confirmation prompt. Required in CI / non-TTY contexts. |
--verbose |
STACKBONE_VERBOSE=1 |
off | Stream the raw firehose of log lines and docker compose output instead of the per-stage spinner UI. |
Environment variables
The CLI reads these from the parent shell:
| Variable | Effect |
|---|---|
STACKBONE_JSON |
=1 ⇒ same as --json. |
STACKBONE_LOG_LEVEL |
Pino logger level (debug, info, warn, error). Defaults to info. |
STACKBONE_FRPC_BIN |
Absolute path to a frpc binary. Used by stackbone dev for the relay tunnel. |
STACKBONE_FRPC_VERSION |
Override the pinned frpc release the auto-fetcher downloads. |
STACKBONE_VERBOSE |
=1 ⇒ same as --verbose (raw firehose of log + docker compose output). |
STACKBONE_CORS_ALLOW_ORIGINS |
CSV of origins added to the stackbone dev Studio CORS allowlist. |
OPENROUTER_API_KEY |
Your own OpenRouter key. When set in your shell, stackbone dev seeds it into your agents so stackbone.ai resolves models with it (bring-your-own-key wins over the organization key). |
OPENROUTER_BASE_URL |
Override the OpenAI-compatible base URL the model client uses. Defaults to OpenRouter cloud. |
STACKBONE_NO_UPDATE_CHECK |
=1 ⇒ silence the "a new release of the Stackbone CLI is available" banner. The check is a background local-cache read that never blocks a command, and it is already suppressed in CI, on a non-TTY, and under --json. |
stackbone dev also injects the env vars your agents and workflows read
through the ambient stackbone client (STACKBONE_POSTGRES_URL,
WORKFLOW_REDIS_URL, STACKBONE_AGENT_ID, …). See
SDK integration → how the CLI connects to the SDK.
State on disk
Per-machine (global)
Lives under ~/.stackbone/:
| Path | What it stores | Permissions |
|---|---|---|
~/.stackbone/credentials.json |
Map keyed by control-plane URL → SessionInfo. Lets one user stay logged into local + staging + prod simultaneously; only one is active at a time. |
0600 (owner read/write). |
~/.stackbone/config.json |
Global preferences — defaultApiUrl, telemetry (opt-in PostHog, default false until V1). |
0644. |
Per-machine (cache)
| Path | What it stores |
|---|---|
~/.cache/stackbone/bin/ |
Auto-downloaded binaries (today: frpc-<version>). |
Per-project
Lives at the project root:
| Path | Tracked? | What it stores |
|---|---|---|
stackbone.config.ts |
Optional | An optional workflow override. By default stackbone dev and stackbone publish derive the workspace registry by convention — scanning every deep-agents/<name>/index.ts and every workflows/<name>.workflow.ts file — so most projects need no config file at all. When this file is present (default-exporting defineWorkspace({ workflows }) from @stackbone/sdk) its workflows win over the convention scan; agents are always discovered from deep-agents/. See Workflows. |
.stackbone/project.json |
No (gitignored by stackbone init) |
{ schemaVersion, organizationId, agentId, controlPlaneUrl }. Identifies which organization this workspace points at. |
.stackbone/*.d.ts |
No (gitignored) | Generated editor types — config.d.ts, agents.d.ts, connect.d.ts — that give the ambient stackbone client typed autocompletion. Regenerated by stackbone dev; config.d.ts also by stackbone config types (no control plane needed). |
stackbone.config.json |
Yes | { studio: { corsOrigins } } and other team-shared knobs. Reviewable in PRs. Different file from stackbone.config.ts above — the .json holds CLI/Studio knobs, the .ts holds the workspace manifest. |
agent.yaml |
Yes | Manifest. See agent.yaml reference. |
dist/stackbone/ |
No (build output) | Written by stackbone publish — the workspace bundle (workspace-bundle.tar) plus its JSON pointer (workspace-bundle.json, carrying the content digest). |
.claude/, .cursor/, .windsurf/, … |
No (gitignored by stackbone init / link) |
Per-coding-agent skill directories the upstream skills registry materialises. See Agent skills overview. |
Output contract
Human mode (default)
Free-form text on stdout. May span multiple lines, may include @clack/prompts
chrome and ANSI colour codes. Never parse this. Logging (debug,
trace, deprecation warnings) goes to stderr through Pino regardless of
mode.
JSON mode
Triggered by --json or STACKBONE_JSON=1. Every command emits a single
JSON line on stdout wrapped in:
// success
{ "schema_version": 1, ...payload }Errors go on stderr and include the original exit code:
{
"schema_version": 1,
"error": {
"code": "auth",
"message": "No active session for https://api.stackbone.ai",
"suggestion": "Run `stackbone login`",
},
"exit_code": 2,
}schema_version is an integer that only bumps on
backward-incompatible shape changes. If a coding agent sees a value
greater than the one it was written against, it should refuse to parse
and surface the version mismatch.
Paginated lists
Every list-style command takes --limit <n> and --cursor <opaque>, and
its JSON payload carries an items array plus nextCursor and prevCursor
(either may be null). To walk forward, pass the previous page's
nextCursor value back as --cursor. Some lists add domain-specific fields
alongside items (for example common_prefixes on a storage listing); the
three pagination keys are always present and always shaped the same way.
{
"schema_version": 1,
"items": [
/* … */
],
"nextCursor": "eyJ…",
"prevCursor": null,
}Exit codes
| Code | Name | When |
|---|---|---|
0 |
ok | Command succeeded. |
1 |
generic | Catch-all error. Anything that isn't auth / project / not-found / permission. |
2 |
auth | Not logged in or session expired. Protected commands never auto-trigger login — run stackbone login first. |
3 |
no project | The command needs a target it can't resolve: the cwd has no .stackbone/project.json, or it targets the local-dev agent but stackbone dev is not running (dev_not_running). |
4 |
not found | Workspace, agent or publication does not exist. |
5 |
permission denied | Caller is authenticated but unauthorised for the resource. |
These map 1:1 from error.code in the JSON envelope, so a coding agent
can branch on either without parsing the message:
# In a shell script
if ! out=$(stackbone whoami --json 2>err); then
case "$(jq -r '.error.code' < err)" in
auth) stackbone login ;;
not_found) echo "stale session" ;;
*) echo "unexpected: $out" ; exit 1 ;;
esac
fiThe mapping is stable across CLI releases — scripts and coding agents can pin to the table above.
Putting it together
A coding-agent-friendly invocation looks like:
STACKBONE_JSON=1 stackbone metadata…and produces a single line that fully describes the organization state. That's the pattern this whole CLI is optimised for.
Not on this page: the stackbone config document
This page is about CLI and on-disk configuration — flags, env vars, and
the files that live in your project. It is not about the versioned
AGENT_CONFIG document your running agents and workflows read at runtime
through the ambient client (stackbone.config.get(...)). That runtime
config is its own surface, edited with the stackbone config command family
(get, set, versions, rollback, types) and shaped by a
config.schema.ts in your project. See the
config module reference.