@manyoars/oars 0.2.0 → 0.4.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.
- package/README.md +147 -28
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +10 -4
- package/dist/cli.js +440 -91
- package/dist/commands/keys.d.ts +31 -0
- package/dist/commands/keys.js +65 -0
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/errors.d.ts +13 -13
- package/dist/errors.js +28 -11
- package/dist/gate/checks.d.ts +68 -0
- package/dist/gate/checks.js +584 -0
- package/dist/gate/command.d.ts +14 -0
- package/dist/gate/command.js +61 -0
- package/dist/gate/hook-fill-main.d.ts +1 -0
- package/dist/gate/hook-fill-main.js +16 -0
- package/dist/gate/hook-fill.d.ts +38 -0
- package/dist/gate/hook-fill.js +94 -0
- package/dist/gate/hook-main.d.ts +1 -0
- package/dist/gate/hook-main.js +47 -0
- package/dist/gate/hook-process.d.ts +24 -0
- package/dist/gate/hook-process.js +112 -0
- package/dist/gate/hook-record.d.ts +3 -0
- package/dist/gate/hook-record.js +8 -0
- package/dist/gate/hook.d.ts +48 -0
- package/dist/gate/hook.js +259 -0
- package/dist/gate/install.d.ts +25 -0
- package/dist/gate/install.js +126 -0
- package/dist/gate/local-bin.d.ts +8 -0
- package/dist/gate/local-bin.js +36 -0
- package/dist/gate/notes.d.ts +19 -0
- package/dist/gate/notes.js +27 -0
- package/dist/gate/novelty.d.ts +35 -0
- package/dist/gate/novelty.js +99 -0
- package/dist/gate/policy.d.ts +25 -0
- package/dist/gate/policy.js +73 -0
- package/dist/gate/record.d.ts +120 -0
- package/dist/gate/record.js +125 -0
- package/dist/gate/registry-state.d.ts +10 -0
- package/dist/gate/registry-state.js +30 -0
- package/dist/gate/registry.d.ts +56 -0
- package/dist/gate/registry.js +94 -0
- package/dist/gate/replay.d.ts +20 -0
- package/dist/gate/replay.js +254 -0
- package/dist/gate/report.d.ts +50 -0
- package/dist/gate/report.js +89 -0
- package/dist/gate/scope.d.ts +7 -0
- package/dist/gate/scope.js +27 -0
- package/dist/gate/transcripts.d.ts +68 -0
- package/dist/gate/transcripts.js +232 -0
- package/dist/hook-bin.d.ts +1 -0
- package/dist/hook-bin.js +50 -0
- package/dist/issuer.d.ts +10 -6
- package/dist/issuer.js +19 -24
- package/dist/main.d.ts +1 -0
- package/dist/main.js +67 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +429 -0
- package/dist/sessions/allow.d.ts +23 -0
- package/dist/sessions/allow.js +71 -0
- package/dist/sessions/archive.d.ts +11 -0
- package/dist/sessions/archive.js +38 -0
- package/dist/sessions/audit.d.ts +42 -0
- package/dist/sessions/audit.js +104 -0
- package/dist/sessions/baseline.d.ts +62 -0
- package/dist/sessions/baseline.js +190 -0
- package/dist/sessions/call-shape.d.ts +19 -0
- package/dist/sessions/call-shape.js +85 -0
- package/dist/sessions/capture-runtime.d.ts +10 -0
- package/dist/sessions/capture-runtime.js +12 -0
- package/dist/sessions/cell-increments.d.ts +47 -0
- package/dist/sessions/cell-increments.js +243 -0
- package/dist/sessions/cells.d.ts +109 -0
- package/dist/sessions/cells.js +261 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +745 -0
- package/dist/sessions/command.d.ts +127 -0
- package/dist/sessions/command.js +419 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/coverage.d.ts +19 -0
- package/dist/sessions/coverage.js +56 -0
- package/dist/sessions/cursor.d.ts +24 -0
- package/dist/sessions/cursor.js +324 -0
- package/dist/sessions/derive.d.ts +57 -0
- package/dist/sessions/derive.js +386 -0
- package/dist/sessions/exclusion.d.ts +87 -0
- package/dist/sessions/exclusion.js +361 -0
- package/dist/sessions/extract.d.ts +181 -0
- package/dist/sessions/extract.js +1806 -0
- package/dist/sessions/fold.d.ts +17 -0
- package/dist/sessions/fold.js +71 -0
- package/dist/sessions/frame.d.ts +24 -0
- package/dist/sessions/frame.js +95 -0
- package/dist/sessions/gemini.d.ts +37 -0
- package/dist/sessions/gemini.js +613 -0
- package/dist/sessions/gold-arms.d.ts +67 -0
- package/dist/sessions/gold-arms.js +172 -0
- package/dist/sessions/gold.d.ts +197 -0
- package/dist/sessions/gold.js +463 -0
- package/dist/sessions/graph.d.ts +12 -0
- package/dist/sessions/graph.js +34 -0
- package/dist/sessions/grok.d.ts +36 -0
- package/dist/sessions/grok.js +421 -0
- package/dist/sessions/hook-calls.d.ts +3 -0
- package/dist/sessions/hook-calls.js +47 -0
- package/dist/sessions/hook-index.d.ts +66 -0
- package/dist/sessions/hook-index.js +164 -0
- package/dist/sessions/hooks.d.ts +17 -0
- package/dist/sessions/hooks.js +281 -0
- package/dist/sessions/identity.d.ts +27 -0
- package/dist/sessions/identity.js +131 -0
- package/dist/sessions/install.d.ts +70 -0
- package/dist/sessions/install.js +246 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +7 -0
- package/dist/sessions/mcp.js +157 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +44 -0
- package/dist/sessions/motif.js +59 -0
- package/dist/sessions/opencode.d.ts +33 -0
- package/dist/sessions/opencode.js +319 -0
- package/dist/sessions/otel.d.ts +60 -0
- package/dist/sessions/otel.js +494 -0
- package/dist/sessions/package-books.d.ts +48 -0
- package/dist/sessions/package-books.js +120 -0
- package/dist/sessions/package-resolve.d.ts +32 -0
- package/dist/sessions/package-resolve.js +185 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- package/dist/sessions/pi.d.ts +36 -0
- package/dist/sessions/pi.js +353 -0
- package/dist/sessions/pool-books.d.ts +21 -0
- package/dist/sessions/pool-books.js +75 -0
- package/dist/sessions/pool-choices.d.ts +5 -0
- package/dist/sessions/pool-choices.js +11 -0
- package/dist/sessions/pool-command.d.ts +115 -0
- package/dist/sessions/pool-command.js +669 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- package/dist/sessions/pool-nightly-deps.d.ts +3 -0
- package/dist/sessions/pool-nightly-deps.js +11 -0
- package/dist/sessions/pool-nightly.d.ts +10 -0
- package/dist/sessions/pool-nightly.js +85 -0
- package/dist/sessions/pool-outbox.d.ts +76 -0
- package/dist/sessions/pool-outbox.js +313 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- package/dist/sessions/pool-raw.d.ts +27 -0
- package/dist/sessions/pool-raw.js +68 -0
- package/dist/sessions/pool-replica.d.ts +35 -0
- package/dist/sessions/pool-replica.js +91 -0
- package/dist/sessions/pool-send-lock.d.ts +20 -0
- package/dist/sessions/pool-send-lock.js +87 -0
- package/dist/sessions/pool-sent.d.ts +4 -0
- package/dist/sessions/pool-sent.js +26 -0
- package/dist/sessions/pool-totals.d.ts +10 -0
- package/dist/sessions/pool-totals.js +21 -0
- package/dist/sessions/pool-wire.d.ts +435 -0
- package/dist/sessions/pool-wire.js +1100 -0
- package/dist/sessions/pool.d.ts +32 -0
- package/dist/sessions/pool.js +164 -0
- package/dist/sessions/projection.d.ts +75 -0
- package/dist/sessions/projection.js +165 -0
- package/dist/sessions/public-vocabulary.d.ts +72 -0
- package/dist/sessions/public-vocabulary.js +295 -0
- package/dist/sessions/query.d.ts +123 -0
- package/dist/sessions/query.js +268 -0
- package/dist/sessions/read-adoption.d.ts +2 -0
- package/dist/sessions/read-adoption.js +27 -0
- package/dist/sessions/read-call-shape.d.ts +2 -0
- package/dist/sessions/read-call-shape.js +25 -0
- package/dist/sessions/read-change.d.ts +2 -0
- package/dist/sessions/read-change.js +30 -0
- package/dist/sessions/read-cost.d.ts +4 -0
- package/dist/sessions/read-cost.js +48 -0
- package/dist/sessions/read-day-move.d.ts +3 -0
- package/dist/sessions/read-day-move.js +65 -0
- package/dist/sessions/read-fit.d.ts +7 -0
- package/dist/sessions/read-fit.js +41 -0
- package/dist/sessions/read-interaction.d.ts +5 -0
- package/dist/sessions/read-interaction.js +62 -0
- package/dist/sessions/read-lead-lag.d.ts +15 -0
- package/dist/sessions/read-lead-lag.js +81 -0
- package/dist/sessions/read-live.d.ts +6 -0
- package/dist/sessions/read-live.js +38 -0
- package/dist/sessions/read-method.d.ts +2 -0
- package/dist/sessions/read-method.js +29 -0
- package/dist/sessions/read-next-step.d.ts +2 -0
- package/dist/sessions/read-next-step.js +35 -0
- package/dist/sessions/read-order.d.ts +54 -0
- package/dist/sessions/read-order.js +62 -0
- package/dist/sessions/read-readable.d.ts +8 -0
- package/dist/sessions/read-readable.js +24 -0
- package/dist/sessions/read-recovery-edge.d.ts +2 -0
- package/dist/sessions/read-recovery-edge.js +26 -0
- package/dist/sessions/read-recovery.d.ts +20 -0
- package/dist/sessions/read-recovery.js +97 -0
- package/dist/sessions/read-rows.d.ts +13 -0
- package/dist/sessions/read-rows.js +47 -0
- package/dist/sessions/read-substitution.d.ts +5 -0
- package/dist/sessions/read-substitution.js +51 -0
- package/dist/sessions/read-support.d.ts +48 -0
- package/dist/sessions/read-support.js +73 -0
- package/dist/sessions/read-tool-day.d.ts +2 -0
- package/dist/sessions/read-tool-day.js +26 -0
- package/dist/sessions/read-version-boundary.d.ts +31 -0
- package/dist/sessions/read-version-boundary.js +95 -0
- package/dist/sessions/read.d.ts +86 -0
- package/dist/sessions/read.js +87 -0
- package/dist/sessions/recall-bundle.d.ts +12 -0
- package/dist/sessions/recall-bundle.js +33 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -0
- package/dist/sessions/recall-frames.d.ts +18 -0
- package/dist/sessions/recall-frames.js +44 -0
- package/dist/sessions/recall-next.d.ts +145 -0
- package/dist/sessions/recall-next.js +241 -0
- package/dist/sessions/recall-object.d.ts +139 -0
- package/dist/sessions/recall-object.js +78 -0
- package/dist/sessions/recall-pool.d.ts +19 -0
- package/dist/sessions/recall-pool.js +96 -0
- package/dist/sessions/recall-push.d.ts +26 -0
- package/dist/sessions/recall-push.js +47 -0
- package/dist/sessions/recall-reads.d.ts +124 -0
- package/dist/sessions/recall-reads.js +417 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/recovery-calls.d.ts +21 -0
- package/dist/sessions/recovery-calls.js +25 -0
- package/dist/sessions/redact.d.ts +17 -0
- package/dist/sessions/redact.js +113 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +694 -0
- package/dist/sessions/schema.js +167 -0
- package/dist/sessions/session-shape.d.ts +76 -0
- package/dist/sessions/session-shape.js +355 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +233 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +309 -0
- package/dist/sessions/store.js +1498 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +27 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/transcript-tail.d.ts +14 -0
- package/dist/sessions/transcript-tail.js +64 -0
- package/dist/sessions/verify.d.ts +53 -0
- package/dist/sessions/verify.js +286 -0
- package/dist/sessions/vocab-load.d.ts +26 -0
- package/dist/sessions/vocab-load.js +61 -0
- package/dist/sessions/vscode-copilot.d.ts +38 -0
- package/dist/sessions/vscode-copilot.js +494 -0
- package/dist/telemetry/command.d.ts +8 -0
- package/dist/telemetry/command.js +85 -0
- package/dist/telemetry/consent.d.ts +17 -0
- package/dist/telemetry/consent.js +63 -0
- package/dist/telemetry/crash.d.ts +8 -0
- package/dist/telemetry/crash.js +52 -0
- package/dist/telemetry/emit.d.ts +31 -0
- package/dist/telemetry/emit.js +42 -0
- package/dist/telemetry/fields.d.ts +16 -0
- package/dist/telemetry/fields.js +26 -0
- package/dist/telemetry/health.d.ts +21 -0
- package/dist/telemetry/health.js +108 -0
- package/dist/telemetry/hook-emit.d.ts +4 -0
- package/dist/telemetry/hook-emit.js +18 -0
- package/dist/telemetry/measures.d.ts +32 -0
- package/dist/telemetry/measures.js +128 -0
- package/dist/telemetry/observe.d.ts +13 -0
- package/dist/telemetry/observe.js +51 -0
- package/dist/telemetry/read-events.d.ts +9 -0
- package/dist/telemetry/read-events.js +17 -0
- package/dist/telemetry/record.d.ts +17 -0
- package/dist/telemetry/record.js +26 -0
- package/dist/telemetry/report.d.ts +77 -0
- package/dist/telemetry/report.js +188 -0
- package/dist/telemetry/schema.d.ts +40 -0
- package/dist/telemetry/schema.js +187 -0
- package/dist/telemetry/send-main.d.ts +1 -0
- package/dist/telemetry/send-main.js +11 -0
- package/dist/telemetry/send.d.ts +36 -0
- package/dist/telemetry/send.js +172 -0
- package/dist/telemetry/spool.d.ts +17 -0
- package/dist/telemetry/spool.js +70 -0
- package/dist/telemetry/wire.d.ts +25 -0
- package/dist/telemetry/wire.js +76 -0
- package/package.json +4 -5
- package/vocab/crates.json +1 -0
- package/vocab/go.json +1 -0
- package/vocab/harness.json +1 -0
- package/vocab/homebrew.json +1 -0
- package/vocab/manifest.json +1 -0
- package/vocab/maven.json +1 -0
- package/vocab/models.json +1 -0
- package/vocab/npm.json +1 -0
- package/vocab/programs.json +1 -0
- package/vocab/pypi.json +1 -0
- package/vocab/rubygems.json +1 -0
- package/dist/commands/deploy-input.d.ts +0 -38
- package/dist/commands/deploy-input.js +0 -150
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -124
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/templates.d.ts +0 -11
- package/dist/commands/templates.js +0 -32
- package/dist/commands/test-charge.d.ts +0 -11
- package/dist/commands/test-charge.js +0 -34
- package/dist/journal.d.ts +0 -26
- package/dist/journal.js +0 -58
- package/parity/deploy-plans/paid-api.json +0 -34
- package/parity/deploy-plans/per-task-agent.json +0 -34
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// The Cursor source: a Cursor user's chat and composer history becomes the same capture records a Claude Code transcript does.
|
|
2
|
+
//
|
|
3
|
+
// Cursor keeps history in SQLite, not in one JSON event per line. The reader opens the store read-only and writes each composer
|
|
4
|
+
// session out as the transcript lines `captureSession` already reads (user and assistant lines, `tool_use` and `tool_result` blocks, one
|
|
5
|
+
// `cwd` per line), so redaction, the 1,000-character `line` cap, the salted `fileHashes`, the repository identity, and the skeleton are
|
|
6
|
+
// the Claude Code path's own code, not a second copy of it. What Cursor has no equivalent of (cache tokens, subagents, hooks, a harness
|
|
7
|
+
// version, `cost-state`) stays at the value capture gives an absent field: zero, empty, or null.
|
|
8
|
+
//
|
|
9
|
+
// Where it reads from (`cursorRoot`): Cursor's `User` folder, `%APPDATA%\Cursor\User` on Windows, `~/Library/Application Support/Cursor/User`
|
|
10
|
+
// on macOS, `$XDG_CONFIG_HOME/Cursor/User` (else `~/.config/Cursor/User`) on Linux. Inside it:
|
|
11
|
+
// globalStorage/state.vscdb table cursorDiskKV: `composerData:<composerId>` (one JSON document per composer session, with
|
|
12
|
+
// `fullConversationHeadersOnly`, the ordered `{bubbleId, type}` list) and `bubbleId:<composerId>:<bubbleId>`
|
|
13
|
+
// (one JSON document per message; type 1 is the user, type 2 the assistant).
|
|
14
|
+
// workspaceStorage/<hash>/ `workspace.json` names the folder; `state.vscdb` table ItemTable key `composer.composerData` lists
|
|
15
|
+
// the composer sessions opened in that workspace.
|
|
16
|
+
// A composer session no workspace lists has no working directory: it is captured with none, so its paths hash as written (base `cwd`).
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
/** The Cursor `User` folder for a platform; the caller passes the environment so a test never reads the real one. */
|
|
23
|
+
export const cursorRoot = (platform, env, home = homedir()) => {
|
|
24
|
+
if (platform === 'win32')
|
|
25
|
+
return join(env.APPDATA ?? join(home, 'AppData', 'Roaming'), 'Cursor', 'User');
|
|
26
|
+
if (platform === 'darwin')
|
|
27
|
+
return join(home, 'Library', 'Application Support', 'Cursor', 'User');
|
|
28
|
+
return join(env.XDG_CONFIG_HOME ?? join(home, '.config'), 'Cursor', 'User');
|
|
29
|
+
};
|
|
30
|
+
const isObj = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
31
|
+
const str = (value) => (typeof value === 'string' && value !== '' ? value : undefined);
|
|
32
|
+
const num = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
|
|
33
|
+
/** Cursor's tool names, in the vocabulary prompt classes and `files` are computed in. A name with no counterpart keeps its own. */
|
|
34
|
+
const TOOL_NAMES = {
|
|
35
|
+
read_file: 'Read',
|
|
36
|
+
edit_file: 'Edit',
|
|
37
|
+
search_replace: 'Edit',
|
|
38
|
+
reapply: 'Edit',
|
|
39
|
+
write: 'Write',
|
|
40
|
+
run_terminal_cmd: 'Bash',
|
|
41
|
+
grep_search: 'Grep',
|
|
42
|
+
grep: 'Grep',
|
|
43
|
+
codebase_search: 'Grep',
|
|
44
|
+
file_search: 'Glob',
|
|
45
|
+
glob_file_search: 'Glob',
|
|
46
|
+
list_dir: 'Glob',
|
|
47
|
+
web_search: 'WebSearch',
|
|
48
|
+
};
|
|
49
|
+
/** A tool call's input in the keys the extractor reads: the path as `file_path` (or `path` for a directory), the command, the query. */
|
|
50
|
+
const inputOf = (params) => {
|
|
51
|
+
if (!isObj(params))
|
|
52
|
+
return {};
|
|
53
|
+
const input = {};
|
|
54
|
+
const file = str(params.relativeWorkspacePath) ?? str(params.targetFile);
|
|
55
|
+
if (file !== undefined)
|
|
56
|
+
input.file_path = file;
|
|
57
|
+
const directory = str(params.directoryPath) ?? str(params.targetDirectory);
|
|
58
|
+
if (directory !== undefined)
|
|
59
|
+
input.path = directory;
|
|
60
|
+
const command = str(params.command);
|
|
61
|
+
if (command !== undefined)
|
|
62
|
+
input.command = command;
|
|
63
|
+
const query = str(params.searchTerm) ?? str(params.query);
|
|
64
|
+
if (query !== undefined)
|
|
65
|
+
input.query = query;
|
|
66
|
+
return input;
|
|
67
|
+
};
|
|
68
|
+
const parse = (text) => {
|
|
69
|
+
if (typeof text !== 'string')
|
|
70
|
+
return undefined;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(text);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/** `node:sqlite` is loaded only when a Cursor store exists: it prints an experimental-feature warning on first use. */
|
|
79
|
+
const open = async (file) => {
|
|
80
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
81
|
+
return new DatabaseSync(file, { readOnly: true });
|
|
82
|
+
};
|
|
83
|
+
const folderOf = (workspace) => {
|
|
84
|
+
if (!isObj(workspace))
|
|
85
|
+
return null;
|
|
86
|
+
const uri = str(workspace.folder) ?? str(workspace.workspace);
|
|
87
|
+
if (uri === undefined || !uri.startsWith('file:'))
|
|
88
|
+
return null;
|
|
89
|
+
let path;
|
|
90
|
+
try {
|
|
91
|
+
path = fileURLToPath(uri);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// A `file:` URI that names no path on this platform (a POSIX path on Windows, a host that is not local) has no folder here.
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
// A `.code-workspace` file stands for its folder.
|
|
98
|
+
return str(workspace.folder) === undefined ? join(path, '..') : path;
|
|
99
|
+
};
|
|
100
|
+
/** Composer id to workspace folder, from every `workspaceStorage/<hash>/`; the first workspace to list a session names its folder. */
|
|
101
|
+
const workspaceFolders = async (root) => {
|
|
102
|
+
const folders = new Map();
|
|
103
|
+
let names;
|
|
104
|
+
try {
|
|
105
|
+
names = (await readdir(join(root, 'workspaceStorage'))).sort();
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return folders;
|
|
109
|
+
}
|
|
110
|
+
for (const name of names) {
|
|
111
|
+
const dir = join(root, 'workspaceStorage', name);
|
|
112
|
+
if (!existsSync(join(dir, 'workspace.json')) || !existsSync(join(dir, 'state.vscdb')))
|
|
113
|
+
continue;
|
|
114
|
+
const folder = folderOf(parse(await readFile(join(dir, 'workspace.json'), 'utf8')));
|
|
115
|
+
if (folder === null)
|
|
116
|
+
continue;
|
|
117
|
+
// A workspace store that cannot be read (locked, corrupt) only leaves its sessions without a folder.
|
|
118
|
+
let db;
|
|
119
|
+
try {
|
|
120
|
+
db = await open(join(dir, 'state.vscdb'));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const row = db.prepare("select cast(value as text) as v from ItemTable where key = 'composer.composerData'").get();
|
|
127
|
+
const listed = parse(row?.v);
|
|
128
|
+
const all = isObj(listed) && Array.isArray(listed.allComposers) ? listed.allComposers : [];
|
|
129
|
+
for (const entry of all) {
|
|
130
|
+
const id = isObj(entry) ? str(entry.composerId) : undefined;
|
|
131
|
+
if (id !== undefined && !folders.has(id))
|
|
132
|
+
folders.set(id, folder);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
db.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return folders;
|
|
143
|
+
};
|
|
144
|
+
/** The session's messages in order: the headers list when present, else the inline `conversation` older Cursor versions wrote. */
|
|
145
|
+
const bubblesOf = (db, composerId, composer) => {
|
|
146
|
+
const headers = Array.isArray(composer.fullConversationHeadersOnly) ? composer.fullConversationHeadersOnly : [];
|
|
147
|
+
const found = [];
|
|
148
|
+
if (headers.length > 0) {
|
|
149
|
+
const prefix = `bubbleId:${composerId}:`;
|
|
150
|
+
const rows = db.prepare('select cast(key as text) as k, cast(value as text) as v from cursorDiskKV where key >= ? and key < ?').all(prefix, `${prefix}\u{10ffff}`);
|
|
151
|
+
const byId = new Map();
|
|
152
|
+
for (const row of rows) {
|
|
153
|
+
const document = parse(row.v);
|
|
154
|
+
if (isObj(document) && typeof row.k === 'string')
|
|
155
|
+
byId.set(row.k.slice(prefix.length), document);
|
|
156
|
+
}
|
|
157
|
+
for (const header of headers) {
|
|
158
|
+
const id = isObj(header) ? str(header.bubbleId) : undefined;
|
|
159
|
+
const document = id === undefined ? undefined : byId.get(id);
|
|
160
|
+
if (id !== undefined && document !== undefined)
|
|
161
|
+
found.push({ id, document });
|
|
162
|
+
}
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
const inline = Array.isArray(composer.conversation) ? composer.conversation : [];
|
|
166
|
+
for (const [index, document] of inline.entries())
|
|
167
|
+
if (isObj(document))
|
|
168
|
+
found.push({ id: str(document.bubbleId) ?? String(index), document });
|
|
169
|
+
return found;
|
|
170
|
+
};
|
|
171
|
+
const iso = (millis) => (millis === undefined ? undefined : new Date(millis).toISOString());
|
|
172
|
+
/** The message of a failed call's `error`: Cursor wraps it as JSON with a model-visible and a client-visible text. */
|
|
173
|
+
const errorText = (raw) => {
|
|
174
|
+
const wrapper = parse(raw);
|
|
175
|
+
const message = isObj(wrapper) ? (str(wrapper.modelVisibleErrorMessage) ?? str(wrapper.clientVisibleErrorMessage)) : undefined;
|
|
176
|
+
return message ?? str(raw) ?? 'error';
|
|
177
|
+
};
|
|
178
|
+
/** An aborted terminal call (the user stopped it) has no outcome: it reads `none`, as a cancelled edit does, so it never counts as a fix. A call the user rejected is settled before this is asked. */
|
|
179
|
+
const aborted = (call, name) => {
|
|
180
|
+
const result = name === 'run_terminal_cmd' ? parse(call.result) : undefined;
|
|
181
|
+
return isObj(result) && (str(result.endedReason) ?? '').endsWith('_ABORTED');
|
|
182
|
+
};
|
|
183
|
+
const REJECTED_TEXT = "The user doesn't want to proceed with this tool use";
|
|
184
|
+
/**
|
|
185
|
+
* Why a call failed, undefined when it did not. Cursor writes a failed shell command as `completed` with a nonzero `exitCodeV2` in the JSON
|
|
186
|
+
* `result` (the shape Claude Code's `Exit code N` text takes), so the exit code is read, not only `status`.
|
|
187
|
+
*/
|
|
188
|
+
const failureOf = (call, name) => {
|
|
189
|
+
// `userDecision` is `accepted` or `rejected` (any other string reads as no decision). A rejected call is the user's refusal, so it takes the
|
|
190
|
+
// text Claude Code writes for one and the extractor classes it `user-rejected`, as it does for Claude Code, Codex, and Gemini.
|
|
191
|
+
if (call.userDecision === 'rejected')
|
|
192
|
+
return REJECTED_TEXT;
|
|
193
|
+
if (call.status === 'error')
|
|
194
|
+
return errorText(call.error ?? call.result);
|
|
195
|
+
const result = name === 'run_terminal_cmd' ? parse(call.result) : undefined;
|
|
196
|
+
const exit = isObj(result) ? num(result.exitCodeV2) : undefined;
|
|
197
|
+
return exit === undefined || exit === 0 ? undefined : `Exit code ${String(exit)}
|
|
198
|
+
${isObj(result) ? (str(result.output) ?? '') : ''}`;
|
|
199
|
+
};
|
|
200
|
+
/** One message, as the lines `captureSession` reads. `at` is the best time known: the message's own, else the one before it. */
|
|
201
|
+
const linesOf = (bubble, base, at, model) => {
|
|
202
|
+
const { document } = bubble;
|
|
203
|
+
if (document.type === 1) {
|
|
204
|
+
const text = str(document.text) ?? '';
|
|
205
|
+
return text === '' ? [] : [{ ...base, type: 'user', uuid: bubble.id, timestamp: at, message: { role: 'user', content: [{ type: 'text', text }] } }];
|
|
206
|
+
}
|
|
207
|
+
const content = [];
|
|
208
|
+
const text = str(document.text);
|
|
209
|
+
if (text !== undefined)
|
|
210
|
+
content.push({ type: 'text', text });
|
|
211
|
+
const call = isObj(document.toolFormerData) ? document.toolFormerData : undefined;
|
|
212
|
+
const name = call === undefined ? undefined : str(call.name);
|
|
213
|
+
const callId = call === undefined ? undefined : (str(call.toolCallId) ?? bubble.id);
|
|
214
|
+
if (call !== undefined && name !== undefined)
|
|
215
|
+
content.push({ type: 'tool_use', id: callId, name: TOOL_NAMES[name] ?? name, native: name, input: inputOf(parse(call.params)) });
|
|
216
|
+
const tokens = isObj(document.tokenCount) ? document.tokenCount : {};
|
|
217
|
+
const input = num(tokens.inputTokens) ?? 0;
|
|
218
|
+
const output = num(tokens.outputTokens) ?? 0;
|
|
219
|
+
const message = { role: 'assistant', id: bubble.id, content };
|
|
220
|
+
// A zero count means Cursor did not record one, so that category is left out rather than written as a measured zero (and with neither, no usage is written).
|
|
221
|
+
if (input > 0 || output > 0)
|
|
222
|
+
message.usage = { ...(input > 0 ? { input_tokens: input } : {}), ...(output > 0 ? { output_tokens: output } : {}) };
|
|
223
|
+
const lines = [{ ...base, type: 'assistant', uuid: bubble.id, requestId: bubble.id, timestamp: at, ...(model === undefined ? {} : { requestedModel: model }), message }];
|
|
224
|
+
if (call !== undefined && name !== undefined && (call.userDecision === 'rejected' || ((call.status === 'error' || call.status === 'completed') && !aborted(call, name)))) {
|
|
225
|
+
const failure = failureOf(call, name);
|
|
226
|
+
lines.push({
|
|
227
|
+
...base,
|
|
228
|
+
type: 'user',
|
|
229
|
+
uuid: `${bubble.id}-result`,
|
|
230
|
+
timestamp: at,
|
|
231
|
+
// Cursor stamps a result with the call's own time, so the gap is not a measured duration: the extractor leaves durationMs null.
|
|
232
|
+
noDuration: true,
|
|
233
|
+
message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: callId, is_error: failure !== undefined, content: failure ?? '' }] },
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// A bubble with token counts but no text or tool call still carries usage, so it is kept.
|
|
237
|
+
return content.length === 0 && message.usage === undefined ? [] : lines;
|
|
238
|
+
};
|
|
239
|
+
/** Opens the Cursor store under `root`; undefined when it holds no global store, so a machine without Cursor is skipped silently. */
|
|
240
|
+
export const openCursorStore = async (root) => {
|
|
241
|
+
const globalFile = join(root, 'globalStorage', 'state.vscdb');
|
|
242
|
+
if (!existsSync(globalFile))
|
|
243
|
+
return undefined;
|
|
244
|
+
const db = await open(globalFile);
|
|
245
|
+
const composers = new Map();
|
|
246
|
+
const sessions = [];
|
|
247
|
+
let rejected;
|
|
248
|
+
try {
|
|
249
|
+
rejected = await collect(db, root, composers, sessions);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
db.close();
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
sessions,
|
|
257
|
+
rejected,
|
|
258
|
+
close: () => {
|
|
259
|
+
db.close();
|
|
260
|
+
},
|
|
261
|
+
transcript: (session) => {
|
|
262
|
+
const composer = composers.get(session.composerId) ?? {};
|
|
263
|
+
const modelConfig = isObj(composer.modelConfig) ? composer.modelConfig : {};
|
|
264
|
+
const base = { sessionId: session.composerId, ...(session.folder === null ? {} : { cwd: session.folder }) };
|
|
265
|
+
const bubbles = bubblesOf(db, session.composerId, composer);
|
|
266
|
+
// A bubble's own `createdAt`, else `timingInfo.clientStartTime` when it is an epoch time at or after the composer's start (a smaller
|
|
267
|
+
// value is a relative clock and says nothing about when); a user bubble with neither takes the next assistant bubble's time.
|
|
268
|
+
const own = (bubble) => {
|
|
269
|
+
const created = str(bubble.document.createdAt);
|
|
270
|
+
if (created !== undefined && !Number.isNaN(Date.parse(created)))
|
|
271
|
+
return created;
|
|
272
|
+
const timing = isObj(bubble.document.timingInfo) ? num(bubble.document.timingInfo.clientStartTime) : undefined;
|
|
273
|
+
return timing !== undefined && session.createdAt > 0 && timing >= session.createdAt ? iso(timing) : undefined;
|
|
274
|
+
};
|
|
275
|
+
const stamps = bubbles.map(own);
|
|
276
|
+
const stamped = (index) => {
|
|
277
|
+
const known = stamps[index];
|
|
278
|
+
if (known !== undefined || bubbles[index]?.document.type !== 1)
|
|
279
|
+
return known;
|
|
280
|
+
return stamps.find((next, after) => after > index && bubbles[after]?.document.type !== 1 && next !== undefined);
|
|
281
|
+
};
|
|
282
|
+
// Cursor does not keep bubble times in order (14 of 9,712 lines in the 2026-11-02 copy ran backward), and the store's own timestamp
|
|
283
|
+
// invariant holds that time never steps back: a bubble earlier than the one before it takes the one before it. The session starts at
|
|
284
|
+
// its first stamped bubble (the composer's `createdAt` only when none is stamped), so a bubble with no time never precedes a stamped one.
|
|
285
|
+
let at = stamps.find((first) => first !== undefined) ?? iso(session.createdAt) ?? new Date(0).toISOString();
|
|
286
|
+
const lines = [];
|
|
287
|
+
for (const [index, bubble] of bubbles.entries()) {
|
|
288
|
+
const time = stamped(index);
|
|
289
|
+
if (time !== undefined && Date.parse(time) >= Date.parse(at))
|
|
290
|
+
at = time;
|
|
291
|
+
const info = isObj(bubble.document.modelInfo) ? bubble.document.modelInfo : {};
|
|
292
|
+
lines.push(...linesOf(bubble, base, at, str(info.modelName) ?? str(modelConfig.modelName)));
|
|
293
|
+
}
|
|
294
|
+
return lines.map((line) => JSON.stringify(line)).join('\n') + (lines.length === 0 ? '' : '\n');
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
/** Reads every composer session's header out of the global store and returns how many it rejected; a throw leaves the caller to close `db`. */
|
|
299
|
+
/** A composer id becomes `skeletons/cursor-<id>.json`, so only the id shape the store writes is accepted: no separator, not all dots. */
|
|
300
|
+
const SAFE_ID = /^(?!\.+$)[A-Za-z0-9._-]+$/;
|
|
301
|
+
const collect = async (db, root, composers, sessions) => {
|
|
302
|
+
let rejected = 0;
|
|
303
|
+
const folders = await workspaceFolders(root);
|
|
304
|
+
for (const row of db.prepare("select cast(key as text) as k, cast(value as text) as v from cursorDiskKV where key like 'composerData:%'").all()) {
|
|
305
|
+
const composer = parse(row.v);
|
|
306
|
+
if (!isObj(composer) || typeof row.k !== 'string')
|
|
307
|
+
continue;
|
|
308
|
+
const composerId = str(composer.composerId) ?? row.k.slice('composerData:'.length);
|
|
309
|
+
const headers = Array.isArray(composer.fullConversationHeadersOnly) ? composer.fullConversationHeadersOnly.length : 0;
|
|
310
|
+
const inline = Array.isArray(composer.conversation) ? composer.conversation.length : 0;
|
|
311
|
+
if (!SAFE_ID.test(composerId)) {
|
|
312
|
+
rejected += 1;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
// A composer with no message was opened and left; it has nothing to capture.
|
|
316
|
+
if (headers + inline === 0)
|
|
317
|
+
continue;
|
|
318
|
+
const createdAt = num(composer.createdAt) ?? 0;
|
|
319
|
+
composers.set(composerId, composer);
|
|
320
|
+
sessions.push({ composerId, folder: folders.get(composerId) ?? null, createdAt, lastUpdatedAt: num(composer.lastUpdatedAt) ?? createdAt, messages: headers > 0 ? headers : inline });
|
|
321
|
+
}
|
|
322
|
+
sessions.sort((a, b) => a.createdAt - b.createdAt || a.composerId.localeCompare(b.composerId));
|
|
323
|
+
return rejected;
|
|
324
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Capture, type ExtractInput } from './extract.js';
|
|
2
|
+
import type { Redactor } from './redact.js';
|
|
3
|
+
import { INTERRUPT_MARKER } from './session-shape.js';
|
|
4
|
+
import { type ErrorLineObservation, type ErrorObservation, type PromptObservation, type SessionObservation, type ShellSegment, type ToolObservation, type UserPromptObservation } from './schema.js';
|
|
5
|
+
/** The capture records of one transcript, as the log holds them. */
|
|
6
|
+
export interface SessionCapture {
|
|
7
|
+
session: SessionObservation;
|
|
8
|
+
prompts: UserPromptObservation[];
|
|
9
|
+
tools: ToolObservation[];
|
|
10
|
+
errorLines: ErrorLineObservation[];
|
|
11
|
+
}
|
|
12
|
+
export interface Derived {
|
|
13
|
+
prompts: PromptObservation[];
|
|
14
|
+
errors: ErrorObservation[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A later edit of a path within this many prompts is a redo. On the operator's store 58% of re-edits with no commit between them are one prompt
|
|
18
|
+
* apart and 82% within three; past that the tail is long and flat (88% at five, 99% at twenty), so a larger window adds unrelated later work.
|
|
19
|
+
*/
|
|
20
|
+
export declare const REDO_WINDOW = 3;
|
|
21
|
+
export { INTERRUPT_MARKER };
|
|
22
|
+
export declare const firstCopies: <T extends {
|
|
23
|
+
side: boolean;
|
|
24
|
+
toolUseId: string | null;
|
|
25
|
+
}>(records: readonly T[]) => T[];
|
|
26
|
+
/** Whether a segment runs a build, a test suite, a type check, or a linter. A rule over the captured `run`, so a change here re-derives without a transcript. */
|
|
27
|
+
export declare const isRunner: (segment: ShellSegment) => boolean;
|
|
28
|
+
/** The version-2 prompt and error records of one transcript, with the outcome signals. */
|
|
29
|
+
export declare const deriveSession: (capture: SessionCapture, redact: Redactor) => Derived;
|
|
30
|
+
/** Capture and derive in one call: the version-2 shape of one transcript, plus its capture records. */
|
|
31
|
+
export type Extract = Omit<Capture, 'prompts'> & Derived;
|
|
32
|
+
export declare const extractSession: (input: ExtractInput) => Extract;
|
|
33
|
+
export type EpisodeOutcome = 'fixed' | 'worked around' | 'abandoned';
|
|
34
|
+
export interface Episode {
|
|
35
|
+
session: string;
|
|
36
|
+
sig: string;
|
|
37
|
+
outcome: EpisodeOutcome;
|
|
38
|
+
/** The position of the resolving call, else the window's length. */
|
|
39
|
+
calls: number;
|
|
40
|
+
}
|
|
41
|
+
/** An episode ends when the same call succeeds, the session ends, or this many calls pass. */
|
|
42
|
+
export declare const RECOVERY_WINDOW = 20;
|
|
43
|
+
/** One episode per failed call of the transcript. */
|
|
44
|
+
export declare const episodesOf: (capture: SessionCapture, redact: Redactor) => Episode[];
|
|
45
|
+
export interface RecoveryRank {
|
|
46
|
+
sig: string;
|
|
47
|
+
sessions: number;
|
|
48
|
+
errors: number;
|
|
49
|
+
medianCalls: number;
|
|
50
|
+
/** Sessions affected times median recovery calls. */
|
|
51
|
+
score: number;
|
|
52
|
+
fixed: number;
|
|
53
|
+
workedAround: number;
|
|
54
|
+
abandoned: number;
|
|
55
|
+
}
|
|
56
|
+
/** Signatures seen in two or more sessions, ranked by sessions times median recovery calls (recovery_episodes.py's order). */
|
|
57
|
+
export declare const rankRecovery: (episodes: readonly Episode[], top: number) => RecoveryRank[];
|