Files
cleveragents-core/docs/reference/output_rendering.md
T

161 lines
5.2 KiB
Markdown

# 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 <value>` 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.