@cairn-tool/cairn 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +74 -28
  2. package/dist/agent/audit/index.d.ts +6 -6
  3. package/dist/agent/audit/index.js +6 -17
  4. package/dist/agent/audit/index.js.map +1 -1
  5. package/dist/archive/run.d.ts +14 -7
  6. package/dist/archive/run.js +4 -1
  7. package/dist/archive/run.js.map +1 -1
  8. package/dist/archive/sets.d.ts +26 -1
  9. package/dist/archive/sets.js +108 -0
  10. package/dist/archive/sets.js.map +1 -1
  11. package/dist/atomic-write.d.ts +22 -0
  12. package/dist/atomic-write.js +42 -0
  13. package/dist/atomic-write.js.map +1 -0
  14. package/dist/binary-kind.d.ts +13 -0
  15. package/dist/binary-kind.js +25 -0
  16. package/dist/binary-kind.js.map +1 -0
  17. package/dist/cli.js +48 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/archive.js +11 -5
  20. package/dist/commands/archive.js.map +1 -1
  21. package/dist/commands/jira.js +1 -21
  22. package/dist/commands/jira.js.map +1 -1
  23. package/dist/commands/pdf.d.ts +29 -0
  24. package/dist/commands/pdf.js +345 -0
  25. package/dist/commands/pdf.js.map +1 -0
  26. package/dist/contract/registry.js +65 -1
  27. package/dist/contract/registry.js.map +1 -1
  28. package/dist/contract/schemas/index.js +2 -0
  29. package/dist/contract/schemas/index.js.map +1 -1
  30. package/dist/contract/schemas/pdf.d.ts +2 -0
  31. package/dist/contract/schemas/pdf.js +292 -0
  32. package/dist/contract/schemas/pdf.js.map +1 -0
  33. package/dist/jira/adf/to-markdown.js +2 -32
  34. package/dist/jira/adf/to-markdown.js.map +1 -1
  35. package/dist/markdown-stringify.d.ts +35 -0
  36. package/dist/markdown-stringify.js +44 -0
  37. package/dist/markdown-stringify.js.map +1 -0
  38. package/dist/pdf/attachments.d.ts +50 -0
  39. package/dist/pdf/attachments.js +229 -0
  40. package/dist/pdf/attachments.js.map +1 -0
  41. package/dist/pdf/diagnostics.d.ts +110 -0
  42. package/dist/pdf/diagnostics.js +137 -0
  43. package/dist/pdf/diagnostics.js.map +1 -0
  44. package/dist/pdf/document.d.ts +159 -0
  45. package/dist/pdf/document.js +272 -0
  46. package/dist/pdf/document.js.map +1 -0
  47. package/dist/pdf/forms.d.ts +9 -0
  48. package/dist/pdf/forms.js +101 -0
  49. package/dist/pdf/forms.js.map +1 -0
  50. package/dist/pdf/inspect.d.ts +42 -0
  51. package/dist/pdf/inspect.js +232 -0
  52. package/dist/pdf/inspect.js.map +1 -0
  53. package/dist/pdf/layout.d.ts +78 -0
  54. package/dist/pdf/layout.js +524 -0
  55. package/dist/pdf/layout.js.map +1 -0
  56. package/dist/pdf/outline.d.ts +30 -0
  57. package/dist/pdf/outline.js +127 -0
  58. package/dist/pdf/outline.js.map +1 -0
  59. package/dist/pdf/read.d.ts +62 -0
  60. package/dist/pdf/read.js +193 -0
  61. package/dist/pdf/read.js.map +1 -0
  62. package/dist/pdf/struct.d.ts +35 -0
  63. package/dist/pdf/struct.js +226 -0
  64. package/dist/pdf/struct.js.map +1 -0
  65. package/dist/pdf/text.d.ts +71 -0
  66. package/dist/pdf/text.js +166 -0
  67. package/dist/pdf/text.js.map +1 -0
  68. package/dist/pdf/to-markdown.d.ts +22 -0
  69. package/dist/pdf/to-markdown.js +289 -0
  70. package/dist/pdf/to-markdown.js.map +1 -0
  71. package/dist/pdf/types.d.ts +205 -0
  72. package/dist/pdf/types.js +2 -0
  73. package/dist/pdf/types.js.map +1 -0
  74. package/dist/pdf/validate.d.ts +51 -0
  75. package/dist/pdf/validate.js +136 -0
  76. package/dist/pdf/validate.js.map +1 -0
  77. package/dist/serve/pdf-tools.d.ts +2 -0
  78. package/dist/serve/pdf-tools.js +196 -0
  79. package/dist/serve/pdf-tools.js.map +1 -0
  80. package/dist/serve/tools.d.ts +2 -17
  81. package/dist/serve/tools.js +4 -0
  82. package/dist/serve/tools.js.map +1 -1
  83. package/dist/serve/types.d.ts +22 -0
  84. package/dist/serve/types.js +2 -0
  85. package/dist/serve/types.js.map +1 -0
  86. package/dist/usage/providers/cursor.d.ts +2 -0
  87. package/dist/usage/providers/cursor.js +570 -0
  88. package/dist/usage/providers/cursor.js.map +1 -0
  89. package/dist/usage/providers/index.js +2 -0
  90. package/dist/usage/providers/index.js.map +1 -1
  91. package/package.json +2 -1
