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.
High because missing machine-readable output blocks CI, automation, and composition with Unix pipelines.
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 ..
ID: cli-quality.io-behavior.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=json flag 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 --json flag 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 deploy or mytool 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))