Configuration reference
Global flags, environment variables, on-disk state and the JSON output contract.
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.Canonical ADR:
2026-04-28-patrones-cli-token-efficient.
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. |
--api-url <url> |
STACKBONE_API_URL |
global config | Override the control plane URL. Falls back to the project config and then to the global config. |
-y, --yes |
(none) | off | Skip every confirmation prompt. Required in CI / non-TTY contexts. |
--no-auto-login |
STACKBONE_AUTO_LOGIN=0 |
off | When a protected command finds no valid session, fail fast with exit code 2 instead of triggering the device-code flow inline. |
Environment variables
The CLI reads these from the parent shell:
| Variable | Effect |
|---|---|
STACKBONE_JSON |
=1 ⇒ same as --json. |
STACKBONE_API_URL |
Override the control plane URL (same as --api-url). |
STACKBONE_AUTO_LOGIN |
=0 ⇒ same as --no-auto-login. |
STACKBONE_LOG_LEVEL |
Pino logger level (debug, info, warn, error). Defaults to info. |
STACKBONE_CLOUDFLARED_BIN |
Absolute path to a cloudflared binary. Used by stackbone dev. |
STACKBONE_CORS_ALLOW_ORIGINS |
CSV of origins added to the stackbone dev Studio CORS allowlist. |
The CLI also injects env vars into the agent process when running
stackbone dev. See SDK integration → env table.
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: cloudflared). |
Per-project
Lives at the project root:
| Path | Tracked? | What it stores |
|---|---|---|
.stackbone/project.json |
No (gitignored by stackbone init) |
{ schemaVersion, workspaceId, agentId, controlPlaneUrl }. Identifies which agent in which workspace this directory points at. |
stackbone.config.json |
Yes | { studio: { corsOrigins } } and other team-shared knobs. Reviewable in PRs. |
agent.yaml |
Yes | Manifest. See agent.yaml reference — TODO. |
.claude/skills/stackbone/SKILL.md |
Yes | Auto-generated skill bundle for Claude Code / coding agents. See Claude skill. |
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.dev",
"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.
See
apps/cli/src/core/output.tsfor the implementation.
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, session expired, or --no-auto-login refused login. |
3 |
no project | The cwd has no .stackbone/project.json and the command needs one. |
4 |
not found | Workspace, agent or deployment 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 full constants live in
apps/cli/src/core/errors.ts.
Multi-environment workflows
The credentials.json schema is keyed by control-plane URL, so a single
machine can be logged into multiple environments:
# Default (production)
stackbone login
# Staging
STACKBONE_API_URL=https://staging.stackbone.dev stackbone login
# Switch back without re-logging in
STACKBONE_API_URL=https://staging.stackbone.dev stackbone whoamiThe "active" environment is whichever URL is selected via
--api-url / STACKBONE_API_URL — falling back to the project's
controlPlaneUrl and then to the global default
(https://api.stackbone.dev). Run stackbone whoami to confirm which one
the CLI is talking to.
Putting it together
A coding-agent-friendly invocation looks like:
STACKBONE_JSON=1 stackbone metadata…and produces a single line that fully describes the workspace state. That's the pattern this whole CLI is optimised for.