Agent-Friendly CLI Design¶
Design CLIs that AI agents can operate reliably. These principles apply to all rararulab CLI tools.
1. Non-Interactive First¶
Agents cannot handle dynamic prompts. Every parameter must be passable via flags.
# Bad: interactive prompt
? Which environment? (use arrow keys)
# Good: flag-driven
my-cli deploy --env staging
Rule: If a command works interactively, it MUST also work with --flags only. No stdin prompts without a flag equivalent.
2. Lazy-Loaded Documentation¶
Agents discover commands incrementally. Don't dump all help at once.
# Good: top-level shows subcommands only
my-cli --help
# Good: detail per subcommand
my-cli deploy --help
Rule: --help at each level shows only that level's options.
3. Example-Driven Help¶
Agents pattern-match better than they parse prose.
# Good: copy-paste-ready examples in help text
EXAMPLES:
my-cli deploy --env staging --service api
my-cli deploy --env prod --service api --dry-run
Rule: Every subcommand's --help includes at least one complete, runnable example.
4. Pipe-Friendly (JSON stdout, logs stderr)¶
Agents chain tools together. Structured output enables downstream parsing.
# stdout: machine-readable JSON
{"ok": true, "deploy_id": "d-123", "url": "https://..."}
# stderr: human-readable logs
Deploying api to staging...
Rule: JSON on stdout, human text on stderr. Never mix formats on the same stream.
Standard JSON Response Shape¶
Success:
Error:
{"ok": false, "error": "service 'foo' not found", "suggestion": "run 'my-cli list' to see available services"}
The suggestion field gives agents a concrete next step when something fails.
5. Fail Fast with Actionable Errors¶
Agents can self-correct, but need clear signals.
# Bad: hangs silently or gives vague error
Error: something went wrong
# Good: immediate exit + what to do
{"ok": false, "error": "missing --env flag", "suggestion": "add --env staging or --env prod"}
Rule: Non-zero exit code + JSON error with suggestion field. Never hang, never swallow errors.
6. Idempotent Operations¶
Agents retry frequently. Repeated calls must not create duplicate side effects.
# First call
{"ok": true, "created": true, "name": "my-service"}
# Second call (same args)
{"ok": true, "created": false, "name": "my-service", "reason": "already exists"}
Rule: create operations return success (exit 0) if resource already exists. Use created: true/false to distinguish.
7. Dry-Run Mode¶
Agents should verify plans before executing. This reduces blast radius.
my-cli deploy --env prod --dry-run
{"ok": true, "dry_run": true, "would_deploy": "api@v1.2.3", "target": "prod"}
Rule: Destructive or stateful commands MUST support --dry-run. Output shows what would happen without doing it.
8. Force/Confirm Bypass¶
Keep safety nets for humans, but let agents automate.
# Human: gets confirmation prompt
my-cli delete --service api
# Agent: bypasses prompt
my-cli delete --service api --yes
Rule: Interactive confirmations must have a --yes or --force flag to bypass.
9. Predictable Command Structure¶
Consistent naming reduces agent learning cost.
resource verb [args] [flags]
my-cli service list
my-cli service create my-service --description "..."
my-cli service deploy my-service --env staging
my-cli service delete my-service --yes
Rule: Follow <noun> <verb> pattern. Same verb means same behavior across resources.
10. Structured Success Feedback¶
Agents need parseable results for downstream decisions.
# Bad: celebration text
🎉 Deployed successfully!
# Good: machine-readable fields
{"ok": true, "action": "deploy", "deploy_id": "d-123", "duration_ms": 4521, "url": "https://..."}
Rule: Success output includes all fields an agent needs for the next step. No emoji, no decoration on stdout.
rararulab Implementation Checklist¶
When building a CLI in rararulab:
- All commands work without interactive prompts (flags only)
- JSON stdout, human logs stderr
- Error responses include
suggestionfield - Create operations are idempotent (exit 0 if exists)
- Destructive commands support
--dry-run - Interactive confirmations have
--yesbypass - Commands follow
noun verborverb nounpattern consistently -
--helpincludes runnable examples per subcommand - Non-zero exit code on failure
- Success output includes all fields for downstream use
Rust Implementation Pattern¶
Using clap + serde_json + snafu (per rust-style.md):
// JSON output helper
fn json_ok(value: &impl serde::Serialize) -> ExitCode {
println!("{}", serde_json::to_string_pretty(value).expect("serialize"));
ExitCode::SUCCESS
}
fn json_err(error: &str, suggestion: &str) -> ExitCode {
println!("{}", serde_json::json!({
"ok": false,
"error": error,
"suggestion": suggestion,
}));
ExitCode::FAILURE
}