@@ -0,0 +1,570 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { addTokens, emptyBucket, emptyTokens, utcDay } from "../events.js";
4
+ import { loadSqlite } from "../../sqlite.js";
5
+ /**
6
+ * Cursor's editor store.
7
+ *
8
+ * Everything is one SQLite database inside the Electron user-data directory:
9
+ *
10
+ * ```
11
+ * User/globalStorage/state.vscdb
12
+ * ItemTable(key, value) -- settings, and the legacy conversation index
13
+ * cursorDiskKV(key, value) -- the corpus, keyed by prefix
14
+ * composerHeaders(composerId, workspaceId, createdAt, lastUpdatedAt,
15
+ * isArchived, isSubagent, recency, checkpointAt, value)
16
+ * ```
17
+ *
18
+ * A conversation is a *composer*, and `composerId` joins every prefix:
19
+ * `composerData:<id>` is the conversation, `bubbleId:<id>:<bubbleId>` its turns.
20
+ *
21
+ * **Cursor stopped writing token counters.** `bubbleId` records carry
22
+ * `tokenCount.{inputTokens,outputTokens}`, and those are genuine per-request
23
+ * figures -- but on a real corpus every nonzero one falls between 2025-06-17 and
24
+ * 2025-12-23. Newer conversations carry the field with zeroes in it and settle
25
+ * usage server-side, behind each bubble's `usageUuid`. So this provider reports
26
+ * real tokens for the window Cursor wrote them and zero afterwards, which is a
27
+ * fact about the host rather than a gap in the parser. See
28
+ * `docs/providers/cursor/usage-logs.md`.
29
+ *
30
+ * The schema has only ever had `inputTokens` and `outputTokens`: there is no
31
+ * cache, reasoning, or web-tool breakdown to read, hence `cacheTokens: false`.
32
+ *
33
+ * `contextTokensUsed` is deliberately **not** read. It is the size of the most
34
+ * recent turn's context, overwritten every turn and excluding output, so it can
35
+ * be neither summed (like Antigravity's) nor differenced (like Codex's). It is
36
+ * the one figure here that would have no defensible interpretation.
37
+ *
38
+ * This is the third provider reading somebody else's live database, so it
39
+ * follows the rules the other two set: open read-only through the **shared**
40
+ * `loadSqlite()`, never a second loader, and treat every failure -- a missing
41
+ * table, a renamed column, a runtime with no `node:sqlite` -- as an empty store
42
+ * rather than an exception.
43
+ */
44
+ const STORE = path.join("User", "globalStorage", "state.vscdb");
45
+ /** The synthetic shard every conversation is filed under; there is one store. */
46
+ const SHARD = "composer";
47
+ /** `ItemTable` key holding the pre-`composerHeaders` conversation index. */
48
+ const LEGACY_INDEX = "composer.composerHeaders";
49
+ /**
50
+ * Prefix bounds for a `cursorDiskKV` key range.
51
+ *
52
+ * `;` is `:` plus one, so `[<prefix>:, <prefix>;)` is exactly the keys under a
53
+ * prefix. A range comparison uses the UNIQUE index on `key` unambiguously; the
54
+ * table holds roughly 450k rows and 5 GB of values, and an unconstrained scan of
55
+ * it is minutes rather than milliseconds.
56
+ */
57
+ function prefixRange(prefix) {
58
+ return [`${prefix}:`, `${prefix};`];
59
+ }
60
+ function text(value) {
61
+ return typeof value === "string" && value ? value : null;
62
+ }
63
+ function count(value) {
64
+ if (typeof value === "number" && Number.isFinite(value))
65
+ return value;
66
+ if (typeof value === "bigint")
67
+ return Number(value);
68
+ return 0;
69
+ }
70
+ /** An epoch-millisecond column as the ISO string every event carries. */
71
+ function isoAt(value) {
72
+ const ms = count(value);
73
+ if (!ms)
74
+ return null;
75
+ const at = new Date(ms);
76
+ return Number.isNaN(at.getTime()) ? null : at.toISOString();
77
+ }
78
+ function parseJson(value) {
79
+ const raw = text(value);
80
+ if (!raw)
81
+ return null;
82
+ try {
83
+ const parsed = JSON.parse(raw);
84
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
85
+ ? parsed
86
+ : null;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ function record(value) {
93
+ return value && typeof value === "object" && !Array.isArray(value)
94
+ ? value
95
+ : null;
96
+ }
97
+ function openDatabase(file) {
98
+ const sqlite = loadSqlite();
99
+ if (!sqlite)
100
+ return null;
101
+ try {
102
+ // The URI form, because the store has live `-wal`/`-shm` sidecars and this
103
+ // is somebody else's database: it is never opened for writing.
104
+ return new sqlite.DatabaseSync(`file:${file}?mode=ro`, { readOnly: true });
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ function closeQuietly(db) {
111
+ try {
112
+ db.close();
113
+ }
114
+ catch {
115
+ // Closing a read-only handle cannot lose anything.
116
+ }
117
+ }
118
+ function query(db, sql, ...parameters) {
119
+ try {
120
+ return db.prepare(sql).all(...parameters);
121
+ }
122
+ catch {
123
+ // A renamed column, or a table this version of Cursor does not have, costs
124
+ // exactly what it names rather than the whole provider. `composerHeaders`
125
+ // is itself recent, and older stores keep that index in `ItemTable`.
126
+ return [];
127
+ }
128
+ }
129
+ function blank(id) {
130
+ return {
131
+ id,
132
+ createdAt: 0,
133
+ lastUpdatedAt: 0,
134
+ bubbles: 0,
135
+ kind: "main",
136
+ parentId: null,
137
+ agentType: null,
138
+ agentPath: null,
139
+ project: "",
140
+ title: null,
141
+ model: null,
142
+ };
143
+ }
144
+ /**
145
+ * The absolute working directory a conversation ran in.
146
+ *
147
+ * `workspaceIdentifier.uri.fsPath` is carried inside the store itself, so
148
+ * project identity never depends on reading `User/workspaceStorage/<id>/`, and
149
+ * never on the `~/.cursor/projects/<slug>` directory names -- those replace both
150
+ * separators and dots and are lossy in both directions, the same trap
151
+ * `claude-code.ts` documents for its own slugs.
152
+ *
153
+ * A multi-root window carries `configPath` instead, naming a `.code-workspace`
154
+ * document rather than a directory. That is several folders, and a
155
+ * `FileAggregate` has exactly one `project`, so it is left unset rather than
156
+ * having one of them picked arbitrarily.
157
+ */
158
+ function projectOf(identifier) {
159
+ const uri = record(identifier?.uri);
160
+ return text(uri?.fsPath) ?? text(uri?.path) ?? "";
161
+ }
162
+ /** Fills in whatever a conversation-index entry knows. */
163
+ function applyIdentity(into, entry) {
164
+ const identifier = record(entry.workspaceIdentifier);
165
+ if (identifier && !into.project)
166
+ into.project = projectOf(identifier);
167
+ if (!into.title)
168
+ into.title = text(entry.name);
169
+ into.createdAt ||= count(entry.createdAt);
170
+ into.lastUpdatedAt = Math.max(into.lastUpdatedAt, count(entry.lastUpdatedAt));
171
+ const subagent = record(entry.subagentInfo);
172
+ if (!subagent)
173
+ return;
174
+ into.kind = "subagent";
175
+ into.parentId ??= text(subagent.parentComposerId) ?? text(subagent.rootParentConversationId);
176
+ // The reusable role, and the task-specific id for this one run -- the same
177
+ // split Codex records as `agent_role` and `agent_path`.
178
+ into.agentType ??= text(subagent.subagentTypeName);
179
+ into.agentPath ??= text(subagent.toolCallId);
180
+ }
181
+ /**
182
+ * Every conversation in the store, with a freshness key derived from its rows.
183
+ *
184
+ * **Discovery is over `composerData:` keys, not the `composerHeaders` table.**
185
+ * That table is recent and holds only the conversations Cursor has migrated into
186
+ * it: on a real corpus it names 1,023 of 1,616 conversations, and 161 of the 229
187
+ * that carry token counters are absent from it *and* from the legacy
188
+ * `ItemTable` index beside it -- 61% of all the tokens on this machine. Both
189
+ * indexes are therefore read only to *enrich* a conversation with the identity
190
+ * they alone carry (workspace, subagent role), never to decide it exists.
191
+ *
192
+ * The freshness key has to be synthesized for the same reason OpenCode's does:
193
+ * the file's mtime is one value shared by every conversation, so any write would
194
+ * invalidate all of them and force a full re-parse on every scan. `mtimeMs` is
195
+ * the latest timestamp the conversation carries and `bubbles` fingerprints its
196
+ * turn count, because `isFresh` compares exactly the pair `(size, mtimeMs)` and
197
+ * neither half alone catches every edit.
198
+ */
199
+ function readIndex(db) {
200
+ const conversations = new Map();
201
+ const at = (id) => {
202
+ let found = conversations.get(id);
203
+ if (!found)
204
+ conversations.set(id, (found = blank(id)));
205
+ return found;
206
+ };
207
+ const [dataLow, dataHigh] = prefixRange("composerData");
208
+ for (const row of query(db, `SELECT substr(key, ?) AS id,
209
+ json_extract(value, '$.createdAt') AS created,
210
+ json_extract(value, '$.lastUpdatedAt') AS updated,
211
+ json_extract(value, '$.modelConfig.modelName') AS model,
212
+ json_extract(value, '$.usageData') AS usage,
213
+ json_extract(value, '$.name') AS name,
214
+ json_extract(value, '$.workspaceIdentifier.uri.fsPath') AS project
215
+ FROM cursorDiskKV WHERE key >= ? AND key < ?`, dataLow.length + 1, dataLow, dataHigh)) {
216
+ const id = text(row.id);
217
+ if (!id)
218
+ continue;
219
+ const conversation = at(id);
220
+ conversation.createdAt = count(row.created);
221
+ conversation.lastUpdatedAt = count(row.updated);
222
+ conversation.title = text(row.name);
223
+ // The conversation carries its own workspace on about a fifth of rows, and
224
+ // is the only source of one for a conversation neither index knows about.
225
+ conversation.project = text(row.project) ?? "";
226
+ // `modelConfig` postdates the token era, so the legacy `usageData` map is
227
+ // the fallback -- but only when it names exactly one model, since a
228
+ // conversation that used several cannot attribute a bubble to one of them.
229
+ conversation.model = text(row.model) ?? soleModel(row.usage);
230
+ }
231
+ // Turn counts, index-only: nothing but `key` is touched, so this reads the
232
+ // UNIQUE index rather than 1.3 GB of turn bodies.
233
+ const [bubbleLow, bubbleHigh] = prefixRange("bubbleId");
234
+ for (const row of query(db, `SELECT substr(key, ?, instr(substr(key, ?), ':') - 1) AS id, count(*) AS turns
235
+ FROM cursorDiskKV WHERE key >= ? AND key < ? GROUP BY id`, bubbleLow.length + 1, bubbleLow.length + 1, bubbleLow, bubbleHigh)) {
236
+ const id = text(row.id);
237
+ if (!id)
238
+ continue;
239
+ at(id).bubbles = count(row.turns);
240
+ }
241
+ for (const row of query(db, "SELECT composerId, isSubagent, createdAt, lastUpdatedAt, value FROM composerHeaders")) {
242
+ const id = text(row.composerId);
243
+ if (!id)
244
+ continue;
245
+ const conversation = at(id);
246
+ if (count(row.isSubagent) === 1)
247
+ conversation.kind = "subagent";
248
+ conversation.createdAt ||= count(row.createdAt);
249
+ conversation.lastUpdatedAt = Math.max(conversation.lastUpdatedAt, count(row.lastUpdatedAt));
250
+ const entry = parseJson(row.value);
251
+ if (entry)
252
+ applyIdentity(conversation, entry);
253
+ }
254
+ for (const entry of legacyIndex(db)) {
255
+ const id = text(entry.composerId);
256
+ if (!id)
257
+ continue;
258
+ applyIdentity(at(id), entry);
259
+ }
260
+ return conversations;
261
+ }
262
+ /**
263
+ * The pre-`composerHeaders` conversation index, a JSON blob in `ItemTable`.
264
+ *
265
+ * Cursor gates the table by a separate `composer.composerHeaders.tableGateEnabled`
266
+ * flag and did not backfill it, so both live side by side and both are read.
267
+ */
268
+ function legacyIndex(db) {
269
+ const rows = query(db, "SELECT value FROM ItemTable WHERE key = ?", LEGACY_INDEX);
270
+ const blob = parseJson(rows[0]?.value);
271
+ const all = blob?.allComposers;
272
+ if (!Array.isArray(all))
273
+ return [];
274
+ return all.filter((entry) => record(entry) !== null);
275
+ }
276
+ /** The single model a legacy `usageData` map names, or null when it names 0 or 2+. */
277
+ function soleModel(value) {
278
+ const usage = parseJson(value);
279
+ if (!usage)
280
+ return null;
281
+ const names = Object.keys(usage);
282
+ return names.length === 1 ? names[0] : null;
283
+ }
284
+ /** Cursor's own subagent-spawning builtin, the counterpart of OpenCode's `task`. */
285
+ const SPAWN_TOOL = "task_v2";
286
+ /**
287
+ * Cursor's flattened MCP tool name, in the form `classifyTool` understands.
288
+ *
289
+ * A call is recorded as `mcp-<server>-<tool>`, and on a real corpus the server
290
+ * half is sometimes repeated (`mcp-cursor-ide-browser-cursor-ide-browser-browser_lock`)
291
+ * while the separator also occurs inside both halves. So an MCP call can be told
292
+ * apart from a builtin, which is why `mcp` is a declared capability here and not
293
+ * for OpenCode or Gemini CLI, whose tool records name no server at all. But the
294
+ * boundary within the remainder is not recoverable. Rewriting to `mcp__<rest>`
295
+ * with no second separator makes `classifyTool` report `kind: "mcp"` with the
296
+ * whole flattened name as the server, which is exactly as much as is known.
297
+ */
298
+ function normalizeTool(name) {
299
+ if (!name.startsWith("mcp-"))
300
+ return name;
301
+ const rest = name.slice("mcp-".length);
302
+ return rest ? `mcp__${rest}` : name;
303
+ }
304
+ function bucketFor(building, timestamp) {
305
+ if (!timestamp)
306
+ return null;
307
+ const day = utcDay(timestamp);
308
+ if (!day)
309
+ return null;
310
+ const aggregate = building.aggregate;
311
+ if (!aggregate.firstTs || timestamp < aggregate.firstTs)
312
+ aggregate.firstTs = timestamp;
313
+ if (!aggregate.lastTs || timestamp > aggregate.lastTs)
314
+ aggregate.lastTs = timestamp;
315
+ return (aggregate.days[day] ??= emptyBucket());
316
+ }
317
+ /**
318
+ * One turn's token counters.
319
+ *
320
+ * Only `input` and `output` are ever populated: no other counter has existed in
321
+ * this schema. A caller only reaches here for a turn whose counters are nonzero.
322
+ */
323
+ function readUsage(input, output) {
324
+ const totals = emptyTokens();
325
+ totals.input = input;
326
+ totals.output = output;
327
+ totals.requests = 1;
328
+ return totals;
329
+ }
330
+ /**
331
+ * One conversation's turns.
332
+ *
333
+ * The projection is done in SQL rather than in JavaScript on purpose: a turn
334
+ * body averages 9 KB and the corpus holds 1.3 GB of them, so selecting `value`
335
+ * and picking fields off it here would move a whole conversation across the
336
+ * boundary in order to read seven scalars.
337
+ */
338
+ function applyBubbles(db, conversation, roles, building) {
339
+ const [low, high] = prefixRange(`bubbleId:${conversation.id}`);
340
+ const fallback = isoAt(conversation.createdAt);
341
+ const model = conversation.model ?? "(unknown)";
342
+ for (const row of query(db, `SELECT json_extract(value, '$.type') AS kind,
343
+ json_extract(value, '$.tokenCount.inputTokens') AS input,
344
+ json_extract(value, '$.tokenCount.outputTokens') AS output,
345
+ json_extract(value, '$.timingInfo.clientRpcSendTime') AS ts,
346
+ json_extract(value, '$.toolFormerData.name') AS tool,
347
+ json_extract(value, '$.toolFormerData.status') AS status,
348
+ json_extract(value, '$.toolFormerData.toolCallId') AS call
349
+ FROM cursorDiskKV WHERE key >= ? AND key < ?`, low, high)) {
350
+ // All but a fraction of a percent of turns carry no timestamp of their own,
351
+ // so the conversation's creation instant is the anchor. Day rollups are
352
+ // therefore per conversation rather than per turn: coarser than every other
353
+ // provider, and documented as such.
354
+ const ts = isoAt(row.ts) ?? fallback;
355
+ const bucket = bucketFor(building, ts);
356
+ if (!bucket || !ts)
357
+ continue;
358
+ if (count(row.kind) === 1) {
359
+ bucket.prompts += 1;
360
+ building.events.push({ ts, kind: "prompt" });
361
+ }
362
+ const input = count(row.input);
363
+ const output = count(row.output);
364
+ // A turn whose counters are absent or all zero emits no response at all.
365
+ // Every turn after 2025 is one of those, and counting them would report a
366
+ // request against no tokens, the same suppression `codex.ts` applies to a
367
+ // zero delta.
368
+ if (input > 0 || output > 0) {
369
+ const totals = readUsage(input, output);
370
+ addTokens((bucket.models[model] ??= emptyTokens()), totals);
371
+ building.events.push({ ts, kind: "response", model, tokens: totals });
372
+ }
373
+ const tool = text(row.tool);
374
+ if (!tool)
375
+ continue;
376
+ const name = normalizeTool(tool);
377
+ bucket.tools[name] = (bucket.tools[name] ?? 0) + 1;
378
+ building.events.push({ ts, kind: "tool_use", tool: name });
379
+ if (tool === SPAWN_TOOL) {
380
+ // The parent's call does not name the role, but the conversation it
381
+ // spawned does, and that conversation's `subagentInfo.toolCallId` points
382
+ // back at this exact call, so the join is by identifier not by guess.
383
+ const role = roles.get(text(row.call) ?? "") ?? "(unrecorded)";
384
+ (bucket.agents[role] ??= { count: 0, maxDepth: 0 }).count += 1;
385
+ building.events.push({ ts, kind: "agent", name: role });
386
+ }
387
+ if (text(row.status) === "error") {
388
+ bucket.errors += 1;
389
+ building.events.push({ ts, kind: "error" });
390
+ }
391
+ }
392
+ }
393
+ let store = null;
394
+ function storeKey(file) {
395
+ try {
396
+ const stats = fs.statSync(file);
397
+ // The path is part of the key, not just the stat: two stores can share a
398
+ // size and an mtime, and the memo holds only one entry.
399
+ return `${file}${stats.mtimeMs}${stats.size}`;
400
+ }
401
+ catch {
402
+ return "";
403
+ }
404
+ }
405
+ /**
406
+ * The index, built once and memoized, with the handle it was built from.
407
+ *
408
+ * Unlike OpenCode this does **not** reduce the whole store up front. That one is
409
+ * a few megabytes; this one is 5.65 GB, of which 1.3 GB is turn bodies and 3 GB
410
+ * is opaque blobs. Only the cheap index is built eagerly, and `parse` then reads
411
+ * one conversation's turns through the key range that serves it. The handle is
412
+ * memoized alongside it because `scan.ts` drives `parse` through a worker pool,
413
+ * and reopening the store per conversation would be the only expensive part
414
+ * left. The memo holds a single entry, so a `--provider all` run does not pin
415
+ * two stores in memory.
416
+ */
417
+ function loadStore(file) {
418
+ const key = storeKey(file);
419
+ if (store && store.key === key)
420
+ return store;
421
+ if (store?.db)
422
+ closeQuietly(store.db);
423
+ const db = openDatabase(file);
424
+ const index = db ? readIndex(db) : new Map();
425
+ const roles = new Map();
426
+ for (const conversation of index.values()) {
427
+ if (conversation.agentPath && conversation.agentType) {
428
+ roles.set(conversation.agentPath, conversation.agentType);
429
+ }
430
+ }
431
+ store = { key, db, index, roles };
432
+ return store;
433
+ }
434
+ function emptyAggregate(file, sessionId) {
435
+ return {
436
+ file: file.file,
437
+ size: file.size,
438
+ mtimeMs: file.mtimeMs,
439
+ provider: cursorProvider.name,
440
+ sessionId,
441
+ kind: file.kind,
442
+ project: "",
443
+ firstTs: "",
444
+ lastTs: "",
445
+ days: {},
446
+ malformedLines: 0,
447
+ };
448
+ }
449
+ export const cursorProvider = {
450
+ name: "cursor",
451
+ title: "Cursor",
452
+ source: "The editor store at <user data>/User/globalStorage/state.vscdb",
453
+ capabilities: {
454
+ // Real per-request figures, but only for conversations from before Cursor
455
+ // stopped writing them in December 2025. See the note at the top.
456
+ tokens: true,
457
+ // `tokenCount` has only ever had `inputTokens` and `outputTokens`: no cache
458
+ // read, cache write, or reasoning counter has ever been recorded.
459
+ cacheTokens: false,
460
+ tools: true,
461
+ // No tool, capability, or turn field names a skill, and no record of one
462
+ // being invoked reaches the store.
463
+ skills: false,
464
+ subagents: true,
465
+ // Cursor runs five hook events out of `~/.cursor/hooks.json`, but no
466
+ // execution of one is written anywhere, the same as Codex.
467
+ hooks: false,
468
+ // A call is recorded as `mcp-<server>-<tool>`, so an MCP call can be told
469
+ // apart from a builtin even though the server boundary cannot be recovered.
470
+ mcp: true,
471
+ // A slash command reaches the store only as the prompt text it expanded to.
472
+ // `~/.cursor/prompt_history.json` is a most-recently-used list, not a log.
473
+ slashCommands: false,
474
+ projects: true,
475
+ },
476
+ /**
477
+ * The Electron user-data directory, guarded on the store inside it.
478
+ *
479
+ * Candidates are tried in order rather than switched on `process.platform`,
480
+ * which keeps the provider hermetic under `--logs` and under a suite that
481
+ * swaps `HOME`, and costs one `statSync` per miss.
482
+ */
483
+ root(context) {
484
+ const override = context.override?.trim();
485
+ const appData = context.env.APPDATA?.trim();
486
+ const candidates = override
487
+ ? [override]
488
+ : [
489
+ ...(appData ? [path.join(appData, "Cursor")] : []),
490
+ path.join(context.home, "Library", "Application Support", "Cursor"),
491
+ path.join(context.home, ".config", "Cursor"),
492
+ ];
493
+ for (const candidate of candidates) {
494
+ try {
495
+ if (fs.statSync(path.join(candidate, STORE)).isFile())
496
+ return candidate;
497
+ }
498
+ catch {
499
+ // Not this one. An absent candidate is the normal case on every
500
+ // platform but the one Cursor is installed on.
501
+ }
502
+ }
503
+ return null;
504
+ },
505
+ /**
506
+ * One entry per conversation, out of a store with no filesystem unit below
507
+ * itself: the same synthesis OpenCode performs, and for the same reason. A
508
+ * `FileAggregate` carries exactly one session id, kind, project and time span,
509
+ * so collapsing the store into one entry would destroy `usage sessions`,
510
+ * `--last`, `--project`, and the main/subagent split.
511
+ */
512
+ discover(root, options) {
513
+ const file = path.join(root, STORE);
514
+ const found = [];
515
+ for (const conversation of loadStore(file).index.values()) {
516
+ // Whether a conversation is a subagent is known from the index, so they
517
+ // are pruned before any turn is read: the fourth provider that can, after
518
+ // claude-code, gemini-cli and opencode.
519
+ if (!options.subagents && conversation.kind === "subagent")
520
+ continue;
521
+ const mtimeMs = Math.max(conversation.lastUpdatedAt, conversation.createdAt);
522
+ if (options.modifiedSince !== undefined && mtimeMs < options.modifiedSince)
523
+ continue;
524
+ found.push({
525
+ file,
526
+ relative: `${SHARD}/${conversation.id}`,
527
+ shard: SHARD,
528
+ kind: conversation.kind,
529
+ size: conversation.bubbles,
530
+ mtimeMs,
531
+ });
532
+ }
533
+ // Byte comparison, never localeCompare: the order must not depend on the
534
+ // ICU build of the machine that ran the scan.
535
+ found.sort((a, b) => (a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0));
536
+ return found;
537
+ },
538
+ async read(file) {
539
+ return (await cursorProvider.parse(file)).aggregate;
540
+ },
541
+ async parse(file) {
542
+ const sessionId = file.relative.slice(`${SHARD}/`.length);
543
+ const loaded = loadStore(file.file);
544
+ const conversation = loaded.index.get(sessionId);
545
+ // A conversation that vanished between discovery and parsing, or a store
546
+ // that would not open. Neither is fatal: it reports as an empty transcript.
547
+ if (!conversation || !loaded.db) {
548
+ return { aggregate: emptyAggregate(file, sessionId), events: [] };
549
+ }
550
+ const aggregate = emptyAggregate(file, sessionId);
551
+ aggregate.kind = conversation.kind;
552
+ aggregate.project = conversation.project;
553
+ if (conversation.parentId)
554
+ aggregate.parentSessionId = conversation.parentId;
555
+ if (conversation.agentType)
556
+ aggregate.agentType = conversation.agentType;
557
+ if (conversation.agentPath)
558
+ aggregate.agentPath = conversation.agentPath;
559
+ if (conversation.title)
560
+ aggregate.title = conversation.title;
561
+ const building = { aggregate, events: [] };
562
+ // Seed the span from the index, so a conversation whose turns are all
563
+ // untimed still reports the day it was created on.
564
+ bucketFor(building, isoAt(conversation.createdAt));
565
+ bucketFor(building, isoAt(conversation.lastUpdatedAt));
566
+ applyBubbles(loaded.db, conversation, loaded.roles, building);
567
+ return { aggregate, events: building.events };
568
+ },
569
+ };
570
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../../src/usage/providers/cursor.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { antigravityProvider } from "./antigravity.js";
2
2
  import { claudeCodeProvider } from "./claude-code.js";
3
3
  import { codexProvider } from "./codex.js";
4
+ import { cursorProvider } from "./cursor.js";
4
5
  import { geminiCliProvider } from "./gemini-cli.js";
5
6
  import { opencodeProvider } from "./opencode.js";
6
7
  /**
@@ -19,6 +20,7 @@ export const PROVIDERS = [
19
20
  // the e2e suite pins the earlier entries by position.
20
21
  geminiCliProvider,
21
22
  opencodeProvider,
23
+ cursorProvider,
22
24
  ];
23
25
  export const DEFAULT_PROVIDER = claudeCodeProvider.name;
24
26
  /** Selects every registered provider rather than one. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/usage/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAA6B;IACjD,kBAAkB;IAClB,aAAa;IACb,mBAAmB;IACnB,4EAA4E;IAC5E,sDAAsD;IACtD,iBAAiB;IACjB,gBAAgB;CACjB,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,IAAI,CAAC;AAExD,yDAAyD;AACzD,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AAEnC,MAAM,UAAU,aAAa;IAC3B,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAwB;IACvD,MAAM,MAAM,GAAG,IAAI,IAAI,gBAAgB,CAAC;IACxC,IAAI,MAAM,KAAK,aAAa;QAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAC1E,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,qBAAqB,MAAM,YAAY,CAAC,GAAG,aAAa,EAAE,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACzF,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,QAAQ,CAAC,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/usage/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAA6B;IACjD,kBAAkB;IAClB,aAAa;IACb,mBAAmB;IACnB,4EAA4E;IAC5E,sDAAsD;IACtD,iBAAiB;IACjB,gBAAgB;IAChB,cAAc;CACf,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,IAAI,CAAC;AAExD,yDAAyD;AACzD,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AAEnC,MAAM,UAAU,aAAa;IAC3B,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAwB;IACvD,MAAM,MAAM,GAAG,IAAI,IAAI,gBAAgB,CAAC;IACxC,IAAI,MAAM,KAAK,aAAa;QAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAC1E,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,qBAAqB,MAAM,YAAY,CAAC,GAAG,aAAa,EAAE,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACzF,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,QAAQ,CAAC,CAAC;AACpB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cairn-tool/cairn",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "type": "module",
5
5
  "description": "An agent-agnostic CLI toolkit for working with markdown files and related assets",
6
6
  "license": "MIT",
@@ -63,6 +63,7 @@
63
63
  "markdownlint": "^0.41.1",
64
64
  "mermaid": "^11.17.2",
65
65
  "minimatch": "^10.2.6",
66
+ "pdfjs-dist": "^6.3.289",
66
67
  "remark-frontmatter": "^5.0.0",
67
68
  "remark-gfm": "^4.0.1",
68
69
  "remark-parse": "^11.0.0",