Machine-readable output format
Why it matters
A CLI without --json output is a CLI that cannot be scripted. Users pipe your tool into jq, feed it into CI, wrap it in Makefile recipes, and compose it with other tools — and all of that requires structured, decoration-free output on stdout. When mytool list prints a pretty table with emoji dividers, the user has to write a regex parser in bash to extract one field. They will not. They will switch tools. Mixing status messages into JSON output (Loading...\n{...}) is worse — it silently breaks | jq without any error.
Severity rationale
High because missing machine-readable output blocks CI, automation, and composition with Unix pipelines.
Remediation
Add a --json flag to every command that produces data, write JSON to stdout with no prefix or decoration, and route status/progress to stderr. Example in src/cli/commands/list.ts:
if (opts.json) {
process.stdout.write(JSON.stringify(items) + '\n')
} else {
console.log(formatTable(items))
}
Confirm with mytool list --json | jq ..
Detection
-
ID:
machine-output -
Severity:
high -
What to look for: List all commands that produce output. For each, check for a
--json,--format json,--output json, or--format=jsonflag on commands that produce data output. Verify that the JSON output is valid JSON (not pretty-printed log-style text), written to stdout, and contains all the data from the human-readable output. Check that JSON mode suppresses non-JSON decorations (headers, dividers, color codes). -
Pass criteria: Commands that produce data output support at least one machine-readable format (JSON preferred). The machine-readable output is valid, parseable, and written cleanly to stdout without decorations or status messages mixed in — at least 1 machine-readable output format (--json, --csv, or --format) available. Report: "X output commands found, Y support --json or equivalent machine-readable format."
-
Fail criteria: No machine-readable output option exists for commands that produce structured data, or
--jsonflag exists but output contains non-JSON text mixed in. -
Skip (N/A) when: The CLI produces no structured data output — it only performs actions (e.g.,
mytool deployormytool init). All checks skip when no CLI entry point is detected. -
Detail on fail: Quote the actual output format showing the missing machine-readable option. Example:
"'list' command outputs a formatted table but has no --json or --format option for scripting"or"--json flag exists but output includes 'Loading...' text before the JSON object" -
Remediation: Machine-readable output makes your CLI scriptable:
program .command('list') .option('--json', 'Output as JSON') .action(async (opts) => { const items = await fetchItems() if (opts.json) { process.stdout.write(JSON.stringify(items, null, 2) + '\n') } else { // human-friendly table output console.log(formatTable(items)) } })@cli.command() @click.option('--json', 'output_json', is_flag=True, help='Output as JSON') def list_items(output_json): items = fetch_items() if output_json: click.echo(json.dumps(items, indent=2)) else: for item in items: click.echo(format_item(item))
Taxons
History
- 2026-04-18·v1.0.0·Initial import from cli-quality·automated