HTML report per run
Self-contained · zero deps · ships with every run
Every successful peepshow extract writes a self-contained report.html + manifest.json next to the frames, plus a one-line append to a global ndjson run history. The report bundles frames grid, transcript, sink fan-out, and LLM analysis into a single file that opens offline. The agent that consumed the frames pipes its synthesis back via peepshow report annotate so the next viewer sees the analysis without re-running the model.
What ships per run
| Artifact | Where | Purpose |
|---|---|---|
manifest.json | <outputDir>/manifest.json | Locked-shape JSON record (schemaVersion 1). Carries video + extraction + frames + transcript + sink fan-out + host info + optional LLM analysis. |
report.html | <outputDir>/report.html | Self-contained HTML dashboard. ~18KB minified — CSS, JS, and template all baked into dist/report.js at build time. |
| ndjson append | ~/.peepshow/runs/index.ndjson | Append-only run history (one line per run). POSIX-atomic under PIPE_BUF. Read by peepshow runs + future peepshow serve. |
What the report contains
- Header badges — codec · resolution · fps · duration · frame count · sink count.
- Video preview — first-frame poster (offline-safe).
- Summary — peepshow's own stats line + concatenated transcript text + hint to annotate when no LLM analysis is attached.
- LLM analysis — appears only when
peepshow report annotateran. Shows the summary + provider + model + collapsible per-frame captions. - Frames grid — click to lightbox, ←/→/J/K/Esc nav, hover-zoom.
- Transcript — only when transcription ran. Click any line to seek the video preview.
- Sink fan-out — list of sinks fired with status badges (✅/❌/⏭) + filter buttons + collapsible stderr on failures.
- Stats grid — every extraction telemetry value (codec, container, fps, bitrate, sizes, ffmpeg source + path, host info).
- Raw manifest — collapsible JSON tree of the full payload.
Flag reference
| Flag | Env var | Effect |
|---|---|---|
--no-report | PEEPSHOW_NO_REPORT=1 | Skip report.html. Manifest + ndjson still write. |
--no-manifest | PEEPSHOW_NO_MANIFEST=1 | Skip both manifest + ndjson. Report still writes. |
--no-index | PEEPSHOW_NO_INDEX=1 | Skip ndjson append only. |
--report-dir <p> | — | Override report.html location. |
--report-open | — | Spawn the OS opener on the rendered file. |
| — | PEEPSHOW_RUNS_INDEX=<p> | Override ndjson location. |
Closing the loop — LLM analysis
When peepshow runs inside an LLM workflow (Claude Code · Cursor · Windsurf · Cline · Codex · Gemini), the LLM is the consumer that understands the frames. Pipe its analysis back so report.html captures the synthesis for whoever opens it next:
echo '{
"summary": "<2-4 sentences describing the timeline>",
"perFrame": [{"idx": 0, "text": "<frame 1 caption>"}],
"provider": "claude-code",
"model": "claude-opus-4-7"
}' | peepshow report annotate "<outputDir>"
<outputDir> is the JSON payload's outputDir field. The annotate subcommand merges into manifest.analysis atomically (tmp + rename) and re-renders report.html. Every supported agent has the annotate instruction wired in — see the per-tool rule files in the repo.
Inspect runs
peepshow runs list # newest-first table
peepshow runs show <runId> # dump that run's manifest.json
peepshow runs prune # drop entries whose outputDir is gone
peepshow runs clear # truncate the index file
peepshow report <run-dir> # re-render report.html
peepshow report annotate <dir> # attach LLM analysis (above)
User preferences (peepshow config)
Per-machine prefs live at ~/.peepshow/config.json (override via PEEPSHOW_CONFIG_FILE). First time peepshow runs in a TTY a one-line hint suggests peepshow config init — a quick wizard that sets:
| Key | Type | Default | Meaning |
|---|---|---|---|
report.enabled | bool | true | Write report.html on every run. |
report.autoOpen | bool | false | Open the report in your browser after each run. |
report.browser | enum | default | default | chrome | firefox | safari | edge | brave | arc — cross-platform: macOS open -a "<App>", Linux browser binary, Windows start <alias>. |
peepshow config init # interactive wizard
peepshow config list # JSON dump
peepshow config get report.browser # print one value
peepshow config set report.browser chrome
peepshow config set report.autoOpen true
peepshow config export ~/peepshow-prefs.json # back up
peepshow config import ~/peepshow-prefs.json # restore on a new machine
peepshow config reset # delete the config file
Env vars override the saved prefs at run time:
PEEPSHOW_BROWSER=chrome— force a specific browser for one invocationPEEPSHOW_REPORT_OPEN=1— force--report-openfor one invocationPEEPSHOW_CONFIG_FILE=/path/to/config.json— alternative config location
What's next — phase 2
peepshow serve will spawn a local HTTP server that indexes ~/.peepshow/runs/index.ndjson for a homepage of every run, serves per-run detail pages reusing the report shell, and exposes a sink-management GUI. The ndjson + manifest format stays the source of truth — phase 2 is a UI on top, not a rewrite.