CLI UX Design
Purpose
Agent-first CLI design skill for commands that serve both machine consumers (JSON envelopes via --json) and human operators (Rich terminal output). Covers the dual-output routing pattern, structured JSON envelopes with HATEOAS next actions, Rich progress indicators, data model conventions, and terminal visual language used across the ai-eng CLI.
Trigger
- Command: agent invokes cli-ux skill or user requests CLI command design/review.
- Context: new CLI command, CLI output review, adding
--jsonsupport, improving terminal UX, adding progress indicators.
When NOT to Use
- API endpoint design — use
dev:api-designfor REST/GraphQL contract-first design. - CI/CD pipeline configuration — use
dev:cicd-generatefor pipeline setup. - General code review — use
dev:code-reviewfor implementation review. - Test strategy for CLI commands — use
dev:test-strategyfor test design.
Procedure
Understand output context — determine if the command needs JSON mode, human mode, or both.
- All user-facing commands MUST support both modes (dual-output).
- Internal/plumbing commands may be JSON-only.
- Read
src/ai_engineering/cli_output.pyfor the routing pattern.
Design data model — every result gets both
to_dict()andto_markdown().to_dict(): returns a JSON-serializable dict for the envelope.to_markdown(): returns a human-readable markdown string.- Serialization rules:
Path→.as_posix(), dates →.isoformat(), enums →.value. - Use Pydantic models or
@dataclass— never raw dicts as primary data structures.
Implement dual-output routing — branch on
is_json_mode().- JSON path:
emit_success(command_name, data_dict, [NextAction(...)])→ stdout. - JSON errors:
emit_error(command_name, message, error_code, fix, [NextAction(...)])→ stdout. - Human path:
result_header(),kv(),status_line(),suggest_next()→ stderr. - Rule: JSON goes to stdout, human messaging goes to stderr (CLIG guideline).
- Use
output()router fromcli_output.pyfor clean branching.
- JSON path:
Design JSON envelope — follow the
SuccessEnvelope/ErrorEnvelopecontract.- Success:
{ ok: true, command: str, result: dict, next_actions: [...] }. - Error:
{ ok: false, command: str, error: { message, code }, fix: str, next_actions: [...] }. - HATEOAS
NextAction:{ command: str, description: str, params?: dict }— suggest follow-up commands. - Use
truncate_list(items, max_items=20)for large collections to protect agent context windows.
- Success:
Add progress indicators — wrap long operations in
spinner()orstep_progress().spinner(description): single-step context manager for indeterminate waits.step_progress(total, description): multi-step tracker withtracker.step(msg).- Auto-suppressed in JSON mode (
is_json_mode()) and non-TTY (CI, piped output). - Transient by default — spinners disappear when done, leaving clean output.
- Gate hooks (pre-commit, commit-msg, pre-push): NEVER add progress indicators.
Apply visual language — consistent Rich markup and color semantics.
- Success:
[success](green). Error:[error](red). Warning:[warning](yellow). Info:[info](blue). - Brand accent:
[brand](teal#00D4AA). Muted:[muted](dim). Paths:[path](teal underline). - Key-value pairs:
kv(key, value). File counts:file_count(label, count). - Section dividers:
header(title). Result summary:result_header(label, status, detail). - Next steps:
suggest_next([(command, description), ...]). - Respect
NO_COLOR,TERM=dumb, and non-TTY detection (handled byget_console()).
- Success:
Validate output contract — verify both output modes work correctly.
- JSON: output is valid JSON, parseable by
json.loads(). - JSON: envelope matches
SuccessEnvelope/ErrorEnvelopeschema. - Human: output is readable, no raw dicts or repr strings.
- Test:
--jsonflag produces stdout-only JSON; human mode produces stderr-only Rich output.
- JSON: output is valid JSON, parseable by
Output Contract
- CLI command with dual-output support (JSON + human).
- Data model with
to_dict()andto_markdown()methods. - JSON envelope conforming to
SuccessEnvelope/ErrorEnvelope. - Progress indicators that auto-suppress in JSON/non-TTY contexts.
Governance Notes
- CLI output modules are framework-managed — changes require governance review.
- JSON envelope schema (
SuccessEnvelope/ErrorEnvelope) is a contract — do not modify fields without versioning. - Human output primitives (
cli_ui.py) are shared — additions are welcome, removals require deprecation. - All new commands must pass
--jsonoutput validation in tests.
Iteration Limits
- Max 3 attempts to resolve the same CLI output issue. After 3 failures, escalate to user with evidence.
Post-Action Validation
- After implementing dual-output, verify JSON is parseable with
json.loads(). - Run the command with and without
--jsonto confirm both paths work. - If validation fails, fix issues and re-validate (max 3 attempts).
References
standards/framework/stacks/python.md— Python stack patterns.
Implementation Files (repo root-relative)
Read these on-demand when implementing or reviewing CLI commands:
src/ai_engineering/cli_envelope.py— JSON envelope (SuccessEnvelope,ErrorEnvelope,NextAction).src/ai_engineering/cli_ui.py— Rich human output primitives (kv,status_line,result_header,suggest_next).src/ai_engineering/cli_output.py— dual-mode router (is_json_mode,output).src/ai_engineering/cli_progress.py— spinner and step_progress context managers.src/ai_engineering/cli_commands/core.py— reference implementation (install, doctor commands).