Skip to content

feat(opencode): first-class opencode insights usage report command#26590

Open
KatantDev wants to merge 2 commits into
anomalyco:devfrom
KatantDev:dev
Open

feat(opencode): first-class opencode insights usage report command#26590
KatantDev wants to merge 2 commits into
anomalyco:devfrom
KatantDev:dev

Conversation

@KatantDev
Copy link
Copy Markdown

@KatantDev KatantDev commented May 9, 2026

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

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds a first-class opencode insights CLI 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.getUsage for cost rates) rather than as a prompt-template wrapper.

Pipeline:

  1. Loads sessions from the local SQLite DB; filters by --days / --project / --limit.
  2. Computes deterministic per-session metrics (token totals, tool usage, languages, multi-clauding via a sliding-window detector).
  3. Optional LLM phase (default on, --no-llm to skip):
    • Per-session facet extraction with versioned on-disk cache.
    • 7 narrative sections generated in parallel via Effect.forEach({ concurrency: 4 }).
  4. Renders to a self-contained HTML file (only external resource is IBM Plex Mono via Google Fonts), with a confirm prompt + post-run cost summary via Session.getUsage.

CLI surface:

opencode insights [--days N] [--project ID] [--limit N]
                  [--no-llm] [--no-open] [--model provider/model]
                  [-y|--yes]

Files: 13 new under packages/opencode/src/insights/ + src/cli/cmd/insights.ts (new command) + a new Prompt.confirm helper in cli/effect/prompt.ts.

How did you verify your code works?

  • bun typecheck passes (full monorepo, 14 packages).
  • bun test test/cli/insights/ — 40 passing, 6 documented test.todo for LLM-bound paths that need a fake LanguageModel.
  • Smoke-tested locally on 133 real sessions (deterministic mode + full LLM mode with Sonnet 4) — generated reports render cleanly in dark/light, mobile + desktop.
  • Coverage includes: aggregate edge cases, multi-clauding three-way concurrency + window boundary, transcript chunking, facet cache (corrupted JSON, missing version, schema mismatch), markdown rendering (CRLF, list continuation, XSS attempts), bar truncation.

To reproduce:

bun install
bun dev insights --no-llm --limit 10 --no-open
# inspect ~/.local/share/opencode/insights/reports/report-latest.html

Screenshots / recordings

image image image

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

KatantDev added 2 commits May 10, 2026 08:04
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant