Machine-readable mode
Every non-streaming sona command accepts --json and emits a single-line envelope. Streaming commands (chat) emit one envelope per chunk (ndJSON). This is the surface you wire orchestrators, CI pipelines, and parent agents against — no scraping of human text, no fragile regex.
Envelope shape
One of two shapes, picked by success or failure:
{ "ok": true, "command": "status", "result": { "running": true, "pid": 12345 } }
{ "ok": false, "command": "uninstall", "error": { "code": "confirmation_required", "message": "uninstall is destructive; --json requires explicit --yes to skip the prompt." } }Fields:
ok(boolean) — true on success, false on error.command(string) — the subcommand that wrote the envelope (status,update,uninstall, …). Matches what you typed aftersona.result(object, success only) — command-specific payload. See the table below for the per-command shape.error.code(string, error only) — machine-readable identifier. Stable across releases.error.message(string, error only) — human-readable explanation. May change between releases; do not match against it.
Exit code reflects the same semantics: 0 for success, non-zero for errors. The exit code is the orchestrator's primary signal; the envelope is the explanation.
Commands
| Command | Success result keys | Notable error codes |
|---|---|---|
sona --version --json | version, jre | — |
sona --help --json | version, commands[], globalOptions[] | — |
sona status --json | running, pid, reason | — (exit 1 when not running) |
sona stop --json | stopped, pid, used_force | signal_rejected, timeout, interrupted |
sona init --json | configExists, configPath | interactive_required (with --add-channel) |
sona update --json | action (one of installed / already_latest), from, to, current | endpoint_unusable, no_download_url, install_failed |
sona update --check --json | action: "check", current, latest, url, upToDate | endpoint_unusable |
sona update --rollback --json | action: "rolled_back" | no_previous_version, rollback_failed |
sona uninstall --dry-run --json | dryRun: true, paths[] | — |
sona uninstall --yes --json | dryRun: false, removed, failed, skipped, removedPaths[], failedPaths[] | confirmation_required, daemon_stop_failed |
sona doctor --json | schemaVersion, overall (one of ok / warn / fail), checks[] | — (own document, not the envelope) |
sona logs --count --json | count | — |
sona chat --json | per-chunk records (ndJSON; see CLI channel) | — |
sona search --json "<q>" | query, total, hits[] | — |
sona config show --json | masked sona.yml as a config map | — |
sona cost --json [WINDOW] | window, totalUsd, byProvider, byWorkspace | — |
doctor --jsonpredates the envelope contract (TNS-466 axis 2). It emits its own document withschemaVersion,overall, andchecks. The shape is stable; do not assume the standard envelope.
Examples
Health check in a CI script
if ! sona doctor --json | jq -e '.overall == "ok"' > /dev/null; then
echo "Sona not healthy; aborting deploy"
exit 1
fiUpgrade only when a newer version exists
LATEST=$(sona update --check --json | jq -r '.result.latest')
CURRENT=$(sona update --check --json | jq -r '.result.current')
if [ "$LATEST" != "$CURRENT" ]; then
sona update --json | jq .
fiAudit what uninstall would touch
sona uninstall --dry-run --json | jq '.result.paths[]'
# → "/Users/me/.sona"
# → "/Users/me/.local/bin/sona"
# → "/Users/me/.local/share/sona"Read config state without triggering the wizard
sona init --json | jq '{exists: .result.configExists, path: .result.configPath}'
# → { "exists": true, "path": "/Users/me/.sona/sona.yml" }init --json is a query. The interactive wizard is never triggered; only configExists + configPath are reported. To bootstrap from scratch, fall back to interactive sona init and let the operator walk the prompts. init --add-channel --json is rejected with interactive_required for the same reason — the channel walkthrough is multi-prompt and doesn't translate to a single envelope.
Destructive commands and --json
sona uninstall is destructive. In --json mode, the prompt would be silently skipped; we refuse rather than allow that. --json alone returns confirmation_required (exit 1):
{ "ok": false, "command": "uninstall", "error": { "code": "confirmation_required", "message": "..." } }Pass --yes explicitly to opt in:
sona uninstall --yes --json--dry-run --json is always safe (no filesystem touch) and does not need --yes.
Streaming vs. one-shot
This page covers one-shot commands. Streaming commands (sona chat --json) emit ndJSON — one envelope per chunk, terminated by a done: true record. See the CLI channel page for the per-chunk shape and a worked example.
Stability
ok,command,result/errorkeys: stable. Renaming would break every orchestrator. Treated as semver-major.error.codevalues: stable within a minor release. New codes may appear in minor releases; existing codes never change spelling.resultpayload keys per command: additive. New keys may appear; existing keys keep their meaning.error.messagetext: unstable. Read for humans; never match against.
See Sona how-it-works for the architecture context, or the TnsAI.Sona release notes for the per-version changelog.