CLI Output Styling Reference¶
This document covers all CLI output styling, formatting, and rendering code in little-loops.
Design philosophy: No third-party styling libraries. All formatting uses Python stdlib (textwrap, shutil) plus manual ANSI escape codes. NO_COLOR env var is respected everywhere. Terminal width is always queried dynamically.
Core Module: scripts/little_loops/cli/output.py¶
The central styling utility imported by most CLI commands.
Terminal width¶
from little_loops.cli.output import terminal_width, wrap_text
w = terminal_width() # int, falls back to 80
text = wrap_text(text, indent=" ", width=None) # wraps at terminal width
terminal_width() calls shutil.get_terminal_size((default, 24)).columns.
ANSI color¶
from little_loops.cli.output import colorize, PRIORITY_COLOR, TYPE_COLOR
colored = colorize("P1", PRIORITY_COLOR["P1"]) # ANSI-wrapped or passthrough
colorize(text, code) wraps text in \033[{code}m...\033[0m. Returns text unchanged when color is disabled.
Color is enabled when sys.stdout.isatty() is True and NO_COLOR env var is unset. This is evaluated at import time into _USE_COLOR.
Default color codes¶
| Key | ANSI Code | Appearance |
|---|---|---|
P0 |
38;5;208;1 |
Bold orange |
P1 |
38;5;208 |
Orange |
P2 |
33 |
Yellow |
P3 |
0 |
Default |
P4 |
2 |
Dim |
P5 |
2 |
Dim |
BUG |
38;5;208 |
Orange |
FEAT |
32 |
Green |
ENH |
34 |
Blue |
EPIC |
35 |
Purple-magenta |
Edge colors (used in FSM diagrams — applied to both label text and connector line characters):
| Label | ANSI Code | Appearance |
|---|---|---|
yes |
32 |
Green |
no |
38;5;208 |
Orange |
error |
31 |
Red |
blocked |
31 |
Red |
partial |
33 |
Yellow |
retry_exhausted |
38;5;208 |
Orange |
rate_limit_exhausted |
38;5;214 |
Amber |
rate_limit_waiting |
38;5;214 |
Amber |
next, _ |
2 |
Dim |
Startup configuration¶
Call once after loading config to apply user-defined colors:
from little_loops.cli.output import configure_output
configure_output(config.cli) # or configure_output(None) for defaults
configure_output merges config.cli.colors.priority and config.cli.colors.type into the module-level dicts. NO_COLOR env var always takes precedence over config.
Logo: scripts/little_loops/logo.py¶
Reads and prints ASCII art from assets/ll-cli-logo.txt. Silent no-op if the file is missing.
from little_loops.logo import print_logo, get_logo
print_logo() # prints logo with surrounding blank lines
logo = get_logo() # returns str | None
Issue Card: scripts/little_loops/cli/issues/show.py¶
_render_card(fields) renders a Unicode box-drawing character summary card for ll-issues show.
Box-drawing characters used¶
| Char | Unicode | Role |
|---|---|---|
─ |
U+2500 | Horizontal border |
│ |
U+2502 | Vertical border |
┌ |
U+250C | Top-left corner |
┐ |
U+2510 | Top-right corner |
└ |
U+2514 | Bottom-left corner |
┘ |
U+2518 | Bottom-right corner |
├ |
U+251C | Left mid-border |
┤ |
U+2524 | Right mid-border |
Layout¶
┌──────────────────────────────────────────────────┐
│ FEAT-518: Issue title │
├──────────────────────────────────────────────────┤
│ Priority: P3 │ Status: Open │
│ Confidence: 85 │ Outcome: 78 │
│ Cmplx: 22 │ Tcov: 24 │ Ambig: 25 │ Chsrf: 22│
├──────────────────────────────────────────────────┤
│ Summary text wrapped to content │
│ width... │
├──────────────────────────────────────────────────┤
│ Source: capture │ Norm: ✓ │ Fmt: ✗ │
│ Integration: 4 files │ Labels: cli, ll-issues │
│ Captured at: 2026-04-18T14:32:07Z │
│ Completed at: 2026-05-01T09:15:44Z │
│ History: /ll:capture-issue, /ll:refine-issue │
├──────────────────────────────────────────────────┤
│ Path: .issues/features/... │
└──────────────────────────────────────────────────┘
Detail line fields:
| Field | Source | Display |
|---|---|---|
Source |
discovered_by frontmatter |
Short alias (capture, scan, audit, format) or first 7 chars; omitted if absent |
Norm |
Filename pattern check | ✓ if matches P[0-5]-TYPE-NNN-desc.md, ✗ otherwise |
Fmt |
Required sections check | ✓ if file has all required template sections, ✗ otherwise |
Captured at |
captured_at frontmatter |
ISO 8601 UTC timestamp of issue capture; omitted if absent |
Completed at |
completed_at frontmatter |
ISO 8601 UTC timestamp of issue completion; omitted if absent |
Width is computed dynamically: the maximum of all content line lengths plus 2 padding, with a minimum of 62 characters. The summary section is wrapped with textwrap.wrap() to fit the structural width.
Issue List: scripts/little_loops/cli/issues/list_cmd.py¶
cmd_list groups issues by type (BUG/FEAT/ENH/EPIC) and colorizes each row:
- Type group headers: bold + type color
- Issue IDs: type color
- Priority labels: priority color
FSM Diagram: scripts/little_loops/cli/loop/layout.py¶
_render_fsm_diagram(fsm, verbose) produces a 2D box-drawing ASCII diagram for ll-loop show.
Diagram structure¶
The renderer produces three sections:
- Main flow — the primary (happy-path) traversal rendered as a horizontal row of boxes connected by labeled arrows
- Branches — alternate forward transitions rendered below with vertical connectors
- Back-edges — transitions to earlier states (cycles) rendered below with U-routing
State box format¶
→prefix marks the initial state◉suffix marks terminal states[type]badge appears in the top border row (not the content area) — e.g.,──[prompt]──- The state name in the first content row is rendered bold for visual hierarchy
- In non-verbose mode, action is truncated to the first non-empty line with
… - Box widths are computed per-state, capped by
max_box_innerderived from terminal width
Edge arrows¶
Main-path arrows between boxes:
U-route for back-edges (main-to-main):
Vertical connectors for off-path states use │, ▲, ▼, with separate label rows for down/up directions to prevent overlap.
Self-loops render as ↺ label below the box row.
Centering¶
The entire diagram is center-indented: indent = (terminal_width - total_diagram_width) // 2
Edge colorization¶
Transition edges are colored by semantic type — both connector line characters (│, ─, ▼, ▶, corner chars) and label text:
- Color is applied at draw time: each grid character (pipe, dash, arrowhead, corner) is wrapped in
colorize(ch, code)via the_edge_line_color(label)helper. _colorize_diagram_labels(diagram)additionally post-processes the rendered string to colorize label words when bounded by box-drawing or whitespace characters._collect_edges()includeson_blocked("blocked"),on_retry_exhausted("retry_exhausted"), andon_rate_limit_exhausted("rate_limit_exhausted") transitions in addition to the standard fields.
Default edge color mapping (see Output Color Reference > Edge colors above for ANSI codes):
| Label keyword | Color |
|---|---|
yes / success |
Green |
no / failure |
Orange |
error |
Red |
blocked |
Red |
partial |
Yellow |
retry_exhausted |
Orange |
rate_limit_exhausted |
Amber |
rate_limit_waiting |
Amber |
next / _ (default) |
Dim |
Edge label colors are user-configurable via cli.colors.fsm_edge_labels in ll-config.json. See CONFIGURATION.md → cli.colors.fsm_edge_labels.
Note:
rate_limit_waitingis a heartbeat event (not a diagram edge label), but it shares the Amber styling withrate_limit_exhaustedso rate-limit activity is visually consistent across diagrams and event logs.Note:
cli.colors.fsm_edge_labelsgoverns more than diagram arrows. As of ENH-1050, the same config key also controls: - The✓/✗verdict symbol colors indisplay_progress()(theyes,no, anderrorkeys map to checkmark and x-mark colors during evaluate events) - The[TERMINAL]marker color inprint_execution_plan()(uses theyeskey, defaulting to green)
The active state highlight color is configurable via cli.colors.fsm_active_state (default: green 32). See CONFIGURATION.md → cli.colors.fsm_active_state.
State overview table¶
_print_state_overview_table(fsm) (cli/loop/info.py) renders a compact aligned table below the diagram:
State Type Action Preview Transitions
───────────── ────── ─────────────────────────────── ──────────────────
→ run prompt Analyze the current issue and… success──→ verify
verify shell python -m pytest scripts/... success/fail──→ ...
Column widths adapt to terminal width. Long values are truncated with … (U+2026).
Sprint Visualization: scripts/little_loops/cli/sprint/show.py¶
_render_dependency_graph(waves, dep_graph) renders an ASCII dependency graph:
_render_health_summary(...) produces a single-line sprint health status:
OK -- 8 issues in 3 waves, overlap serialized
BLOCKED -- dependency cycles detected
REVIEW -- 2 potential dependency(ies) to review
Dependency Map: scripts/little_loops/dependency_mapper/formatting.py¶
format_report(report) renders a Markdown dependency analysis report with tables.
format_text_graph(issues, proposals) renders an ASCII dependency graph with three arrow styles:
| Arrow | Meaning |
|---|---|
──→ |
Existing blocked_by dependency |
--> |
Soft depends_on prerequisite |
-.→ |
Proposed dependency |
Issue History: scripts/little_loops/issue_history/formatting.py¶
Multiple output format functions for ll-history:
| Function | Output format |
|---|---|
format_summary_text(summary) |
Plain text with =/- headers and aligned columns |
format_summary_json(summary) |
JSON |
format_analysis_text(analysis) |
Plain text with unicode trend arrows (↑↓→) |
format_analysis_json(analysis) |
JSON |
format_analysis_yaml(analysis) |
YAML (falls back to JSON if pyyaml not installed) |
format_analysis_markdown(analysis) |
Markdown with tables and emoji badges (🔥⚡🔴🟠🟡✓⚠️) |
The Markdown formatter uses emoji only for machine-rendered output (files/PRs), not for TTY display.
Adding New Styled Output¶
- Import from
little_loops.cli.output: - Use
terminal_width()for any layout calculations — never hardcode widths. - Call
colorize(text, code)for color — it automatically no-ops when color is disabled. - For new color mappings, add to
PRIORITY_COLORorTYPE_COLORinoutput.py, or define a local dict following the same ANSI code format. - Respect
NO_COLORby routing all color throughcolorize()rather than embedding raw ANSI escapes inline.