toolpath-cursor
Derive Toolpath provenance documents from Cursor sessions.
Cursor stores every conversation across two places — a thin JSONL
transcript at ~/.cursor/projects/<slug>/agent-transcripts/<id>/<id>.jsonl
(the agent's I/O log, lossy) and a rich SQLite bubble store at
~/Library/Application Support/Cursor/User/globalStorage/state.vscdb
(the source of truth). This crate reads the SQLite store (read-only,
with content-blob lookup) and maps it to Toolpath documents.
Overview
- Reading: open
state.vscdbviarusqlite, readItemTable.composer.composerHeadersfor the cross-workspace composer index, then per composer pullcursorDiskKV.composerData:<id>and thecursorDiskKV.bubbleId:<comp>:<bubble>rows. Content- addressed file blobs undercomposer.content.<hash>are looked up while reading so the IR-builder is pure. - Provider: implements
toolpath_convo::ConversationProvider, pairing user/assistant bubbles into turns, liftingtoolFormerDataintoToolInvocation, decodingparams/resultJSON strings, and resolvingedit_file_v2to real before/after file content via the content store. - Derivation: produces
toolpath::v1::Pathdocuments through the sharedtoolpath_convo::derive_path. Edit-tool calls surface as sibling artifacts with the full unified diff asraw.
Mapping
| Cursor source | Toolpath destination |
|---|---|
composerData.composerId |
ConversationView.id, path.id = path-cursor-<first-8> |
composerData.modelConfig.modelName |
Default Turn.model |
composerData.name |
path.meta.title |
composerHeader.workspaceIdentifier.uri.fsPath |
path.base.uri (as file://…) |
composerData.agentBackend ("cursor-agent") |
producer.name ("cursor") |
Bubble type: 1 (user) |
Step with actor: "human:user" |
Bubble type: 2 (assistant) |
Step with actor: "agent:<model>" |
bubble.allThinkingBlocks[*].text |
Turn.thinking |
bubble.toolFormerData |
one entry in Turn.tool_uses[] |
toolFormerData.tool == 38 (edit_file_v2) |
ToolCategory::FileWrite + FileMutation with resolved before/after content and unified diff |
toolFormerData.tool == 15 (run_terminal_command_v2) |
ToolCategory::Shell + ToolResult { content: output, is_error: exitCode != 0 } |
toolFormerData.tool == 40 (read_file_v2) |
ToolCategory::FileRead |
toolFormerData.tool == 42 (glob_file_search) |
ToolCategory::FileSearch |
bubble.tokenCount |
Turn.token_usage |
bubble.modelInfo.modelName |
Turn.model (overrides composer default) |
Unknown toolFormerData.tool ids |
preserved as ToolInvocation { category: None } — name + input still carry the call |
Provider-specific UI metadata (checkpointId, requestId,
capabilityType, richText, Lexical editor DOM, the
anysphere.cursor-commits checkpoint store, the AI-authorship
SQLite db) is intentionally not smuggled through the IR — those
fields don't belong in a cross-harness representation, and the
IR has no Turn.extra slot. They round-trip to nothing.
Usage
use ;
let manager = new;
let composer_id = "724686cd-875e-47da-a90b-dbc3e523efb8";
let session = manager.read_session?;
let path = derive_path;
# Ok::
What's not read
~/Library/Application Support/Cursor/User/globalStorage/anysphere.cursor-commits/— per-request file snapshots. Their contents duplicate thecomposer.content.<hash>blobs already keyed incursorDiskKV.~/.cursor/ai-tracking/ai-code-tracking.db— per-line AI-vs-human authorship stats. Derivable from the bubble store + git.~/Library/Application Support/Cursor/User/workspaceStorage/<id>/state.vscdb— per-workspace UI state. The cross-workspacecomposer.composerHeadersindex in the global database covers session listing without it.cursorAuth/*keys — live OAuth tokens. Never read.~/Library/Application Support/Cursor/logs/— Chromium / renderer logs.
See
docs/agents/formats/cursor.md
in the workspace for the full on-disk format reference.
Part of Toolpath
This crate is part of the Toolpath workspace. See also:
toolpath— core provenance typestoolpath-convo— provider-agnostic conversation abstractiontoolpath-claude— Claude Code providertoolpath-codex— Codex CLI providertoolpath-gemini— Gemini CLI providertoolpath-opencode— opencode providertoolpath-pi— Pi providerpath-cli— unified CLI (cargo install path-cli)