Reference
Output & scripting
Output formats, exit codes, workspace linking, environment variables, and shell completion.
The CLI is built to be scripted. Output is machine-parseable, exit codes are a stable contract, and the app a command operates on can be resolved from the environment instead of a flag on every invocation.
Output formats
Section titled “Output formats”Data-returning commands generally take --output; mutation commands that
only print a confirmation line (apps delete, keys revoke, agents revoke,
and similar) do not. Formatted commands accept all three formats:
| Format | Best for | Notes |
|---|---|---|
table | Interactive use | Plain-ASCII, the default for list commands. |
json | Scripts and jq | Pretty-printed. The default for single-object commands. |
jsonl | Line-oriented list pipelines | One JSON object per line — pipe into jq -c / grep / awk. The CLI validates the page before writing it, so an invalid --fields projection never leaves a partial stream. Falls back to json for single-object responses. |
alter apps list --output jsonalter audit list --since 7d --output jsonl | jq -c 'select(.action == "token.retrieved")'Field selection
Section titled “Field selection”Narrow JSON / JSONL output to specific top-level keys with --fields:
alter apps list --fields id,name# [# { "id": "<app-id>", "name": "demo" },# ...# ]The value is comma-separated (spaces around commas are fine; spaces inside a name are rejected as a typo). An unknown field name fails loudly rather than emitting null — so a typo surfaces on the first run. --fields is inert with --output table, since table columns are already a fixed slice.
Exit codes
Section titled “Exit codes”The CLI returns a structured exit code so scripts can branch on the failure mode without parsing stderr. These are a stable contract across releases.
| Code | Name | Meaning |
|---|---|---|
0 | OK | Command succeeded. |
1 | ERROR | Generic runtime failure, including network/SDK errors and backend statuses without a dedicated mapping (for example 400, 422, and 5xx). |
2 | USAGE | Bad flag, argument, or input format. Always paired with a stderr line naming the offending input. |
3 | AUTH | Not signed in, or the PAT was revoked / expired. Fix: re-run alter auth login. |
4 | NOT_FOUND | Resource not found (404), or a referenced local file is missing. |
5 | CONFLICT | A 409 server-state conflict, such as an active dependent that blocks an operation or an existing config entry. |
6 | RATE_LIMIT | A 429 — retry with backoff. |
7 | FORBIDDEN | The request was refused with a 403: either the PAT lacks the required scope, or the organization’s plan does not include the feature. Fix: re-mint the PAT with broader scopes, or upgrade the plan — the printed message says which. |
8 | CANCELLED | A destructive-action prompt was declined or could not read a matching confirmation. Pass the command’s --yes or --confirm <value> flag in CI. An explicit wrong --confirm value is normally USAGE (2) before the request. |
managed-secrets delete is the exception for an explicit confirmation: the
CLI can avoid the read needed to discover the secret slug, so it forwards
--confirm and a mismatch returns generic server error code 1.
branding set without --replace, against an app that has no branding yet,
exits 2 (usage) rather than 4: the backend’s 404 carries the machine
code branding_not_configured, which is a fixable input problem — re-run with
--replace — not an absent resource. A script branching on 4 to mean
“create it instead” will never fire.
The pre-minted-token probe in auth login, the remote probe in auth status,
and pats whoami use generic code 1 when the backend rejects a resolved PAT.
Regular resource commands that use the shared authenticated-command path map a
backend 401 to 3.
alter verify also uses code 1 for its verification-input failures: a
malformed --key, a malformed --grant used with --runtime, or a missing
or malformed --agent required by an agent-runtime design.
alter link predates the dedicated cancellation code: declining its
overwrite prompt currently returns 1, and --force is the non-interactive
form. All destructive resource prompts use 8.
Example — probe whether an app exists without erroring on the not-found case:
alter apps show "$APP_ID" --output json > /dev/null 2>&1status=$?if [ "$status" -eq 0 ]; then echo "app exists"elif [ "$status" -eq 4 ]; then echo "app not found"elif [ "$status" -eq 7 ]; then echo "PAT lacks dashboard_apps:read — re-mint with broader scopes"else echo "unexpected error" && exit 1fiCapture $? into a variable immediately — every command clobbers it.
Environment variables
Section titled “Environment variables”| Variable | Used by | Effect |
|---|---|---|
ALTER_PAT | All commands | The PAT to authenticate with. Highest-precedence credential source. |
ALTER_APP_ID | App-scoped commands | Default app ID when --app is omitted (UUID only — the env var and workspace pin never take a name). |
ALTER_API_KEY | sdk-passthrough | Runtime API key for the one-off request escape hatch (not a PAT). |
XDG_CONFIG_HOME | Authentication, Fish completion | Moves the plaintext fallback to $XDG_CONFIG_HOME/alter/auth.toml and the Fish install target to $XDG_CONFIG_HOME/fish/completions/alter.fish. |
XDG_CACHE_HOME | Update notifier | Moves the 24-hour update-check cache from ~/.cache/alter/update-check.json. |
XDG_DATA_HOME | Bash completion | Changes the Bash install target to $XDG_DATA_HOME/bash-completion/completions/alter. |
SHELL | Completion | Auto-detects bash, zsh, or fish when --shell is omitted. |
ALTER_NO_UPDATE_NOTIFIER, NO_UPDATE_NOTIFIER, CI | Update notifier | Any non-empty value other than 0 or false disables the update notice. |
ALTER_NO_TELEMETRY, DO_NOT_TRACK | Usage telemetry | 1 or true disables telemetry when the CLI build has telemetry configured. |
Published builds do not emit telemetry unless telemetry has been explicitly
configured. When enabled, events use a random anonymous installation ID stored
at ~/.config/alter/telemetry-id; no user PII is attached. Delivery is
best-effort and bounded, so it cannot change a command’s result.
Workspace linking
Section titled “Workspace linking”If you work primarily on one app, pin it to the current directory tree so app-scoped commands don’t need --app:
cd ~/code/my-productalter link <app-id># alter: pinned app_id=<app-id> in .../.alter/config.yaml# alter: appended `.alter/` to .gitignore so the pin isn't committed.
# From anywhere in this tree, --app is now optional:alter keys listalter agents create --name worker --type service \ --scopes '{"google":["openid"]}'alter policy show-app
# Inspect or clear the pinalter link --statusalter unlinkThe app ID is resolved in this order (highest precedence first):
--app <id>on the command lineALTER_APP_IDenvironment variable- The nearest
.alter/config.yaml, found by walking up from the current directory - Otherwise:
no app selected(exit code 2)
Discovery checks each parent directory through $HOME itself, then stops
before walking above it. Therefore $HOME/.alter/config.yaml intentionally
acts as a user-wide default; configs above the home directory are never read.
The search tests for .alter/config.yaml, not merely an .alter/ directory.
alter link <app-id> validates the UUID and verifies that the app exists
before writing. If the current directory already pins a different app, it
prompts before overwriting; use --force for scripts. In an enclosing git
repository it appends .alter/ to the repository root’s .gitignore.
alter unlink removes only ./.alter/config.yaml in the current directory;
it does not remove a pin discovered from a parent.
Shell completion
Section titled “Shell completion”Generate static completion scripts for bash, zsh, or fish:
# Write the script to the conventional location and print the line to add to the rc filealter completion install
# Or print to stdout to install it yourselfalter completion print --shell zsh >> ~/.zsh/completions/_alterinstall writes the script and prints the line to activate it; it never edits
.bashrc / .zshrc automatically. The shell is auto-detected from $SHELL
unless --shell is passed. Unsupported or undetectable shells return usage
code 2; installation on Windows also returns 2 and directs the operator
to completion print.
| Shell | Default install path |
|---|---|
| Bash | $XDG_DATA_HOME/bash-completion/completions/alter, or ~/.local/share/bash-completion/completions/alter |
| Zsh | ~/.zsh/completions/_alter |
| Fish | $XDG_CONFIG_HOME/fish/completions/alter.fish, or ~/.config/fish/completions/alter.fish |
The generated scripts do not query Alter. They statically complete this
current top-level subset: auth, apps, keys, agents, providers,
policy, audit, pats, link, unlink, completion,
sdk-passthrough, self-update, and help, plus a static subset of their
verbs. They do not complete flags, resource IDs, or the remaining namespaces.
Upgrading
Section titled “Upgrading”# Upgrade to the latest published versionalter self-update
# Pin a specific version, or preview the command without running italter self-update --to 0.3.0alter self-update --to nextalter self-update --dry-run--to accepts strict SemVer (including prerelease/build metadata) or the npm
tags latest and next; other values fail locally with usage code 2.
Legacy self-update --version <value> and --version=<value> are rewritten
to --to with a deprecation warning, while a bare --version still prints
the installed CLI version.
self-update runs npm install -g @alter-ai/cli@<version> and smoke-tests
the resolved binary. If the active binary is not under npm’s global prefix,
the command refuses with usage code 2; update Homebrew, apt, or other
package-manager installations through that manager. A failed npm install
forwards npm’s own exit code.
After a successful interactive command, the CLI may print a newer-version
notice to stderr. It checks npm at most once per 24 hours, caches under the
XDG cache directory, skips piped stdout, --version, and self-update, and
silently ignores registry/cache failures. Use one of the notifier opt-out
variables in the environment table when deterministic interactive stderr is
required.