painted

CLI Harness (fidelity)

The fidelity layer standardizes a common CLI UI spectrum:

  • STATIC: print once and exit (scrolls away)
  • LIVE: in-place updates with cursor control
  • INTERACTIVE: a full TUI loop (alt screen + keyboard input)

It also standardizes:

  • Zoom (-q, -v, -vv) as “detail level”
  • Format (--plain, --json) as “serialization”

See also:

  • CLAUDE.md: current API reference (../../CLAUDE.md)

Core types

class Zoom(IntEnum):
    """Detail level for rendering."""

    MINIMAL = 0  # One-liner, counts only
    SUMMARY = 1  # Key information, tree structure
    DETAILED = 2  # Everything visible, nested expansion
    FULL = 3  # All fields, full depth
class OutputMode(Enum):
    """Delivery mechanism."""

    AUTO = "auto"  # Detect from TTY/pipe
    STATIC = "static"  # print_block, scrolls away
    LIVE = "live"  # InPlaceRenderer, cursor control
    INTERACTIVE = "interactive"  # Surface, alt screen
class Format(Enum):
    """Serialization format."""

    AUTO = "auto"  # Detect from TTY
    ANSI = "ansi"  # Styled terminal output
    PLAIN = "plain"  # No escape codes
    JSON = "json"  # Machine-readable
@dataclass(frozen=True)
class CliContext:
    """Resolved runtime context."""

    zoom: Zoom
    mode: OutputMode  # Resolved (never AUTO)
    format: Format  # Resolved (never AUTO)
    is_tty: bool
    width: int
    height: int

Entry point

run_cli() is the intended “one call” entry point.

def run_cli(
    args: list[str],
    render: Callable[[CliContext, T], "Block"],
    fetch: Callable[[], T],
    *,
    fetch_stream: Callable[[], "AsyncIterator[T]"] | None = None,
    handlers: dict[OutputMode, Callable[[CliContext], R]] | None = None,
    default_zoom: Zoom = Zoom.SUMMARY,
    description: str | None = None,
    prog: str | None = None,
    add_args: Callable[[argparse.ArgumentParser], None] | None = None,
) -> int:

Pattern: state → Block, decoupled from I/O

The harness keeps a strict separation:

  • your code owns state fetching (fetch / fetch_stream)
  • your code owns rendering (render(ctx, state) -> Block)
  • painted owns delivery (static/live/interactive formatting and terminal behavior)

Demo output (fidelity.py)

Zoom.MINIMAL

67% used (134.0G/200.0G)

Zoom.DETAILED

╭─ Disk: /home ─────────────────────────────────────────╮
│ 67.0% ████████████████████░░░░░░░░░░ 134.0G/200.0G    │
╰───────────────────────────────────────────────────────╯
                                                         
╭─ By Directory ────────────────────────────────────────╮
│ 45.0G ▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░  33.6%  projects │
│ 28.0G ▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░  20.9%  downloads│
│ 22.0G ▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░  16.4%  .cache   │
│ 18.0G ▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░  13.4%  documents│
│ 12.0G ▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░   9.0%  pictures │
│  9.0G ▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░   6.7%  .local   │
╰───────────────────────────────────────────────────────╯
                                                         
  Free: 66.0G