Confirmed — you're looking at a real path, but it's the wrong (and mostly empty) source. Here's the actual layout on your machine.
Why agent-transcripts/**/*.jsonl is empty
That directory exists but is essentially unpopulated — across all your projects there's exactly one 12-line file, and it only has {role, message} (no token data). Cursor writes JSONL transcripts only for a narrow slice (background/cloud agents), not normal Composer/chat sessions. It is not the system of record. Don't build on it.
Where Cursor actually keeps everything (SQLite, not JSONL)
The real store is a LevelDB-in-SQLite blob DB:
~/Library/Application Support/Cursor/User/globalStorage/state.vscdb → table cursorDiskKV
Key namespaces I see in yours:
| Key prefix | Count | What it is |
|---|---|---|
bubbleId:<composer>:<bubble> |
1029 | individual messages — this is your gold |
composerData:<uuid> |
57 | conversation-level metadata |
messageRequestContext:* |
124 | per-request attached context |
checkpointId:*, codeBlockDiff:* |
— | edit/checkpoint state |
And the per-message token data is populated — each bubbleId blob has:
tokenCount: { inputTokens, outputTokens }
usageUuid // links to server-side billing record
type // 1 = user, 2 = assistant
timingInfo
text, toolResults, codeBlocks, ...
Sampled assistant bubbles: in=5713 out=753, in=15677 out=324, in=24881, in=34634, in=49568… — note the input side climbs monotonically within a conversation, i.e. it's recording the running context window, so you can reconstruct context growth per turn.
composerData:* carries createdAt, unifiedMode, and a usageData field (conversation rollup). Per-workspace copies also live at .../workspaceStorage/<hash>/state.vscdb.
Separately, ~/.cursor/ai-tracking/ai-code-tracking.db is a different concern — it tracks AI-authored code % (lines added by tab/composer/human, scored_commits, conversation_summaries with model/mode). Useful for "how much code is AI-written" but not for token cost.
The two caveats that matter for an org tool
- Cache is not broken out locally. You get input/output token counts and a
usageUuid, but nocacheReadTokens/cacheWriteTokenssplit. So local scraping gives you context-size and I/O volume, not true cache-hit economics. That's the part Cursor keeps server-side (theusageUuidis the join key to it). - Model name isn't cleanly on the bubble (
_vis a schema version int, not the model). Pull model fromcomposerData/conversation_summaries.model.
For org-level cost, don't scrape at all
Per-machine SQLite scraping only ever sees one dev's laptop and misses cache economics. For the org rollup you actually want the Cursor Admin API (Teams/Business plan): POST /teams/daily-usage-data and /teams/spend return per-user, per-model token counts and spend server-side — which is exactly the cross-org, cache-aware view your plugin infra needs. Use local state.vscdb scraping only as the supplement for fine-grained per-turn context-window analysis that the API doesn't expose.
Want me to sketch the schema-extraction queries (a cursorDiskKV → normalized per-turn table) or map out the Admin API side? If so, tell me whether you'd like it as a Markdown design doc or an HTML page.