painted — CLI Harness Fidelity
Description: painted documentation

# 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