CLI output → HTML preview
Produce a throwaway HTML file that faithfully mocks what CLI output will look
like in a real terminal, then open it in the browser for review. This is a
design/preview step — it renders a mock, it does not run the CLI. Use it to
get sign-off on a color theme or table layout before implementing anything.
When to use
- User wants to see a proposed color/output theme before you build it.
- Comparing "before/after" of a restyle across many subcommands at once.
- Any CLI/TUI output (tables, status lines, reports, prompts) worth eyeballing.
Method
Generate the HTML with a small script (Python is easiest) rather than
hand-writing spans — a generator keeps column alignment perfect and scales to
many commands. Key rules that make the mock faithful:
- One card per subcommand. Each output surface gets its own "terminal
window" card (title bar with traffic-light dots + the
$ command, then a
<pre> body). This lets the user scan every command's output on one page.
- Monospace
<pre>, white-space:pre. Preserves exact spacing so
columns line up. Dark background (#010409), light default fg.
- Mirror the real format widths. If the code uses
%-20s / fixed-width
columns, replicate those widths in the generator so headers and rows align
exactly as they will in the terminal. Reproduce separator rules
(strings.Repeat("-", N)) at the same length.
- Pad THEN color. Build the padded field first (
f"{text:<{w}}"), then
wrap it in a color span. In a real terminal, ANSI codes must not count
toward column width; mocking pad-then-color keeps the preview honest about
alignment.
- Palette as CSS classes. Map each semantic color to a hex from the
project's real palette (grep the theme source — e.g. for volcano-cli it's
internal/theme/theme.go). Add a legend at the top explaining each color.
- Escape content (
html.escape) before wrapping in spans — sample data
may contain <, >, &, ".
- Note the gating. If color is TTY-gated (
NO_COLOR/pipes/--json stay
plain), say so in the page header, so the reviewer knows the mock is the
TTY-only case.
Generator template (adapt per project)
#!/usr/bin/env python3
import html
PALETTE = { # pull hexes from the project's real theme source
"ok": "#f97316", # success / active
"warn": "#eab308", # warning / pending
"err": "#dc2626", # error / failed
"head": "#f37a58", # titles / table headers
"hint": "#f54019", # suggested commands
"dim": "#6b7280", # summaries / dim detail
}
def esc(t): return html.escape(t)
def span(text, cls=None, bold=False):
if not cls and not bold: return esc(text)
classes = (cls or "") + (" b" if bold else "")
return f'<span class="{classes.strip()}">{esc(text)}</span>'
def cell(text, width, cls=None, bold=False): # pad THEN color
return span(f"{text:<{width}}", cls, bold)
SECTIONS = [] # (command, [lines...]) — build hdr/rows with cell() at real widths
def render():
css = "\n".join(f".{k}{{color:{v}}}" for k, v in PALETTE.items())
cards = "".join(
f'<section class="card"><div class="bar">'
f'<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>'
f'<span class="cmd">$ {esc(cmd)}</span></div><pre>{chr(10).join(lines)}</pre></section>'
for cmd, lines in SECTIONS)
return f"""<!doctype html><meta charset=utf-8><style>
body{{background:#0d1117;color:#c9d1d9;font-family:system-ui;margin:0}}
.grid{{display:grid;grid-template-columns:repeat(auto-fill,minmax(560px,1fr));gap:18px;padding:24px}}
.card{{background:#010409;border:1px solid #21262d;border-radius:10px;overflow:hidden}}
.bar{{display:flex;gap:7px;align-items:center;padding:9px 12px;background:#161b22;border-bottom:1px solid #21262d}}
.dot{{width:11px;height:11px;border-radius:50%}}
.dot.r{{background:#ff5f56}}.dot.y{{background:#ffbd2e}}.dot.g{{background:#27c93f}}
.cmd{{margin-left:8px;font:12px ui-monospace,Menlo,monospace;color:#8b949e}}
pre{{margin:0;padding:16px;font:13px/1.55 ui-monospace,Menlo,monospace;color:#e6edf3;white-space:pre;overflow-x:auto}}
.b{{font-weight:700}}
{css}
</style><div class="grid">{cards}</div>"""
import pathlib
out = pathlib.Path("/tmp/cli-preview/preview.html")
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(render())
print(out)
Open it
python3 /tmp/cli-preview/gen.py && open /tmp/cli-preview/preview.html # macOS
# linux: xdg-open ; wsl: wslview
Report the file:// path too, in case the browser doesn't auto-launch.
Notes
- Keep the artifact in a temp dir (
/tmp/...); it is a preview, not a
deliverable. Don't commit it.
- To show a before/after, render two cards per command (plain vs themed) or two
columns.
- After sign-off, discard the mock and implement from the agreed palette/layout.
1---2name: cli-html-preview3description: Render proposed CLI/terminal output (colors, tables, status lines, TUI reports) as a standalone HTML file and open it in the browser, so the user can review a styling/output design before it is implemented. Use whenever the user asks to "show me what the CLI output would look like", "preview the colored output", "mock up the terminal output in a browser", or wants to eyeball a color theme / table layout before code is written.4---56# CLI output → HTML preview78Produce a throwaway HTML file that faithfully mocks what CLI output will look9like in a real terminal, then open it in the browser for review. This is a10design/preview step — it renders a *mock*, it does not run the CLI. Use it to11get sign-off on a color theme or table layout before implementing anything.1213## When to use1415- User wants to see a proposed color/output theme before you build it.16- Comparing "before/after" of a restyle across many subcommands at once.17- Any CLI/TUI output (tables, status lines, reports, prompts) worth eyeballing.1819## Method2021Generate the HTML with a small script (Python is easiest) rather than22hand-writing spans — a generator keeps column alignment perfect and scales to23many commands. Key rules that make the mock faithful:24251. **One card per subcommand.** Each output surface gets its own "terminal26 window" card (title bar with traffic-light dots + the `$ command`, then a27 `<pre>` body). This lets the user scan every command's output on one page.282. **Monospace `<pre>`, `white-space:pre`.** Preserves exact spacing so29 columns line up. Dark background (`#010409`), light default fg.303. **Mirror the real format widths.** If the code uses `%-20s` / fixed-width31 columns, replicate those widths in the generator so headers and rows align32 exactly as they will in the terminal. Reproduce separator rules33 (`strings.Repeat("-", N)`) at the same length.344. **Pad THEN color.** Build the padded field first (`f"{text:<{w}}"`), then35 wrap it in a color span. In a real terminal, ANSI codes must not count36 toward column width; mocking pad-then-color keeps the preview honest about37 alignment.385. **Palette as CSS classes.** Map each semantic color to a hex from the39 project's real palette (grep the theme source — e.g. for volcano-cli it's40 `internal/theme/theme.go`). Add a legend at the top explaining each color.416. **Escape content** (`html.escape`) before wrapping in spans — sample data42 may contain `<`, `>`, `&`, `"`.437. **Note the gating.** If color is TTY-gated (`NO_COLOR`/pipes/`--json` stay44 plain), say so in the page header, so the reviewer knows the mock is the45 TTY-only case.4647## Generator template (adapt per project)4849```python50#!/usr/bin/env python351import html5253PALETTE = { # pull hexes from the project's real theme source54 "ok": "#f97316", # success / active55 "warn": "#eab308", # warning / pending56 "err": "#dc2626", # error / failed57 "head": "#f37a58", # titles / table headers58 "hint": "#f54019", # suggested commands59 "dim": "#6b7280", # summaries / dim detail60}6162def esc(t): return html.escape(t)63def span(text, cls=None, bold=False):64 if not cls and not bold: return esc(text)65 classes = (cls or "") + (" b" if bold else "")66 return f'<span class="{classes.strip()}">{esc(text)}</span>'67def cell(text, width, cls=None, bold=False): # pad THEN color68 return span(f"{text:<{width}}", cls, bold)6970SECTIONS = [] # (command, [lines...]) — build hdr/rows with cell() at real widths7172def render():73 css = "\n".join(f".{k}{{color:{v}}}" for k, v in PALETTE.items())74 cards = "".join(75 f'<section class="card"><div class="bar">'76 f'<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>'77 f'<span class="cmd">$ {esc(cmd)}</span></div><pre>{chr(10).join(lines)}</pre></section>'78 for cmd, lines in SECTIONS)79 return f"""<!doctype html><meta charset=utf-8><style>80 body{{background:#0d1117;color:#c9d1d9;font-family:system-ui;margin:0}}81 .grid{{display:grid;grid-template-columns:repeat(auto-fill,minmax(560px,1fr));gap:18px;padding:24px}}82 .card{{background:#010409;border:1px solid #21262d;border-radius:10px;overflow:hidden}}83 .bar{{display:flex;gap:7px;align-items:center;padding:9px 12px;background:#161b22;border-bottom:1px solid #21262d}}84 .dot{{width:11px;height:11px;border-radius:50%}}85 .dot.r{{background:#ff5f56}}.dot.y{{background:#ffbd2e}}.dot.g{{background:#27c93f}}86 .cmd{{margin-left:8px;font:12px ui-monospace,Menlo,monospace;color:#8b949e}}87 pre{{margin:0;padding:16px;font:13px/1.55 ui-monospace,Menlo,monospace;color:#e6edf3;white-space:pre;overflow-x:auto}}88 .b{{font-weight:700}}89 {css}90 </style><div class="grid">{cards}</div>"""9192import pathlib93out = pathlib.Path("/tmp/cli-preview/preview.html")94out.parent.mkdir(parents=True, exist_ok=True)95out.write_text(render())96print(out)97```9899## Open it100101```bash102python3 /tmp/cli-preview/gen.py && open /tmp/cli-preview/preview.html # macOS103# linux: xdg-open ; wsl: wslview104```105106Report the `file://` path too, in case the browser doesn't auto-launch.107108## Notes109110- Keep the artifact in a temp dir (`/tmp/...`); it is a preview, not a111 deliverable. Don't commit it.112- To show a before/after, render two cards per command (plain vs themed) or two113 columns.114- After sign-off, discard the mock and implement from the agreed palette/layout.