Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pnpm db:migrate:remote # Apply to production D1 (requires auth)
- **Cursor SDK**: SDK agents (TypeScript `@cursor/sdk`) write to `~/.cursor/projects/<workspace>/sdk-agent-store/<projectHash>/index.db` (tables: `agents`, `runs`, `run_events`) and a parallel JSONL transcript at `agent-transcripts/<agentId>/<agentId>.jsonl`. The transcript is the source of user prompts (SDK doesn't store them in events) and the SDK index.db supplies tool *results*, structured per-run timing, and per-turn model. See `packages/provider-cursor/src/cursor/sdk-reader.ts`. Detection is by sessionId prefix `agent-` — IDE chat sessions (UUID-only) skip the SDK SQLite probe. Newer stores add `runs.usage_json` (camelCase: `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`) and it is the only token source for SDK sessions; optional columns are probed with `PRAGMA table_info` so older stores still parse. Tool duration comes from the first/last `run_events.created_at` of a call. Tool enrichment pairs positionally but validates the tool name (Edit/Write/MultiEdit count as one family) and prefers a matching path/command, leaving a block unenriched rather than attaching another tool's result.
- **Cursor subagents**: IDE delegation trajectories live beside the parent transcript at `agent-transcripts/<sessionId>/subagents/<subAgentId>.jsonl`. Parent `Task`/`Subagent` calls do not carry the file ID, so `packages/provider-cursor/src/cursor/parser.ts` links them by normalized delegated-prompt containment and intentionally leaves unmatched files unattached rather than guessing by completion order. Current files contain tool calls and assistant text/reasoning, but usually no tool IDs/results, timestamps, token usage, or actual subagent model.
- **Cursor structured subagents**: Global-state `composer.composerHeaders` (stored in `ItemTable`) may identify child composers through `subagentInfo.parentComposerId/toolCallId`. Discovery hides those children from the top-level session list, and parsing links them to the exact parent `Agent` tool call. Structured IDs take precedence; transcript prompt containment remains the legacy fallback.
- **Cursor project paths**: `~/.cursor/projects/<encoded>` encodes the workspace path by replacing `/` with `-` **and dropping each segment's leading dot**, so `~/.cursor` arrives as `Users-me-cursor`. `decodeProjectDir` resolves the ambiguity by walking the real filesystem, trying `<candidate>` then `.<candidate>` at each level. The walk now returns the *deepest* directory it could confirm: once a real parent is found, everything left over is kept as one segment rather than split on `-`, because these workspaces are routinely deleted and splitting shredded their run ids (a scratch dir ending in a UUID became five path segments and a project named after the UUID's tail). Only when nothing below the root resolves does it fall back to replacing every `-` with `/`.
- **Agent run workspaces**: Automation creates one scratch workspace per run (Cursor SDK artifacts, PR-review worktrees, `/var/folders` temp dirs), each its own project. `agentRunWorkspaceParent` in `dashboard-utils.ts` spots a run id at the end of a directory name — a UUID or a hex digest of 12+ chars, short enough digests are left alone so `ros-4` and PR numbers don't match — and rolls the session up under the directory that holds it. `rollupProject` applies that alongside the Claude `.claude/worktrees` rule, so every surface gets it. Sessions, Replays, and Projects hide these workspaces by default (`agentRuns=true` in the URL shows them); the filter runs on the raw project path, since the rollup produces a parent that no longer looks like a run workspace.
- **Cursor duplicate transcripts**: Discovery coalesces duplicate transcript copies by session ID without adding their byte counts, and parsing removes identical records only across distinct files. Inline tool blocks are authoritative; mtime sidecars may fill unresolved tools but must not create duplicate calls.
- **Claude Cowork replay duplicates**: Cowork `audit.jsonl` can contain a host-loop user record plus an `isReplay: true` copy of the same prompt. Discovery excludes replay copies that match an original by content/UUID, and rich scans use the Cowork parser so dashboard prompt analytics match replay output.
- **Cowork `result` billing**: each `type: "result"` record is one completed host-loop run's final bill — `usage`, `modelUsage`, `total_cost_usd` and `duration_ms` are per-run, not cumulative. Session totals are the sum over UUID-deduplicated results; never mix them with assistant `message.usage` snapshots (those are partial streams and undercount output badly), and never add `usage` to `modelUsage` or `total_cost_usd` to `modelUsage.*.costUSD`. Audits with no `result` records fall back to assistant snapshots plus the timestamp-gap duration estimate. `system/api_retry` maps to `apiErrors` with attempt metadata only — the raw error text is never stored.
Expand All @@ -70,6 +72,7 @@ pnpm db:migrate:remote # Apply to production D1 (requires auth)
- **Windows support**: Cursor encodes workspace dirs as `C:\a\b` → `C-a-b` (drive colon dropped, separators → `-`); `decodeProjectDir` has a `win32` branch that resolves these against the real filesystem (POSIX uses `/` root, Windows uses the drive root). Cursor on Windows stores IDE chats only in the `globalStorage/state.vscdb` under `%APPDATA%\Cursor` (there is no `~/.cursor/chats` dir). Replay output normalizes file paths to `/` for cross-platform display via `redactFilePath` in `transform.ts` — never apply that to prose. Build scripts shell out to `scripts/copy-file.mjs` instead of `mkdir -p`/`cp` (not available in PowerShell). `.gitattributes` forces `eol=lf` so Windows clones don't trip `oxfmt --check` with CRLF.
- **Pi harness tools**: Pi sessions use both native tool names (`bash`, `edit`, `write`) and harness names (`exec_command`, `apply_patch`). The Pi provider maps `exec_command` to replay `Bash` and parses `apply_patch` into replay `Edit` inputs (including all touched `file_paths`, with the first file represented by the single-diff viewer). Harness tools report failure through `details.exit_code` rather than the `isError` flag native tools use, so a non-zero exit code marks the result as an error. Native `edit` can carry several replacements; all of them are joined into the single `old_string`/`new_string` diff the viewer renders, and the tool stays named `Edit`.
- **Pi token accounting**: `compaction` and `branch_summary` entries carry the usage of a *separate* summarization call, so their `usage` adds to session totals (matching Pi's own billed totals). `tokensBefore` is context size, not usage, and only feeds `compactions[].preTokens`. Summary usage never enters `turnStats`. Session `model` is the last selected model, which is what discovery reports.
- **Usage index (tool / MCP / skill)**: `scanner.ts` derives one `UsageEvent` per invocation (name, turn, timestamp, duration, status, subagent) plus a per-session `SessionUsageSummary`; only the latest 100 detail events are retained, and tool inputs/results are never stored. MCP naming differs per provider and is normalized in `parseMcpUsage`: `mcp__<server>__<tool>` (Claude/Codex), `CallMcpTool {server, toolName}` (Cursor SDK), `mcp-<server>-<tool>` (Cursor IDE — split at the *last* dash because server IDs are kebab-case, and the normalized input's `server`/`tool_name` wins), `mcp_<server>_<tool>` (older Cursor — split at the *first* underscore because tool names are snake_case, with `mcp_auth`/`mcp_get_tools`/`mcp_meta_tool*` excluded as MCP management tools), and Pi's single `mcp` tool (`server` field, or `<server>_<tool>` in `tool`). An MCP call is counted under `mcpServers`/`mcpTools` only — never also as a tool — so the Tool facet lists real tools instead of repeating the MCP facets. Cowork names servers by UUID, so its parser exposes `mcpServerNames` from the sibling `local_{id}.json` `remoteMcpServersConfig`. OpenCode/Hermes and deferred Cursor scans emit no usage events — they say so in `dataQualityNotes` rather than looking like zero usage. Cursor reports the same server under several ids (`user-<name>`, `<name>::mcpScope:profile:...:cfg:...`); `stripCursorServerScope` folds them so one server is one facet. `/api/scan/results` strips events; per-session events come from `/api/usage/events`, and `/api/usage/rollup` serves the compact per-session `{ startTime, usage }` projection the Insights page aggregates client-side (`engine/usage-rollup.ts`, range-filtered by instant so switching 7d/30d/90d costs no request). The dashboard renders Tool and MCP server facets plus a per-session breakdown (`engine/session-usage.ts`) built from the summary alone, so expanding a card costs no request; the MCP tool facet is a drilldown that only appears once a server or tool is selected, and long facet lists scroll inside their section so the ones below stay reachable. The scan-results cache key carries `SCANNER_VERSION`, so a bump can't serve results in the previous shape. Insights renders a "Tools & MCP" card (top tools / MCP servers / MCP tools / skills, each with calls and session reach). Cursor SQLite sessions are scanned twice: a fast pass with rich parsing deferred, then a background `backfillDeferredUsage` pass in `server.ts` that indexes their usage and rewrites the scan cache (progress in `/api/scan/status` as `usageBackfill`).

## Rules

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ npx vibe-replay

### All your sessions, one place

Launch with `npx vibe-replay -d` and see every Claude, Cursor, Codex, OpenCode, Hermes, and Pi session across all projects — with a daily activity snapshot, activity heatmaps, cost totals, and project analytics. Search sessions, filter by git repo, and generate any replay in one click.
Launch with `npx vibe-replay -d` and see every Claude, Cursor, Codex, OpenCode, Hermes, and Pi session across all projects — with a daily activity snapshot, activity heatmaps, cost totals, and project analytics. Search sessions, filter by git repo, tool, MCP server/tool, or skill, expand any session to see its own tool/MCP breakdown, and generate any replay in one click.

<p align="center">
<img src="docs/screenshots/dashboard.png" alt="Local dashboard — browse sessions, activity heatmap, project analytics" width="800" />
Expand Down Expand Up @@ -126,7 +126,7 @@ curl -o ~/.claude/skills/replay/SKILL.md \
- **Cross-platform** — runs on macOS, Linux, and Windows
- **Single HTML file** — self-contained, works offline, and makes no automatic external requests. Remote image attachments load only after an explicit click
- **Claude, Cursor, Codex, OpenCode, Hermes, and Pi** — all providers auto-discovered, including multi-file and resumed sessions
- **Local dashboard** — browse and search every session, filter by git repo, with activity heatmaps, per-project analytics, and a personal-insights view across all your coding
- **Local dashboard** — browse and search every session, filter by git repo, tool, MCP server/tool, or skill, expand a session for its own tool/MCP/skill counts, with activity heatmaps, per-project analytics, and a personal-insights view (including which tools and MCP servers you lean on) across all your coding
- **Share & export** — GitHub Gist, animated SVG, GIF, markdown summary, or cloud upload. Secret redaction built in
- **Sub-agent visualization** — see delegated tool calls and sub-agent trees rendered inline
- **Comments** — leave notes on any scene. Comments persist in the HTML and travel with the replay
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ function getDevViewerOpts(): { externalViewerUrl: string } | undefined {
// Bumped v2 → v3 alongside the Cowork sessionId fix (see server.ts
// sourcesCacheKey comment). Old caches carry the wrong Cowork identity and
// must be thrown out so the next discovery sweep writes a correct one.
const SESSION_DISCOVERY_CACHE_KEY = "session-discovery-v3";
// v3 → v4: Cursor project paths decode differently, so cached entries would
// keep showing the old exploded paths in the picker.
const SESSION_DISCOVERY_CACHE_KEY = "session-discovery-v4";

function normalizePromptTitle(value?: string): string {
return normalizeTitle(cleanPromptText(value || "")) || "";
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/insights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ export function scanResultToInsight(scan: SessionScanResult): SessionInsight {
prLinks: scan.prLinks,
skillsUsed: scan.skillsUsed,
mcpServersUsed: scan.mcpServersUsed,
usageSummary: scan.usageSummary,
usageEvents: scan.usageEvents,
subAgentCount: scan.subAgentCount,
apiErrorCount: scan.apiErrorCount,
compactionCount: scan.compactionCount,
Expand Down
Loading