feat(opencode): first-class opencode insights usage report command#26590
Open
KatantDev wants to merge 2 commits into
Open
feat(opencode): first-class opencode insights usage report command#26590KatantDev wants to merge 2 commits into
opencode insights usage report command#26590KatantDev wants to merge 2 commits into
Conversation
Generates a self-contained HTML usage report from local OpenCode session
history. Mirrors the Claude Code /insights command with OpenCode-native
conventions (Effect, AI SDK v6, models.dev cost rates).
Pipeline:
1. Load sessions from the local SQLite DB; apply --days/--project/--limit.
2. Per session, derive deterministic SessionMeta (token totals, tool
usage, languages, friction signals, multi-clauding via sliding-window
detector).
3. Optional LLM phase (default on, --no-llm to skip):
- extractFacet per session (cached on disk, versioned cache key)
- 7 narrative sections via Effect.forEach({ concurrency: 4 })
4. Render to a self-contained HTML file (no external assets except IBM
Plex Mono via Google Fonts) with dark/light theme and CSS-only
charts; written to ~/.local/share/opencode/insights/reports/.
Hardened against prompt injection: per-session transcripts are wrapped in
<<<TRANSCRIPT/SESSION/DATA markers and the model is told explicitly not
to follow any directives inside. All LLM-generated narrative is rendered
through the same XSS-safe escape path as deterministic data.
UX:
- Pre-flight notice + clack confirm prompt before any LLM work; --yes/-y
skips it, non-TTY without --yes aborts cleanly.
- Inline progress bar over LLM calls, with cursor hide/restore deferred
to first tick so it doesn't poison the confirm prompt; SIGINT/SIGTERM
handlers restore terminal state on Ctrl-C mid-run.
- Post-run summary with real cost via Session.getUsage and wall-clock
duration.
CLI: 'opencode insights [--days N] [--project ID] [--limit N] [--llm]
[--open] [--model provider/model] [-y|--yes]'
Layout under packages/opencode/src/insights/:
- schema.ts: Zod domain types (SessionMeta, SessionFacets, Aggregate,
7 section schemas) plus a wire-shape SessionFacetsInput that avoids
z.record (Anthropic rejects propertyNames in JSON Schema). Pair-array
encoding has .int().nonnegative() + .describe() coaching.
- aggregate.ts: deterministic per-session metrics + cross-session
aggregation. Response time uses time.completed (not time.created);
apply_patch parser matches OpenCode's '*** Begin Patch' envelope (not
unified diff); tool input narrowed via typeof rather than 'as string'.
- multi-clauding.ts: sliding-window concurrent-session detector.
NUL-separated pair keys; user_messages_during counts every
interleaved message rather than just the first.
- transcript.ts: format + chunk session transcripts for prompts.
- facets.ts: Effect.tryPromise-based facet extractor; on-disk cache with
versioned key + atomic rename + auto-delete on corruption / version
mismatch / schema mismatch. AI-SDK error narrowing via
AISDKError.isInstance. Promise.allSettled on chunk summaries so one
failed chunk doesn't waste the others.
- sections.ts: parallel section generator with per-section
Effect.orElseSucceed fallback, onUsage telemetry, hoisted dataBlock
so prompt-caching kicks in across the 7 sections.
- model.ts: resolveLanguageModel + resolveModelMetadata via Provider.
- paths.ts: OPENCODE_INSIGHTS_DIR override; flat exports.
- insights.ts: end-to-end orchestration; returns a RunResult with real
cost, duration, llmCalls breakdown, cachedFacets count. Symlink
update is platform-aware (skipped on Windows, warned on POSIX
failure); 'open' command resolved via Bun.which so missing xdg-open
on headless Linux doesn't crash the run.
- render.ts: self-contained HTML renderer with inline + block markdown
(**bold**, lists, headings, fenced code as <pre><code>),
CRLF + NUL normalization, list continuation support, XSS-safe
escaping everywhere, OpenCode wordmark + warm neutral palette,
WCAG-AA contrast, mobile viewport meta, NaN-guard on hour
histogram.
CLI command + 'cli/effect/prompt.ts' confirm helper. Builder validates
--days/--limit are positive integers and --model is in 'provider/model'
form before any LLM call. Model-not-found errors use
Provider.ModelNotFoundError.isInstance instead of message matching.
Tests under packages/opencode/test/cli/insights/ (40 passing + 6 todo
documenting LLM-path stub work):
- aggregate.test.ts (4) including apply_patch envelope + Add File
- multi-clauding.test.ts (8) including window boundary, three-way
concurrency, same-millisecond dedup, empty input
- transcript.test.ts (4)
- facets-cache.test.ts (6) including corrupted JSON, missing version
field, schema mismatch with auto-delete
- sections-shape.test.ts (2)
- progress-wiring.test.ts (3 + 6 todo)
- render.test.ts (13) including markdown blocks, CRLF, list
continuations, XSS, viewport meta, bar-row truncation
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
No issue — opening directly per maintainer discretion. Happy to convert to draft and pair with an issue if preferred.
Type of change
What does this PR do?
Adds a first-class
opencode insightsCLI command that generates a self-contained HTML usage report from local OpenCode session history. Inspired by Claude Code's/insights, but built natively on OpenCode primitives (Effect, AI SDK v6,Session.getUsagefor cost rates) rather than as a prompt-template wrapper.Pipeline:
--days/--project/--limit.--no-llmto skip):Effect.forEach({ concurrency: 4 }).Session.getUsage.CLI surface:
Files: 13 new under
packages/opencode/src/insights/+src/cli/cmd/insights.ts(new command) + a newPrompt.confirmhelper incli/effect/prompt.ts.How did you verify your code works?
bun typecheckpasses (full monorepo, 14 packages).bun test test/cli/insights/— 40 passing, 6 documentedtest.todofor LLM-bound paths that need a fakeLanguageModel.To reproduce:
bun install bun dev insights --no-llm --limit 10 --no-open # inspect ~/.local/share/opencode/insights/reports/report-latest.htmlScreenshots / recordings
Checklist