JSON Output and Errors
CLI output that humans can read and agents can parse.
Beltline gives generated commands consistent human output, --json output, machine-readable errors, redaction conventions, and non-interactive modes.
Do not make agents scrape terminal prose. Every command should have a human contract and a machine contract. Structured output turns a CLI into an automation surface.
Pretty output is not enough anymore.
Humans need helpful tables and hints. CI jobs need stable JSON. Agents need structured results and errors they can recover from. One CLI needs to serve all three.
Most CLIs are designed for the terminal and then retrofitted for automation — with inconsistent --json flags, text-format errors, and no redaction for sensitive values. The result is brittle scripts and unreliable agent integrations.
Before and after Beltline
| Before | After Beltline |
|--------|----------------|
| Scripts scraping terminal tables with awk | Human tables by default, stable JSON with --json |
| Errors only make sense to humans | Structured error envelope with code, message, and retryable |
| Agents guess what failed | Errors agents can branch on without parsing |
| Sensitive data leaks into logs | Redaction conventions for tokens and secrets |
| CI needs special wrappers and prompt-killers | --non-interactive is first-class |
| Half-finished --json flags here and there | The same JSON shape on every command |
Human and machine output from the same command
Human mode:
acme deployments list --env prod
DEPLOYMENT SERVICE STATUS AGE
dep_123 billing-api failed 4m
dep_122 web success 18m
JSON mode:
acme deployments list --env prod --json
{
"data": [
{ "id": "dep_123", "service": "billing-api", "status": "failed" },
{ "id": "dep_122", "service": "web", "status": "success" }
],
"meta": { "count": 2, "page": 1 }
}
Structured error:
{
"error": {
"code": "DEPLOYMENT_NOT_FOUND",
"message": "Deployment dep_999 was not found.",
"retryable": false,
"docsUrl": "https://beltline.dev/errors/deployment-not-found"
}
}
Note that data goes to stdout and errors go to stderr — even with --json. That keeps acme deployments list --json | jq clean even when the request fails.
What Beltline handles for you
Human-friendly by default Tables, aligned columns, and status indicators for interactive use. The default experience is designed for a person at a terminal.
Stable JSON for automation
--json produces the same envelope shape every time. Scripts and agents can rely on the output shape without defensive parsing.
Machine-readable errors
Every error has a code, a message, and a retryable flag. Agents know whether to retry, escalate, or stop. No parsing required.
Redaction for sensitive output Tokens, secrets, and sensitive fields are redacted from output by default. Explicit flags or scoped output modes expose them when actually needed.
Non-interactive mode for CI and agents Commands that prompt for input interactively accept the same values via flags or environment variables. CI and agents get deterministic behavior.
Frequently asked questions
Should every command support JSON? For read commands, yes. For write commands, the output still includes a structured result or error so scripts can verify the outcome.
Does this help MCP? Yes. Structured outputs and errors make generated MCP tools more reliable. Agents can parse results and handle errors without guessing the format.
Can we keep pretty terminal output?
Yes. Human output remains the default. JSON is available on demand via --json. The two modes are independent.
Can we add custom fields to JSON output?
Yes. TypeScript handlers control the output shape. The --json envelope is enforced; the content inside data is yours.