CLI Development
Consult standards-for-python-development for the shared rules. Load python3-testing for test patterns.
Audience First
Before choosing presentation libraries, identify the primary consumer.
- A CLI implemented inside an Agent Skill or plugin is agent-facing by default. Do not use Rich for its command output. Emit compact JSON on stdout, preferably from a Pydantic response model with
model_dump_json(); do not indent it. Send diagnostics to stderr and keep the JSON schema stable.
- Human-facing CLIs may use Typer + Rich for terminal presentation.
- For mixed audiences, compact JSON is the automation contract; human formatting is an explicit presentation mode.
Agents must not need to parse tables, colours, progress bars, panels, or explanatory prose to consume a tool result.
Standards
Annotated[Type, typer.Option(...)] syntax for all CLI params
rich_help_panel to group options
- Rich emoji tokens (
:white_check_mark:) not Unicode literals for human-facing output
- Architecture: CLI → Business Logic → Services → Output boundary (compact JSON for agents; Rich display for humans)
uv run <script> over python3 <script>
- Factory pattern for dependency injection
App Structure
import typer
from rich.console import Console
app = typer.Typer()
console = Console()
@app.command()
def process(
input_file: Annotated[Path, typer.Argument(help="Input file")],
verbose: Annotated[bool, typer.Option("--verbose", "-v")] = False,
) -> None:
"""Process input file."""
...
Rich Width Handling
from rich.console import Console
from rich.table import Table
from rich.measure import Measurement
def get_table_width(table: Table) -> int:
temp = Console(width=9999)
m = Measurement.get(temp, temp.options, table)
return int(m.maximum)
Testing
from typer.testing import CliRunner
runner = CliRunner()
def test_app_runs() -> None:
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
Async Patterns
Use semaphores for I/O-bound CLI tasks:
import asyncio
import typer
from typing import Annotated
@app.command()
def fetch(urls: Annotated[list[str], typer.Argument()], max_concurrent: Annotated[int, typer.Option()] = 10) -> None:
"""Fetch multiple URLs concurrently."""
results = asyncio.run(_fetch_all(urls, max_concurrent))
for result in results:
console.print(result)
async def _fetch_all(urls: list[str], limit: int) -> list[str]:
sem = asyncio.Semaphore(limit)
async with httpx.AsyncClient() as client:
tasks = [_fetch_one(client, u, sem) for u in urls]
return await asyncio.gather(*tasks)
PEP 723 Shebang
#!/usr/bin/env -S uv run --quiet --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["typer>=0.21", "rich>=13.0"] # human-facing example only
# ///
References
references/typer-app-and-commands.md, references/typer-parameters.md, references/typer-parameter-types.md, references/typer-advanced-patterns.md, references/typer-subcommands.md, references/typer-testing.md — Typer commands, arguments, parameters, subcommands
references/rich-console-and-markup.md, references/rich-renderables.md, references/rich-text-and-syntax.md, references/rich-advanced-patterns.md, references/rich-progress-and-live.md, references/rich-logging-and-tracebacks.md — Rich tables, panels, progress, live displays
references/typer-rich-non-tty-patterns.md, references/typer-rich-tables.md, references/typer-rich-exception-handling.md, references/typer-rich-testing-patterns.md — Typer+Rich integration, non-TTY, width, testing
Related Skills
Load python-engineering:textual when the task involves Textual TUI widgets, screen stack, CSS styling, reactive attributes, Pilot testing, or background workers.
Load python-engineering:typer when the task is focused on Typer commands, parameter configuration, subcommand composition, or Typer-specific documentation.
Load python-engineering:typer-and-rich when the task involves Rich table rendering in non-TTY contexts, Typer/Rich integration pitfalls, or correctness review of CLI output handling.
Assets
assets/python-cli-demo.py — complete working example
assets/typer_examples/index.md — working scripts demonstrating non-TTY display solutions (Panel/Table width, wrapping, cropping)
assets/nested-typer-exceptions/ — runnable demos of Typer nested exception anti-patterns and fixes