# Output Rendering Framework The output rendering framework decouples command output data from its visual presentation. Every CLI command produces structured output through a common abstraction layer, and the active **format** determines how that output is rendered to the terminal or piped to external consumers. ## Architecture All CLI output flows through a five-stage reactive pipeline: 1. **Command Logic** opens an `OutputSession` and creates typed **element handles**. 2. **OutputSession** coordinates handles, tracks their lifecycle, and emits events to the active materialisation strategy. 3. **ElementHandles** are the producer-facing API — thread-safe, format-agnostic write interfaces. 4. **MaterializationStrategy** decides *when* and *how* to render content. 5. **Terminal / Pipe** receives the final byte stream. ## Quick Start ```python from cleveragents.cli.output import OutputSession with OutputSession(format="plain") as session: panel = session.panel("Project Details") panel.set_entry("Name", "local/api-service") panel.set_entry("Status", "active") panel.close() table = session.table(columns=["Resource", "Type"]) table.add_row({"Resource": "api-repo", "Type": "git-checkout"}) table.close() ``` ## OutputSession The `OutputSession` is the central coordinator. It is used as a context manager and provides factory methods for each element type: - `session.panel(title)` → `PanelHandle` - `session.table(columns=[...])` → `TableHandle` - `session.status(message)` → `StatusHandle` - `session.progress(label)` → `ProgressHandle` The session can be created with: - `format` — the desired output format (`rich`, `color`, `table`, `plain`, `json`, `yaml`) - `command` — the command name for metadata - `strategy` — an explicit materialisation strategy (overrides format auto-selection) ## Element Handles ### PanelHandle Key-value pair output with a title. ```python panel = session.panel("Details") panel.set_entry("Name", "my-project") panel.set_entries({"A": "1", "B": "2"}) panel.remove_entry("A") panel.close() ``` ### TableHandle Tabular data with typed columns. ```python table = session.table(columns=["Name", "Status", "Count"]) table.add_row({"Name": "svc-1", "Status": "up", "Count": "42"}) table.add_rows([{"Name": "svc-2", "Status": "down", "Count": "0"}]) table.set_summary({"total": "42"}) table.set_sort_key("Name") table.close() ``` ### StatusHandle Single-line status messages with level indicators. ```python status = session.status("Processing...", level="info") status.set_message("Done") status.set_level("ok") status.set_detail("All 42 items processed") status.close() ``` ### ProgressHandle Progress bars and spinners with throttled updates (max 10 FPS in rich mode). ```python prog = session.progress("Uploading", total=100) prog.set_progress(50, 100) prog.tick() prog.increment(10) prog.set_label("Finalising") prog.set_indeterminate() prog.set_step_status("validate", "done") prog.close() ``` ## Materialisation Strategies | Format | Strategy | Behaviour | |----------|------------------------|-------------------------------------------------| | `rich` | `RichMaterializer` | In-place terminal updates via Rich library | | `color` | `ColorMaterializer` | ANSI codes, scrolling output (no cursor moves) | | `table` | `TableMaterializer` | ASCII box-drawing for tables | | `plain` | `PlainMaterializer` | ASCII-only, no ANSI, no Unicode box characters | | `json` | `JsonMaterializer` | Accumulate-then-serialize as JSON | | `yaml` | `YamlMaterializer` | Accumulate-then-dump as YAML | ### Automatic Fallback When terminal capabilities are insufficient, the framework automatically falls back: - `rich` → `color` → `plain` (based on TTY, ANSI, cursor support) - `color` → `plain` (when no ANSI support) - `table` → `plain` (when not a TTY) - `json` / `yaml` — always available (no terminal needed) Use `--format ` to explicitly set a format and skip fallback. ## Error Envelope JSON and YAML materializers support a unified error envelope: ```json { "error": { "code": "NOT_FOUND", "message": "Resource not found", "details": {"id": "abc-123"} } } ``` ## Producer Integration Guide ### Writing Format-Agnostic Commands Commands should create handles and write data without knowing which format is active: ```python def my_command(session: OutputSession, items: list[Item]) -> None: panel = session.panel("Summary") panel.set_entry("Total", str(len(items))) panel.close() table = session.table(columns=["Name", "Status"]) for item in items: table.add_row({"Name": item.name, "Status": item.status}) table.close() ``` ### Thread Safety Multiple handles can be written concurrently from different threads. The session ensures elements are rendered in declaration order even when handles close out of order. ### Backward Compatibility The existing `format_output()` function in `cleveragents.cli.formatting` continues to work unchanged. A new `format_output_session()` function is also available that routes through the `OutputSession` framework.