Optional tools; not part of the OpenCode plugin runtime.
Quick stats (no extra deps):
# Python
python3 -c "import json,sys; r=[json.loads(x) for x in open(sys.argv[1]) if x.strip()]; h=[x['hitPercent'] for x in r if x.get('hitPercent') is not None]; print(f\"{len(r)} calls, avg hit {sum(h)/len(h):.1f}%\")" logs/timeline-2026-05-31.jsonl
# Bun
bun -e "const t=await Bun.file(process.argv[1]).text();const rows=t.trim().split('\n').filter(Boolean).map(l=>JSON.parse(l));const h=rows.map(r=>r.hitPercent).filter((p):p is number=>p!=null);console.log(rows.length+' calls, avg hit '+(h.reduce((a,b)=>a+b,0)/h.length).toFixed(1)+'%')" logs/timeline-2026-05-31.jsonlExport TSV for spreadsheets (time fields are ISO 8601 strings):
jq -r 'select(.hitPercent!=null) | [.completedAt,.scope,.hitPercent,.cost]|@tsv' logs/timeline-2026-05-31.jsonlInteractive HTML dashboard with charts, filters, and data tables:
bun scripts/timeline-dashboard.ts # auto-detect logs/ (no browser)
bun scripts/timeline-dashboard.ts --open # write HTML, then open browser
bun scripts/timeline-dashboard.ts -o /tmp/report.html # custom output path
bun scripts/timeline-dashboard.ts ~/logs/timeline-*.jsonl # globs expanded by script
bun scripts/timeline-dashboard.ts --output /tmp/report.html --open # combinedDefault output: /tmp/timeline-dashboard-YYYY-MM-DD-HHmmss.html (timestamp suffix to avoid overwrites).
Browser: not opened by default; pass --open (macOS open, Linux xdg-open, Windows start).
Features:
- Summary cards follow active filters (records, tokens, cost, avg hit rate)
- Time / session / scope / model / text search filters
- 3 Chart.js charts: token volume (stacked bar), hit rate + cost (dual axis), duration (bar)
- Session summary table (mixed main+child scope shown as
main+child), withAvg TTFTandAvg TPScolumns - Per-call detail table with expandable rows (all JSONL fields),
TTFTandTPScolumns - Embedded data — no server needed, just open the HTML file
How it works:
- Reads
timeline-*.jsonland rotation backupstimeline-*.jsonl.Nfrom the default log dir (~/.local/share/opencode/logs/cache-hit/) or user-supplied paths/globs - Parses each JSONL line (
schema: 1validation), sorts bycompletedAt/created - Aggregates per-session statistics in the browser when filters change
- Generates a self-contained HTML file with:
- All data embedded as JSON in a
<script>tag (<escaped for safety) - Chart.js 4.4.7 from CDN (
cdn.jsdelivr.net) - Vanilla JS for interactivity (no framework dependency)
- All data embedded as JSON in a
- Optional
--openopens the output file in the default browser
Avg hit rate: excludes skippedForHit rows (same rule as plot-hit-rate.ts); null hitPercent still appear in tables/charts.
Cost display: reads currency / costUnit / rate from ~/.config/opencode/cache-hit.json when present (same as the TUI sidebar). No config file → defaults (CNY display, USD JSONL unit, rate 6.77). Invalid or partial cost fields are normalized; corrupt config falls back without failing the script. JSONL always stores raw cost in costUnit (usually USD).
Note: The generated HTML is self-contained except Chart.js CDN. Re-run the script to refresh data (static snapshot).
Option A (current — static HTML): Data is embedded into the HTML at build time by Bun. The browser just renders.
bun scripts/timeline-dashboard.ts → /tmp/timeline-dashboard-YYYY-MM-DD-HHmmss.html (self-contained)
- Pros: No server needed, zero runtime deps, can email/share the file
- Cons: Snapshot only — re-run to refresh data
Option B (live server):
Starts an HTTP server; the browser fetches JSONL via fetch("/api/logs").
bun scripts/timeline-dashboard.ts serve → http://localhost:PORT
- Pros: Live reload — refresh the page to see new logs
- Cons: Must keep a process running, cannot send the page as a file
Terminal ASCII chart; optional SVG (open /tmp/hit.svg):
bun scripts/plot-hit-rate.ts logs/timeline-2026-05-31.jsonl
bun scripts/plot-hit-rate.ts logs/timeline-2026-05-31.jsonl --root YOUR_ROOT_SESSION_ID -o /tmp/hit.svg
# one SVG, one colored line per rootSessionId (time-aligned)
bun scripts/plot-hit-rate.ts logs/timeline-2026-05-31.jsonl --by-root -o /tmp/hit.svgUse a real path, not $LOG, unless you exported it first:
set -x LOG logs/timeline-(date +%Y-%m-%d).jsonl # fish: set log ...
bun scripts/plot-hit-rate.ts $LOG -o /tmp/hit.svg