vibe-replay 0.2.2 → 0.2.3

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/dist/index.js CHANGED
@@ -75,13 +75,13 @@ var init_utils = __esm({
75
75
  });
76
76
 
77
77
  // src/overlays.ts
78
- import { readFile as readFile14 } from "fs/promises";
79
- import { join as join13, resolve } from "path";
78
+ import { readFile as readFile16 } from "fs/promises";
79
+ import { join as join16, resolve } from "path";
80
80
  async function loadOverlays(baseDir, slug) {
81
- const dirs = [join13(baseDir, slug), resolve("./vibe-replay", slug)];
81
+ const dirs = [join16(baseDir, slug), resolve("./vibe-replay", slug)];
82
82
  for (const dir of dirs) {
83
83
  try {
84
- const raw = await readFile14(join13(dir, "overlays.json"), "utf-8");
84
+ const raw = await readFile16(join16(dir, "overlays.json"), "utf-8");
85
85
  const parsed = JSON.parse(raw);
86
86
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.overlays)) {
87
87
  return parsed;
@@ -140,9 +140,9 @@ __export(cloud_exports, {
140
140
  saveAuthTokenSync: () => saveAuthTokenSync
141
141
  });
142
142
  import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
143
- import { mkdir as mkdir3, readFile as readFile15, unlink, writeFile as writeFile3 } from "fs/promises";
144
- import { homedir as homedir12 } from "os";
145
- import { basename as basename5, dirname as dirname3, join as join14 } from "path";
143
+ import { mkdir as mkdir3, readFile as readFile17, unlink, writeFile as writeFile3 } from "fs/promises";
144
+ import { homedir as homedir14 } from "os";
145
+ import { basename as basename7, dirname as dirname3, join as join17 } from "path";
146
146
  function authOrigin(url) {
147
147
  try {
148
148
  return new URL(url).origin;
@@ -243,8 +243,8 @@ async function publishCloud(outputDir, opts) {
243
243
  if (!auth) {
244
244
  throw new Error("Not logged in. Run `vibe-replay auth login` first.");
245
245
  }
246
- const jsonPath = join14(outputDir, "replay.json");
247
- const content = await readFile15(jsonPath, "utf-8");
246
+ const jsonPath = join17(outputDir, "replay.json");
247
+ const content = await readFile17(jsonPath, "utf-8");
248
248
  const replay = JSON.parse(content);
249
249
  const sizeBytes = Buffer.byteLength(content, "utf-8");
250
250
  const MAX_SIZE = 10 * 1024 * 1024;
@@ -285,14 +285,14 @@ async function publishCloud(outputDir, opts) {
285
285
  return data;
286
286
  }
287
287
  async function publishCloudWithOverlays(outputDir, opts) {
288
- const slug = basename5(outputDir);
288
+ const slug = basename7(outputDir);
289
289
  const baseDir = dirname3(outputDir);
290
290
  const overlays = await loadOverlays(baseDir, slug);
291
291
  if (overlays.overlays.length === 0) {
292
292
  return publishCloud(outputDir, opts);
293
293
  }
294
- const replayPath = join14(outputDir, "replay.json");
295
- const originalContent = await readFile15(replayPath, "utf-8");
294
+ const replayPath = join17(outputDir, "replay.json");
295
+ const originalContent = await readFile17(replayPath, "utf-8");
296
296
  const session = JSON.parse(originalContent);
297
297
  const merged = sessionWithEffectiveContent(session, overlays);
298
298
  await writeFile3(replayPath, JSON.stringify(merged), "utf-8");
@@ -304,7 +304,7 @@ async function publishCloudWithOverlays(outputDir, opts) {
304
304
  }
305
305
  async function loadSavedCloudInfo(outputDir) {
306
306
  try {
307
- const raw = await readFile15(join14(outputDir, CLOUD_META_FILE), "utf-8");
307
+ const raw = await readFile17(join17(outputDir, CLOUD_META_FILE), "utf-8");
308
308
  const parsed = JSON.parse(raw);
309
309
  if (!parsed?.id || !parsed?.url) return void 0;
310
310
  return parsed;
@@ -313,7 +313,7 @@ async function loadSavedCloudInfo(outputDir) {
313
313
  }
314
314
  }
315
315
  async function saveCloudInfo(outputDir, info) {
316
- await writeFile3(join14(outputDir, CLOUD_META_FILE), JSON.stringify(info, null, 2), "utf-8");
316
+ await writeFile3(join17(outputDir, CLOUD_META_FILE), JSON.stringify(info, null, 2), "utf-8");
317
317
  }
318
318
  var CLOUD_META_FILE, DEFAULT_API_URL, AUTH_DIR, AUTH_FILE;
319
319
  var init_cloud = __esm({
@@ -322,8 +322,8 @@ var init_cloud = __esm({
322
322
  init_overlays();
323
323
  CLOUD_META_FILE = ".vibe-replay-cloud.json";
324
324
  DEFAULT_API_URL = "https://vibe-replay.com";
325
- AUTH_DIR = join14(homedir12(), ".config", "vibe-replay");
326
- AUTH_FILE = join14(AUTH_DIR, "auth.json");
325
+ AUTH_DIR = join17(homedir14(), ".config", "vibe-replay");
326
+ AUTH_FILE = join17(AUTH_DIR, "auth.json");
327
327
  }
328
328
  });
329
329
 
@@ -338,7 +338,7 @@ var init_src = __esm({
338
338
 
339
339
  // src/machine-id.ts
340
340
  import { execSync } from "child_process";
341
- import { createHash as createHash3 } from "crypto";
341
+ import { createHash as createHash4 } from "crypto";
342
342
  import { readFileSync as readFileSync3 } from "fs";
343
343
  import { hostname, platform } from "os";
344
344
  function getRawMachineId() {
@@ -379,7 +379,7 @@ function getRawMachineId() {
379
379
  function getMachineId() {
380
380
  if (cachedId) return cachedId;
381
381
  const raw = getRawMachineId();
382
- cachedId = createHash3("sha256").update(raw).digest("hex").slice(0, 32);
382
+ cachedId = createHash4("sha256").update(raw).digest("hex").slice(0, 32);
383
383
  return cachedId;
384
384
  }
385
385
  function getMachineName() {
@@ -406,12 +406,12 @@ __export(insights_exports, {
406
406
  scanResultToInsight: () => scanResultToInsight,
407
407
  writeInsightsStore: () => writeInsightsStore
408
408
  });
409
- import { mkdir as mkdir4, readFile as readFile17, rename, writeFile as writeFile5 } from "fs/promises";
410
- import { homedir as homedir13 } from "os";
411
- import { dirname as dirname4, join as join16 } from "path";
409
+ import { mkdir as mkdir4, readFile as readFile19, rename, writeFile as writeFile5 } from "fs/promises";
410
+ import { homedir as homedir15 } from "os";
411
+ import { dirname as dirname4, join as join19 } from "path";
412
412
  async function readInsightsStore() {
413
413
  try {
414
- const raw = await readFile17(STORE_PATH, "utf-8");
414
+ const raw = await readFile19(STORE_PATH, "utf-8");
415
415
  const parsed = JSON.parse(raw);
416
416
  return migrateInsightsStore(parsed);
417
417
  } catch {
@@ -607,8 +607,8 @@ var init_insights = __esm({
607
607
  init_machine_id();
608
608
  init_utils();
609
609
  init_version();
610
- INSIGHTS_DIR = join16(homedir13(), ".vibe-replay", "insights");
611
- STORE_PATH = join16(INSIGHTS_DIR, "store.json");
610
+ INSIGHTS_DIR = join19(homedir15(), ".vibe-replay", "insights");
611
+ STORE_PATH = join19(INSIGHTS_DIR, "store.json");
612
612
  }
613
613
  });
614
614
 
@@ -2063,11 +2063,13 @@ async function parseClaudeCodeLines(lines, options = {}) {
2063
2063
  }
2064
2064
  if (!obj.message) continue;
2065
2065
  const { role, content: msgContent, id: msgId } = obj.message;
2066
- if (obj.isApiErrorMessage && obj.timestamp) {
2066
+ if ((obj.isApiErrorMessage || obj.apiErrorStatus !== void 0) && obj.timestamp) {
2067
2067
  const text = extractMessageText(msgContent);
2068
- const errorType = inferApiErrorMessageType(text);
2068
+ const statusCode = apiErrorStatusCode(obj.apiErrorStatus);
2069
+ const errorType = errorTypeFromStatus(obj.apiErrorStatus) || inferApiErrorMessageType(text);
2069
2070
  apiErrors.push({
2070
2071
  timestamp: obj.timestamp,
2072
+ ...statusCode ? { statusCode } : {},
2071
2073
  ...errorType ? { errorType } : {}
2072
2074
  });
2073
2075
  }
@@ -2107,7 +2109,7 @@ async function parseClaudeCodeLines(lines, options = {}) {
2107
2109
  continue;
2108
2110
  }
2109
2111
  if (role === "user" && Array.isArray(msgContent)) {
2110
- const isToolSearchResponse = !!obj.sourceToolAssistantUUID;
2112
+ const isToolSearchResponse = !!obj.sourceToolAssistantUUID || !!obj.sourceToolUseID || obj.origin === "tool_result";
2111
2113
  const textParts = [];
2112
2114
  const userImages = [];
2113
2115
  for (const block of msgContent) {
@@ -2146,6 +2148,8 @@ async function parseClaudeCodeLines(lines, options = {}) {
2146
2148
  }
2147
2149
  if (role === "assistant" && msgId && Array.isArray(msgContent)) {
2148
2150
  if (!model && obj.message.model) model = obj.message.model;
2151
+ if (obj.attributionMcpServer) mcpServersUsed.add(obj.attributionMcpServer);
2152
+ if (obj.attributionSkill) skillsUsed.add(obj.attributionSkill);
2149
2153
  const usage = obj.message.usage;
2150
2154
  if (usage && msgId) {
2151
2155
  usageByMsgId.set(msgId, { ...usage, model: obj.message.model });
@@ -2194,10 +2198,10 @@ async function parseClaudeCodeLines(lines, options = {}) {
2194
2198
  const agentId = agentMapping.get(block.id);
2195
2199
  const subAgent = agentId ? subAgentData.get(agentId) : void 0;
2196
2200
  const resultTs = toolResultTimestamps.get(block.id);
2197
- let toolDurationMs;
2201
+ let toolDurationMs2;
2198
2202
  if (resultTs && prevToolEndTs) {
2199
2203
  const diff = Date.parse(resultTs) - Date.parse(prevToolEndTs);
2200
- if (diff > 0 && diff < 36e5) toolDurationMs = diff;
2204
+ if (diff > 0 && diff < 36e5) toolDurationMs2 = diff;
2201
2205
  }
2202
2206
  if (resultTs) prevToolEndTs = resultTs;
2203
2207
  return {
@@ -2208,7 +2212,7 @@ async function parseClaudeCodeLines(lines, options = {}) {
2208
2212
  // SubAgentParsed.scenes widens Scene.type to string during parsing;
2209
2213
  // the final attached shape matches SubAgent after scanSubAgent post-processing.
2210
2214
  ...subAgent ? { _subAgent: subAgent } : {},
2211
- ...toolDurationMs ? { _durationMs: toolDurationMs } : {}
2215
+ ...toolDurationMs2 ? { _durationMs: toolDurationMs2 } : {}
2212
2216
  };
2213
2217
  }
2214
2218
  return block;
@@ -2335,6 +2339,22 @@ function inferApiErrorMessageType(text) {
2335
2339
  if (lower.includes("api error")) return "api_error";
2336
2340
  return void 0;
2337
2341
  }
2342
+ function apiErrorStatusCode(status) {
2343
+ if (typeof status === "number" && Number.isFinite(status)) return status;
2344
+ if (typeof status !== "string") return void 0;
2345
+ const match = status.match(/\b(\d{3})\b/);
2346
+ return match ? Number(match[1]) : void 0;
2347
+ }
2348
+ function errorTypeFromStatus(status) {
2349
+ if (typeof status !== "string") return void 0;
2350
+ const lower = status.toLowerCase();
2351
+ if (lower.includes("rate")) return "rate_limit_error";
2352
+ if (lower.includes("overload")) return "overloaded_error";
2353
+ if (lower.includes("error")) {
2354
+ return lower.replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").replace(/_\d+$/, "");
2355
+ }
2356
+ return void 0;
2357
+ }
2338
2358
  function extractImages(block) {
2339
2359
  if (block.type !== "tool_result") return [];
2340
2360
  const { content } = block;
@@ -2433,12 +2453,12 @@ function buildTurnStats(finalTurns, usageByMsgId, turnDurations) {
2433
2453
  } else if (group.startTimestamp && group.endTimestamp) {
2434
2454
  durationMs = estimateActiveDuration([group.startTimestamp, group.endTimestamp]);
2435
2455
  }
2436
- const stat11 = { turnIndex: i };
2437
- if (turnModel) stat11.model = turnModel;
2438
- if (durationMs !== void 0) stat11.durationMs = durationMs;
2439
- if (turnTokens) stat11.tokenUsage = turnTokens;
2440
- if (maxContextTokens > 0) stat11.contextTokens = maxContextTokens;
2441
- stats.push(stat11);
2456
+ const stat12 = { turnIndex: i };
2457
+ if (turnModel) stat12.model = turnModel;
2458
+ if (durationMs !== void 0) stat12.durationMs = durationMs;
2459
+ if (turnTokens) stat12.tokenUsage = turnTokens;
2460
+ if (maxContextTokens > 0) stat12.contextTokens = maxContextTokens;
2461
+ stats.push(stat12);
2442
2462
  }
2443
2463
  return stats;
2444
2464
  }
@@ -3901,31 +3921,31 @@ function buildCodexTurnStats(turns, snapshots, taskDurations, model) {
3901
3921
  const usable = snapshots.filter((s) => s.last);
3902
3922
  return userTurns.map((turn, i) => {
3903
3923
  const usage = usable[i]?.last;
3904
- const stat11 = { turnIndex: i };
3905
- if (model) stat11.model = model;
3924
+ const stat12 = { turnIndex: i };
3925
+ if (model) stat12.model = model;
3906
3926
  if (usage) {
3907
3927
  const input = usage.input_tokens || 0;
3908
3928
  const cached = usage.cached_input_tokens || 0;
3909
- stat11.tokenUsage = {
3929
+ stat12.tokenUsage = {
3910
3930
  inputTokens: Math.max(0, input - cached),
3911
3931
  outputTokens: usage.output_tokens || 0,
3912
3932
  cacheCreationTokens: 0,
3913
3933
  cacheReadTokens: cached
3914
3934
  };
3915
- stat11.contextTokens = input;
3935
+ stat12.contextTokens = input;
3916
3936
  }
3917
3937
  const nextUser = userTurns[i + 1]?.timestamp;
3918
3938
  const taskDuration = taskDurations[i];
3919
3939
  if (typeof taskDuration === "number" && taskDuration > 0) {
3920
- stat11.durationMs = taskDuration;
3940
+ stat12.durationMs = taskDuration;
3921
3941
  } else if (turn.timestamp) {
3922
3942
  const end = nextUser || usable[i]?.timestamp;
3923
3943
  if (end) {
3924
3944
  const diff = Date.parse(end) - Date.parse(turn.timestamp);
3925
- if (diff > 0 && diff < 12 * 60 * 60 * 1e3) stat11.durationMs = diff;
3945
+ if (diff > 0 && diff < 12 * 60 * 60 * 1e3) stat12.durationMs = diff;
3926
3946
  }
3927
3947
  }
3928
- return stat11;
3948
+ return stat12;
3929
3949
  });
3930
3950
  }
3931
3951
  function pushUnique(items, value) {
@@ -3970,15 +3990,15 @@ var codexProvider = {
3970
3990
  // src/providers/cursor/discover.ts
3971
3991
  init_utils();
3972
3992
  import { readdir as readdir8, readFile as readFile12, stat as stat7 } from "fs/promises";
3973
- import { homedir as homedir10 } from "os";
3974
- import { basename as basename3, join as join11 } from "path";
3993
+ import { homedir as homedir11 } from "os";
3994
+ import { basename as basename3, join as join12 } from "path";
3975
3995
 
3976
3996
  // src/providers/cursor/sqlite-reader.ts
3977
- import { createHash } from "crypto";
3997
+ import { createHash as createHash2 } from "crypto";
3978
3998
  import { execFile } from "child_process";
3979
3999
  import { readdir as readdir6, readFile as readFile10, stat as stat5 } from "fs/promises";
3980
- import { homedir as homedir8 } from "os";
3981
- import { basename as basename2, dirname as dirname2, join as join9, posix } from "path";
4000
+ import { homedir as homedir9 } from "os";
4001
+ import { basename as basename2, dirname as dirname2, join as join10, posix } from "path";
3982
4002
  import { promisify } from "util";
3983
4003
  init_utils();
3984
4004
 
@@ -4051,16 +4071,11 @@ function looksLikeInternalPlanningBody(text) {
4051
4071
  );
4052
4072
  }
4053
4073
 
4054
- // src/providers/cursor/sqlite-reader.ts
4074
+ // src/providers/cursor/sqlite-io.ts
4075
+ import { createHash } from "crypto";
4076
+ import { homedir as homedir8 } from "os";
4077
+ import { join as join9 } from "path";
4055
4078
  var CURSOR_CHATS_DIR = join9(homedir8(), ".cursor", "chats");
4056
- var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4057
- var MIN_STORE_DB_SIZE = 8192;
4058
- var MAX_CURSOR_REQUEST_CONTEXT_ROWS = 500;
4059
- var SQLITE_CLI_MAX_BUFFER = 256 * 1024 * 1024;
4060
- var SQLITE_CLI_QUERY_TIMEOUT_MS = 12e4;
4061
- var MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS = 1e4;
4062
- var GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE = 200;
4063
- var execFileAsync = promisify(execFile);
4064
4079
  function createRetryableInit(factory) {
4065
4080
  let promise = null;
4066
4081
  return async () => {
@@ -4073,115 +4088,419 @@ function createRetryableInit(factory) {
4073
4088
  return promise;
4074
4089
  };
4075
4090
  }
4076
- var getSqlJs = createRetryableInit(async () => {
4077
- const mod = await import("sql.js");
4078
- return mod.default();
4079
- });
4080
- var cachedGlobalStateDb = null;
4081
- function closeCachedSqlJsDb() {
4082
- if (cachedGlobalStateDb?.backend !== "sqljs") return;
4091
+ function workspaceHash(absolutePath) {
4092
+ return createHash("md5").update(absolutePath).digest("hex");
4093
+ }
4094
+
4095
+ // src/providers/cursor/tool-mapping.ts
4096
+ function parseJson(raw) {
4097
+ if (typeof raw !== "string") return null;
4083
4098
  try {
4084
- cachedGlobalStateDb.db.close();
4099
+ return JSON.parse(raw);
4085
4100
  } catch {
4101
+ return null;
4086
4102
  }
4087
4103
  }
4088
- var cachedStoreDbIndex = null;
4089
- var resolvedProjectRootCache = /* @__PURE__ */ new Map();
4090
- var GLOBAL_STATE_DISCOVERY_CACHE_PREFIX = "cursor-global-state-discovery-v4";
4091
- function workspaceHash(absolutePath) {
4092
- return createHash("md5").update(absolutePath).digest("hex");
4104
+ function mapCursorToolName(name) {
4105
+ const mapping = {
4106
+ Shell: "Bash",
4107
+ shell: "Bash",
4108
+ // Cursor SDK lowercase tool name
4109
+ run_terminal_command_v2: "Bash",
4110
+ run_terminal_cmd: "Bash",
4111
+ Read: "Read",
4112
+ ReadFile: "Read",
4113
+ read: "Read",
4114
+ // Cursor SDK lowercase tool name
4115
+ read_file_v2: "Read",
4116
+ read_file: "Read",
4117
+ read_lints: "ReadLints",
4118
+ Grep: "Grep",
4119
+ ripgrep_raw_search: "Grep",
4120
+ ripgrep: "Grep",
4121
+ rg: "Grep",
4122
+ grep: "Grep",
4123
+ grep_search: "Grep",
4124
+ Glob: "Glob",
4125
+ glob_file_search: "Glob",
4126
+ file_search: "Glob",
4127
+ list_dir: "Glob",
4128
+ list_dir_v2: "Glob",
4129
+ LS: "Glob",
4130
+ StrReplace: "Edit",
4131
+ EditFile: "Edit",
4132
+ edit: "Edit",
4133
+ // Cursor SDK lowercase tool name
4134
+ edit_file_v2: "Edit",
4135
+ edit_file: "Edit",
4136
+ search_replace: "Edit",
4137
+ ApplyPatch: "Edit",
4138
+ apply_patch: "Edit",
4139
+ MultiEdit: "MultiEdit",
4140
+ multi_edit: "MultiEdit",
4141
+ Write: "Write",
4142
+ WriteFile: "Write",
4143
+ write: "Write",
4144
+ Delete: "Delete",
4145
+ delete_file: "Delete",
4146
+ Task: "Agent",
4147
+ task_v2: "Agent",
4148
+ todo_write: "TodoWrite",
4149
+ ask_question: "AskQuestion",
4150
+ semantic_search_full: "SemanticSearch",
4151
+ codebase_search: "SemanticSearch",
4152
+ web_search: "WebSearch",
4153
+ WebFetch: "WebFetch",
4154
+ web_fetch: "WebFetch",
4155
+ create_plan: "Plan"
4156
+ };
4157
+ if (mapping[name]) return mapping[name];
4158
+ if (name.startsWith("mcp-cursor-ide-browser-cursor-ide-browser-browser_")) return "Browser";
4159
+ if (name.startsWith("chrome-devtools-")) return "Browser";
4160
+ return name;
4093
4161
  }
4094
- function globalStateDbCandidates() {
4095
- const candidates = [
4096
- join9(
4097
- homedir8(),
4098
- "Library",
4099
- "Application Support",
4100
- "Cursor",
4101
- "User",
4102
- "globalStorage",
4103
- "state.vscdb"
4104
- ),
4105
- join9(homedir8(), ".config", "Cursor", "User", "globalStorage", "state.vscdb")
4106
- ];
4107
- const appData = process.env.APPDATA;
4108
- if (appData) {
4109
- candidates.push(join9(appData, "Cursor", "User", "globalStorage", "state.vscdb"));
4162
+ function parseDiffStringSnippet(diffString) {
4163
+ const oldLines = [];
4164
+ const newLines = [];
4165
+ for (const line of diffString.split("\n")) {
4166
+ if (line.startsWith("@@") || line.startsWith("*** ") || line.startsWith("---") || line.startsWith("+++")) {
4167
+ continue;
4168
+ }
4169
+ if (line.startsWith("-")) {
4170
+ oldLines.push(line.slice(1));
4171
+ } else if (line.startsWith("+")) {
4172
+ newLines.push(line.slice(1));
4173
+ } else if (line.startsWith(" ")) {
4174
+ const shared = line.slice(1);
4175
+ oldLines.push(shared);
4176
+ newLines.push(shared);
4177
+ }
4110
4178
  }
4111
- return [...new Set(candidates)];
4179
+ return { oldText: oldLines.join("\n"), newText: newLines.join("\n") };
4112
4180
  }
4113
- async function findGlobalStateDb() {
4114
- for (const candidate of globalStateDbCandidates()) {
4115
- const s = await stat5(candidate).catch(() => null);
4116
- if (s?.isFile() && s.size >= MIN_STORE_DB_SIZE) return candidate;
4181
+ function inferEditStringsFromResult(resultText) {
4182
+ if (!resultText.trim()) return {};
4183
+ const parsed = parseJson(resultText);
4184
+ if (!parsed || typeof parsed !== "object") return {};
4185
+ const out = {};
4186
+ if (typeof parsed.contentsAfterEdit === "string" && parsed.contentsAfterEdit.trim()) {
4187
+ out.new_string = parsed.contentsAfterEdit;
4117
4188
  }
4118
- return null;
4189
+ const chunks = parsed.diff && typeof parsed.diff === "object" && Array.isArray(parsed.diff.chunks) ? parsed.diff.chunks : [];
4190
+ const oldParts = [];
4191
+ const newParts = [];
4192
+ for (const chunk of chunks) {
4193
+ if (!chunk || typeof chunk !== "object") continue;
4194
+ if (typeof chunk.diffString !== "string" || !chunk.diffString.trim()) continue;
4195
+ const parsedSnippet = parseDiffStringSnippet(chunk.diffString);
4196
+ if (parsedSnippet.oldText) oldParts.push(parsedSnippet.oldText);
4197
+ if (parsedSnippet.newText) newParts.push(parsedSnippet.newText);
4198
+ }
4199
+ if (oldParts.length > 0) out.old_string = oldParts.join("\n");
4200
+ if (!out.new_string && newParts.length > 0) out.new_string = newParts.join("\n");
4201
+ return out;
4119
4202
  }
4120
- async function globalStateWalFingerprint(dbPath) {
4121
- const walStat = await stat5(`${dbPath}-wal`).catch(() => null);
4203
+ function parseApplyPatchArgs(rawPatch) {
4204
+ const fileMatch = rawPatch.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+)$/m);
4205
+ const oldLines = [];
4206
+ const newLines = [];
4207
+ for (const line of rawPatch.split("\n")) {
4208
+ if (line.startsWith("*** ") || line.startsWith("@@") || line.startsWith("---") || line.startsWith("+++")) {
4209
+ continue;
4210
+ }
4211
+ if (line.startsWith("-")) {
4212
+ oldLines.push(line.slice(1));
4213
+ } else if (line.startsWith("+")) {
4214
+ newLines.push(line.slice(1));
4215
+ } else if (line.startsWith(" ")) {
4216
+ const shared = line.slice(1);
4217
+ oldLines.push(shared);
4218
+ newLines.push(shared);
4219
+ }
4220
+ }
4122
4221
  return {
4123
- walSize: walStat?.isFile() ? walStat.size : 0,
4124
- walMtimeMs: walStat?.isFile() ? walStat.mtimeMs : 0
4222
+ ...fileMatch?.[1] ? { file_path: fileMatch[1].trim() } : {},
4223
+ old_string: oldLines.join("\n"),
4224
+ new_string: newLines.join("\n"),
4225
+ patch: rawPatch
4125
4226
  };
4126
4227
  }
4127
- function sqlString(value) {
4128
- return `'${value.replaceAll("'", "''")}'`;
4129
- }
4130
- function nextStringPrefix(prefix) {
4131
- if (!prefix) return null;
4132
- const chars = [...prefix];
4133
- const last = chars.pop();
4134
- if (!last) return null;
4135
- const codePoint = last.codePointAt(0);
4136
- if (codePoint === void 0 || codePoint >= 1114111) return null;
4137
- const next = codePoint + 1;
4138
- if (next >= 55296 && next <= 57343) return null;
4139
- return `${chars.join("")}${String.fromCodePoint(next)}`;
4140
- }
4141
- function sqlKeyPrefixRange(prefix, column = "key") {
4142
- const upperBound = nextStringPrefix(prefix);
4143
- if (!upperBound) return `${column} >= ${sqlString(prefix)}`;
4144
- return `${column} >= ${sqlString(prefix)} AND ${column} < ${sqlString(upperBound)}`;
4145
- }
4146
- function projectedCursorBubbleSelectSql() {
4147
- const value = "value";
4148
- const toolResult = `json_extract(${value}, '$.toolFormerData.result')`;
4149
- const truncatedToolResult = [
4150
- "CASE",
4151
- `WHEN json_type(${value}, '$.toolFormerData.result') = 'text'`,
4152
- `THEN substr(${toolResult}, 1, ${MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS})`,
4153
- `ELSE ${toolResult}`,
4154
- "END"
4155
- ].join(" ");
4156
- return [
4157
- "key",
4158
- `json_extract(${value}, '$.type') AS type`,
4159
- `json_extract(${value}, '$.text') AS text`,
4160
- `json_extract(${value}, '$.thinking') AS thinking`,
4161
- `json_extract(${value}, '$.createdAt') AS createdAt`,
4162
- `json_extract(${value}, '$.lastUpdatedAt') AS lastUpdatedAt`,
4163
- `json_extract(${value}, '$.timingInfo') AS timingInfo`,
4164
- `json_extract(${value}, '$.thinkingDurationMs') AS thinkingDurationMs`,
4165
- `json_extract(${value}, '$.tokenCount') AS tokenCount`,
4166
- `json_extract(${value}, '$.modelInfo') AS modelInfo`,
4167
- `json_extract(${value}, '$.modelName') AS modelName`,
4168
- `json_extract(${value}, '$.modelConfig') AS modelConfig`,
4169
- `json_extract(${value}, '$.toolFormerData.name') AS toolName`,
4170
- `json_extract(${value}, '$.toolFormerData.params') AS toolParams`,
4171
- `${truncatedToolResult} AS toolResult`,
4172
- `length(${toolResult}) AS toolResultLength`,
4173
- `json_extract(${value}, '$.toolFormerData.toolCallId') AS toolCallId`,
4174
- `json_extract(${value}, '$.pullRequests') AS pullRequests`,
4175
- `json_extract(${value}, '$.errorDetails') AS errorDetails`,
4176
- `json_extract(${value}, '$.retryAttempt') AS retryAttempt`,
4177
- `json_extract(${value}, '$.relevantFiles') AS relevantFiles`,
4178
- `json_extract(${value}, '$.recentlyViewedFiles') AS recentlyViewedFiles`
4179
- ].join(", ");
4228
+ function mapEditLikeArgs(argsObj, resultText) {
4229
+ const mapped = {
4230
+ file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? "",
4231
+ old_string: argsObj.old_string ?? argsObj.oldStr ?? "",
4232
+ new_string: argsObj.new_string ?? argsObj.newStr ?? argsObj.streamingContent ?? argsObj.content ?? ""
4233
+ };
4234
+ if (!mapped.old_string || !mapped.new_string) {
4235
+ const inferred = inferEditStringsFromResult(resultText);
4236
+ if (!mapped.old_string && inferred.old_string) mapped.old_string = inferred.old_string;
4237
+ if (!mapped.new_string && inferred.new_string) mapped.new_string = inferred.new_string;
4238
+ }
4239
+ return mapped;
4180
4240
  }
4181
- function replayableGlobalStateBubblePredicateSql(valueExpr) {
4182
- const nonEmptyText = (path) => `(json_type(${valueExpr}, ${sqlString(path)}) = 'text' AND length(trim(COALESCE(json_extract(${valueExpr}, ${sqlString(
4183
- path
4184
- )}), ''))) > 0)`;
4241
+ function mapToolArgs(toolName, args, resultText = "") {
4242
+ const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : null;
4243
+ if (toolName === "ApplyPatch" || toolName === "apply_patch") {
4244
+ const rawPatch = typeof args === "string" ? args : typeof argsObj?.patch === "string" ? argsObj.patch : typeof argsObj?.input === "string" ? argsObj.input : typeof argsObj?.content === "string" ? argsObj.content : typeof argsObj?.contents === "string" ? argsObj.contents : "";
4245
+ if (rawPatch) return parseApplyPatchArgs(rawPatch);
4246
+ if (argsObj && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
4247
+ return mapEditLikeArgs(argsObj, resultText);
4248
+ }
4249
+ return argsObj || {};
4250
+ }
4251
+ if (typeof args === "string") {
4252
+ return { raw: args };
4253
+ }
4254
+ if (!argsObj) return {};
4255
+ if (toolName === "Shell" && argsObj.command) {
4256
+ return {
4257
+ command: argsObj.command,
4258
+ ...argsObj.description ? { description: argsObj.description } : {}
4259
+ };
4260
+ }
4261
+ if (toolName === "StrReplace" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
4262
+ return mapEditLikeArgs(argsObj, resultText);
4263
+ }
4264
+ if (toolName === "EditFile" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
4265
+ return mapEditLikeArgs(argsObj, resultText);
4266
+ }
4267
+ if (toolName === "Edit" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
4268
+ return mapEditLikeArgs(argsObj, resultText);
4269
+ }
4270
+ if ((toolName === "MultiEdit" || toolName === "multi_edit") && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
4271
+ return {
4272
+ file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath,
4273
+ edits: Array.isArray(argsObj.edits) ? argsObj.edits : []
4274
+ };
4275
+ }
4276
+ if ((toolName === "Write" || toolName === "WriteFile") && (argsObj.path || argsObj.relativeWorkspacePath || argsObj.targetFile || argsObj.file_path)) {
4277
+ const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
4278
+ return {
4279
+ file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile,
4280
+ content: argsObj.contents ?? argsObj.content ?? codeValue
4281
+ };
4282
+ }
4283
+ if ((toolName === "Read" || toolName === "ReadFile") && argsObj.path) {
4284
+ return { file_path: argsObj.path };
4285
+ }
4286
+ if (toolName === "run_terminal_command_v2" && argsObj.command) {
4287
+ return {
4288
+ command: argsObj.command,
4289
+ ...argsObj.commandDescription ? { description: argsObj.commandDescription } : {},
4290
+ ...argsObj.cwd ? { cwd: argsObj.cwd } : {}
4291
+ };
4292
+ }
4293
+ if (toolName === "run_terminal_cmd" && argsObj.command) {
4294
+ return {
4295
+ command: argsObj.command,
4296
+ ...argsObj.cwd ? { cwd: argsObj.cwd } : {},
4297
+ ...argsObj.requireUserApproval !== void 0 ? { requireUserApproval: Boolean(argsObj.requireUserApproval) } : {}
4298
+ };
4299
+ }
4300
+ if (toolName === "read_file_v2" && argsObj.targetFile) {
4301
+ return { file_path: argsObj.targetFile };
4302
+ }
4303
+ if (toolName === "read_file" && argsObj.targetFile) {
4304
+ return { file_path: argsObj.targetFile };
4305
+ }
4306
+ if (toolName === "edit_file_v2" && argsObj.relativeWorkspacePath) {
4307
+ return {
4308
+ file_path: argsObj.relativeWorkspacePath,
4309
+ new_string: argsObj.streamingContent ?? ""
4310
+ };
4311
+ }
4312
+ if (toolName === "edit_file" || toolName === "search_replace") {
4313
+ return mapEditLikeArgs(argsObj, resultText);
4314
+ }
4315
+ if (toolName === "write" && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.targetFile)) {
4316
+ const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
4317
+ return {
4318
+ file_path: argsObj.relativeWorkspacePath ?? argsObj.path ?? argsObj.targetFile,
4319
+ content: argsObj.content ?? argsObj.contents ?? codeValue
4320
+ };
4321
+ }
4322
+ if ((toolName === "Delete" || toolName === "delete_file") && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.file_path || argsObj.targetFile)) {
4323
+ return {
4324
+ file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile
4325
+ };
4326
+ }
4327
+ if (toolName === "list_dir" || toolName === "list_dir_v2" || toolName === "LS") {
4328
+ return {
4329
+ path: argsObj.targetDirectory ?? argsObj.target_directory ?? ""
4330
+ };
4331
+ }
4332
+ if (toolName === "glob_file_search") {
4333
+ return {
4334
+ pattern: argsObj.globPattern ?? "",
4335
+ path: argsObj.targetDirectory ?? ""
4336
+ };
4337
+ }
4338
+ if (toolName === "file_search") {
4339
+ return {
4340
+ pattern: argsObj.pattern ?? argsObj.query ?? argsObj.searchTerm ?? "",
4341
+ path: argsObj.path ?? argsObj.targetDirectory ?? ""
4342
+ };
4343
+ }
4344
+ if (toolName === "ripgrep_raw_search") {
4345
+ return {
4346
+ pattern: argsObj.pattern ?? "",
4347
+ path: argsObj.path ?? "",
4348
+ ...argsObj.glob ? { glob: argsObj.glob } : {},
4349
+ ...argsObj.caseInsensitive !== void 0 ? { case_insensitive: Boolean(argsObj.caseInsensitive) } : {}
4350
+ };
4351
+ }
4352
+ if (toolName === "ripgrep") {
4353
+ return {
4354
+ pattern: argsObj.pattern ?? argsObj.query ?? "",
4355
+ path: argsObj.path ?? argsObj.targetDirectory ?? "",
4356
+ ...argsObj.glob ? { glob: argsObj.glob } : {}
4357
+ };
4358
+ }
4359
+ if (toolName === "web_search" && argsObj.searchTerm) {
4360
+ return { search_term: argsObj.searchTerm };
4361
+ }
4362
+ if (toolName === "codebase_search") {
4363
+ return {
4364
+ query: argsObj.query ?? "",
4365
+ path: argsObj.repositoryInfo && typeof argsObj.repositoryInfo === "object" && typeof argsObj.repositoryInfo.relativeWorkspacePath === "string" ? argsObj.repositoryInfo.relativeWorkspacePath : "",
4366
+ ...argsObj.includePattern ? { includePattern: argsObj.includePattern } : {}
4367
+ };
4368
+ }
4369
+ if (toolName === "task_v2" || toolName === "Task") {
4370
+ return {
4371
+ ...argsObj.description ? { description: argsObj.description } : {},
4372
+ ...argsObj.prompt ? { prompt: argsObj.prompt } : {},
4373
+ ...argsObj.subagentType ? { subagent_type: argsObj.subagentType } : {}
4374
+ };
4375
+ }
4376
+ if (toolName.startsWith("mcp-") && Array.isArray(argsObj.tools) && argsObj.tools.length > 0) {
4377
+ const first = argsObj.tools[0] || {};
4378
+ const parsedParameters = typeof first.parameters === "string" ? parseJson(first.parameters) ?? first.parameters : first.parameters;
4379
+ return {
4380
+ ...first.serverName ? { server: first.serverName } : {},
4381
+ ...first.name ? { tool_name: first.name } : {},
4382
+ ...parsedParameters !== void 0 ? { arguments: parsedParameters } : {}
4383
+ };
4384
+ }
4385
+ return argsObj;
4386
+ }
4387
+
4388
+ // src/providers/cursor/sqlite-reader.ts
4389
+ var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4390
+ var MIN_STORE_DB_SIZE = 8192;
4391
+ var MAX_CURSOR_REQUEST_CONTEXT_ROWS = 500;
4392
+ var SQLITE_CLI_MAX_BUFFER = 256 * 1024 * 1024;
4393
+ var SQLITE_CLI_QUERY_TIMEOUT_MS = 12e4;
4394
+ var MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS = 1e4;
4395
+ var GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE = 200;
4396
+ var CURSOR_AGENTKV_BLOB_PREFIX = "agentKv:blob:";
4397
+ var execFileAsync = promisify(execFile);
4398
+ var getSqlJs = createRetryableInit(async () => {
4399
+ const mod = await import("sql.js");
4400
+ return mod.default();
4401
+ });
4402
+ var cachedGlobalStateDb = null;
4403
+ function closeCachedSqlJsDb() {
4404
+ if (cachedGlobalStateDb?.backend !== "sqljs") return;
4405
+ try {
4406
+ cachedGlobalStateDb.db.close();
4407
+ } catch {
4408
+ }
4409
+ }
4410
+ var cachedStoreDbIndex = null;
4411
+ var resolvedProjectRootCache = /* @__PURE__ */ new Map();
4412
+ var GLOBAL_STATE_DISCOVERY_CACHE_PREFIX = "cursor-global-state-discovery-v4";
4413
+ function globalStateDbCandidates() {
4414
+ const candidates = [
4415
+ join10(
4416
+ homedir9(),
4417
+ "Library",
4418
+ "Application Support",
4419
+ "Cursor",
4420
+ "User",
4421
+ "globalStorage",
4422
+ "state.vscdb"
4423
+ ),
4424
+ join10(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb")
4425
+ ];
4426
+ const appData = process.env.APPDATA;
4427
+ if (appData) {
4428
+ candidates.push(join10(appData, "Cursor", "User", "globalStorage", "state.vscdb"));
4429
+ }
4430
+ return [...new Set(candidates)];
4431
+ }
4432
+ async function findGlobalStateDb() {
4433
+ for (const candidate of globalStateDbCandidates()) {
4434
+ const s = await stat5(candidate).catch(() => null);
4435
+ if (s?.isFile() && s.size >= MIN_STORE_DB_SIZE) return candidate;
4436
+ }
4437
+ return null;
4438
+ }
4439
+ async function globalStateWalFingerprint(dbPath) {
4440
+ const walStat = await stat5(`${dbPath}-wal`).catch(() => null);
4441
+ return {
4442
+ walSize: walStat?.isFile() ? walStat.size : 0,
4443
+ walMtimeMs: walStat?.isFile() ? walStat.mtimeMs : 0
4444
+ };
4445
+ }
4446
+ function sqlString(value) {
4447
+ return `'${value.replaceAll("'", "''")}'`;
4448
+ }
4449
+ function nextStringPrefix(prefix) {
4450
+ if (!prefix) return null;
4451
+ const chars = [...prefix];
4452
+ const last = chars.pop();
4453
+ if (!last) return null;
4454
+ const codePoint = last.codePointAt(0);
4455
+ if (codePoint === void 0 || codePoint >= 1114111) return null;
4456
+ const next = codePoint + 1;
4457
+ if (next >= 55296 && next <= 57343) return null;
4458
+ return `${chars.join("")}${String.fromCodePoint(next)}`;
4459
+ }
4460
+ function sqlKeyPrefixRange(prefix, column = "key") {
4461
+ const upperBound = nextStringPrefix(prefix);
4462
+ if (!upperBound) return `${column} >= ${sqlString(prefix)}`;
4463
+ return `${column} >= ${sqlString(prefix)} AND ${column} < ${sqlString(upperBound)}`;
4464
+ }
4465
+ function projectedCursorBubbleSelectSql() {
4466
+ const value = "value";
4467
+ const toolResult = `json_extract(${value}, '$.toolFormerData.result')`;
4468
+ const truncatedToolResult = [
4469
+ "CASE",
4470
+ `WHEN json_type(${value}, '$.toolFormerData.result') = 'text'`,
4471
+ `THEN substr(${toolResult}, 1, ${MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS})`,
4472
+ `ELSE ${toolResult}`,
4473
+ "END"
4474
+ ].join(" ");
4475
+ return [
4476
+ "key",
4477
+ `json_extract(${value}, '$.type') AS type`,
4478
+ `json_extract(${value}, '$.text') AS text`,
4479
+ `json_extract(${value}, '$.thinking') AS thinking`,
4480
+ `json_extract(${value}, '$.createdAt') AS createdAt`,
4481
+ `json_extract(${value}, '$.lastUpdatedAt') AS lastUpdatedAt`,
4482
+ `json_extract(${value}, '$.timingInfo') AS timingInfo`,
4483
+ `json_extract(${value}, '$.thinkingDurationMs') AS thinkingDurationMs`,
4484
+ `json_extract(${value}, '$.tokenCount') AS tokenCount`,
4485
+ `json_extract(${value}, '$.modelInfo') AS modelInfo`,
4486
+ `json_extract(${value}, '$.modelName') AS modelName`,
4487
+ `json_extract(${value}, '$.modelConfig') AS modelConfig`,
4488
+ `json_extract(${value}, '$.toolFormerData.name') AS toolName`,
4489
+ `json_extract(${value}, '$.toolFormerData.params') AS toolParams`,
4490
+ `${truncatedToolResult} AS toolResult`,
4491
+ `length(${toolResult}) AS toolResultLength`,
4492
+ `json_extract(${value}, '$.toolFormerData.toolCallId') AS toolCallId`,
4493
+ `json_extract(${value}, '$.pullRequests') AS pullRequests`,
4494
+ `json_extract(${value}, '$.errorDetails') AS errorDetails`,
4495
+ `json_extract(${value}, '$.retryAttempt') AS retryAttempt`,
4496
+ `json_extract(${value}, '$.relevantFiles') AS relevantFiles`,
4497
+ `json_extract(${value}, '$.recentlyViewedFiles') AS recentlyViewedFiles`
4498
+ ].join(", ");
4499
+ }
4500
+ function replayableGlobalStateBubblePredicateSql(valueExpr) {
4501
+ const nonEmptyText = (path) => `(json_type(${valueExpr}, ${sqlString(path)}) = 'text' AND length(trim(COALESCE(json_extract(${valueExpr}, ${sqlString(
4502
+ path
4503
+ )}), ''))) > 0)`;
4185
4504
  return [
4186
4505
  nonEmptyText("$.text"),
4187
4506
  nonEmptyText("$.thinking"),
@@ -4270,6 +4589,22 @@ async function queryGlobalStateRows(globalStateDb, sql) {
4270
4589
  }
4271
4590
  return sqlJsRows(globalStateDb.db, sql);
4272
4591
  }
4592
+ async function queryCursorDiskKvRowsByKeys(db, keys) {
4593
+ if (keys.length === 0) return [];
4594
+ const uniqueKeys = [...new Set(keys)];
4595
+ const rows = [];
4596
+ for (let i = 0; i < uniqueKeys.length; i += GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE) {
4597
+ const chunk = uniqueKeys.slice(i, i + GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE);
4598
+ const chunkRows = await queryGlobalStateRows(
4599
+ db,
4600
+ `SELECT key, value FROM cursorDiskKV WHERE key IN (${chunk.map(sqlString).join(",")})`
4601
+ );
4602
+ for (const row of chunkRows) {
4603
+ if ("key" in row && "value" in row) rows.push({ key: row.key, value: row.value });
4604
+ }
4605
+ }
4606
+ return rows;
4607
+ }
4273
4608
  async function queryGlobalStateTextValue(globalStateDb, key) {
4274
4609
  if (globalStateDb.backend === "sqlite-cli") {
4275
4610
  const value = await querySqliteCliText(
@@ -4343,7 +4678,7 @@ async function buildStoreDbIndex() {
4343
4678
  return index;
4344
4679
  }
4345
4680
  for (const wsHash of workspaceDirs) {
4346
- const wsDir = join9(CURSOR_CHATS_DIR, wsHash);
4681
+ const wsDir = join10(CURSOR_CHATS_DIR, wsHash);
4347
4682
  const wsStat = await stat5(wsDir).catch(() => null);
4348
4683
  if (!wsStat?.isDirectory()) continue;
4349
4684
  let sessions;
@@ -4354,7 +4689,7 @@ async function buildStoreDbIndex() {
4354
4689
  }
4355
4690
  for (const sessionId of sessions) {
4356
4691
  if (!SESSION_ID_RE.test(sessionId)) continue;
4357
- const dbPath = join9(wsDir, sessionId, "store.db");
4692
+ const dbPath = join10(wsDir, sessionId, "store.db");
4358
4693
  const dbStat = await stat5(dbPath).catch(() => null);
4359
4694
  if (!dbStat?.isFile() || dbStat.size < MIN_STORE_DB_SIZE) continue;
4360
4695
  index.set(sessionId, {
@@ -4410,7 +4745,7 @@ async function readCursorLiveDiagnostics(sessionId) {
4410
4745
  const globalStateDb = await openGlobalStateDb();
4411
4746
  if (!globalStateDb || !await hasGlobalStateSession(globalStateDb, sessionId)) return null;
4412
4747
  const rawComposer = await queryGlobalStateTextValue(globalStateDb, `composerData:${sessionId}`);
4413
- const composer = parseJson(rawComposer || "");
4748
+ const composer = parseJson2(rawComposer || "");
4414
4749
  if (!composer) return null;
4415
4750
  const headerBubbleIds = Array.isArray(composer.fullConversationHeadersOnly) ? composer.fullConversationHeadersOnly.map(
4416
4751
  (header) => header && typeof header === "object" && typeof header.bubbleId === "string" ? header.bubbleId : ""
@@ -4519,7 +4854,7 @@ function valueToString(value) {
4519
4854
  if (value == null) return "";
4520
4855
  return String(value);
4521
4856
  }
4522
- function parseJson(raw) {
4857
+ function parseJson2(raw) {
4523
4858
  if (typeof raw !== "string") return null;
4524
4859
  try {
4525
4860
  return JSON.parse(raw);
@@ -4529,7 +4864,7 @@ function parseJson(raw) {
4529
4864
  }
4530
4865
  function parseJsonColumn(value) {
4531
4866
  if (typeof value !== "string") return value;
4532
- return parseJson(value) ?? value;
4867
+ return parseJson2(value) ?? value;
4533
4868
  }
4534
4869
  function optionalJsonColumn(value) {
4535
4870
  if (value === null || value === void 0 || value === "") return void 0;
@@ -4592,6 +4927,19 @@ function toIsoTimestamp(value) {
4592
4927
  }
4593
4928
  return void 0;
4594
4929
  }
4930
+ function cursorComposerSidecarMetadata(composer) {
4931
+ const conversationCheckpointLastUpdatedAt = toIsoTimestamp(
4932
+ composer.conversationCheckpointLastUpdatedAt
4933
+ );
4934
+ const restrictAgentModeSwitching = typeof composer.restrictAgentModeSwitching === "boolean" ? composer.restrictAgentModeSwitching : void 0;
4935
+ const glassMetaParentAgent = valueToString(composer.glassMetaParentAgent);
4936
+ const metadata = {
4937
+ ...conversationCheckpointLastUpdatedAt ? { conversationCheckpointLastUpdatedAt } : {},
4938
+ ...restrictAgentModeSwitching !== void 0 ? { restrictAgentModeSwitching } : {},
4939
+ ...glassMetaParentAgent ? { glassMetaParentAgent } : {}
4940
+ };
4941
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
4942
+ }
4595
4943
  function maxIsoTimestamp(values) {
4596
4944
  let maxMs;
4597
4945
  for (const value of values) {
@@ -4697,7 +5045,7 @@ function tokenUsageFromCursorTokenCount(value) {
4697
5045
  }
4698
5046
  function hashWorkspacePaths(paths) {
4699
5047
  const normalized = [...new Set(paths.filter(Boolean))].sort().join("\n");
4700
- return createHash("sha1").update(normalized).digest("hex").slice(0, 16);
5048
+ return createHash2("sha1").update(normalized).digest("hex").slice(0, 16);
4701
5049
  }
4702
5050
  async function resolveProjectRootFromPath(rawPath) {
4703
5051
  const candidate = rawPath.replaceAll("\\\\", "/").replace(/\\n.*$/, "").replace(/["']+$/g, "").replace(/[),]+$/g, "");
@@ -4713,7 +5061,7 @@ async function resolveProjectRootFromPath(rawPath) {
4713
5061
  const dirStat = await stat5(current).catch(() => null);
4714
5062
  if (dirStat?.isDirectory()) {
4715
5063
  if (!deepestExisting) deepestExisting = current;
4716
- const gitStat = await stat5(join9(current, ".git")).catch(() => null);
5064
+ const gitStat = await stat5(join10(current, ".git")).catch(() => null);
4717
5065
  if (gitStat) return current;
4718
5066
  }
4719
5067
  const parent = dirname2(current);
@@ -4818,7 +5166,7 @@ function canUseComposerProjectHintDirectly(pathValue) {
4818
5166
  }
4819
5167
  function hasReplayableRootBlob(data) {
4820
5168
  if (!(data instanceof Uint8Array) || data.length === 0) return false;
4821
- return extractChildBlobIds(data).length > 0;
5169
+ return extractChildBlobIds(data).length > 0 || extractAgentKvBlobIds(valueToString(data)).length > 0;
4822
5170
  }
4823
5171
  function buildHashToProjectMap(decodedWorkspacePaths) {
4824
5172
  const map = /* @__PURE__ */ new Map();
@@ -4973,7 +5321,7 @@ async function discoverGlobalStateOnlySessions(knownSessionIds, decodedWorkspace
4973
5321
  const sessionId = key.startsWith("composerData:") ? key.slice("composerData:".length) : "";
4974
5322
  if (!SESSION_ID_RE.test(sessionId)) continue;
4975
5323
  const rawComposer = row.value === void 0 ? "" : valueToString(row.value);
4976
- const composer = rawComposer ? parseJson(rawComposer) : null;
5324
+ const composer = rawComposer ? parseJson2(rawComposer) : null;
4977
5325
  if (rawComposer && !composer) continue;
4978
5326
  const headerCount = composer ? countComposerConversationHeaders(composer) : toNonNegativeInt(row.headerCount);
4979
5327
  if (headerCount === 0) continue;
@@ -5003,22 +5351,99 @@ async function discoverGlobalStateOnlySessions(knownSessionIds, decodedWorkspace
5003
5351
  };
5004
5352
  discoveredSessions.push(sessionInfo);
5005
5353
  }
5006
- } catch {
5354
+ } catch {
5355
+ }
5356
+ await writeFileCache(cacheKey, {
5357
+ dbPath,
5358
+ size: globalStateDb.size,
5359
+ mtimeMs: globalStateDb.mtimeMs,
5360
+ walSize: globalStateDb.walSize,
5361
+ walMtimeMs: globalStateDb.walMtimeMs,
5362
+ decodedPathsHash,
5363
+ sessions: discoveredSessions,
5364
+ sessionIds: [...sessionIds]
5365
+ });
5366
+ return {
5367
+ sessions: finalizeGlobalStateDiscovery(discoveredSessions, knownSessionIds),
5368
+ sessionIds
5369
+ };
5370
+ }
5371
+ function extractAgentKvBlobIds(value) {
5372
+ if (typeof value !== "string" || !value.includes(CURSOR_AGENTKV_BLOB_PREFIX)) return [];
5373
+ const ids = [];
5374
+ const seen = /* @__PURE__ */ new Set();
5375
+ const re = /agentKv:blob:([a-f0-9]{64})/gi;
5376
+ let match;
5377
+ while (match = re.exec(value)) {
5378
+ const id = match[1].toLowerCase();
5379
+ if (seen.has(id)) continue;
5380
+ seen.add(id);
5381
+ ids.push(id);
5382
+ }
5383
+ return ids;
5384
+ }
5385
+ function agentKvBlobMessageToCursorMessage(message) {
5386
+ if (!message || typeof message !== "object") return void 0;
5387
+ if (!["system", "user", "assistant", "tool"].includes(message.role)) return void 0;
5388
+ const content = typeof message.content === "string" ? message.content : "";
5389
+ if (message.role === "assistant") {
5390
+ const toolMessage = message;
5391
+ if (toolMessage.toolCallId && toolMessage.toolName) {
5392
+ return {
5393
+ role: "assistant",
5394
+ content: [
5395
+ {
5396
+ type: "tool-call",
5397
+ toolCallId: toolMessage.toolCallId,
5398
+ toolName: toolMessage.toolName,
5399
+ args: toolMessage.args || {}
5400
+ }
5401
+ ]
5402
+ };
5403
+ }
5404
+ }
5405
+ if (message.role === "tool") {
5406
+ const toolMessage = message;
5407
+ return {
5408
+ role: "tool",
5409
+ content: [
5410
+ {
5411
+ type: "tool-result",
5412
+ toolCallId: toolMessage.toolCallId || "agentkv-tool-result",
5413
+ toolName: toolMessage.toolName || "CursorAgentKv",
5414
+ result: content
5415
+ }
5416
+ ]
5417
+ };
5418
+ }
5419
+ return { role: message.role, content };
5420
+ }
5421
+ function appendAgentKvBlobMessages(messages, blobRows) {
5422
+ let appended = 0;
5423
+ let pendingToolCallId = "";
5424
+ let pendingToolName = "CursorAgentKv";
5425
+ for (const row of blobRows) {
5426
+ const raw = valueToString(row.value);
5427
+ const parsed = parseJson2(raw);
5428
+ if (!parsed) continue;
5429
+ const toolCallId = parsed.toolCallId || parsed.tool_call_id || (parsed.role === "assistant" ? raw.match(/(?:toolCallId|tool_call_id)["'\s:=]+([A-Za-z0-9_-]+)/i)?.[1] : void 0);
5430
+ if (toolCallId) pendingToolCallId = toolCallId;
5431
+ const toolName = parsed.toolName || parsed.tool_name || (parsed.role === "assistant" ? raw.match(/(?:toolName|tool_name)["'\s:=]+([A-Za-z0-9_-]+)/i)?.[1] : void 0);
5432
+ if (toolName) pendingToolName = toolName;
5433
+ const message = agentKvBlobMessageToCursorMessage({
5434
+ ...parsed,
5435
+ ...pendingToolCallId && parsed.role === "assistant" && toolCallId ? { toolCallId: pendingToolCallId, toolName: pendingToolName } : {},
5436
+ ...parsed.role === "tool" ? { toolCallId: pendingToolCallId, toolName: pendingToolName } : {}
5437
+ });
5438
+ if (!message) continue;
5439
+ messages.push(message);
5440
+ appended++;
5441
+ if (parsed.role === "tool") {
5442
+ pendingToolCallId = "";
5443
+ pendingToolName = "CursorAgentKv";
5444
+ }
5007
5445
  }
5008
- await writeFileCache(cacheKey, {
5009
- dbPath,
5010
- size: globalStateDb.size,
5011
- mtimeMs: globalStateDb.mtimeMs,
5012
- walSize: globalStateDb.walSize,
5013
- walMtimeMs: globalStateDb.walMtimeMs,
5014
- decodedPathsHash,
5015
- sessions: discoveredSessions,
5016
- sessionIds: [...sessionIds]
5017
- });
5018
- return {
5019
- sessions: finalizeGlobalStateDiscovery(discoveredSessions, knownSessionIds),
5020
- sessionIds
5021
- };
5446
+ return appended;
5022
5447
  }
5023
5448
  function extractChildBlobIds(data) {
5024
5449
  const ids = [];
@@ -5078,7 +5503,8 @@ async function parseCursorStoreDb(sessionId, workspacePath = "") {
5078
5503
  if (!rootRows.length || !rootRows[0].values.length) return null;
5079
5504
  const rootData = rootRows[0].values[0][0];
5080
5505
  const childIds = extractChildBlobIds(rootData);
5081
- if (childIds.length === 0) return null;
5506
+ const agentKvBlobIds = extractAgentKvBlobIds(new TextDecoder().decode(rootData));
5507
+ if (childIds.length === 0 && agentKvBlobIds.length === 0) return null;
5082
5508
  const messages = [];
5083
5509
  const stmt = db.prepare("SELECT data FROM blobs WHERE id = ?");
5084
5510
  for (const cid of childIds) {
@@ -5097,6 +5523,24 @@ async function parseCursorStoreDb(sessionId, workspacePath = "") {
5097
5523
  }
5098
5524
  }
5099
5525
  stmt.free();
5526
+ if (agentKvBlobIds.length > 0) {
5527
+ try {
5528
+ const agentKvStmt = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key = ?");
5529
+ try {
5530
+ for (const id of agentKvBlobIds) {
5531
+ agentKvStmt.bind([`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`]);
5532
+ if (agentKvStmt.step()) {
5533
+ const row = agentKvStmt.getAsObject();
5534
+ appendAgentKvBlobMessages(messages, [{ key: row.key, value: row.value }]);
5535
+ }
5536
+ agentKvStmt.reset();
5537
+ }
5538
+ } finally {
5539
+ agentKvStmt.free();
5540
+ }
5541
+ } catch {
5542
+ }
5543
+ }
5100
5544
  return buildCursorStoreResult(sessionId, workspacePath, metaJson, messages);
5101
5545
  } finally {
5102
5546
  db.close();
@@ -5115,8 +5559,10 @@ async function parseCursorStoreDbWithSqliteCli(dbPath, sessionId, workspacePath)
5115
5559
  );
5116
5560
  const rootDataHex = typeof rootRows[0]?.dataHex === "string" ? rootRows[0].dataHex : "";
5117
5561
  if (!rootDataHex) return null;
5118
- const childIds = extractChildBlobIds(Buffer.from(rootDataHex, "hex"));
5119
- if (childIds.length === 0) return null;
5562
+ const rootData = Buffer.from(rootDataHex, "hex");
5563
+ const childIds = extractChildBlobIds(rootData);
5564
+ const agentKvBlobIds = extractAgentKvBlobIds(rootData.toString("utf-8"));
5565
+ if (childIds.length === 0 && agentKvBlobIds.length === 0) return null;
5120
5566
  const blobHexById = /* @__PURE__ */ new Map();
5121
5567
  const chunkSize = 200;
5122
5568
  for (let i = 0; i < childIds.length; i += chunkSize) {
@@ -5141,13 +5587,27 @@ async function parseCursorStoreDbWithSqliteCli(dbPath, sessionId, workspacePath)
5141
5587
  } catch {
5142
5588
  }
5143
5589
  }
5590
+ if (agentKvBlobIds.length > 0) {
5591
+ try {
5592
+ const rows = await querySqliteCli(
5593
+ dbPath,
5594
+ `SELECT key, value FROM cursorDiskKV WHERE key IN (${agentKvBlobIds.map((id) => sqlString(`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)).join(",")})`
5595
+ );
5596
+ const rowsByKey = new Map(rows.map((row) => [valueToString(row.key), row]));
5597
+ appendAgentKvBlobMessages(
5598
+ messages,
5599
+ agentKvBlobIds.map((id) => rowsByKey.get(`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)).filter((row) => Boolean(row))
5600
+ );
5601
+ } catch {
5602
+ }
5603
+ }
5144
5604
  return buildCursorStoreResult(sessionId, workspacePath, metaJson, messages);
5145
5605
  }
5146
5606
  function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
5147
5607
  const { turns, turnStats, totalDurationMs } = messagesToTurns(messages);
5148
5608
  const slug = sessionId.slice(0, 8);
5149
5609
  const title = metaJson.name || firstUserTextSnippet(turns);
5150
- const hasDurationStats = turnStats.some((stat11) => (stat11.durationMs || 0) > 0);
5610
+ const hasDurationStats = turnStats.some((stat12) => (stat12.durationMs || 0) > 0);
5151
5611
  const notes = [];
5152
5612
  if (hasDurationStats) {
5153
5613
  notes.push("Per-turn duration is estimated from Cursor tool execution metadata.");
@@ -5160,7 +5620,7 @@ function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
5160
5620
  slug,
5161
5621
  title,
5162
5622
  cwd: workspacePath,
5163
- model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((stat11) => typeof stat11.model === "string" && stat11.model)?.model,
5623
+ model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((stat12) => typeof stat12.model === "string" && stat12.model)?.model,
5164
5624
  startTime: metaJson.createdAt ? new Date(metaJson.createdAt).toISOString() : void 0,
5165
5625
  ...totalDurationMs !== void 0 ? { totalDurationMs } : {},
5166
5626
  ...turnStats.length > 0 ? { turnStats } : {},
@@ -5196,7 +5656,7 @@ function normalizeAssistantTurnText(raw, hasToolContext) {
5196
5656
  }
5197
5657
  function extractToolResultText2(value) {
5198
5658
  if (typeof value === "string") {
5199
- const parsed = parseJson(value);
5659
+ const parsed = parseJson2(value);
5200
5660
  if (!parsed) return value;
5201
5661
  return extractToolResultText2(parsed);
5202
5662
  }
@@ -5214,7 +5674,7 @@ function extractToolResultText2(value) {
5214
5674
  if (typeof obj.contents === "string" && obj.contents.trim()) return obj.contents;
5215
5675
  if (typeof obj.markdown === "string" && obj.markdown.trim()) return obj.markdown;
5216
5676
  if (typeof obj.result === "string" && obj.result.trim()) {
5217
- const nested = parseJson(obj.result);
5677
+ const nested = parseJson2(obj.result);
5218
5678
  if (nested) {
5219
5679
  const nestedText = extractToolResultText2(nested);
5220
5680
  if (nestedText.trim()) return nestedText;
@@ -5230,7 +5690,7 @@ function extractToolResultText2(value) {
5230
5690
  function hasToolError(value) {
5231
5691
  if (!value) return false;
5232
5692
  if (typeof value === "string") {
5233
- const parsed = parseJson(value);
5693
+ const parsed = parseJson2(value);
5234
5694
  if (!parsed) return false;
5235
5695
  return hasToolError(parsed);
5236
5696
  }
@@ -5252,7 +5712,7 @@ function hasToolError(value) {
5252
5712
  function extractToolExecutionTimeMs(value) {
5253
5713
  if (!value) return void 0;
5254
5714
  if (typeof value === "string") {
5255
- const parsed = parseJson(value);
5715
+ const parsed = parseJson2(value);
5256
5716
  return parsed ? extractToolExecutionTimeMs(parsed) : toPositiveMs(value);
5257
5717
  }
5258
5718
  if (Array.isArray(value)) {
@@ -5277,7 +5737,7 @@ function extractToolExecutionTimeMs(value) {
5277
5737
  function parseToolFormerBlock(bubbleId, toolFormerData) {
5278
5738
  const name = typeof toolFormerData.name === "string" ? toolFormerData.name : "";
5279
5739
  if (!name) return null;
5280
- const parsedParams = parseJson(toolFormerData.params);
5740
+ const parsedParams = parseJson2(toolFormerData.params);
5281
5741
  const paramsRaw = parsedParams ?? toolFormerData.params ?? {};
5282
5742
  const result = extractToolResultText2(toolFormerData.result);
5283
5743
  return {
@@ -5370,9 +5830,9 @@ function extractCursorApiErrors(entries) {
5370
5830
  for (const entry of entries) {
5371
5831
  const rawDetails = entry.bubble.errorDetails;
5372
5832
  if (!rawDetails) continue;
5373
- const details = typeof rawDetails === "string" ? parseJson(rawDetails) ?? { message: rawDetails } : typeof rawDetails === "object" ? rawDetails : null;
5833
+ const details = typeof rawDetails === "string" ? parseJson2(rawDetails) ?? { message: rawDetails } : typeof rawDetails === "object" ? rawDetails : null;
5374
5834
  if (!details) continue;
5375
- const nestedError = typeof details.error === "string" ? parseJson(details.error) : typeof details.error === "object" ? details.error : void 0;
5835
+ const nestedError = typeof details.error === "string" ? parseJson2(details.error) : typeof details.error === "object" ? details.error : void 0;
5376
5836
  const statusCode = toNonNegativeInt(details.statusCode) || toNonNegativeInt(details.status) || toNonNegativeInt(nestedError?.statusCode) || toNonNegativeInt(nestedError?.status) || void 0;
5377
5837
  const errorType = typeof nestedError?.error === "string" && nestedError.error || typeof nestedError?.type === "string" && nestedError.type || typeof details.type === "string" && details.type || void 0;
5378
5838
  const retryAttempt = toNonNegativeInt(details.retryAttempt) || toNonNegativeInt(entry.bubble.retryAttempt) || void 0;
@@ -5422,7 +5882,7 @@ function addUniqueContextFile(files, seen, value) {
5422
5882
  files.push(file);
5423
5883
  }
5424
5884
  function addContextFilesFromAttachedFolderResult(files, seen, value) {
5425
- const parsed = typeof value === "string" ? parseJson(value) : value && typeof value === "object" ? value : void 0;
5885
+ const parsed = typeof value === "string" ? parseJson2(value) : value && typeof value === "object" ? value : void 0;
5426
5886
  if (!parsed) return;
5427
5887
  const directory = normalizeCursorContextFile(parsed.directoryRelativeWorkspacePath) || normalizeCursorContextFile(parsed.directoryPath);
5428
5888
  if (Array.isArray(parsed.files)) {
@@ -5486,6 +5946,19 @@ function extractCursorContextSummary(entries, requestContexts) {
5486
5946
  hasCursorRules
5487
5947
  };
5488
5948
  }
5949
+ async function loadAgentKvBlobMessages(globalStateDb, agentKvBlobIds) {
5950
+ const messages = [];
5951
+ const rows = await queryCursorDiskKvRowsByKeys(
5952
+ globalStateDb,
5953
+ agentKvBlobIds.map((id) => `${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)
5954
+ );
5955
+ const rowsByKey = new Map(rows.map((row) => [valueToString(row.key), row]));
5956
+ appendAgentKvBlobMessages(
5957
+ messages,
5958
+ agentKvBlobIds.map((id) => rowsByKey.get(`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)).filter((row) => Boolean(row))
5959
+ );
5960
+ return messages;
5961
+ }
5489
5962
  function buildBubbleProjectHintData(entries) {
5490
5963
  return entries.map((entry) => {
5491
5964
  const bubble = entry.bubble;
@@ -5507,7 +5980,7 @@ async function loadCursorRequestContexts(globalStateDb, sessionId) {
5507
5980
  const contexts = [];
5508
5981
  for (const row of rows) {
5509
5982
  const raw = valueToString(row.value);
5510
- const parsed = parseJson(raw);
5983
+ const parsed = parseJson2(raw);
5511
5984
  if (parsed) contexts.push(parsed);
5512
5985
  }
5513
5986
  return contexts;
@@ -5549,13 +6022,13 @@ function mergeUniqueStrings(...groups) {
5549
6022
  }
5550
6023
  function mergeCursorParseResults(primary, enrichment) {
5551
6024
  const mergedModel = chooseMergedCursorModel(primary.model, enrichment.model);
5552
- const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((stat11) => stat11.durationMs !== void 0) === true);
6025
+ const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((stat12) => stat12.durationMs !== void 0) === true);
5553
6026
  const mergedTurnStats = mergeTurnStats(primary.turnStats, enrichment.turnStats, {
5554
6027
  preferEnrichmentDuration
5555
6028
  });
5556
6029
  const mergedTokenUsage = primary.tokenUsage || enrichment.tokenUsage;
5557
6030
  const mergedTokenUsageByModel = primary.tokenUsageByModel || enrichment.tokenUsageByModel;
5558
- const mergedTotalDurationMs = (preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || primary.totalDurationMs || (!preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || (mergedTurnStats && mergedTurnStats.length > 0 ? mergedTurnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0 : void 0);
6031
+ const mergedTotalDurationMs = (preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || primary.totalDurationMs || (!preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || (mergedTurnStats && mergedTurnStats.length > 0 ? mergedTurnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0 : void 0);
5559
6032
  const mergedDuration = mergedTotalDurationMs !== primary.totalDurationMs;
5560
6033
  const mergedTokens = !primary.tokenUsage && !!enrichment.tokenUsage || !primary.tokenUsageByModel && !!enrichment.tokenUsageByModel;
5561
6034
  const supplements = mergeUniqueStrings(
@@ -5599,11 +6072,25 @@ function mergeCursorParseResults(primary, enrichment) {
5599
6072
  }
5600
6073
  function mergeCursorSidecars(primary, enrichment) {
5601
6074
  if (!primary && !enrichment) return void 0;
5602
- const merged = {
5603
- ...primary?.requestContextCount ?? enrichment?.requestContextCount ? { requestContextCount: primary?.requestContextCount ?? enrichment?.requestContextCount } : {},
5604
- ...primary?.checkpointCount ?? enrichment?.checkpointCount ? { checkpointCount: primary?.checkpointCount ?? enrichment?.checkpointCount } : {},
5605
- ...(primary?.hasWorkspaceRules ?? enrichment?.hasWorkspaceRules) !== void 0 ? { hasWorkspaceRules: primary?.hasWorkspaceRules ?? enrichment?.hasWorkspaceRules } : {}
5606
- };
6075
+ const merged = {};
6076
+ if (primary?.requestContextCount ?? enrichment?.requestContextCount) {
6077
+ merged.requestContextCount = primary?.requestContextCount ?? enrichment?.requestContextCount;
6078
+ }
6079
+ if (primary?.checkpointCount ?? enrichment?.checkpointCount) {
6080
+ merged.checkpointCount = primary?.checkpointCount ?? enrichment?.checkpointCount;
6081
+ }
6082
+ if ((primary?.hasWorkspaceRules ?? enrichment?.hasWorkspaceRules) !== void 0) {
6083
+ merged.hasWorkspaceRules = primary?.hasWorkspaceRules ?? enrichment?.hasWorkspaceRules;
6084
+ }
6085
+ if (primary?.conversationCheckpointLastUpdatedAt || enrichment?.conversationCheckpointLastUpdatedAt) {
6086
+ merged.conversationCheckpointLastUpdatedAt = primary?.conversationCheckpointLastUpdatedAt ?? enrichment?.conversationCheckpointLastUpdatedAt;
6087
+ }
6088
+ if (primary?.restrictAgentModeSwitching !== void 0 || enrichment?.restrictAgentModeSwitching !== void 0) {
6089
+ merged.restrictAgentModeSwitching = primary?.restrictAgentModeSwitching ?? enrichment?.restrictAgentModeSwitching;
6090
+ }
6091
+ if (primary?.glassMetaParentAgent || enrichment?.glassMetaParentAgent) {
6092
+ merged.glassMetaParentAgent = primary?.glassMetaParentAgent ?? enrichment?.glassMetaParentAgent;
6093
+ }
5607
6094
  return Object.keys(merged).length > 0 ? merged : void 0;
5608
6095
  }
5609
6096
  function normalizeCursorModelName(model) {
@@ -5625,11 +6112,11 @@ function mergeTurnStats(primary, enrichment, options) {
5625
6112
  if (!primary?.length) return enrichment;
5626
6113
  if (!enrichment?.length) return primary;
5627
6114
  const maxTurnIndex = Math.max(
5628
- ...primary.map((stat11) => stat11.turnIndex),
5629
- ...enrichment.map((stat11) => stat11.turnIndex)
6115
+ ...primary.map((stat12) => stat12.turnIndex),
6116
+ ...enrichment.map((stat12) => stat12.turnIndex)
5630
6117
  );
5631
- const primaryByIndex = new Map(primary.map((stat11) => [stat11.turnIndex, stat11]));
5632
- const enrichmentByIndex = new Map(enrichment.map((stat11) => [stat11.turnIndex, stat11]));
6118
+ const primaryByIndex = new Map(primary.map((stat12) => [stat12.turnIndex, stat12]));
6119
+ const enrichmentByIndex = new Map(enrichment.map((stat12) => [stat12.turnIndex, stat12]));
5633
6120
  const merged = [];
5634
6121
  for (let turnIndex = 0; turnIndex <= maxTurnIndex; turnIndex++) {
5635
6122
  const current = primaryByIndex.get(turnIndex);
@@ -5723,17 +6210,17 @@ function applyGlobalStateWallClockDurations(entries, turnStats) {
5723
6210
  if (durationsByTurn.size === 0) {
5724
6211
  return {
5725
6212
  turnStats,
5726
- totalDurationMs: turnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0,
6213
+ totalDurationMs: turnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0,
5727
6214
  usedWallClock: false
5728
6215
  };
5729
6216
  }
5730
- const mergedTurnStats = turnStats.map((stat11) => {
5731
- const wallClockDuration = durationsByTurn.get(stat11.turnIndex);
5732
- return wallClockDuration !== void 0 ? { ...stat11, durationMs: wallClockDuration } : stat11;
6217
+ const mergedTurnStats = turnStats.map((stat12) => {
6218
+ const wallClockDuration = durationsByTurn.get(stat12.turnIndex);
6219
+ return wallClockDuration !== void 0 ? { ...stat12, durationMs: wallClockDuration } : stat12;
5733
6220
  });
5734
6221
  return {
5735
6222
  turnStats: mergedTurnStats,
5736
- totalDurationMs: mergedTurnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0,
6223
+ totalDurationMs: mergedTurnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0,
5737
6224
  usedWallClock: true
5738
6225
  };
5739
6226
  }
@@ -5779,15 +6266,15 @@ function buildGlobalStateMetrics(entries, fallbackModel, sessionTokenUsage) {
5779
6266
  current.durationMs = (current.durationMs || 0) + bubbleDurationMs;
5780
6267
  }
5781
6268
  }
5782
- for (const stat11 of turnStats) {
5783
- if (stat11.tokenUsage && !hasAnyTokens(stat11.tokenUsage)) {
5784
- delete stat11.tokenUsage;
6269
+ for (const stat12 of turnStats) {
6270
+ if (stat12.tokenUsage && !hasAnyTokens(stat12.tokenUsage)) {
6271
+ delete stat12.tokenUsage;
5785
6272
  }
5786
- if ((stat11.durationMs || 0) <= 0) {
5787
- delete stat11.durationMs;
6273
+ if ((stat12.durationMs || 0) <= 0) {
6274
+ delete stat12.durationMs;
5788
6275
  }
5789
- if ((stat11.contextTokens || 0) <= 0) {
5790
- delete stat11.contextTokens;
6276
+ if ((stat12.contextTokens || 0) <= 0) {
6277
+ delete stat12.contextTokens;
5791
6278
  }
5792
6279
  }
5793
6280
  const {
@@ -5839,10 +6326,11 @@ async function parseCursorGlobalStateDb(sessionId, globalStateDb, preferredWorks
5839
6326
  `composerData:${sessionId}`
5840
6327
  );
5841
6328
  if (!rawComposer) return null;
5842
- const composer = parseJson(rawComposer);
6329
+ const composer = parseJson2(rawComposer);
5843
6330
  if (!composer) return null;
5844
6331
  const entries = [];
5845
6332
  const bubbleEntries = [];
6333
+ const agentKvBlobIds = extractAgentKvBlobIds(rawComposer);
5846
6334
  const addBubble = (bubble) => {
5847
6335
  bubbleEntries.push({
5848
6336
  bubble,
@@ -5878,6 +6366,20 @@ async function parseCursorGlobalStateDb(sessionId, globalStateDb, preferredWorks
5878
6366
  if (item && typeof item === "object") addBubble(item);
5879
6367
  }
5880
6368
  }
6369
+ if (agentKvBlobIds.length > 0) {
6370
+ const { turns: agentKvTurns } = messagesToTurns(
6371
+ await loadAgentKvBlobMessages(resolvedGlobalStateDb, agentKvBlobIds)
6372
+ );
6373
+ for (const turn of agentKvTurns) {
6374
+ const bubble = {
6375
+ type: turn.role === "user" ? 1 : 2,
6376
+ text: turn.blocks.filter(
6377
+ (block) => block.type === "text"
6378
+ ).map((block) => block.text).join("\n")
6379
+ };
6380
+ entries.push({ turn, bubble });
6381
+ }
6382
+ }
5881
6383
  const turns = entries.map((entry) => entry.turn);
5882
6384
  if (turns.length === 0) return null;
5883
6385
  const inferredProject = preferredWorkspacePath || await inferProjectFromComposerData(
@@ -5892,6 +6394,7 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
5892
6394
  const startTime = toIsoTimestamp(composer.createdAt);
5893
6395
  const endTime = maxIsoTimestamp([
5894
6396
  composer.lastUpdatedAt,
6397
+ composer.conversationCheckpointLastUpdatedAt,
5895
6398
  ...turns.map((turn) => turn.timestamp).filter(Boolean)
5896
6399
  ]);
5897
6400
  const sessionTokenUsage = tokenUsageFromCursorTokenCount(composer.tokenCount);
@@ -5919,7 +6422,7 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
5919
6422
  }
5920
6423
  const hasDetailedTurnStats = Boolean(
5921
6424
  metrics.turnStats?.some(
5922
- (stat11) => !!stat11.durationMs || !!stat11.contextTokens || !!stat11.tokenUsage
6425
+ (stat12) => !!stat12.durationMs || !!stat12.contextTokens || !!stat12.tokenUsage
5923
6426
  )
5924
6427
  );
5925
6428
  if (!hasDetailedTurnStats) {
@@ -5933,10 +6436,21 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
5933
6436
  "Context files are inferred from Cursor relevantFiles and request-context sidecars."
5934
6437
  );
5935
6438
  }
5936
- const cursorSidecars = contextSummary.requestContextCount || checkpointCount > 0 || contextSummary.hasCursorRules ? {
6439
+ const composerSidecarMetadata = cursorComposerSidecarMetadata(composer);
6440
+ if (composerSidecarMetadata?.conversationCheckpointLastUpdatedAt) {
6441
+ notes.push("Cursor composerData reports a conversation checkpoint timestamp.");
6442
+ }
6443
+ if (composerSidecarMetadata?.restrictAgentModeSwitching !== void 0) {
6444
+ notes.push("Cursor composerData reports agent mode switching restrictions.");
6445
+ }
6446
+ if (composerSidecarMetadata?.glassMetaParentAgent) {
6447
+ notes.push("Cursor composerData links this session to a Glass parent agent.");
6448
+ }
6449
+ const cursorSidecars = contextSummary.requestContextCount || checkpointCount > 0 || contextSummary.hasCursorRules || composerSidecarMetadata ? {
5937
6450
  ...contextSummary.requestContextCount ? { requestContextCount: contextSummary.requestContextCount } : {},
5938
6451
  ...checkpointCount > 0 ? { checkpointCount } : {},
5939
- ...contextSummary.hasCursorRules ? { hasWorkspaceRules: true } : {}
6452
+ ...contextSummary.hasCursorRules ? { hasWorkspaceRules: true } : {},
6453
+ ...composerSidecarMetadata
5940
6454
  } : void 0;
5941
6455
  return {
5942
6456
  sessionId,
@@ -5959,419 +6473,136 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
5959
6473
  turns,
5960
6474
  dataSource: "global-state",
5961
6475
  dataSourceInfo: {
5962
- primary: "global-state",
5963
- sources: ["cursor/user/globalStorage/state.vscdb"],
5964
- notes
5965
- }
5966
- };
5967
- } catch {
5968
- return null;
5969
- }
5970
- }
5971
- function buildToolResultMap(messages) {
5972
- const map = /* @__PURE__ */ new Map();
5973
- for (const msg of messages) {
5974
- if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
5975
- for (const block of msg.content) {
5976
- if (block.type === "tool-result" && block.toolCallId) {
5977
- const resultText = extractToolResultText2(block.result);
5978
- const topLevelResult = msg.providerOptions?.cursor?.highLevelToolCallResult;
5979
- const combinedSource = topLevelResult || block.result;
5980
- map.set(block.toolCallId, {
5981
- result: resultText,
5982
- ...hasToolError(combinedSource) ? { isError: true } : {},
5983
- ...extractToolExecutionTimeMs(combinedSource) !== void 0 ? { executionTimeMs: extractToolExecutionTimeMs(combinedSource) } : {}
5984
- });
5985
- }
5986
- }
5987
- }
5988
- return map;
5989
- }
5990
- function messagesToTurns(messages) {
5991
- const toolResults = buildToolResultMap(messages);
5992
- const turns = [];
5993
- for (const msg of messages) {
5994
- if (msg.role === "system" || msg.role === "tool") continue;
5995
- if (msg.role === "user") {
5996
- const blocks = parseUserContent(msg.content);
5997
- if (blocks.length > 0) {
5998
- turns.push({ role: "user", blocks });
5999
- }
6000
- continue;
6001
- }
6002
- if (msg.role === "assistant") {
6003
- const blocks = parseAssistantContent(msg.content, toolResults);
6004
- if (blocks.length > 0) {
6005
- turns.push({
6006
- role: "assistant",
6007
- blocks,
6008
- model: extractModel(msg)
6009
- });
6010
- }
6011
- }
6012
- }
6013
- const turnStats = buildStoreTurnStats(turns);
6014
- const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0 : void 0;
6015
- return { turns, turnStats, totalDurationMs };
6016
- }
6017
- var CURSOR_SYSTEM_CONTEXT_RE = /^<(?:user_info|system_reminder|agent_transcripts|rules|git_status)>/;
6018
- function isSystemContextText(text) {
6019
- return CURSOR_SYSTEM_CONTEXT_RE.test(text.trim());
6020
- }
6021
- function parseUserContent(content) {
6022
- if (!content) return [];
6023
- if (typeof content === "string") {
6024
- if (isSystemContextText(content)) return [];
6025
- const cleaned = sanitizeCursorUserText(content);
6026
- if (isSystemContextText(cleaned)) return [];
6027
- return cleaned ? [{ type: "text", text: cleaned }] : [];
6028
- }
6029
- const blocks = [];
6030
- for (const b of content) {
6031
- if (b.type === "text" && b.text) {
6032
- if (isSystemContextText(b.text)) continue;
6033
- const cleaned = sanitizeCursorUserText(b.text);
6034
- if (isSystemContextText(cleaned)) continue;
6035
- if (cleaned) blocks.push({ type: "text", text: cleaned });
6036
- }
6037
- }
6038
- return blocks;
6039
- }
6040
- function parseAssistantContent(content, toolResults) {
6041
- if (!content) return [];
6042
- if (typeof content === "string") {
6043
- const cleaned = sanitizeCursorAssistantText(content, false);
6044
- return cleaned ? [{ type: "text", text: cleaned }] : [];
6045
- }
6046
- const blocks = [];
6047
- const hasToolCalls = content.some((block) => block.type === "tool-call");
6048
- for (const b of content) {
6049
- if (b.type === "reasoning" && b.text?.trim()) {
6050
- const thinking = sanitizeCursorReasoningText(b.text);
6051
- if (thinking) blocks.push({ type: "thinking", thinking });
6052
- } else if (b.type === "text" && b.text?.trim()) {
6053
- const text = sanitizeCursorAssistantText(b.text, hasToolCalls);
6054
- if (text) blocks.push({ type: "text", text });
6055
- } else if (b.type === "tool-call" && b.toolCallId && b.toolName) {
6056
- const result = toolResults.get(b.toolCallId);
6057
- const toolBlock = {
6058
- type: "tool_use",
6059
- id: b.toolCallId,
6060
- name: mapCursorToolName(b.toolName),
6061
- input: mapToolArgs(b.toolName, b.args || {}, result?.result || ""),
6062
- _result: result?.result || "",
6063
- ...result?.isError ? { _isError: true } : {},
6064
- ...result?.executionTimeMs ? { _durationMs: result.executionTimeMs } : {}
6065
- };
6066
- blocks.push(toolBlock);
6067
- }
6068
- }
6069
- return blocks;
6070
- }
6071
- function buildStoreTurnStats(turns) {
6072
- const turnStats = [];
6073
- let currentTurnIndex = -1;
6074
- for (const turn of turns) {
6075
- if (turn.role === "user") {
6076
- currentTurnIndex++;
6077
- turnStats.push({ turnIndex: currentTurnIndex });
6078
- continue;
6079
- }
6080
- if (currentTurnIndex < 0) continue;
6081
- const current = turnStats[currentTurnIndex];
6082
- if (!current.model && turn.model) current.model = turn.model;
6083
- for (const block of turn.blocks) {
6084
- if (block.type !== "tool_use") continue;
6085
- const ms = toPositiveMs(block._durationMs);
6086
- if (ms !== void 0) {
6087
- current.durationMs = (current.durationMs || 0) + ms;
6088
- }
6089
- }
6090
- }
6091
- return turnStats;
6092
- }
6093
- function mapCursorToolName(name) {
6094
- const mapping = {
6095
- Shell: "Bash",
6096
- shell: "Bash",
6097
- // Cursor SDK lowercase tool name
6098
- run_terminal_command_v2: "Bash",
6099
- run_terminal_cmd: "Bash",
6100
- Read: "Read",
6101
- ReadFile: "Read",
6102
- read: "Read",
6103
- // Cursor SDK lowercase tool name
6104
- read_file_v2: "Read",
6105
- read_file: "Read",
6106
- read_lints: "ReadLints",
6107
- Grep: "Grep",
6108
- ripgrep_raw_search: "Grep",
6109
- ripgrep: "Grep",
6110
- rg: "Grep",
6111
- grep: "Grep",
6112
- grep_search: "Grep",
6113
- Glob: "Glob",
6114
- glob_file_search: "Glob",
6115
- file_search: "Glob",
6116
- list_dir: "Glob",
6117
- list_dir_v2: "Glob",
6118
- LS: "Glob",
6119
- StrReplace: "Edit",
6120
- EditFile: "Edit",
6121
- edit: "Edit",
6122
- // Cursor SDK lowercase tool name
6123
- edit_file_v2: "Edit",
6124
- edit_file: "Edit",
6125
- search_replace: "Edit",
6126
- ApplyPatch: "Edit",
6127
- apply_patch: "Edit",
6128
- MultiEdit: "MultiEdit",
6129
- multi_edit: "MultiEdit",
6130
- Write: "Write",
6131
- WriteFile: "Write",
6132
- write: "Write",
6133
- Delete: "Delete",
6134
- delete_file: "Delete",
6135
- Task: "Agent",
6136
- task_v2: "Agent",
6137
- todo_write: "TodoWrite",
6138
- ask_question: "AskQuestion",
6139
- semantic_search_full: "SemanticSearch",
6140
- codebase_search: "SemanticSearch",
6141
- web_search: "WebSearch",
6142
- WebFetch: "WebFetch",
6143
- web_fetch: "WebFetch",
6144
- create_plan: "Plan"
6145
- };
6146
- if (mapping[name]) return mapping[name];
6147
- if (name.startsWith("mcp-cursor-ide-browser-cursor-ide-browser-browser_")) return "Browser";
6148
- if (name.startsWith("chrome-devtools-")) return "Browser";
6149
- return name;
6150
- }
6151
- function parseDiffStringSnippet(diffString) {
6152
- const oldLines = [];
6153
- const newLines = [];
6154
- for (const line of diffString.split("\n")) {
6155
- if (line.startsWith("@@") || line.startsWith("*** ") || line.startsWith("---") || line.startsWith("+++")) {
6156
- continue;
6157
- }
6158
- if (line.startsWith("-")) {
6159
- oldLines.push(line.slice(1));
6160
- } else if (line.startsWith("+")) {
6161
- newLines.push(line.slice(1));
6162
- } else if (line.startsWith(" ")) {
6163
- const shared = line.slice(1);
6164
- oldLines.push(shared);
6165
- newLines.push(shared);
6166
- }
6476
+ primary: "global-state",
6477
+ sources: ["cursor/user/globalStorage/state.vscdb"],
6478
+ notes
6479
+ }
6480
+ };
6481
+ } catch {
6482
+ return null;
6167
6483
  }
6168
- return { oldText: oldLines.join("\n"), newText: newLines.join("\n") };
6169
6484
  }
6170
- function inferEditStringsFromResult(resultText) {
6171
- if (!resultText.trim()) return {};
6172
- const parsed = parseJson(resultText);
6173
- if (!parsed || typeof parsed !== "object") return {};
6174
- const out = {};
6175
- if (typeof parsed.contentsAfterEdit === "string" && parsed.contentsAfterEdit.trim()) {
6176
- out.new_string = parsed.contentsAfterEdit;
6177
- }
6178
- const chunks = parsed.diff && typeof parsed.diff === "object" && Array.isArray(parsed.diff.chunks) ? parsed.diff.chunks : [];
6179
- const oldParts = [];
6180
- const newParts = [];
6181
- for (const chunk of chunks) {
6182
- if (!chunk || typeof chunk !== "object") continue;
6183
- if (typeof chunk.diffString !== "string" || !chunk.diffString.trim()) continue;
6184
- const parsedSnippet = parseDiffStringSnippet(chunk.diffString);
6185
- if (parsedSnippet.oldText) oldParts.push(parsedSnippet.oldText);
6186
- if (parsedSnippet.newText) newParts.push(parsedSnippet.newText);
6485
+ function buildToolResultMap(messages) {
6486
+ const map = /* @__PURE__ */ new Map();
6487
+ for (const msg of messages) {
6488
+ if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
6489
+ for (const block of msg.content) {
6490
+ if (block.type === "tool-result" && block.toolCallId) {
6491
+ const resultText = extractToolResultText2(block.result);
6492
+ const topLevelResult = msg.providerOptions?.cursor?.highLevelToolCallResult;
6493
+ const combinedSource = topLevelResult || block.result;
6494
+ map.set(block.toolCallId, {
6495
+ result: resultText,
6496
+ ...hasToolError(combinedSource) ? { isError: true } : {},
6497
+ ...extractToolExecutionTimeMs(combinedSource) !== void 0 ? { executionTimeMs: extractToolExecutionTimeMs(combinedSource) } : {}
6498
+ });
6499
+ }
6500
+ }
6187
6501
  }
6188
- if (oldParts.length > 0) out.old_string = oldParts.join("\n");
6189
- if (!out.new_string && newParts.length > 0) out.new_string = newParts.join("\n");
6190
- return out;
6502
+ return map;
6191
6503
  }
6192
- function parseApplyPatchArgs(rawPatch) {
6193
- const fileMatch = rawPatch.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+)$/m);
6194
- const oldLines = [];
6195
- const newLines = [];
6196
- for (const line of rawPatch.split("\n")) {
6197
- if (line.startsWith("*** ") || line.startsWith("@@") || line.startsWith("---") || line.startsWith("+++")) {
6504
+ function messagesToTurns(messages) {
6505
+ const toolResults = buildToolResultMap(messages);
6506
+ const turns = [];
6507
+ for (const msg of messages) {
6508
+ if (msg.role === "system" || msg.role === "tool") continue;
6509
+ if (msg.role === "user") {
6510
+ const blocks = parseUserContent(msg.content);
6511
+ if (blocks.length > 0) {
6512
+ turns.push({ role: "user", blocks });
6513
+ }
6198
6514
  continue;
6199
6515
  }
6200
- if (line.startsWith("-")) {
6201
- oldLines.push(line.slice(1));
6202
- } else if (line.startsWith("+")) {
6203
- newLines.push(line.slice(1));
6204
- } else if (line.startsWith(" ")) {
6205
- const shared = line.slice(1);
6206
- oldLines.push(shared);
6207
- newLines.push(shared);
6516
+ if (msg.role === "assistant") {
6517
+ const blocks = parseAssistantContent(msg.content, toolResults);
6518
+ if (blocks.length > 0) {
6519
+ turns.push({
6520
+ role: "assistant",
6521
+ blocks,
6522
+ model: extractModel(msg)
6523
+ });
6524
+ }
6208
6525
  }
6209
6526
  }
6210
- return {
6211
- ...fileMatch?.[1] ? { file_path: fileMatch[1].trim() } : {},
6212
- old_string: oldLines.join("\n"),
6213
- new_string: newLines.join("\n"),
6214
- patch: rawPatch
6215
- };
6527
+ const turnStats = buildStoreTurnStats(turns);
6528
+ const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0 : void 0;
6529
+ return { turns, turnStats, totalDurationMs };
6216
6530
  }
6217
- function mapEditLikeArgs(argsObj, resultText) {
6218
- const mapped = {
6219
- file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? "",
6220
- old_string: argsObj.old_string ?? argsObj.oldStr ?? "",
6221
- new_string: argsObj.new_string ?? argsObj.newStr ?? argsObj.streamingContent ?? argsObj.content ?? ""
6222
- };
6223
- if (!mapped.old_string || !mapped.new_string) {
6224
- const inferred = inferEditStringsFromResult(resultText);
6225
- if (!mapped.old_string && inferred.old_string) mapped.old_string = inferred.old_string;
6226
- if (!mapped.new_string && inferred.new_string) mapped.new_string = inferred.new_string;
6227
- }
6228
- return mapped;
6531
+ var CURSOR_SYSTEM_CONTEXT_RE = /^<(?:user_info|system_reminder|agent_transcripts|rules|git_status)>/;
6532
+ function isSystemContextText(text) {
6533
+ return CURSOR_SYSTEM_CONTEXT_RE.test(text.trim());
6229
6534
  }
6230
- function mapToolArgs(toolName, args, resultText = "") {
6231
- const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : null;
6232
- if (toolName === "ApplyPatch" || toolName === "apply_patch") {
6233
- const rawPatch = typeof args === "string" ? args : typeof argsObj?.patch === "string" ? argsObj.patch : typeof argsObj?.input === "string" ? argsObj.input : typeof argsObj?.content === "string" ? argsObj.content : typeof argsObj?.contents === "string" ? argsObj.contents : "";
6234
- if (rawPatch) return parseApplyPatchArgs(rawPatch);
6235
- if (argsObj && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
6236
- return mapEditLikeArgs(argsObj, resultText);
6237
- }
6238
- return argsObj || {};
6239
- }
6240
- if (typeof args === "string") {
6241
- return { raw: args };
6242
- }
6243
- if (!argsObj) return {};
6244
- if (toolName === "Shell" && argsObj.command) {
6245
- return {
6246
- command: argsObj.command,
6247
- ...argsObj.description ? { description: argsObj.description } : {}
6248
- };
6249
- }
6250
- if (toolName === "StrReplace" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
6251
- return mapEditLikeArgs(argsObj, resultText);
6252
- }
6253
- if (toolName === "EditFile" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
6254
- return mapEditLikeArgs(argsObj, resultText);
6255
- }
6256
- if (toolName === "Edit" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
6257
- return mapEditLikeArgs(argsObj, resultText);
6258
- }
6259
- if ((toolName === "MultiEdit" || toolName === "multi_edit") && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
6260
- return {
6261
- file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath,
6262
- edits: Array.isArray(argsObj.edits) ? argsObj.edits : []
6263
- };
6264
- }
6265
- if ((toolName === "Write" || toolName === "WriteFile") && (argsObj.path || argsObj.relativeWorkspacePath || argsObj.targetFile || argsObj.file_path)) {
6266
- const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
6267
- return {
6268
- file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile,
6269
- content: argsObj.contents ?? argsObj.content ?? codeValue
6270
- };
6271
- }
6272
- if ((toolName === "Read" || toolName === "ReadFile") && argsObj.path) {
6273
- return { file_path: argsObj.path };
6274
- }
6275
- if (toolName === "run_terminal_command_v2" && argsObj.command) {
6276
- return {
6277
- command: argsObj.command,
6278
- ...argsObj.commandDescription ? { description: argsObj.commandDescription } : {},
6279
- ...argsObj.cwd ? { cwd: argsObj.cwd } : {}
6280
- };
6281
- }
6282
- if (toolName === "run_terminal_cmd" && argsObj.command) {
6283
- return {
6284
- command: argsObj.command,
6285
- ...argsObj.cwd ? { cwd: argsObj.cwd } : {},
6286
- ...argsObj.requireUserApproval !== void 0 ? { requireUserApproval: Boolean(argsObj.requireUserApproval) } : {}
6287
- };
6288
- }
6289
- if (toolName === "read_file_v2" && argsObj.targetFile) {
6290
- return { file_path: argsObj.targetFile };
6291
- }
6292
- if (toolName === "read_file" && argsObj.targetFile) {
6293
- return { file_path: argsObj.targetFile };
6294
- }
6295
- if (toolName === "edit_file_v2" && argsObj.relativeWorkspacePath) {
6296
- return {
6297
- file_path: argsObj.relativeWorkspacePath,
6298
- new_string: argsObj.streamingContent ?? ""
6299
- };
6300
- }
6301
- if (toolName === "edit_file" || toolName === "search_replace") {
6302
- return mapEditLikeArgs(argsObj, resultText);
6303
- }
6304
- if (toolName === "write" && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.targetFile)) {
6305
- const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
6306
- return {
6307
- file_path: argsObj.relativeWorkspacePath ?? argsObj.path ?? argsObj.targetFile,
6308
- content: argsObj.content ?? argsObj.contents ?? codeValue
6309
- };
6310
- }
6311
- if ((toolName === "Delete" || toolName === "delete_file") && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.file_path || argsObj.targetFile)) {
6312
- return {
6313
- file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile
6314
- };
6315
- }
6316
- if (toolName === "list_dir" || toolName === "list_dir_v2" || toolName === "LS") {
6317
- return {
6318
- path: argsObj.targetDirectory ?? argsObj.target_directory ?? ""
6319
- };
6320
- }
6321
- if (toolName === "glob_file_search") {
6322
- return {
6323
- pattern: argsObj.globPattern ?? "",
6324
- path: argsObj.targetDirectory ?? ""
6325
- };
6326
- }
6327
- if (toolName === "file_search") {
6328
- return {
6329
- pattern: argsObj.pattern ?? argsObj.query ?? argsObj.searchTerm ?? "",
6330
- path: argsObj.path ?? argsObj.targetDirectory ?? ""
6331
- };
6332
- }
6333
- if (toolName === "ripgrep_raw_search") {
6334
- return {
6335
- pattern: argsObj.pattern ?? "",
6336
- path: argsObj.path ?? "",
6337
- ...argsObj.glob ? { glob: argsObj.glob } : {},
6338
- ...argsObj.caseInsensitive !== void 0 ? { case_insensitive: Boolean(argsObj.caseInsensitive) } : {}
6339
- };
6340
- }
6341
- if (toolName === "ripgrep") {
6342
- return {
6343
- pattern: argsObj.pattern ?? argsObj.query ?? "",
6344
- path: argsObj.path ?? argsObj.targetDirectory ?? "",
6345
- ...argsObj.glob ? { glob: argsObj.glob } : {}
6346
- };
6535
+ function parseUserContent(content) {
6536
+ if (!content) return [];
6537
+ if (typeof content === "string") {
6538
+ if (isSystemContextText(content)) return [];
6539
+ const cleaned = sanitizeCursorUserText(content);
6540
+ if (isSystemContextText(cleaned)) return [];
6541
+ return cleaned ? [{ type: "text", text: cleaned }] : [];
6347
6542
  }
6348
- if (toolName === "web_search" && argsObj.searchTerm) {
6349
- return { search_term: argsObj.searchTerm };
6543
+ const blocks = [];
6544
+ for (const b of content) {
6545
+ if (b.type === "text" && b.text) {
6546
+ if (isSystemContextText(b.text)) continue;
6547
+ const cleaned = sanitizeCursorUserText(b.text);
6548
+ if (isSystemContextText(cleaned)) continue;
6549
+ if (cleaned) blocks.push({ type: "text", text: cleaned });
6550
+ }
6350
6551
  }
6351
- if (toolName === "codebase_search") {
6352
- return {
6353
- query: argsObj.query ?? "",
6354
- path: argsObj.repositoryInfo && typeof argsObj.repositoryInfo === "object" && typeof argsObj.repositoryInfo.relativeWorkspacePath === "string" ? argsObj.repositoryInfo.relativeWorkspacePath : "",
6355
- ...argsObj.includePattern ? { includePattern: argsObj.includePattern } : {}
6356
- };
6552
+ return blocks;
6553
+ }
6554
+ function parseAssistantContent(content, toolResults) {
6555
+ if (!content) return [];
6556
+ if (typeof content === "string") {
6557
+ const cleaned = sanitizeCursorAssistantText(content, false);
6558
+ return cleaned ? [{ type: "text", text: cleaned }] : [];
6357
6559
  }
6358
- if (toolName === "task_v2" || toolName === "Task") {
6359
- return {
6360
- ...argsObj.description ? { description: argsObj.description } : {},
6361
- ...argsObj.prompt ? { prompt: argsObj.prompt } : {},
6362
- ...argsObj.subagentType ? { subagent_type: argsObj.subagentType } : {}
6363
- };
6560
+ const blocks = [];
6561
+ const hasToolCalls = content.some((block) => block.type === "tool-call");
6562
+ for (const b of content) {
6563
+ if (b.type === "reasoning" && b.text?.trim()) {
6564
+ const thinking = sanitizeCursorReasoningText(b.text);
6565
+ if (thinking) blocks.push({ type: "thinking", thinking });
6566
+ } else if (b.type === "text" && b.text?.trim()) {
6567
+ const text = sanitizeCursorAssistantText(b.text, hasToolCalls);
6568
+ if (text) blocks.push({ type: "text", text });
6569
+ } else if (b.type === "tool-call" && b.toolCallId && b.toolName) {
6570
+ const result = toolResults.get(b.toolCallId);
6571
+ const toolBlock = {
6572
+ type: "tool_use",
6573
+ id: b.toolCallId,
6574
+ name: mapCursorToolName(b.toolName),
6575
+ input: mapToolArgs(b.toolName, b.args || {}, result?.result || ""),
6576
+ _result: result?.result || "",
6577
+ ...result?.isError ? { _isError: true } : {},
6578
+ ...result?.executionTimeMs ? { _durationMs: result.executionTimeMs } : {}
6579
+ };
6580
+ blocks.push(toolBlock);
6581
+ }
6364
6582
  }
6365
- if (toolName.startsWith("mcp-") && Array.isArray(argsObj.tools) && argsObj.tools.length > 0) {
6366
- const first = argsObj.tools[0] || {};
6367
- const parsedParameters = typeof first.parameters === "string" ? parseJson(first.parameters) ?? first.parameters : first.parameters;
6368
- return {
6369
- ...first.serverName ? { server: first.serverName } : {},
6370
- ...first.name ? { tool_name: first.name } : {},
6371
- ...parsedParameters !== void 0 ? { arguments: parsedParameters } : {}
6372
- };
6583
+ return blocks;
6584
+ }
6585
+ function buildStoreTurnStats(turns) {
6586
+ const turnStats = [];
6587
+ let currentTurnIndex = -1;
6588
+ for (const turn of turns) {
6589
+ if (turn.role === "user") {
6590
+ currentTurnIndex++;
6591
+ turnStats.push({ turnIndex: currentTurnIndex });
6592
+ continue;
6593
+ }
6594
+ if (currentTurnIndex < 0) continue;
6595
+ const current = turnStats[currentTurnIndex];
6596
+ if (!current.model && turn.model) current.model = turn.model;
6597
+ for (const block of turn.blocks) {
6598
+ if (block.type !== "tool_use") continue;
6599
+ const ms = toPositiveMs(block._durationMs);
6600
+ if (ms !== void 0) {
6601
+ current.durationMs = (current.durationMs || 0) + ms;
6602
+ }
6603
+ }
6373
6604
  }
6374
- return argsObj;
6605
+ return turnStats;
6375
6606
  }
6376
6607
  function extractModel(msg) {
6377
6608
  if (!Array.isArray(msg.content)) return void 0;
@@ -6385,10 +6616,10 @@ function extractModel(msg) {
6385
6616
  // src/providers/cursor/sdk-reader.ts
6386
6617
  import { execFile as execFile2 } from "child_process";
6387
6618
  import { readdir as readdir7, readFile as readFile11, stat as stat6 } from "fs/promises";
6388
- import { homedir as homedir9 } from "os";
6389
- import { join as join10 } from "path";
6619
+ import { homedir as homedir10 } from "os";
6620
+ import { join as join11 } from "path";
6390
6621
  import { promisify as promisify2 } from "util";
6391
- var CURSOR_PROJECTS_ROOT = join10(homedir9(), ".cursor", "projects");
6622
+ var CURSOR_PROJECTS_ROOT = join11(homedir10(), ".cursor", "projects");
6392
6623
  var SDK_STORE_SUBDIR = "sdk-agent-store";
6393
6624
  var SDK_INDEX_DB_BASENAME = "index.db";
6394
6625
  var MIN_INDEX_DB_SIZE = 1024;
@@ -6742,12 +6973,12 @@ async function listSdkIndexDbPaths(projectsRoot) {
6742
6973
  const perProjectPaths = await Promise.all(
6743
6974
  projects.map(async (project) => {
6744
6975
  const out = [];
6745
- const sdkRoot = join10(projectsRoot, project, SDK_STORE_SUBDIR);
6976
+ const sdkRoot = join11(projectsRoot, project, SDK_STORE_SUBDIR);
6746
6977
  const sdkStat = await stat6(sdkRoot).catch(() => null);
6747
6978
  if (!sdkStat?.isDirectory()) return out;
6748
6979
  const projectHashes = await readdir7(sdkRoot).catch(() => []);
6749
6980
  for (const hash of projectHashes) {
6750
- const dbPath = join10(sdkRoot, hash, SDK_INDEX_DB_BASENAME);
6981
+ const dbPath = join11(sdkRoot, hash, SDK_INDEX_DB_BASENAME);
6751
6982
  const dbStat = await stat6(dbPath).catch(() => null);
6752
6983
  if (!dbStat?.isFile() || dbStat.size < MIN_INDEX_DB_SIZE) continue;
6753
6984
  out.push(dbPath);
@@ -6782,7 +7013,7 @@ function safeJsonStringify(value) {
6782
7013
  }
6783
7014
 
6784
7015
  // src/providers/cursor/discover.ts
6785
- var CURSOR_DIR = join11(homedir10(), ".cursor", "projects");
7016
+ var CURSOR_DIR = join12(homedir11(), ".cursor", "projects");
6786
7017
  var PROJECT_DISCOVERY_CONCURRENCY = 6;
6787
7018
  var TRANSCRIPT_INFO_CONCURRENCY = 6;
6788
7019
  var ENTRY_STAT_CONCURRENCY = 32;
@@ -6821,14 +7052,14 @@ async function discoverCursorSessions() {
6821
7052
  return sessions;
6822
7053
  }
6823
7054
  async function discoverProjectSessions(projDir) {
6824
- const transcriptsDir = join11(CURSOR_DIR, projDir, "agent-transcripts");
7055
+ const transcriptsDir = join12(CURSOR_DIR, projDir, "agent-transcripts");
6825
7056
  const dirStat = await stat7(transcriptsDir).catch(() => null);
6826
7057
  if (!dirStat?.isDirectory()) return [];
6827
7058
  const project = await decodeProjectDir2(projDir);
6828
7059
  const transcriptEntries = await collectTranscriptEntries(transcriptsDir);
6829
7060
  if (transcriptEntries.length === 0) return [];
6830
7061
  transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
6831
- const toolEntries = await collectToolEntries(join11(CURSOR_DIR, projDir, "agent-tools"));
7062
+ const toolEntries = await collectToolEntries(join12(CURSOR_DIR, projDir, "agent-tools"));
6832
7063
  toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
6833
7064
  const jobs = [];
6834
7065
  let toolStart = 0;
@@ -6927,7 +7158,7 @@ async function resolveEncodedProjectParts(parts, idx, current) {
6927
7158
  );
6928
7159
  for (let end = idx + 1; end <= parts.length; end++) {
6929
7160
  const candidate = parts.slice(idx, end).join("-");
6930
- const candidatePath = join11(current, candidate);
7161
+ const candidatePath = join12(current, candidate);
6931
7162
  if (!dirNames.has(candidate)) {
6932
7163
  if (process.platform !== "win32") continue;
6933
7164
  const candidateStat = await stat7(candidatePath).catch(() => null);
@@ -7017,7 +7248,7 @@ async function collectTranscriptEntries(transcriptsDir) {
7017
7248
  return [];
7018
7249
  }
7019
7250
  const transcripts = await mapLimit(entries, ENTRY_STAT_CONCURRENCY, async (entry) => {
7020
- const entryPath = join11(transcriptsDir, entry);
7251
+ const entryPath = join12(transcriptsDir, entry);
7021
7252
  const entryStat = await stat7(entryPath).catch(() => null);
7022
7253
  if (!entryStat) return null;
7023
7254
  if (entry.endsWith(".jsonl") && entryStat.isFile()) {
@@ -7028,7 +7259,7 @@ async function collectTranscriptEntries(transcriptsDir) {
7028
7259
  };
7029
7260
  }
7030
7261
  if (!entryStat.isDirectory()) return null;
7031
- const innerPath = join11(entryPath, `${entry}.jsonl`);
7262
+ const innerPath = join12(entryPath, `${entry}.jsonl`);
7032
7263
  const innerStat = await stat7(innerPath).catch(() => null);
7033
7264
  if (!innerStat?.isFile()) return null;
7034
7265
  return {
@@ -7050,7 +7281,7 @@ async function collectToolEntries(toolDir) {
7050
7281
  }
7051
7282
  const toolFiles = entries.filter((entry) => entry.endsWith(".txt"));
7052
7283
  const tools = await mapLimit(toolFiles, ENTRY_STAT_CONCURRENCY, async (entry) => {
7053
- const entryPath = join11(toolDir, entry);
7284
+ const entryPath = join12(toolDir, entry);
7054
7285
  const entryStat = await stat7(entryPath).catch(() => null);
7055
7286
  if (!entryStat?.isFile()) return null;
7056
7287
  return { path: entryPath, mtimeMs: entryStat.mtimeMs };
@@ -7076,8 +7307,8 @@ async function mapLimit(items, concurrency, worker) {
7076
7307
 
7077
7308
  // src/providers/cursor/parser.ts
7078
7309
  import { readdir as readdir9, readFile as readFile13, stat as stat8 } from "fs/promises";
7079
- import { homedir as homedir11 } from "os";
7080
- import { basename as basename4, extname as extname2, join as join12 } from "path";
7310
+ import { homedir as homedir12 } from "os";
7311
+ import { basename as basename4, extname as extname2, join as join13 } from "path";
7081
7312
  var CURSOR_UUID_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7082
7313
  function toErrorMessage(err) {
7083
7314
  if (err instanceof Error && err.message) return err.message;
@@ -7433,7 +7664,7 @@ function normalizeImagePlaceholderLines(text) {
7433
7664
  }
7434
7665
  function resolveImagePath(pathValue) {
7435
7666
  if (pathValue.startsWith("~/") || pathValue.startsWith("~\\")) {
7436
- return join12(homedir11(), pathValue.slice(2));
7667
+ return join13(homedir12(), pathValue.slice(2));
7437
7668
  }
7438
7669
  return pathValue;
7439
7670
  }
@@ -7596,203 +7827,840 @@ function mergeJsonlSupplementsIntoCursorTurns(primaryTurns, jsonlTurns) {
7596
7827
  const withThinking = mergeJsonlThinkingIntoCursorTurns(primaryTurns, jsonlTurns);
7597
7828
  return mergeJsonlUserImagesIntoCursorTurns(withThinking, jsonlTurns);
7598
7829
  }
7599
- async function sortByMtime(paths) {
7600
- const entries = [];
7601
- for (const path of paths) {
7602
- const st = await stat8(path).catch(() => null);
7603
- if (!st?.isFile()) continue;
7604
- entries.push({ path, mtimeMs: st.mtimeMs });
7830
+ async function sortByMtime(paths) {
7831
+ const entries = [];
7832
+ for (const path of paths) {
7833
+ const st = await stat8(path).catch(() => null);
7834
+ if (!st?.isFile()) continue;
7835
+ entries.push({ path, mtimeMs: st.mtimeMs });
7836
+ }
7837
+ entries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7838
+ return entries.map((e) => e.path);
7839
+ }
7840
+ async function inferToolPaths(transcriptPaths) {
7841
+ const toolPaths = [];
7842
+ const seen = /* @__PURE__ */ new Set();
7843
+ const projects = /* @__PURE__ */ new Map();
7844
+ for (const transcriptPath of transcriptPaths) {
7845
+ const projectRoot = getCursorProjectRoot(transcriptPath);
7846
+ if (!projectRoot) continue;
7847
+ if (!projects.has(projectRoot)) projects.set(projectRoot, /* @__PURE__ */ new Set());
7848
+ projects.get(projectRoot)?.add(transcriptPath);
7849
+ }
7850
+ for (const [projectRoot, selectedPaths] of projects.entries()) {
7851
+ const transcriptsDir = join13(projectRoot, "agent-transcripts");
7852
+ const toolDir = join13(projectRoot, "agent-tools");
7853
+ const transcriptEntries = await collectTranscriptEntries2(transcriptsDir);
7854
+ const toolEntries = await collectToolEntries2(toolDir);
7855
+ if (transcriptEntries.length === 0 || toolEntries.length === 0) continue;
7856
+ transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7857
+ toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7858
+ for (let i = 0; i < transcriptEntries.length; i++) {
7859
+ const transcript = transcriptEntries[i];
7860
+ if (!selectedPaths.has(transcript.path)) continue;
7861
+ const prevMtimeMs = i === 0 ? Number.NEGATIVE_INFINITY : transcriptEntries[i - 1].mtimeMs;
7862
+ for (const tool of toolEntries) {
7863
+ if (tool.mtimeMs <= prevMtimeMs || tool.mtimeMs > transcript.mtimeMs) continue;
7864
+ if (seen.has(tool.path)) continue;
7865
+ seen.add(tool.path);
7866
+ toolPaths.push(tool.path);
7867
+ }
7868
+ }
7869
+ }
7870
+ return sortByMtime(toolPaths);
7871
+ }
7872
+ async function collectTranscriptEntries2(transcriptsDir) {
7873
+ let entries;
7874
+ try {
7875
+ entries = await readdir9(transcriptsDir);
7876
+ } catch {
7877
+ return [];
7878
+ }
7879
+ const transcripts = [];
7880
+ for (const entry of entries) {
7881
+ const entryPath = join13(transcriptsDir, entry);
7882
+ const st = await stat8(entryPath).catch(() => null);
7883
+ if (!st) continue;
7884
+ if (entry.endsWith(".jsonl") && st.isFile()) {
7885
+ transcripts.push({ path: entryPath, mtimeMs: st.mtimeMs });
7886
+ continue;
7887
+ }
7888
+ if (!st.isDirectory()) continue;
7889
+ const nested = join13(entryPath, `${entry}.jsonl`);
7890
+ const nestedStat = await stat8(nested).catch(() => null);
7891
+ if (!nestedStat?.isFile()) continue;
7892
+ transcripts.push({ path: nested, mtimeMs: nestedStat.mtimeMs });
7893
+ }
7894
+ return transcripts;
7895
+ }
7896
+ async function collectToolEntries2(toolDir) {
7897
+ let entries;
7898
+ try {
7899
+ entries = await readdir9(toolDir);
7900
+ } catch {
7901
+ return [];
7902
+ }
7903
+ const tools = [];
7904
+ for (const entry of entries) {
7905
+ if (!entry.endsWith(".txt")) continue;
7906
+ const entryPath = join13(toolDir, entry);
7907
+ const st = await stat8(entryPath).catch(() => null);
7908
+ if (!st?.isFile()) continue;
7909
+ tools.push({ path: entryPath, mtimeMs: st.mtimeMs });
7910
+ }
7911
+ return tools;
7912
+ }
7913
+ function getCursorProjectRoot(transcriptPath) {
7914
+ const parts = transcriptPath.split(/[/\\]agent-transcripts[/\\]/);
7915
+ if (parts.length < 2) return null;
7916
+ return parts[0];
7917
+ }
7918
+ async function loadToolEvents(toolPaths) {
7919
+ const events = [];
7920
+ for (const path of toolPaths) {
7921
+ const content = await readFile13(path, "utf-8").catch(() => "");
7922
+ const result = content.trim();
7923
+ if (!result) continue;
7924
+ const st = await stat8(path).catch(() => null);
7925
+ const id = basename4(path, extname2(path));
7926
+ events.push({
7927
+ id,
7928
+ name: inferToolName(result),
7929
+ input: { source: basename4(path) },
7930
+ result,
7931
+ timestamp: st ? new Date(st.mtimeMs).toISOString() : void 0
7932
+ });
7933
+ }
7934
+ return events;
7935
+ }
7936
+ function inferToolName(result) {
7937
+ const firstLine = result.split("\n", 1)[0] || "";
7938
+ if (result.startsWith("diff --git")) return "Diff";
7939
+ if (firstLine.startsWith("http://") || firstLine.startsWith("https://")) return "WebFetch";
7940
+ if (firstLine.startsWith("{") || firstLine.startsWith("[")) return "API";
7941
+ if (/\$ |\bCommand\b|^\w+(\s+\w+){0,3}\s+-/.test(firstLine)) return "Bash";
7942
+ return "ToolOutput";
7943
+ }
7944
+ function attachToolEvents(turns, tools) {
7945
+ const markerBlocks = [];
7946
+ for (const turn of turns) {
7947
+ if (turn.role !== "assistant") continue;
7948
+ for (let bi = 0; bi < turn.blocks.length; bi++) {
7949
+ const block = turn.blocks[bi];
7950
+ if (block.type === "tool_use" && block._isPendingMarker) {
7951
+ markerBlocks.push({ block, turn, blockIndex: bi });
7952
+ }
7953
+ }
7954
+ }
7955
+ const paired = Math.min(markerBlocks.length, tools.length);
7956
+ for (let i = 0; i < paired; i++) {
7957
+ const marker = markerBlocks[i];
7958
+ const tool = tools[i];
7959
+ marker.block.name = tool.name;
7960
+ marker.block.input = {
7961
+ ...marker.block.input,
7962
+ ...tool.input
7963
+ };
7964
+ marker.block._result = tool.result;
7965
+ const durationMs = durationBetween(marker.turn.timestamp, tool.timestamp);
7966
+ if (durationMs !== void 0) marker.block._durationMs = durationMs;
7967
+ marker.block._isPendingMarker = void 0;
7968
+ if (!marker.turn.timestamp && tool.timestamp) {
7969
+ marker.turn.timestamp = tool.timestamp;
7970
+ }
7971
+ }
7972
+ for (let i = paired; i < markerBlocks.length; i++) {
7973
+ const { turn, blockIndex, block } = markerBlocks[i];
7974
+ const markerText = typeof block.input?.marker === "string" ? block.input.marker : "";
7975
+ const thinkingBlock = {
7976
+ type: "thinking",
7977
+ thinking: block.name || markerText
7978
+ };
7979
+ turn.blocks[blockIndex] = thinkingBlock;
7980
+ }
7981
+ for (let i = paired; i < tools.length; i++) {
7982
+ const tool = tools[i];
7983
+ turns.push({
7984
+ role: "assistant",
7985
+ timestamp: tool.timestamp,
7986
+ blocks: [toToolUseBlock(tool)]
7987
+ });
7988
+ }
7989
+ }
7990
+ function toToolUseBlock(tool) {
7991
+ return {
7992
+ type: "tool_use",
7993
+ id: tool.id,
7994
+ name: tool.name,
7995
+ input: tool.input,
7996
+ _result: tool.result
7997
+ };
7998
+ }
7999
+ function durationBetween(start, end) {
8000
+ if (!start || !end) return void 0;
8001
+ const startMs = Date.parse(start);
8002
+ const endMs = Date.parse(end);
8003
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs <= startMs) return void 0;
8004
+ return Math.round(endMs - startMs);
8005
+ }
8006
+ function splitToolMarker(text) {
8007
+ const trimmed = text.trim();
8008
+ const single = trimmed.match(/^\*\*([^*\n]{2,120})\*\*$/);
8009
+ if (single) return { markerName: single[1].trim() };
8010
+ const trailing = trimmed.match(/^([\s\S]*?)\n+\*\*([^*\n]{2,120})\*\*$/);
8011
+ if (trailing) {
8012
+ const textBody = trailing[1].trim();
8013
+ return {
8014
+ markerName: trailing[2].trim(),
8015
+ ...textBody ? { textBody } : {}
8016
+ };
8017
+ }
8018
+ return void 0;
8019
+ }
8020
+
8021
+ // src/providers/cursor/index.ts
8022
+ var cursorProvider = {
8023
+ name: "cursor",
8024
+ displayName: "Cursor",
8025
+ discover: discoverCursorSessions,
8026
+ parse: (filePaths, sessionInfo) => parseCursorSession(filePaths, sessionInfo)
8027
+ };
8028
+
8029
+ // src/providers/pi/discover.ts
8030
+ import { createReadStream as createReadStream4 } from "fs";
8031
+ import { readdir as readdir10, stat as stat9 } from "fs/promises";
8032
+ import { basename as basename5, join as join15 } from "path";
8033
+ import { createInterface as createInterface4 } from "readline";
8034
+
8035
+ // src/providers/pi/config.ts
8036
+ import { readFile as readFile14 } from "fs/promises";
8037
+ import { homedir as homedir13 } from "os";
8038
+ import { join as join14 } from "path";
8039
+ function getPiAgentDir() {
8040
+ return process.env.PI_CODING_AGENT_DIR || join14(homedir13(), ".pi", "agent");
8041
+ }
8042
+ function getPiSessionsDir() {
8043
+ return process.env.PI_CODING_AGENT_SESSION_DIR || join14(getPiAgentDir(), "sessions");
8044
+ }
8045
+ async function readPiModelContextWindows() {
8046
+ const result = /* @__PURE__ */ new Map();
8047
+ let raw;
8048
+ try {
8049
+ raw = await readFile14(join14(getPiAgentDir(), "models.json"), "utf-8");
8050
+ } catch {
8051
+ return result;
7605
8052
  }
7606
- entries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7607
- return entries.map((e) => e.path);
8053
+ let parsed;
8054
+ try {
8055
+ parsed = JSON.parse(raw);
8056
+ } catch {
8057
+ return result;
8058
+ }
8059
+ collectModelContextWindows(parsed, result);
8060
+ return result;
7608
8061
  }
7609
- async function inferToolPaths(transcriptPaths) {
7610
- const toolPaths = [];
7611
- const seen = /* @__PURE__ */ new Set();
7612
- const projects = /* @__PURE__ */ new Map();
7613
- for (const transcriptPath of transcriptPaths) {
7614
- const projectRoot = getCursorProjectRoot(transcriptPath);
7615
- if (!projectRoot) continue;
7616
- if (!projects.has(projectRoot)) projects.set(projectRoot, /* @__PURE__ */ new Set());
7617
- projects.get(projectRoot)?.add(transcriptPath);
8062
+ function collectModelContextWindows(value, result) {
8063
+ if (!value || typeof value !== "object") return;
8064
+ if (Array.isArray(value)) {
8065
+ for (const item of value) collectModelContextWindows(item, result);
8066
+ return;
7618
8067
  }
7619
- for (const [projectRoot, selectedPaths] of projects.entries()) {
7620
- const transcriptsDir = join12(projectRoot, "agent-transcripts");
7621
- const toolDir = join12(projectRoot, "agent-tools");
7622
- const transcriptEntries = await collectTranscriptEntries2(transcriptsDir);
7623
- const toolEntries = await collectToolEntries2(toolDir);
7624
- if (transcriptEntries.length === 0 || toolEntries.length === 0) continue;
7625
- transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7626
- toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7627
- for (let i = 0; i < transcriptEntries.length; i++) {
7628
- const transcript = transcriptEntries[i];
7629
- if (!selectedPaths.has(transcript.path)) continue;
7630
- const prevMtimeMs = i === 0 ? Number.NEGATIVE_INFINITY : transcriptEntries[i - 1].mtimeMs;
7631
- for (const tool of toolEntries) {
7632
- if (tool.mtimeMs <= prevMtimeMs || tool.mtimeMs > transcript.mtimeMs) continue;
7633
- if (seen.has(tool.path)) continue;
7634
- seen.add(tool.path);
7635
- toolPaths.push(tool.path);
7636
- }
7637
- }
8068
+ const obj = value;
8069
+ if (typeof obj.id === "string" && typeof obj.contextWindow === "number") {
8070
+ result.set(obj.id, obj.contextWindow);
7638
8071
  }
7639
- return sortByMtime(toolPaths);
8072
+ for (const nested of Object.values(obj)) collectModelContextWindows(nested, result);
7640
8073
  }
7641
- async function collectTranscriptEntries2(transcriptsDir) {
7642
- let entries;
8074
+
8075
+ // src/providers/pi/discover.ts
8076
+ var PROMPT_SCAN_LIMIT = 2;
8077
+ async function discoverPiSessions() {
8078
+ const sessionsRoot = getPiSessionsDir();
8079
+ const sessions = [];
8080
+ let projectDirs;
7643
8081
  try {
7644
- entries = await readdir9(transcriptsDir);
8082
+ projectDirs = await readdir10(sessionsRoot);
7645
8083
  } catch {
7646
- return [];
8084
+ return sessions;
7647
8085
  }
7648
- const transcripts = [];
7649
- for (const entry of entries) {
7650
- const entryPath = join12(transcriptsDir, entry);
7651
- const st = await stat8(entryPath).catch(() => null);
7652
- if (!st) continue;
7653
- if (entry.endsWith(".jsonl") && st.isFile()) {
7654
- transcripts.push({ path: entryPath, mtimeMs: st.mtimeMs });
8086
+ for (const projectDir of projectDirs) {
8087
+ const projectPath = join15(sessionsRoot, projectDir);
8088
+ const projectStat = await stat9(projectPath).catch(() => null);
8089
+ if (!projectStat?.isDirectory()) continue;
8090
+ let files;
8091
+ try {
8092
+ files = await readdir10(projectPath);
8093
+ } catch {
7655
8094
  continue;
7656
8095
  }
7657
- if (!st.isDirectory()) continue;
7658
- const nested = join12(entryPath, `${entry}.jsonl`);
7659
- const nestedStat = await stat8(nested).catch(() => null);
7660
- if (!nestedStat?.isFile()) continue;
7661
- transcripts.push({ path: nested, mtimeMs: nestedStat.mtimeMs });
8096
+ for (const file of files) {
8097
+ if (!file.endsWith(".jsonl")) continue;
8098
+ const filePath = join15(projectPath, file);
8099
+ const fileStat = await stat9(filePath).catch(() => null);
8100
+ if (!fileStat) continue;
8101
+ const info = await extractPiSessionInfo(filePath, fileStat.size, projectDir);
8102
+ if (info) sessions.push(info);
8103
+ }
7662
8104
  }
7663
- return transcripts;
8105
+ sessions.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
8106
+ return sessions;
7664
8107
  }
7665
- async function collectToolEntries2(toolDir) {
7666
- let entries;
8108
+ async function extractPiSessionInfo(filePath, fileSize, encodedProjectDir) {
8109
+ let header;
8110
+ let timestamp = "";
8111
+ let lineCount = 0;
8112
+ let promptCount = 0;
8113
+ let toolCallCount = 0;
8114
+ let editCountEst = 0;
8115
+ let model;
8116
+ let title;
8117
+ const prompts = [];
8118
+ const input = createReadStream4(filePath, { encoding: "utf-8" });
8119
+ const rl = createInterface4({ input, crlfDelay: Number.POSITIVE_INFINITY });
7667
8120
  try {
7668
- entries = await readdir9(toolDir);
8121
+ for await (const rawLine of rl) {
8122
+ const line = rawLine.trim();
8123
+ if (!line) continue;
8124
+ lineCount++;
8125
+ let entry;
8126
+ try {
8127
+ entry = JSON.parse(line);
8128
+ } catch {
8129
+ continue;
8130
+ }
8131
+ if (typeof entry.timestamp === "string" && entry.timestamp) {
8132
+ timestamp = entry.timestamp;
8133
+ }
8134
+ if (!header) {
8135
+ if (entry.type !== "session" || typeof entry.id !== "string") return null;
8136
+ header = entry;
8137
+ if (typeof entry.timestamp === "string" && entry.timestamp) timestamp = entry.timestamp;
8138
+ continue;
8139
+ }
8140
+ if (entry.type === "session_info") {
8141
+ const name = typeof entry.name === "string" ? entry.name.trim() : "";
8142
+ title = name || void 0;
8143
+ continue;
8144
+ }
8145
+ if (entry.type === "model_change" && typeof entry.modelId === "string") {
8146
+ model = entry.modelId;
8147
+ continue;
8148
+ }
8149
+ if (entry.type !== "message") continue;
8150
+ const message = entry.message;
8151
+ if (!message || typeof message !== "object") continue;
8152
+ if (message.role === "user") {
8153
+ const text = extractText(message.content).trim();
8154
+ if (text) {
8155
+ promptCount++;
8156
+ if (prompts.length < PROMPT_SCAN_LIMIT) {
8157
+ prompts.push(cleanPromptText(text).slice(0, 200));
8158
+ }
8159
+ }
8160
+ } else if (message.role === "assistant") {
8161
+ if (!model && typeof message.model === "string") model = message.model;
8162
+ if (Array.isArray(message.content)) {
8163
+ for (const block of message.content) {
8164
+ if (block?.type !== "toolCall") continue;
8165
+ toolCallCount++;
8166
+ if (isEditTool2(block.name)) editCountEst++;
8167
+ }
8168
+ }
8169
+ }
8170
+ }
7669
8171
  } catch {
7670
- return [];
8172
+ return null;
8173
+ } finally {
8174
+ rl.close();
7671
8175
  }
7672
- const tools = [];
7673
- for (const entry of entries) {
7674
- if (!entry.endsWith(".txt")) continue;
7675
- const entryPath = join12(toolDir, entry);
7676
- const st = await stat8(entryPath).catch(() => null);
7677
- if (!st?.isFile()) continue;
7678
- tools.push({ path: entryPath, mtimeMs: st.mtimeMs });
8176
+ if (!header || prompts.length === 0) return null;
8177
+ const cwd = header.cwd || decodeProjectDir3(encodedProjectDir);
8178
+ const slug = basename5(filePath, ".jsonl").replace(/^\d{4}-\d{2}-\d{2}T[^_]+_/, "");
8179
+ const fallbackTimestamp = timestamp || header.timestamp || (/* @__PURE__ */ new Date()).toISOString();
8180
+ return {
8181
+ provider: "pi",
8182
+ sessionId: header.id,
8183
+ slug,
8184
+ title,
8185
+ project: cwd,
8186
+ cwd,
8187
+ version: String(header.version || 1),
8188
+ timestamp: fallbackTimestamp,
8189
+ lineCount,
8190
+ fileSize,
8191
+ filePath,
8192
+ filePaths: [filePath],
8193
+ firstPrompt: prompts[0] || "(pi session)",
8194
+ prompts,
8195
+ promptCount,
8196
+ toolCallCount,
8197
+ model,
8198
+ editCountEst
8199
+ };
8200
+ }
8201
+ function decodeProjectDir3(encoded) {
8202
+ if (encoded.startsWith("--") && encoded.endsWith("--")) {
8203
+ return `/${encoded.slice(2, -2).replace(/-/g, "/")}`;
7679
8204
  }
7680
- return tools;
8205
+ return encoded;
7681
8206
  }
7682
- function getCursorProjectRoot(transcriptPath) {
7683
- const parts = transcriptPath.split(/[/\\]agent-transcripts[/\\]/);
7684
- if (parts.length < 2) return null;
7685
- return parts[0];
8207
+ function extractText(content) {
8208
+ if (typeof content === "string") return content;
8209
+ if (!Array.isArray(content)) return "";
8210
+ return content.filter((block) => block?.type === "text").map((block) => block.text || "").join("\n");
7686
8211
  }
7687
- async function loadToolEvents(toolPaths) {
7688
- const events = [];
7689
- for (const path of toolPaths) {
7690
- const content = await readFile13(path, "utf-8").catch(() => "");
7691
- const result = content.trim();
7692
- if (!result) continue;
7693
- const st = await stat8(path).catch(() => null);
7694
- const id = basename4(path, extname2(path));
7695
- events.push({
7696
- id,
7697
- name: inferToolName(result),
7698
- input: { source: basename4(path) },
7699
- result,
7700
- timestamp: st ? new Date(st.mtimeMs).toISOString() : void 0
8212
+ function isEditTool2(name) {
8213
+ return name === "edit" || name === "write" || name === "Edit" || name === "Write";
8214
+ }
8215
+
8216
+ // src/providers/pi/parser.ts
8217
+ init_utils();
8218
+ import { readFile as readFile15 } from "fs/promises";
8219
+ import { basename as basename6 } from "path";
8220
+ async function parsePiSession(filePaths, sessionInfo) {
8221
+ const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
8222
+ const allLines = [];
8223
+ for (const fp of paths) {
8224
+ const content = await readFile15(fp, "utf-8");
8225
+ allLines.push(...content.split("\n"));
8226
+ }
8227
+ return parsePiLines(allLines, {
8228
+ sourcePath: paths[0],
8229
+ sessionInfo,
8230
+ sessionsDir: getPiSessionsDir(),
8231
+ modelContextWindows: await readPiModelContextWindows()
8232
+ });
8233
+ }
8234
+ function parsePiLines(lines, options = {}) {
8235
+ let header;
8236
+ const entries = [];
8237
+ const byId = /* @__PURE__ */ new Map();
8238
+ const parseWarnings = [];
8239
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
8240
+ const line = lines[lineIndex];
8241
+ if (!line.trim()) continue;
8242
+ let entry;
8243
+ try {
8244
+ entry = JSON.parse(line);
8245
+ } catch {
8246
+ addParseWarning(parseWarnings, {
8247
+ kind: "malformed-json",
8248
+ source: "pi JSONL",
8249
+ firstLine: lineIndex + 1,
8250
+ message: "Skipped malformed JSONL line",
8251
+ sample: line
8252
+ });
8253
+ continue;
8254
+ }
8255
+ if (entry.type === "session") {
8256
+ if (!header && typeof entry.id === "string") {
8257
+ header = entry;
8258
+ }
8259
+ continue;
8260
+ }
8261
+ entries.push(entry);
8262
+ if (entry.id) byId.set(entry.id, entry);
8263
+ }
8264
+ const branchSelection = selectActiveBranch(entries, byId);
8265
+ const branchEntries = branchSelection.entries;
8266
+ const toolResults = collectToolResults(branchEntries);
8267
+ const turns = [];
8268
+ const usageByMessageId = /* @__PURE__ */ new Map();
8269
+ const allTimestamps = [];
8270
+ const compactions = [];
8271
+ const apiErrors = [];
8272
+ let title = options.sessionInfo?.title;
8273
+ let model = options.sessionInfo?.model;
8274
+ let currentModel = model;
8275
+ let firstTimestamp;
8276
+ let lastTimestamp;
8277
+ for (const entry of branchEntries) {
8278
+ if (entry.timestamp) {
8279
+ allTimestamps.push(entry.timestamp);
8280
+ if (!firstTimestamp || entry.timestamp < firstTimestamp) firstTimestamp = entry.timestamp;
8281
+ if (!lastTimestamp || entry.timestamp > lastTimestamp) lastTimestamp = entry.timestamp;
8282
+ }
8283
+ if (entry.type === "session_info") {
8284
+ const sessionInfoEntry = entry;
8285
+ const name = typeof sessionInfoEntry.name === "string" ? sessionInfoEntry.name.trim() : "";
8286
+ title = name || title;
8287
+ continue;
8288
+ }
8289
+ if (entry.type === "model_change") {
8290
+ const nextModel = entry.modelId;
8291
+ if (typeof nextModel === "string" && nextModel) {
8292
+ currentModel = nextModel;
8293
+ model = model || nextModel;
8294
+ }
8295
+ continue;
8296
+ }
8297
+ if (entry.type === "compaction") {
8298
+ const compaction = entry;
8299
+ const summary = typeof compaction.summary === "string" ? compaction.summary : "";
8300
+ if (summary) {
8301
+ turns.push({
8302
+ role: "user",
8303
+ subtype: "compaction-summary",
8304
+ timestamp: entry.timestamp,
8305
+ blocks: [{ type: "text", text: summary }]
8306
+ });
8307
+ }
8308
+ compactions.push({
8309
+ timestamp: entry.timestamp || lastTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
8310
+ trigger: "pi",
8311
+ ...typeof compaction.tokensBefore === "number" ? { preTokens: compaction.tokensBefore } : {}
8312
+ });
8313
+ continue;
8314
+ }
8315
+ if (entry.type === "branch_summary") {
8316
+ const summary = entry.summary;
8317
+ if (typeof summary === "string" && summary.trim()) {
8318
+ turns.push({
8319
+ role: "user",
8320
+ subtype: "context-injection",
8321
+ timestamp: entry.timestamp,
8322
+ blocks: [{ type: "text", text: `Branch summary:
8323
+ ${summary}` }]
8324
+ });
8325
+ }
8326
+ continue;
8327
+ }
8328
+ if (entry.type === "custom_message") {
8329
+ const custom = entry;
8330
+ const text = extractText2(custom.content).trim();
8331
+ if (text && custom.display !== false) {
8332
+ const customType = typeof custom.customType === "string" ? custom.customType : "custom";
8333
+ turns.push({
8334
+ role: "user",
8335
+ subtype: "context-injection",
8336
+ timestamp: entry.timestamp,
8337
+ blocks: [{ type: "text", text: `[${customType}]
8338
+ ${text}` }]
8339
+ });
8340
+ }
8341
+ continue;
8342
+ }
8343
+ if (entry.type !== "message") continue;
8344
+ const messageEntry = entry;
8345
+ const message = messageEntry.message;
8346
+ if (!message) continue;
8347
+ if (message.role === "user") {
8348
+ const blocks = buildUserBlocks(message.content);
8349
+ if (blocks.length > 0) {
8350
+ turns.push({ role: "user", timestamp: entry.timestamp, blocks });
8351
+ }
8352
+ continue;
8353
+ }
8354
+ if (message.role === "assistant") {
8355
+ const blocks = buildAssistantBlocks(message.content, toolResults, entry.timestamp);
8356
+ if (message.errorMessage) {
8357
+ blocks.push({ type: "text", text: message.errorMessage });
8358
+ apiErrors.push({
8359
+ timestamp: entry.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
8360
+ ...parseApiErrorMessage(message.errorMessage)
8361
+ });
8362
+ }
8363
+ if (blocks.length > 0) {
8364
+ const msgModel = message.model || currentModel;
8365
+ if (msgModel) model = model || msgModel;
8366
+ if (message.usage && entry.id) {
8367
+ const usage = normalizeUsage(message.usage);
8368
+ usageByMessageId.set(entry.id, {
8369
+ usage,
8370
+ model: msgModel,
8371
+ contextTokens: usage.inputTokens + usage.cacheCreationTokens + usage.cacheReadTokens
8372
+ });
8373
+ }
8374
+ turns.push({
8375
+ role: "assistant",
8376
+ messageId: entry.id,
8377
+ model: msgModel,
8378
+ timestamp: entry.timestamp,
8379
+ blocks,
8380
+ ...message.stopReason === "length" ? { stopReason: "max_tokens" } : {}
8381
+ });
8382
+ }
8383
+ continue;
8384
+ }
8385
+ if (message.role === "bashExecution") {
8386
+ const block = buildBashExecutionBlock(message, entry.id || `bash-${turns.length}`);
8387
+ turns.push({ role: "assistant", timestamp: entry.timestamp, blocks: [block] });
8388
+ }
8389
+ }
8390
+ const tokenUsage = aggregateUsage([...usageByMessageId.values()].map((value) => value.usage));
8391
+ const tokenUsageByModel = aggregateUsageByModel(usageByMessageId);
8392
+ const turnStats = buildTurnStats2(turns, usageByMessageId);
8393
+ const sessionId = header?.id || options.sessionInfo?.sessionId || "pi-session";
8394
+ const sourcePath = options.sourcePath || options.sessionInfo?.filePath || "";
8395
+ const slug = options.sessionInfo?.slug || basename6(sourcePath || sessionId, ".jsonl");
8396
+ const cwd = header?.cwd || options.sessionInfo?.cwd || options.sessionInfo?.project || "";
8397
+ return {
8398
+ sessionId,
8399
+ slug,
8400
+ title,
8401
+ cwd,
8402
+ model,
8403
+ startTime: firstTimestamp || header?.timestamp,
8404
+ endTime: lastTimestamp,
8405
+ totalDurationMs: estimateActiveDuration2(allTimestamps),
8406
+ turns,
8407
+ tokenUsage,
8408
+ ...tokenUsageByModel ? { tokenUsageByModel } : {},
8409
+ ...turnStats.length > 0 ? { turnStats } : {},
8410
+ ...compactions.length > 0 ? { compactions } : {},
8411
+ ...apiErrors.length > 0 ? { apiErrors } : {},
8412
+ dataSource: "jsonl",
8413
+ dataSourceInfo: {
8414
+ primary: "jsonl",
8415
+ sources: [shortenPath(options.sessionsDir || getPiSessionsDir())],
8416
+ ...branchSelection.abandonedEntries > 0 ? { notes: [`${branchSelection.abandonedEntries} off-branch Pi entries were omitted.`] } : {}
8417
+ },
8418
+ ...model ? contextLimitForModel(model, options.modelContextWindows) : {},
8419
+ ...parseWarnings.length > 0 ? { parseWarnings } : {}
8420
+ };
8421
+ }
8422
+ function parseApiErrorMessage(message) {
8423
+ const status = message.match(/\b(\d{3})\b/);
8424
+ const errorType = message.match(/"([a-z][a-z0-9_]*error[a-z0-9_]*)"/i);
8425
+ return {
8426
+ ...status ? { statusCode: Number(status[1]) } : {},
8427
+ ...errorType ? { errorType: errorType[1] } : {}
8428
+ };
8429
+ }
8430
+ function contextLimitForModel(model, modelContextWindows) {
8431
+ const contextLimit = modelContextWindows?.get(model);
8432
+ return contextLimit ? { contextLimit } : {};
8433
+ }
8434
+ function selectActiveBranch(entries, byId) {
8435
+ if (entries.length === 0) return { entries: [], abandonedEntries: 0 };
8436
+ if (byId.size === 0) return { entries, abandonedEntries: 0 };
8437
+ const leaf = entries.toReversed().find((entry) => entry.id && byId.has(entry.id));
8438
+ if (!leaf?.id) return { entries, abandonedEntries: 0 };
8439
+ const branchIds = /* @__PURE__ */ new Set();
8440
+ const branch = [];
8441
+ const seen = /* @__PURE__ */ new Set();
8442
+ let current = leaf;
8443
+ while (current) {
8444
+ if (!current.id || seen.has(current.id)) break;
8445
+ seen.add(current.id);
8446
+ branchIds.add(current.id);
8447
+ branch.unshift(current);
8448
+ const parentId = current.parentId;
8449
+ current = typeof parentId === "string" ? byId.get(parentId) : void 0;
8450
+ }
8451
+ if (branch.length === 0) return { entries, abandonedEntries: 0 };
8452
+ const idlessEntries = entries.filter((entry) => !entry.id);
8453
+ if (idlessEntries.length === 0) {
8454
+ return { entries: branch, abandonedEntries: Math.max(0, entries.length - branch.length) };
8455
+ }
8456
+ const selected = new Set(branch);
8457
+ const branchWithIdless = entries.filter((entry) => selected.has(entry) || !entry.id);
8458
+ return {
8459
+ entries: branchWithIdless,
8460
+ abandonedEntries: entries.filter((entry) => entry.id && !branchIds.has(entry.id)).length
8461
+ };
8462
+ }
8463
+ function collectToolResults(entries) {
8464
+ const results = /* @__PURE__ */ new Map();
8465
+ for (const entry of entries) {
8466
+ if (entry.type !== "message") continue;
8467
+ const message = entry.message;
8468
+ if (message?.role !== "toolResult" || typeof message.toolCallId !== "string") continue;
8469
+ const { text, images } = extractTextAndImages(message.content);
8470
+ results.set(message.toolCallId, {
8471
+ text,
8472
+ images,
8473
+ isError: message.isError,
8474
+ timestamp: entry.timestamp
7701
8475
  });
7702
8476
  }
7703
- return events;
8477
+ return results;
7704
8478
  }
7705
- function inferToolName(result) {
7706
- const firstLine = result.split("\n", 1)[0] || "";
7707
- if (result.startsWith("diff --git")) return "Diff";
7708
- if (firstLine.startsWith("http://") || firstLine.startsWith("https://")) return "WebFetch";
7709
- if (firstLine.startsWith("{") || firstLine.startsWith("[")) return "API";
7710
- if (/\$ |\bCommand\b|^\w+(\s+\w+){0,3}\s+-/.test(firstLine)) return "Bash";
7711
- return "ToolOutput";
8479
+ function buildUserBlocks(content) {
8480
+ const { text, images } = extractTextAndImages(content);
8481
+ const blocks = [];
8482
+ if (text.trim()) blocks.push({ type: "text", text });
8483
+ if (images.length > 0) blocks.push({ type: "_user_images", images });
8484
+ return blocks;
7712
8485
  }
7713
- function attachToolEvents(turns, tools) {
7714
- const markerBlocks = [];
7715
- for (const turn of turns) {
7716
- if (turn.role !== "assistant") continue;
7717
- for (let bi = 0; bi < turn.blocks.length; bi++) {
7718
- const block = turn.blocks[bi];
7719
- if (block.type === "tool_use" && block._isPendingMarker) {
7720
- markerBlocks.push({ block, turn, blockIndex: bi });
7721
- }
8486
+ function buildAssistantBlocks(content, toolResults, assistantTimestamp) {
8487
+ if (!Array.isArray(content)) return [];
8488
+ const blocks = [];
8489
+ for (const block of content) {
8490
+ if (block.type === "thinking" && block.thinking?.trim()) {
8491
+ blocks.push({ type: "thinking", thinking: block.thinking });
8492
+ } else if (block.type === "text" && block.text?.trim()) {
8493
+ blocks.push({ type: "text", text: block.text });
8494
+ } else if (block.type === "toolCall") {
8495
+ const result = toolResults.get(block.id);
8496
+ blocks.push({
8497
+ type: "tool_use",
8498
+ id: block.id,
8499
+ name: mapToolName(block.name),
8500
+ input: normalizeToolInput2(block.name, block.arguments || {}),
8501
+ _result: result?.text || "",
8502
+ ...result?.images.length ? { _images: result.images } : {},
8503
+ ...result?.isError ? { _isError: true } : {},
8504
+ ...assistantTimestamp && result?.timestamp ? { _durationMs: toolDurationMs(assistantTimestamp, result.timestamp) } : {}
8505
+ });
7722
8506
  }
7723
8507
  }
7724
- const paired = Math.min(markerBlocks.length, tools.length);
7725
- for (let i = 0; i < paired; i++) {
7726
- const marker = markerBlocks[i];
7727
- const tool = tools[i];
7728
- marker.block.name = tool.name;
7729
- marker.block.input = {
7730
- ...marker.block.input,
7731
- ...tool.input
7732
- };
7733
- marker.block._result = tool.result;
7734
- const durationMs = durationBetween(marker.turn.timestamp, tool.timestamp);
7735
- if (durationMs !== void 0) marker.block._durationMs = durationMs;
7736
- marker.block._isPendingMarker = void 0;
7737
- if (!marker.turn.timestamp && tool.timestamp) {
7738
- marker.turn.timestamp = tool.timestamp;
7739
- }
8508
+ return blocks;
8509
+ }
8510
+ function toolDurationMs(start, end) {
8511
+ const duration = Date.parse(end) - Date.parse(start);
8512
+ return duration > 0 && duration < 60 * 6e4 ? duration : void 0;
8513
+ }
8514
+ function buildBashExecutionBlock(message, fallbackId) {
8515
+ const outputParts = [message.output || ""];
8516
+ if (message.cancelled) outputParts.push("\n(command cancelled)");
8517
+ if (message.truncated && message.fullOutputPath) {
8518
+ outputParts.push(`
8519
+ [Output truncated. Full output: ${message.fullOutputPath}]`);
7740
8520
  }
7741
- for (let i = paired; i < markerBlocks.length; i++) {
7742
- const { turn, blockIndex, block } = markerBlocks[i];
7743
- const markerText = typeof block.input?.marker === "string" ? block.input.marker : "";
7744
- const thinkingBlock = {
7745
- type: "thinking",
7746
- thinking: block.name || markerText
8521
+ return {
8522
+ type: "tool_use",
8523
+ id: fallbackId,
8524
+ name: "Bash",
8525
+ input: { command: message.command || "" },
8526
+ _result: outputParts.join(""),
8527
+ ...message.exitCode !== void 0 && message.exitCode !== 0 ? { _isError: true } : {}
8528
+ };
8529
+ }
8530
+ function extractTextAndImages(content) {
8531
+ if (typeof content === "string") return { text: content, images: [] };
8532
+ if (!Array.isArray(content)) return { text: "", images: [] };
8533
+ const text = [];
8534
+ const images = [];
8535
+ for (const block of content) {
8536
+ if (block.type === "text") {
8537
+ text.push(block.text || "");
8538
+ } else if (block.type === "image" && block.data) {
8539
+ images.push(`data:${block.mimeType || "image/png"};base64,${block.data}`);
8540
+ }
8541
+ }
8542
+ return { text: text.join("\n"), images };
8543
+ }
8544
+ function extractText2(content) {
8545
+ return extractTextAndImages(content).text;
8546
+ }
8547
+ function mapToolName(name) {
8548
+ const normalized = name.toLowerCase();
8549
+ if (normalized === "bash") return "Bash";
8550
+ if (normalized === "read") return "Read";
8551
+ if (normalized === "write") return "Write";
8552
+ if (normalized === "edit") return "Edit";
8553
+ if (normalized === "grep") return "Grep";
8554
+ if (normalized === "find") return "Find";
8555
+ if (normalized === "ls") return "LS";
8556
+ return name;
8557
+ }
8558
+ function normalizeToolInput2(name, input) {
8559
+ const normalized = name.toLowerCase();
8560
+ if (normalized === "write") {
8561
+ return {
8562
+ ...input,
8563
+ ...typeof input.path === "string" ? { file_path: input.path } : {}
7747
8564
  };
7748
- turn.blocks[blockIndex] = thinkingBlock;
7749
8565
  }
7750
- for (let i = paired; i < tools.length; i++) {
7751
- const tool = tools[i];
7752
- turns.push({
7753
- role: "assistant",
7754
- timestamp: tool.timestamp,
7755
- blocks: [toToolUseBlock(tool)]
7756
- });
8566
+ if (normalized === "edit") {
8567
+ const firstEdit = Array.isArray(input.edits) ? input.edits[0] : void 0;
8568
+ const edit = firstEdit && typeof firstEdit === "object" ? firstEdit : input;
8569
+ return {
8570
+ ...input,
8571
+ ...typeof input.path === "string" ? { file_path: input.path } : {},
8572
+ ...typeof edit.oldText === "string" ? { old_string: edit.oldText } : {},
8573
+ ...typeof edit.newText === "string" ? { new_string: edit.newText } : {}
8574
+ };
7757
8575
  }
8576
+ return input;
7758
8577
  }
7759
- function toToolUseBlock(tool) {
8578
+ function normalizeUsage(usage) {
7760
8579
  return {
7761
- type: "tool_use",
7762
- id: tool.id,
7763
- name: tool.name,
7764
- input: tool.input,
7765
- _result: tool.result
8580
+ inputTokens: usage.input || 0,
8581
+ outputTokens: usage.output || 0,
8582
+ cacheCreationTokens: usage.cacheWrite || 0,
8583
+ cacheReadTokens: usage.cacheRead || 0
7766
8584
  };
7767
8585
  }
7768
- function durationBetween(start, end) {
7769
- if (!start || !end) return void 0;
7770
- const startMs = Date.parse(start);
7771
- const endMs = Date.parse(end);
7772
- if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs <= startMs) return void 0;
7773
- return Math.round(endMs - startMs);
8586
+ function aggregateUsage(usages) {
8587
+ if (usages.length === 0) return void 0;
8588
+ return usages.reduce(
8589
+ (total, usage) => ({
8590
+ inputTokens: total.inputTokens + usage.inputTokens,
8591
+ outputTokens: total.outputTokens + usage.outputTokens,
8592
+ cacheCreationTokens: total.cacheCreationTokens + usage.cacheCreationTokens,
8593
+ cacheReadTokens: total.cacheReadTokens + usage.cacheReadTokens
8594
+ }),
8595
+ { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 }
8596
+ );
7774
8597
  }
7775
- function splitToolMarker(text) {
7776
- const trimmed = text.trim();
7777
- const single = trimmed.match(/^\*\*([^*\n]{2,120})\*\*$/);
7778
- if (single) return { markerName: single[1].trim() };
7779
- const trailing = trimmed.match(/^([\s\S]*?)\n+\*\*([^*\n]{2,120})\*\*$/);
7780
- if (trailing) {
7781
- const textBody = trailing[1].trim();
7782
- return {
7783
- markerName: trailing[2].trim(),
7784
- ...textBody ? { textBody } : {}
7785
- };
8598
+ function aggregateUsageByModel(usageByMessageId) {
8599
+ if (usageByMessageId.size === 0) return void 0;
8600
+ const result = {};
8601
+ for (const { usage, model } of usageByMessageId.values()) {
8602
+ const key = model || "unknown";
8603
+ result[key] = aggregateUsage(
8604
+ [result[key], usage].filter((value) => !!value)
8605
+ );
7786
8606
  }
7787
- return void 0;
8607
+ return result;
8608
+ }
8609
+ function buildTurnStats2(turns, usageByMessageId) {
8610
+ const stats = [];
8611
+ let current;
8612
+ let turnIndex = -1;
8613
+ for (const turn of turns) {
8614
+ if (turn.role === "user" && !turn.subtype) {
8615
+ if (current && current.messageIds.length > 0)
8616
+ stats.push(buildTurnStat(current, usageByMessageId));
8617
+ turnIndex++;
8618
+ current = { turnIndex, messageIds: [] };
8619
+ } else if (turn.role === "assistant" && turn.messageId && current) {
8620
+ current.messageIds.push(turn.messageId);
8621
+ }
8622
+ }
8623
+ if (current && current.messageIds.length > 0)
8624
+ stats.push(buildTurnStat(current, usageByMessageId));
8625
+ return stats;
8626
+ }
8627
+ function buildTurnStat(turn, usageByMessageId) {
8628
+ const usages = [];
8629
+ let model;
8630
+ let contextTokens = 0;
8631
+ for (const messageId of turn.messageIds) {
8632
+ const item = usageByMessageId.get(messageId);
8633
+ if (!item) continue;
8634
+ usages.push(item.usage);
8635
+ model = model || item.model;
8636
+ contextTokens = Math.max(contextTokens, item.contextTokens || 0);
8637
+ }
8638
+ const tokenUsage = aggregateUsage(usages);
8639
+ return {
8640
+ turnIndex: turn.turnIndex,
8641
+ ...model ? { model } : {},
8642
+ ...tokenUsage ? { tokenUsage } : {},
8643
+ ...contextTokens ? { contextTokens } : {}
8644
+ };
8645
+ }
8646
+ function estimateActiveDuration2(timestamps) {
8647
+ if (timestamps.length < 2) return void 0;
8648
+ const sorted = timestamps.map((timestamp) => Date.parse(timestamp)).filter((timestamp) => !Number.isNaN(timestamp)).sort((a, b) => a - b);
8649
+ if (sorted.length < 2) return void 0;
8650
+ let total = 0;
8651
+ for (let i = 1; i < sorted.length; i++) {
8652
+ const gap = sorted[i] - sorted[i - 1];
8653
+ if (gap > 0 && gap < 30 * 6e4) total += gap;
8654
+ }
8655
+ return total || void 0;
7788
8656
  }
7789
8657
 
7790
- // src/providers/cursor/index.ts
7791
- var cursorProvider = {
7792
- name: "cursor",
7793
- displayName: "Cursor",
7794
- discover: discoverCursorSessions,
7795
- parse: (filePaths, sessionInfo) => parseCursorSession(filePaths, sessionInfo)
8658
+ // src/providers/pi/index.ts
8659
+ var piProvider = {
8660
+ name: "pi",
8661
+ displayName: "Pi",
8662
+ discover: discoverPiSessions,
8663
+ parse: parsePiSession
7796
8664
  };
7797
8665
 
7798
8666
  // src/providers/index.ts
@@ -7801,9 +8669,17 @@ var providers = [
7801
8669
  claudeDesktopProvider,
7802
8670
  claudeCodeProvider,
7803
8671
  codexProvider,
7804
- cursorProvider
8672
+ cursorProvider,
8673
+ piProvider
8674
+ ];
8675
+ var PROVIDER_PRIORITY = [
8676
+ "claude-cowork",
8677
+ "claude-desktop",
8678
+ "claude-code",
8679
+ "codex",
8680
+ "cursor",
8681
+ "pi"
7805
8682
  ];
7806
- var PROVIDER_PRIORITY = ["claude-cowork", "claude-desktop", "claude-code", "codex", "cursor"];
7807
8683
  function getAllProviders() {
7808
8684
  return providers;
7809
8685
  }
@@ -7832,9 +8708,9 @@ init_cloud();
7832
8708
 
7833
8709
  // src/publishers/gist.ts
7834
8710
  init_cloud();
7835
- import { createHash as createHash2 } from "crypto";
7836
- import { readFile as readFile16, writeFile as writeFile4 } from "fs/promises";
7837
- import { join as join15 } from "path";
8711
+ import { createHash as createHash3 } from "crypto";
8712
+ import { readFile as readFile18, writeFile as writeFile4 } from "fs/promises";
8713
+ import { join as join18 } from "path";
7838
8714
  var GIST_META_FILE = ".vibe-replay-gist.json";
7839
8715
  function checkPublishStatus() {
7840
8716
  const auth = loadAuthToken();
@@ -7846,8 +8722,8 @@ async function publishGist(outputDir, title, opts) {
7846
8722
  if (!auth) {
7847
8723
  throw new Error("Not logged in. Run `vibe-replay auth login` first.");
7848
8724
  }
7849
- const jsonPath = join15(outputDir, "replay.json");
7850
- const content = await readFile16(jsonPath, "utf-8");
8725
+ const jsonPath = join18(outputDir, "replay.json");
8726
+ const content = await readFile18(jsonPath, "utf-8");
7851
8727
  const overwrite = opts?.overwrite;
7852
8728
  const filename = overwrite?.filename || `${sanitizeFilename(title)}.json`;
7853
8729
  const description = `vibe-replay: ${title}`;
@@ -7898,7 +8774,7 @@ async function publishGist(outputDir, title, opts) {
7898
8774
  viewerUrl = data.viewerUrl;
7899
8775
  mode = "created";
7900
8776
  }
7901
- const contentHash = createHash2("sha256").update(content).digest("hex").slice(0, 16);
8777
+ const contentHash = createHash3("sha256").update(content).digest("hex").slice(0, 16);
7902
8778
  await saveGistInfo(outputDir, {
7903
8779
  gistId,
7904
8780
  filename,
@@ -7911,7 +8787,7 @@ async function publishGist(outputDir, title, opts) {
7911
8787
  }
7912
8788
  async function loadSavedGistInfo(outputDir) {
7913
8789
  try {
7914
- const raw = await readFile16(join15(outputDir, GIST_META_FILE), "utf-8");
8790
+ const raw = await readFile18(join18(outputDir, GIST_META_FILE), "utf-8");
7915
8791
  const parsed = JSON.parse(raw);
7916
8792
  if (!parsed?.gistId || !parsed?.filename) return void 0;
7917
8793
  return parsed;
@@ -7920,7 +8796,7 @@ async function loadSavedGistInfo(outputDir) {
7920
8796
  }
7921
8797
  }
7922
8798
  async function saveGistInfo(outputDir, info) {
7923
- await writeFile4(join15(outputDir, GIST_META_FILE), JSON.stringify(info, null, 2), "utf-8");
8799
+ await writeFile4(join18(outputDir, GIST_META_FILE), JSON.stringify(info, null, 2), "utf-8");
7924
8800
  }
7925
8801
  function sanitizeFilename(s) {
7926
8802
  return s.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").slice(0, 60);
@@ -8032,19 +8908,19 @@ function scanForSecrets(content) {
8032
8908
 
8033
8909
  // src/server.ts
8034
8910
  import { execFile as execFile3 } from "child_process";
8035
- import { createHash as createHash4 } from "crypto";
8911
+ import { createHash as createHash5 } from "crypto";
8036
8912
  import { watch as fsWatch } from "fs";
8037
8913
  import {
8038
- mkdir as mkdir5,
8914
+ mkdir as mkdir6,
8039
8915
  open as fsOpen,
8040
- readdir as readdir11,
8041
- readFile as readFile19,
8042
- stat as stat10,
8916
+ readdir as readdir12,
8917
+ readFile as readFile22,
8918
+ stat as stat11,
8043
8919
  unlink as unlink2,
8044
- writeFile as writeFile6
8920
+ writeFile as writeFile7
8045
8921
  } from "fs/promises";
8046
- import { homedir as homedir16 } from "os";
8047
- import { basename as basename6, dirname as dirname5, join as join18, resolve as resolve2 } from "path";
8922
+ import { homedir as homedir19 } from "os";
8923
+ import { join as join22, resolve as resolve3 } from "path";
8048
8924
  import { promisify as promisify3 } from "util";
8049
8925
  import { serve } from "@hono/node-server";
8050
8926
  import chalk from "chalk";
@@ -8300,7 +9176,7 @@ async function executeFeedback(prompt, tool) {
8300
9176
  return runOpencode(prompt, tool.command);
8301
9177
  }
8302
9178
  function runClaude(prompt, cmd) {
8303
- return new Promise((resolve3, reject) => {
9179
+ return new Promise((resolve4, reject) => {
8304
9180
  const proc = spawnTool(cmd, ["-p", "--output-format", "json"], {
8305
9181
  env: { ...process.env, NO_COLOR: "1" },
8306
9182
  timeout: 6e5,
@@ -8314,9 +9190,9 @@ function runClaude(prompt, cmd) {
8314
9190
  if (code === 0) {
8315
9191
  try {
8316
9192
  const parsed = JSON.parse(stdout);
8317
- resolve3(parsed.result || stdout);
9193
+ resolve4(parsed.result || stdout);
8318
9194
  } catch {
8319
- resolve3(stdout);
9195
+ resolve4(stdout);
8320
9196
  }
8321
9197
  } else {
8322
9198
  reject(new Error(`claude exited ${code}: ${stderr.slice(0, 500)}`));
@@ -8328,7 +9204,7 @@ function runClaude(prompt, cmd) {
8328
9204
  });
8329
9205
  }
8330
9206
  function runOpencode(prompt, cmd) {
8331
- return new Promise((resolve3, reject) => {
9207
+ return new Promise((resolve4, reject) => {
8332
9208
  const proc = spawnTool(cmd, ["run"], {
8333
9209
  env: { ...process.env, NO_COLOR: "1", TERM: "dumb" },
8334
9210
  timeout: 6e5,
@@ -8340,7 +9216,7 @@ function runOpencode(prompt, cmd) {
8340
9216
  proc.stderr.on("data", (d) => stderr += d.toString());
8341
9217
  proc.on("close", (code) => {
8342
9218
  if (code === 0) {
8343
- resolve3(stripAnsi(stdout));
9219
+ resolve4(stripAnsi(stdout));
8344
9220
  } else {
8345
9221
  reject(new Error(`opencode exited ${code}: ${stripAnsi(stderr).slice(0, 500)}`));
8346
9222
  }
@@ -8351,7 +9227,7 @@ function runOpencode(prompt, cmd) {
8351
9227
  });
8352
9228
  }
8353
9229
  function runAgent(prompt, cmd) {
8354
- return new Promise((resolve3, reject) => {
9230
+ return new Promise((resolve4, reject) => {
8355
9231
  const proc = spawnTool(cmd, ["-p", "--output-format", "json", "--mode", "ask", "--trust"], {
8356
9232
  env: { ...process.env, NO_COLOR: "1" },
8357
9233
  timeout: 6e5,
@@ -8365,9 +9241,9 @@ function runAgent(prompt, cmd) {
8365
9241
  if (code === 0) {
8366
9242
  try {
8367
9243
  const parsed = JSON.parse(stdout);
8368
- resolve3(typeof parsed.result === "string" ? parsed.result : stdout);
9244
+ resolve4(typeof parsed.result === "string" ? parsed.result : stdout);
8369
9245
  } catch {
8370
- resolve3(stdout);
9246
+ resolve4(stdout);
8371
9247
  }
8372
9248
  } else {
8373
9249
  reject(new Error(`agent exited ${code}: ${stderr.slice(0, 500)}`));
@@ -8955,40 +9831,210 @@ async function generateToneAdjustment(session, tool, opts) {
8955
9831
  }
8956
9832
  if (allOverlays.length === 0 && totalSkipped === 0) return null;
8957
9833
  return {
8958
- overlays: allOverlays,
8959
- stats: { adjusted: allOverlays.length, skipped: totalSkipped }
9834
+ overlays: allOverlays,
9835
+ stats: { adjusted: allOverlays.length, skipped: totalSkipped }
9836
+ };
9837
+ }
9838
+ function stripAnsi(str) {
9839
+ return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\][^\x07]*\x07/g, "");
9840
+ }
9841
+ function locateCommand(cmd) {
9842
+ return new Promise((resolve4, reject) => {
9843
+ const locator = IS_WINDOWS ? "where" : "which";
9844
+ const proc = spawn(locator, [cmd], { timeout: 5e3 });
9845
+ let out = "";
9846
+ proc.stdout.on("data", (d) => out += d.toString());
9847
+ proc.on("close", (code) => {
9848
+ if (code !== 0) {
9849
+ resolve4(null);
9850
+ return;
9851
+ }
9852
+ const first = out.split(/\r?\n/).find((line) => line.trim());
9853
+ resolve4(first ? first.trim() : null);
9854
+ });
9855
+ proc.on("error", reject);
9856
+ });
9857
+ }
9858
+
9859
+ // src/server.ts
9860
+ init_insights();
9861
+ init_overlays();
9862
+ init_cloud();
9863
+
9864
+ // src/server-persistence.ts
9865
+ import { mkdir as mkdir5, readFile as readFile20, writeFile as writeFile6 } from "fs/promises";
9866
+ import { join as join20, resolve as resolve2 } from "path";
9867
+ async function loadAnnotations(baseDir, slug) {
9868
+ const dirs = [join20(baseDir, slug), resolve2("./vibe-replay", slug)];
9869
+ for (const dir of dirs) {
9870
+ try {
9871
+ const raw = await readFile20(join20(dir, "annotations.json"), "utf-8");
9872
+ const anns = JSON.parse(raw);
9873
+ if (Array.isArray(anns)) return anns;
9874
+ } catch {
9875
+ }
9876
+ }
9877
+ return [];
9878
+ }
9879
+ async function saveAnnotations(baseDir, slug, annotations) {
9880
+ await mkdir5(join20(baseDir, slug), { recursive: true });
9881
+ const annPath = join20(baseDir, slug, "annotations.json");
9882
+ await writeFile6(annPath, JSON.stringify(annotations, null, 2), "utf-8");
9883
+ }
9884
+ async function saveOverlays(baseDir, slug, overlays) {
9885
+ await mkdir5(join20(baseDir, slug), { recursive: true });
9886
+ const overlayPath = join20(baseDir, slug, "overlays.json");
9887
+ await writeFile6(overlayPath, JSON.stringify(overlays, null, 2), "utf-8");
9888
+ }
9889
+
9890
+ // src/server-routes/session-assets.ts
9891
+ init_overlays();
9892
+
9893
+ // src/server-core.ts
9894
+ import { homedir as homedir16 } from "os";
9895
+ import { basename as basename8 } from "path";
9896
+ function safeSlug(raw) {
9897
+ if (!raw) return null;
9898
+ const clean = basename8(raw);
9899
+ if (!clean || clean !== raw || clean === "." || clean === "..") return null;
9900
+ return clean;
9901
+ }
9902
+ function requireSlug(raw) {
9903
+ const slug = safeSlug(raw);
9904
+ if (!slug) return { error: "slug parameter is required" };
9905
+ return { slug };
9906
+ }
9907
+ function getErrorMessage(err) {
9908
+ if (err instanceof Error) return err.message;
9909
+ if (typeof err === "string") return err;
9910
+ return "Unknown error";
9911
+ }
9912
+ function normalizeProjectPath(project) {
9913
+ const home = homedir16();
9914
+ return project.startsWith(home) ? `~${project.slice(home.length)}` : project;
9915
+ }
9916
+ var MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST = 90;
9917
+ function chunkItems(items, maxItems) {
9918
+ if (maxItems <= 0) return [items.slice()];
9919
+ const chunks = [];
9920
+ for (let i = 0; i < items.length; i += maxItems) {
9921
+ chunks.push(items.slice(i, i + maxItems));
9922
+ }
9923
+ return chunks;
9924
+ }
9925
+ function buildInsightsSyncBatches(days, existingDates, today, maxDaysPerBatch = MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST) {
9926
+ const existing = new Set(existingDates);
9927
+ const pending = days.filter((day) => !existing.has(day.date) || day.date === today);
9928
+ return chunkItems(pending, maxDaysPerBatch);
9929
+ }
9930
+ function toStringArray(value) {
9931
+ if (value === void 0) return [];
9932
+ if (!Array.isArray(value)) return null;
9933
+ if (value.some((item) => typeof item !== "string")) return null;
9934
+ return value;
9935
+ }
9936
+ function resolveGenerateInputs(body, discoveredSessions) {
9937
+ const filePaths = toStringArray(body.filePaths);
9938
+ if (!filePaths) {
9939
+ return { ok: false, error: "filePaths must be an array of strings" };
9940
+ }
9941
+ const toolPaths = toStringArray(body.toolPaths);
9942
+ if (!toolPaths) {
9943
+ return { ok: false, error: "toolPaths must be an array of strings" };
9944
+ }
9945
+ const requestedSessionSlug = typeof body.sessionSlug === "string" ? safeSlug(body.sessionSlug) : null;
9946
+ const requestedSessionProject = typeof body.sessionProject === "string" ? normalizeProjectPath(body.sessionProject) : void 0;
9947
+ let sessionInfo;
9948
+ if (requestedSessionSlug) {
9949
+ const slugMatches = discoveredSessions.filter((s) => s.slug === requestedSessionSlug);
9950
+ if (requestedSessionProject) {
9951
+ sessionInfo = slugMatches.find(
9952
+ (s) => normalizeProjectPath(s.project) === requestedSessionProject
9953
+ );
9954
+ }
9955
+ sessionInfo = sessionInfo || slugMatches[0];
9956
+ }
9957
+ if (!sessionInfo && typeof body.sessionId === "string" && body.sessionId) {
9958
+ sessionInfo = discoveredSessions.find((s) => s.sessionId === body.sessionId);
9959
+ }
9960
+ const fallbackFilePaths = sessionInfo?.filePaths || [];
9961
+ const fallbackToolPaths = sessionInfo?.toolPaths || [];
9962
+ const paths = [
9963
+ ...filePaths.length > 0 ? filePaths : fallbackFilePaths,
9964
+ ...toolPaths.length > 0 ? toolPaths : fallbackToolPaths
9965
+ ];
9966
+ const hasCursorSessionFallback = body.provider === "cursor" && Boolean(sessionInfo?.sessionId);
9967
+ if (paths.length === 0 && !hasCursorSessionFallback) {
9968
+ return {
9969
+ ok: false,
9970
+ error: "filePaths is required (or provide a resolvable Cursor sessionSlug for SQLite/global-state sessions)"
9971
+ };
9972
+ }
9973
+ return {
9974
+ ok: true,
9975
+ value: {
9976
+ paths,
9977
+ sessionInfo
9978
+ }
8960
9979
  };
8961
9980
  }
8962
- function stripAnsi(str) {
8963
- return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\][^\x07]*\x07/g, "");
8964
- }
8965
- function locateCommand(cmd) {
8966
- return new Promise((resolve3, reject) => {
8967
- const locator = IS_WINDOWS ? "where" : "which";
8968
- const proc = spawn(locator, [cmd], { timeout: 5e3 });
8969
- let out = "";
8970
- proc.stdout.on("data", (d) => out += d.toString());
8971
- proc.on("close", (code) => {
8972
- if (code !== 0) {
8973
- resolve3(null);
8974
- return;
8975
- }
8976
- const first = out.split(/\r?\n/).find((line) => line.trim());
8977
- resolve3(first ? first.trim() : null);
8978
- });
8979
- proc.on("error", reject);
9981
+
9982
+ // src/server-routes/session-assets.ts
9983
+ function registerSessionAssetRoutes(app, deps) {
9984
+ const { baseDir } = deps;
9985
+ app.get("/api/annotations", async (c) => {
9986
+ const result = requireSlug(c.req.query("slug"));
9987
+ if ("error" in result) return c.json({ error: result.error }, 400);
9988
+ const anns = await loadAnnotations(baseDir, result.slug);
9989
+ return c.json(anns);
9990
+ });
9991
+ app.post("/api/annotations", async (c) => {
9992
+ const result = requireSlug(c.req.query("slug"));
9993
+ if ("error" in result) return c.json({ error: result.error }, 400);
9994
+ let body;
9995
+ try {
9996
+ body = await c.req.json();
9997
+ } catch {
9998
+ return c.json({ error: "invalid JSON body" }, 400);
9999
+ }
10000
+ try {
10001
+ await saveAnnotations(baseDir, result.slug, body);
10002
+ } catch (err) {
10003
+ return c.json({ error: `Failed to save annotations: ${getErrorMessage(err)}` }, 500);
10004
+ }
10005
+ return c.json({ ok: true });
10006
+ });
10007
+ app.get("/api/overlays", async (c) => {
10008
+ const result = requireSlug(c.req.query("slug"));
10009
+ if ("error" in result) return c.json({ error: result.error }, 400);
10010
+ const overlays = await loadOverlays(baseDir, result.slug);
10011
+ return c.json(overlays);
10012
+ });
10013
+ app.post("/api/overlays", async (c) => {
10014
+ const result = requireSlug(c.req.query("slug"));
10015
+ if ("error" in result) return c.json({ error: result.error }, 400);
10016
+ let body;
10017
+ try {
10018
+ body = await c.req.json();
10019
+ } catch {
10020
+ return c.json({ error: "invalid JSON body" }, 400);
10021
+ }
10022
+ if (!body || !Array.isArray(body.overlays)) {
10023
+ return c.json({ error: "invalid overlays shape" }, 400);
10024
+ }
10025
+ try {
10026
+ await saveOverlays(baseDir, result.slug, body);
10027
+ } catch (err) {
10028
+ return c.json({ error: `Failed to save overlays: ${getErrorMessage(err)}` }, 500);
10029
+ }
10030
+ return c.json({ ok: true });
8980
10031
  });
8981
10032
  }
8982
10033
 
8983
- // src/server.ts
8984
- init_insights();
8985
- init_overlays();
8986
- init_cloud();
8987
-
8988
10034
  // src/scanner.ts
8989
- import { readdir as readdir10, readFile as readFile18, stat as stat9 } from "fs/promises";
8990
- import { homedir as homedir14 } from "os";
8991
- import { join as join17 } from "path";
10035
+ import { readdir as readdir11, readFile as readFile21, stat as stat10 } from "fs/promises";
10036
+ import { homedir as homedir17 } from "os";
10037
+ import { join as join21 } from "path";
8992
10038
 
8993
10039
  // src/pricing.ts
8994
10040
  var MODEL_PRICING = {
@@ -9168,6 +10214,12 @@ async function scanSession(input) {
9168
10214
  } catch {
9169
10215
  }
9170
10216
  }
10217
+ if (input.provider === "pi") {
10218
+ try {
10219
+ return await scanPiSession(input);
10220
+ } catch {
10221
+ }
10222
+ }
9171
10223
  let startTime;
9172
10224
  let endTime;
9173
10225
  let model;
@@ -9193,7 +10245,7 @@ async function scanSession(input) {
9193
10245
  for (const filePath of input.filePaths) {
9194
10246
  let content;
9195
10247
  try {
9196
- content = await readFile18(filePath, "utf-8");
10248
+ content = await readFile21(filePath, "utf-8");
9197
10249
  } catch {
9198
10250
  continue;
9199
10251
  }
@@ -9315,15 +10367,15 @@ async function scanSession(input) {
9315
10367
  if (input.filePaths.length > 0) {
9316
10368
  const mainFile = input.filePaths[0];
9317
10369
  const sessionDir = mainFile.replace(/\.jsonl$/, "");
9318
- const subagentsDir = join17(sessionDir, "subagents");
10370
+ const subagentsDir = join21(sessionDir, "subagents");
9319
10371
  try {
9320
- const files = await readdir10(subagentsDir);
10372
+ const files = await readdir11(subagentsDir);
9321
10373
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
9322
10374
  subAgentCount = jsonlFiles.length;
9323
10375
  for (const saFile of jsonlFiles) {
9324
10376
  let saContent;
9325
10377
  try {
9326
- saContent = await readFile18(join17(subagentsDir, saFile), "utf-8");
10378
+ saContent = await readFile21(join21(subagentsDir, saFile), "utf-8");
9327
10379
  } catch {
9328
10380
  continue;
9329
10381
  }
@@ -9444,6 +10496,25 @@ async function scanCodexSession(input) {
9444
10496
  const parsed = await parseCodexSession(input.filePaths, sessionInfo);
9445
10497
  return buildScanResultFromParsed(input, parsed);
9446
10498
  }
10499
+ async function scanPiSession(input) {
10500
+ const sessionInfo = {
10501
+ provider: "pi",
10502
+ sessionId: input.sessionId,
10503
+ slug: input.slug,
10504
+ title: input.title,
10505
+ project: input.project,
10506
+ cwd: input.workspacePath || input.project,
10507
+ version: "",
10508
+ timestamp: input.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
10509
+ lineCount: 0,
10510
+ fileSize: 0,
10511
+ filePath: input.filePaths[0] || "",
10512
+ filePaths: input.filePaths,
10513
+ firstPrompt: input.firstPrompt || input.title || "(pi session)"
10514
+ };
10515
+ const parsed = await parsePiSession(input.filePaths, sessionInfo);
10516
+ return buildScanResultFromParsed(input, parsed);
10517
+ }
9447
10518
  async function scanCursorSession(input) {
9448
10519
  if (input.deferRichCursorParse && input.hasSqlite) {
9449
10520
  return buildLightweightCursorScanResult(input);
@@ -9616,7 +10687,7 @@ async function getFileMeta(filePaths) {
9616
10687
  let maxMtime = 0;
9617
10688
  for (const fp of filePaths) {
9618
10689
  try {
9619
- const s = await stat9(fp);
10690
+ const s = await stat10(fp);
9620
10691
  totalSize += s.size;
9621
10692
  if (s.mtimeMs > maxMtime) maxMtime = s.mtimeMs;
9622
10693
  } catch {
@@ -9969,33 +11040,33 @@ function buildAggregateDataQuality(scans) {
9969
11040
  }
9970
11041
  return notes.length > 0 ? { notes } : void 0;
9971
11042
  }
9972
- var CLAUDE_DIR2 = join17(homedir14(), ".claude", "projects");
11043
+ var CLAUDE_DIR2 = join21(homedir17(), ".claude", "projects");
9973
11044
  function encodeProjectDir(project) {
9974
11045
  let resolved = project;
9975
11046
  if (resolved.startsWith("~/")) {
9976
- resolved = join17(homedir14(), resolved.slice(2));
11047
+ resolved = join21(homedir17(), resolved.slice(2));
9977
11048
  } else if (resolved === "~") {
9978
- resolved = homedir14();
11049
+ resolved = homedir17();
9979
11050
  }
9980
11051
  return resolved.replace(/\//g, "-");
9981
11052
  }
9982
11053
  async function readProjectMemory(project) {
9983
11054
  const encoded = encodeProjectDir(project);
9984
- const projectDir = join17(CLAUDE_DIR2, encoded);
9985
- const memoryDir = join17(projectDir, "memory");
11055
+ const projectDir = join21(CLAUDE_DIR2, encoded);
11056
+ const memoryDir = join21(projectDir, "memory");
9986
11057
  const memoryFiles = [];
9987
11058
  let claudeMd;
9988
11059
  try {
9989
- const content = await readFile18(join17(projectDir, "CLAUDE.md"), "utf-8");
11060
+ const content = await readFile21(join21(projectDir, "CLAUDE.md"), "utf-8");
9990
11061
  if (content.trim()) claudeMd = content.slice(0, 5e3);
9991
11062
  } catch {
9992
11063
  }
9993
11064
  try {
9994
- const files = await readdir10(memoryDir);
11065
+ const files = await readdir11(memoryDir);
9995
11066
  for (const file of files) {
9996
11067
  if (!file.endsWith(".md") || file === "MEMORY.md") continue;
9997
11068
  try {
9998
- const content = await readFile18(join17(memoryDir, file), "utf-8");
11069
+ const content = await readFile21(join21(memoryDir, file), "utf-8");
9999
11070
  const fm = parseFrontmatter(content);
10000
11071
  memoryFiles.push({
10001
11072
  name: fm.name || file.replace(/\.md$/, ""),
@@ -10036,7 +11107,7 @@ function extractMetaText(content) {
10036
11107
  }
10037
11108
 
10038
11109
  // src/transform.ts
10039
- import { homedir as homedir15 } from "os";
11110
+ import { homedir as homedir18 } from "os";
10040
11111
 
10041
11112
  // src/utils/tokenEstimate.ts
10042
11113
  function estimateTokens(s) {
@@ -10046,7 +11117,7 @@ function estimateTokens(s) {
10046
11117
  }
10047
11118
 
10048
11119
  // src/transform.ts
10049
- var HOME = homedir15();
11120
+ var HOME = homedir18();
10050
11121
  function redactPath(s) {
10051
11122
  if (!HOME) return s;
10052
11123
  return s.replaceAll(HOME, "~");
@@ -10482,108 +11553,23 @@ function redactSecrets(s) {
10482
11553
  // src/server.ts
10483
11554
  init_utils();
10484
11555
  init_version();
10485
- function safeSlug(raw) {
10486
- if (!raw) return null;
10487
- const clean = basename6(raw);
10488
- if (!clean || clean !== raw || clean === "." || clean === "..") return null;
10489
- return clean;
10490
- }
10491
- function requireSlug(raw) {
10492
- const slug = safeSlug(raw);
10493
- if (!slug) return { error: "slug parameter is required" };
10494
- return { slug };
10495
- }
10496
- function getErrorMessage(err) {
10497
- if (err instanceof Error) return err.message;
10498
- if (typeof err === "string") return err;
10499
- return "Unknown error";
10500
- }
10501
- function normalizeProjectPath(project) {
10502
- const home = homedir16();
10503
- return project.startsWith(home) ? `~${project.slice(home.length)}` : project;
10504
- }
10505
- var MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST = 90;
10506
- function chunkItems(items, maxItems) {
10507
- if (maxItems <= 0) return [items.slice()];
10508
- const chunks = [];
10509
- for (let i = 0; i < items.length; i += maxItems) {
10510
- chunks.push(items.slice(i, i + maxItems));
10511
- }
10512
- return chunks;
10513
- }
10514
- function buildInsightsSyncBatches(days, existingDates, today, maxDaysPerBatch = MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST) {
10515
- const existing = new Set(existingDates);
10516
- const pending = days.filter((day) => !existing.has(day.date) || day.date === today);
10517
- return chunkItems(pending, maxDaysPerBatch);
10518
- }
10519
- function toStringArray(value) {
10520
- if (value === void 0) return [];
10521
- if (!Array.isArray(value)) return null;
10522
- if (value.some((item) => typeof item !== "string")) return null;
10523
- return value;
10524
- }
10525
- function resolveGenerateInputs(body, discoveredSessions) {
10526
- const filePaths = toStringArray(body.filePaths);
10527
- if (!filePaths) {
10528
- return { ok: false, error: "filePaths must be an array of strings" };
10529
- }
10530
- const toolPaths = toStringArray(body.toolPaths);
10531
- if (!toolPaths) {
10532
- return { ok: false, error: "toolPaths must be an array of strings" };
10533
- }
10534
- const requestedSessionSlug = typeof body.sessionSlug === "string" ? safeSlug(body.sessionSlug) : null;
10535
- const requestedSessionProject = typeof body.sessionProject === "string" ? normalizeProjectPath(body.sessionProject) : void 0;
10536
- let sessionInfo;
10537
- if (requestedSessionSlug) {
10538
- const slugMatches = discoveredSessions.filter((s) => s.slug === requestedSessionSlug);
10539
- if (requestedSessionProject) {
10540
- sessionInfo = slugMatches.find(
10541
- (s) => normalizeProjectPath(s.project) === requestedSessionProject
10542
- );
10543
- }
10544
- sessionInfo = sessionInfo || slugMatches[0];
10545
- }
10546
- if (!sessionInfo && typeof body.sessionId === "string" && body.sessionId) {
10547
- sessionInfo = discoveredSessions.find((s) => s.sessionId === body.sessionId);
10548
- }
10549
- const fallbackFilePaths = sessionInfo?.filePaths || [];
10550
- const fallbackToolPaths = sessionInfo?.toolPaths || [];
10551
- const paths = [
10552
- ...filePaths.length > 0 ? filePaths : fallbackFilePaths,
10553
- ...toolPaths.length > 0 ? toolPaths : fallbackToolPaths
10554
- ];
10555
- const hasCursorSessionFallback = body.provider === "cursor" && Boolean(sessionInfo?.sessionId);
10556
- if (paths.length === 0 && !hasCursorSessionFallback) {
10557
- return {
10558
- ok: false,
10559
- error: "filePaths is required (or provide a resolvable Cursor sessionSlug for SQLite/global-state sessions)"
10560
- };
10561
- }
10562
- return {
10563
- ok: true,
10564
- value: {
10565
- paths,
10566
- sessionInfo
10567
- }
10568
- };
10569
- }
10570
11556
  var ARCHIVE_DIR = ".archive";
10571
11557
  async function getArchivedSlugs(baseDir) {
10572
11558
  try {
10573
- const entries = await readdir11(join18(baseDir, ARCHIVE_DIR));
11559
+ const entries = await readdir12(join22(baseDir, ARCHIVE_DIR));
10574
11560
  return new Set(entries);
10575
11561
  } catch {
10576
11562
  return /* @__PURE__ */ new Set();
10577
11563
  }
10578
11564
  }
10579
11565
  async function archiveSlug(baseDir, slug) {
10580
- const dir = join18(baseDir, ARCHIVE_DIR);
10581
- await mkdir5(dir, { recursive: true });
10582
- await writeFile6(join18(dir, slug), "");
11566
+ const dir = join22(baseDir, ARCHIVE_DIR);
11567
+ await mkdir6(dir, { recursive: true });
11568
+ await writeFile7(join22(dir, slug), "");
10583
11569
  }
10584
11570
  async function unarchiveSlug(baseDir, slug) {
10585
11571
  try {
10586
- await unlink2(join18(baseDir, ARCHIVE_DIR, slug));
11572
+ await unlink2(join22(baseDir, ARCHIVE_DIR, slug));
10587
11573
  } catch {
10588
11574
  }
10589
11575
  }
@@ -10591,29 +11577,22 @@ async function scanSessionsFromDir(baseDir) {
10591
11577
  const results = [];
10592
11578
  let entries;
10593
11579
  try {
10594
- entries = await readdir11(baseDir);
11580
+ entries = await readdir12(baseDir);
10595
11581
  } catch {
10596
11582
  return results;
10597
11583
  }
10598
11584
  for (const entry of entries) {
10599
- const replayPath = join18(baseDir, entry, "replay.json");
11585
+ const replayPath = join22(baseDir, entry, "replay.json");
10600
11586
  try {
10601
- const raw = await readFile19(replayPath, "utf-8");
11587
+ const raw = await readFile22(replayPath, "utf-8");
10602
11588
  const session = JSON.parse(raw);
10603
- const annotationsPath = join18(baseDir, entry, "annotations.json");
10604
- let annotationCount = 0;
10605
- try {
10606
- const annRaw = await readFile19(annotationsPath, "utf-8");
10607
- const anns = JSON.parse(annRaw);
10608
- annotationCount = Array.isArray(anns) ? anns.length : 0;
10609
- } catch {
10610
- }
11589
+ const annotationCount = (await loadAnnotations(baseDir, entry)).length;
10611
11590
  let gist;
10612
11591
  try {
10613
- gist = await loadSavedGistInfo(join18(baseDir, entry));
11592
+ gist = await loadSavedGistInfo(join22(baseDir, entry));
10614
11593
  } catch {
10615
11594
  }
10616
- const cloudInfo = await loadSavedCloudInfo(join18(baseDir, entry));
11595
+ const cloudInfo = await loadSavedCloudInfo(join22(baseDir, entry));
10617
11596
  const userPrompts = (session.scenes || []).filter((sc) => sc.type === "user-prompt").map((sc) => previewPrompt(sc.content)).filter((m) => m.length >= 10);
10618
11597
  const firstMessage = userPrompts[0] || void 0;
10619
11598
  const messages = userPrompts.length > 0 ? userPrompts.slice(0, 2) : void 0;
@@ -10641,8 +11620,8 @@ async function scanSessionsFromDir(baseDir) {
10641
11620
  let outdated = false;
10642
11621
  if (gist?.contentHash) {
10643
11622
  try {
10644
- const content = await readFile19(replayPath, "utf-8");
10645
- const currentHash = createHash4("sha256").update(content).digest("hex").slice(0, 16);
11623
+ const content = await readFile22(replayPath, "utf-8");
11624
+ const currentHash = createHash5("sha256").update(content).digest("hex").slice(0, 16);
10646
11625
  outdated = currentHash !== gist?.contentHash;
10647
11626
  } catch {
10648
11627
  }
@@ -10668,7 +11647,7 @@ async function scanSessionsFromDir(baseDir) {
10668
11647
  }
10669
11648
  async function scanSessions(baseDir) {
10670
11649
  const dirs = [baseDir];
10671
- const cwdLocal = resolve2("./vibe-replay");
11650
+ const cwdLocal = resolve3("./vibe-replay");
10672
11651
  if (cwdLocal !== baseDir) {
10673
11652
  dirs.push(cwdLocal);
10674
11653
  }
@@ -10687,25 +11666,19 @@ async function scanSessions(baseDir) {
10687
11666
  return allResults;
10688
11667
  }
10689
11668
  async function loadSessionFromDisk(baseDir, slug) {
10690
- let replayPath = join18(baseDir, slug, "replay.json");
11669
+ let replayPath = join22(baseDir, slug, "replay.json");
10691
11670
  try {
10692
- await stat10(replayPath);
11671
+ await stat11(replayPath);
10693
11672
  } catch {
10694
- const fallback = resolve2("./vibe-replay", slug, "replay.json");
10695
- await stat10(fallback);
11673
+ const fallback = resolve3("./vibe-replay", slug, "replay.json");
11674
+ await stat11(fallback);
10696
11675
  replayPath = fallback;
10697
11676
  }
10698
- const raw = await readFile19(replayPath, "utf-8");
11677
+ const raw = await readFile22(replayPath, "utf-8");
10699
11678
  const session = JSON.parse(raw);
10700
- const sessionDir = dirname5(replayPath);
10701
- const annotationsPath = join18(sessionDir, "annotations.json");
10702
- try {
10703
- const annRaw = await readFile19(annotationsPath, "utf-8");
10704
- const anns = JSON.parse(annRaw);
10705
- if (Array.isArray(anns) && anns.length > 0) {
10706
- session.annotations = anns;
10707
- }
10708
- } catch {
11679
+ const annotations = await loadAnnotations(baseDir, slug);
11680
+ if (annotations.length > 0) {
11681
+ session.annotations = annotations;
10709
11682
  }
10710
11683
  return session;
10711
11684
  }
@@ -10873,13 +11846,13 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
10873
11846
  const projectExistsMap = /* @__PURE__ */ new Map();
10874
11847
  const projectIsGitMap = /* @__PURE__ */ new Map();
10875
11848
  for (const p of uniqueProjects) {
10876
- const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ? join18(home, p.slice(2)) : p;
11849
+ const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ? join22(home, p.slice(2)) : p;
10877
11850
  try {
10878
- const s = await stat10(resolved);
11851
+ const s = await stat11(resolved);
10879
11852
  projectExistsMap.set(p, s.isDirectory());
10880
11853
  if (s.isDirectory()) {
10881
11854
  try {
10882
- await stat10(join18(resolved, ".git"));
11855
+ await stat11(join22(resolved, ".git"));
10883
11856
  projectIsGitMap.set(p, true);
10884
11857
  } catch {
10885
11858
  projectIsGitMap.set(p, false);
@@ -10959,10 +11932,10 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
10959
11932
  });
10960
11933
  }
10961
11934
  async function readClaudeSessionState(sessionId) {
10962
- const sessionsDir = join18(homedir16(), ".claude", "sessions");
11935
+ const sessionsDir = join22(homedir19(), ".claude", "sessions");
10963
11936
  let files;
10964
11937
  try {
10965
- files = await readdir11(sessionsDir);
11938
+ files = await readdir12(sessionsDir);
10966
11939
  } catch {
10967
11940
  return "stopped";
10968
11941
  }
@@ -10970,7 +11943,7 @@ async function readClaudeSessionState(sessionId) {
10970
11943
  if (!file.endsWith(".json")) continue;
10971
11944
  let data;
10972
11945
  try {
10973
- const content = await readFile19(join18(sessionsDir, file), "utf-8");
11946
+ const content = await readFile22(join22(sessionsDir, file), "utf-8");
10974
11947
  data = JSON.parse(content);
10975
11948
  } catch {
10976
11949
  continue;
@@ -11019,32 +11992,12 @@ function mergeSameSessions(sessions) {
11019
11992
  result.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
11020
11993
  return result;
11021
11994
  }
11022
- async function loadAnnotations(baseDir, slug) {
11023
- const dirs = [join18(baseDir, slug), resolve2("./vibe-replay", slug)];
11024
- for (const dir of dirs) {
11025
- try {
11026
- const raw = await readFile19(join18(dir, "annotations.json"), "utf-8");
11027
- const anns = JSON.parse(raw);
11028
- if (Array.isArray(anns)) return anns;
11029
- } catch {
11030
- }
11031
- }
11032
- return [];
11033
- }
11034
- async function saveAnnotations(baseDir, slug, annotations) {
11035
- const annPath = join18(baseDir, slug, "annotations.json");
11036
- await writeFile6(annPath, JSON.stringify(annotations, null, 2), "utf-8");
11037
- }
11038
- async function saveOverlays(baseDir, slug, overlays) {
11039
- const overlayPath = join18(baseDir, slug, "overlays.json");
11040
- await writeFile6(overlayPath, JSON.stringify(overlays, null, 2), "utf-8");
11041
- }
11042
11995
  async function startServer(baseDir, opts) {
11043
- await mkdir5(baseDir, { recursive: true });
11996
+ await mkdir6(baseDir, { recursive: true });
11044
11997
  const isDevMode = !!opts?.externalViewerUrl;
11045
11998
  const viewerHtml = isDevMode ? "" : await loadViewerHtml();
11046
11999
  const cleanupPeriodDays = await getClaudeCodeCleanupPeriod();
11047
- const cacheKeySuffix = createHash4("sha1").update(baseDir).digest("hex").slice(0, 12);
12000
+ const cacheKeySuffix = createHash5("sha1").update(baseDir).digest("hex").slice(0, 12);
11048
12001
  const sourcesCacheKey = `dashboard-sources-v4-${cacheKeySuffix}`;
11049
12002
  const replaysCacheKey = `dashboard-replays-v1-${cacheKeySuffix}`;
11050
12003
  const scanResultsCacheKey = `dashboard-scan-results-v1-${cacheKeySuffix}`;
@@ -11384,7 +12337,7 @@ async function startServer(baseDir, opts) {
11384
12337
  allSessions.push(...sessions);
11385
12338
  }
11386
12339
  const merged = mergeSameSessions(allSessions);
11387
- const home = homedir16();
12340
+ const home = homedir19();
11388
12341
  for (const s of merged) {
11389
12342
  if (s.project.startsWith(home)) {
11390
12343
  s.project = `~${s.project.slice(home.length)}`;
@@ -11498,7 +12451,7 @@ async function startServer(baseDir, opts) {
11498
12451
  await sendError(`Session not found: ${sessionId}`);
11499
12452
  return;
11500
12453
  }
11501
- const home = homedir16();
12454
+ const home = homedir19();
11502
12455
  const projectFor = (info) => info.project.startsWith(home) ? `~${info.project.slice(home.length)}` : info.project;
11503
12456
  const watchers = [];
11504
12457
  const watchedPaths = /* @__PURE__ */ new Set();
@@ -11511,7 +12464,8 @@ async function startServer(baseDir, opts) {
11511
12464
  const isClaudeProvider = providerName === "claude-code";
11512
12465
  const isCursorProvider = providerName === "cursor";
11513
12466
  const isCodexProvider = providerName === "codex";
11514
- const isJsonlLiveProvider = isClaudeProvider || isCodexProvider;
12467
+ const isPiProvider = providerName === "pi";
12468
+ const isJsonlLiveProvider = isClaudeProvider || isCodexProvider || isPiProvider;
11515
12469
  let lastLiveState = isClaudeProvider ? "busy" : "unknown";
11516
12470
  let cursorDbWatchAttached = false;
11517
12471
  const cursorDbWatchedSessionIds = /* @__PURE__ */ new Set();
@@ -11565,13 +12519,13 @@ async function startServer(baseDir, opts) {
11565
12519
  const cached = jsonlTail.get(filePath);
11566
12520
  let size;
11567
12521
  try {
11568
- size = (await stat10(filePath)).size;
12522
+ size = (await stat11(filePath)).size;
11569
12523
  } catch {
11570
12524
  jsonlTail.delete(filePath);
11571
12525
  return cached?.lines ?? [];
11572
12526
  }
11573
12527
  if (!cached || size < cached.offset) {
11574
- const content = await readFile19(filePath);
12528
+ const content = await readFile22(filePath);
11575
12529
  const { lines, partial } = splitDecodedLines(content);
11576
12530
  jsonlTail.set(filePath, { offset: content.length, partial, lines });
11577
12531
  return lines;
@@ -11629,7 +12583,7 @@ async function startServer(baseDir, opts) {
11629
12583
  const lines = await tailReadJsonl(fp);
11630
12584
  allLines.push(...lines);
11631
12585
  }
11632
- parsed = isClaudeProvider ? await parseClaudeCodeLines(allLines, { subagentsSourcePath: paths[0] }) : parseCodexLines(allLines, info, paths);
12586
+ parsed = isClaudeProvider ? await parseClaudeCodeLines(allLines, { subagentsSourcePath: paths[0] }) : isCodexProvider ? parseCodexLines(allLines, info, paths) : parsePiLines(allLines, { sourcePath: paths[0], sessionInfo: info });
11633
12587
  } else {
11634
12588
  parsed = await provider.parse(paths, info);
11635
12589
  }
@@ -11717,7 +12671,7 @@ async function startServer(baseDir, opts) {
11717
12671
  if (aborted) return;
11718
12672
  scheduleRebuild(0);
11719
12673
  }, RESOLVE_REFRESH_INTERVAL_MS);
11720
- await new Promise((resolve3) => {
12674
+ await new Promise((resolve4) => {
11721
12675
  stream.onAbort(() => {
11722
12676
  aborted = true;
11723
12677
  if (pendingTimer) clearTimeout(pendingTimer);
@@ -11731,7 +12685,7 @@ async function startServer(baseDir, opts) {
11731
12685
  } catch {
11732
12686
  }
11733
12687
  }
11734
- resolve3();
12688
+ resolve4();
11735
12689
  });
11736
12690
  });
11737
12691
  });
@@ -11763,8 +12717,8 @@ async function startServer(baseDir, opts) {
11763
12717
  try {
11764
12718
  const target = await loadSessionFromDisk(baseDir, slug);
11765
12719
  target.meta.title = normalizeTitle(body.title);
11766
- const targetDir = join18(baseDir, slug);
11767
- await writeFile6(join18(targetDir, "replay.json"), JSON.stringify(target), "utf-8");
12720
+ const targetDir = join22(baseDir, slug);
12721
+ await writeFile7(join22(targetDir, "replay.json"), JSON.stringify(target), "utf-8");
11768
12722
  await generateOutput(target, targetDir);
11769
12723
  const updatedReplays = await refreshReplaysCache();
11770
12724
  if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
@@ -11778,7 +12732,7 @@ async function startServer(baseDir, opts) {
11778
12732
  if (!slug) return c.json({ error: "invalid slug" }, 400);
11779
12733
  try {
11780
12734
  const { rm } = await import("fs/promises");
11781
- await rm(join18(baseDir, slug), { recursive: true });
12735
+ await rm(join22(baseDir, slug), { recursive: true });
11782
12736
  const updatedReplays = await refreshReplaysCache();
11783
12737
  if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
11784
12738
  return c.json({ ok: true });
@@ -11820,7 +12774,7 @@ async function startServer(baseDir, opts) {
11820
12774
  }
11821
12775
  const cursorProvider2 = getProvider("cursor");
11822
12776
  if (!cursorProvider2) return c.json({ ok: false, message: "Cursor provider unavailable" }, 404);
11823
- const home = homedir16();
12777
+ const home = homedir19();
11824
12778
  let cursorSessions = lastDiscoveredMergedSessions.filter(
11825
12779
  (session) => session.provider === "cursor"
11826
12780
  );
@@ -11851,12 +12805,12 @@ async function startServer(baseDir, opts) {
11851
12805
  allSessions.push(...sessions);
11852
12806
  }
11853
12807
  const merged = mergeSameSessions(allSessions);
11854
- lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir16());
12808
+ lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir19());
11855
12809
  const previous = await readFileCache(sourcesCacheKey);
11856
12810
  const result = await buildSourcesResult(
11857
12811
  merged,
11858
12812
  baseDir,
11859
- homedir16(),
12813
+ homedir19(),
11860
12814
  previous?.data || [],
11861
12815
  cleanupPeriodDays
11862
12816
  );
@@ -11886,12 +12840,12 @@ async function startServer(baseDir, opts) {
11886
12840
  }
11887
12841
  }
11888
12842
  const merged = mergeSameSessions(allSessions);
11889
- lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir16());
12843
+ lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir19());
11890
12844
  const previous = await readFileCache(sourcesCacheKey);
11891
12845
  const result = await buildSourcesResult(
11892
12846
  merged,
11893
12847
  baseDir,
11894
- homedir16(),
12848
+ homedir19(),
11895
12849
  previous?.data || [],
11896
12850
  cleanupPeriodDays
11897
12851
  );
@@ -11926,7 +12880,7 @@ async function startServer(baseDir, opts) {
11926
12880
  return c.json({ error: "title must be a string" }, 400);
11927
12881
  }
11928
12882
  const parsed = await provider.parse(resolved.value.paths, resolved.value.sessionInfo);
11929
- const home = homedir16();
12883
+ const home = homedir19();
11930
12884
  const rawProject = body.sessionProject || parsed.cwd;
11931
12885
  const project = rawProject.startsWith(home) ? `~${rawProject.slice(home.length)}` : rawProject;
11932
12886
  const replay = transformToReplay(parsed, body.provider, project, {
@@ -11944,7 +12898,7 @@ async function startServer(baseDir, opts) {
11944
12898
  }
11945
12899
  const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
11946
12900
  const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
11947
- const outputDir = join18(baseDir, slug);
12901
+ const outputDir = join22(baseDir, slug);
11948
12902
  await generateOutput(replay, outputDir);
11949
12903
  const updatedReplays = await refreshReplaysCache();
11950
12904
  if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
@@ -11967,7 +12921,7 @@ async function startServer(baseDir, opts) {
11967
12921
  });
11968
12922
  app.post("/api/regenerate-all", async (c) => {
11969
12923
  const replaysDir = baseDir;
11970
- const { readdir: readdir12, readFile: readF } = await import("fs/promises");
12924
+ const { readdir: readdir13, readFile: readF } = await import("fs/promises");
11971
12925
  const results = [];
11972
12926
  const allProviders = getAllProviders();
11973
12927
  const allSessions = [];
@@ -11980,14 +12934,14 @@ async function startServer(baseDir, opts) {
11980
12934
  }
11981
12935
  let entries;
11982
12936
  try {
11983
- entries = await readdir12(replaysDir);
12937
+ entries = await readdir13(replaysDir);
11984
12938
  } catch {
11985
12939
  return c.json({ error: "No replays directory" }, 404);
11986
12940
  }
11987
12941
  for (const slug of entries) {
11988
12942
  if (slug.startsWith(".") || slug === "cache") continue;
11989
12943
  try {
11990
- const replayPath = join18(replaysDir, slug, "replay.json");
12944
+ const replayPath = join22(replaysDir, slug, "replay.json");
11991
12945
  const raw = await readF(replayPath, "utf-8").catch(() => null);
11992
12946
  if (!raw) continue;
11993
12947
  const oldReplay = JSON.parse(raw);
@@ -12009,7 +12963,7 @@ async function startServer(baseDir, opts) {
12009
12963
  }
12010
12964
  const paths = [...sessionInfo.filePaths, ...sessionInfo.toolPaths || []];
12011
12965
  const parsed = await provider.parse(paths, sessionInfo);
12012
- const home = homedir16();
12966
+ const home = homedir19();
12013
12967
  const project = sessionInfo.project.startsWith(home) ? `~${sessionInfo.project.slice(home.length)}` : sessionInfo.project;
12014
12968
  const replay = transformToReplay(parsed, providerName, project, {
12015
12969
  generator: {
@@ -12019,7 +12973,7 @@ async function startServer(baseDir, opts) {
12019
12973
  }
12020
12974
  });
12021
12975
  if (oldReplay.meta?.title) replay.meta.title = oldReplay.meta.title;
12022
- const outputDir = join18(replaysDir, slug);
12976
+ const outputDir = join22(replaysDir, slug);
12023
12977
  await generateOutput(replay, outputDir);
12024
12978
  results.push({ slug, status: "regenerated", scenes: replay.scenes.length });
12025
12979
  } catch (err) {
@@ -12124,28 +13078,7 @@ async function startServer(baseDir, opts) {
12124
13078
  if (!memory) return c.json({ memoryFiles: [], claudeMd: null });
12125
13079
  return c.json(memory);
12126
13080
  });
12127
- app.get("/api/annotations", async (c) => {
12128
- const result = requireSlug(c.req.query("slug"));
12129
- if ("error" in result) return c.json({ error: result.error }, 400);
12130
- const anns = await loadAnnotations(baseDir, result.slug);
12131
- return c.json(anns);
12132
- });
12133
- app.post("/api/annotations", async (c) => {
12134
- const result = requireSlug(c.req.query("slug"));
12135
- if ("error" in result) return c.json({ error: result.error }, 400);
12136
- let body;
12137
- try {
12138
- body = await c.req.json();
12139
- } catch {
12140
- return c.json({ error: "invalid JSON body" }, 400);
12141
- }
12142
- try {
12143
- await saveAnnotations(baseDir, result.slug, body);
12144
- } catch (err) {
12145
- return c.json({ error: `Failed to save annotations: ${getErrorMessage(err)}` }, 500);
12146
- }
12147
- return c.json({ ok: true });
12148
- });
13081
+ registerSessionAssetRoutes(app, { baseDir });
12149
13082
  app.get("/api/gh-status", (c) => {
12150
13083
  return c.json(checkPublishStatus());
12151
13084
  });
@@ -12526,7 +13459,7 @@ async function startServer(baseDir, opts) {
12526
13459
  app.get("/api/gist-info", async (c) => {
12527
13460
  const result = requireSlug(c.req.query("slug"));
12528
13461
  if ("error" in result) return c.json({ error: result.error }, 400);
12529
- const targetDir = join18(baseDir, result.slug);
13462
+ const targetDir = join22(baseDir, result.slug);
12530
13463
  const gist = await loadSavedGistInfo(targetDir);
12531
13464
  if (!gist) return c.json({ gist: null });
12532
13465
  return c.json({ gist });
@@ -12534,7 +13467,7 @@ async function startServer(baseDir, opts) {
12534
13467
  app.delete("/api/gist-info", async (c) => {
12535
13468
  const result = requireSlug(c.req.query("slug"));
12536
13469
  if ("error" in result) return c.json({ error: result.error }, 400);
12537
- const metaPath = join18(baseDir, result.slug, ".vibe-replay-gist.json");
13470
+ const metaPath = join22(baseDir, result.slug, ".vibe-replay-gist.json");
12538
13471
  await unlink2(metaPath).catch(() => {
12539
13472
  });
12540
13473
  return c.json({ ok: true });
@@ -12542,7 +13475,7 @@ async function startServer(baseDir, opts) {
12542
13475
  app.get("/api/cloud-info", async (c) => {
12543
13476
  const result = requireSlug(c.req.query("slug"));
12544
13477
  if ("error" in result) return c.json({ error: result.error }, 400);
12545
- const targetDir = join18(baseDir, result.slug);
13478
+ const targetDir = join22(baseDir, result.slug);
12546
13479
  const cloud = await loadSavedCloudInfo(targetDir);
12547
13480
  if (!cloud) return c.json({ cloud: null });
12548
13481
  return c.json({ cloud });
@@ -12550,11 +13483,11 @@ async function startServer(baseDir, opts) {
12550
13483
  app.post("/api/cloud-info", async (c) => {
12551
13484
  const result = requireSlug(c.req.query("slug"));
12552
13485
  if ("error" in result) return c.json({ error: result.error }, 400);
12553
- const targetDir = join18(baseDir, result.slug);
13486
+ const targetDir = join22(baseDir, result.slug);
12554
13487
  const body = await c.req.json();
12555
13488
  if (!body.id || !body.url) return c.json({ error: "Missing id/url" }, 400);
12556
- const metaPath = join18(targetDir, ".vibe-replay-cloud.json");
12557
- await writeFile6(
13489
+ const metaPath = join22(targetDir, ".vibe-replay-cloud.json");
13490
+ await writeFile7(
12558
13491
  metaPath,
12559
13492
  JSON.stringify(
12560
13493
  {
@@ -12573,7 +13506,7 @@ async function startServer(baseDir, opts) {
12573
13506
  app.delete("/api/cloud-info", async (c) => {
12574
13507
  const result = requireSlug(c.req.query("slug"));
12575
13508
  if ("error" in result) return c.json({ error: result.error }, 400);
12576
- const metaPath = join18(baseDir, result.slug, ".vibe-replay-cloud.json");
13509
+ const metaPath = join22(baseDir, result.slug, ".vibe-replay-cloud.json");
12577
13510
  await unlink2(metaPath).catch(() => {
12578
13511
  });
12579
13512
  return c.json({ ok: true });
@@ -12581,14 +13514,14 @@ async function startServer(baseDir, opts) {
12581
13514
  app.post("/api/publish/gist", async (c) => {
12582
13515
  const result = requireSlug(c.req.query("slug"));
12583
13516
  if ("error" in result) return c.json({ error: result.error }, 400);
12584
- const targetDir = join18(baseDir, result.slug);
13517
+ const targetDir = join22(baseDir, result.slug);
12585
13518
  try {
12586
13519
  const rawSession = await loadSessionFromDisk(baseDir, result.slug);
12587
13520
  const overlaysData = await loadOverlays(baseDir, result.slug);
12588
13521
  const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
12589
- const replayPath = join18(targetDir, "replay.json");
12590
- const originalContent = await readFile19(replayPath, "utf-8");
12591
- await writeFile6(replayPath, JSON.stringify(targetSession), "utf-8");
13522
+ const replayPath = join22(targetDir, "replay.json");
13523
+ const originalContent = await readFile22(replayPath, "utf-8");
13524
+ await writeFile7(replayPath, JSON.stringify(targetSession), "utf-8");
12592
13525
  try {
12593
13526
  const title = targetSession.meta.title || targetSession.meta.slug;
12594
13527
  const savedGist = await loadSavedGistInfo(targetDir);
@@ -12597,7 +13530,7 @@ async function startServer(baseDir, opts) {
12597
13530
  });
12598
13531
  return c.json(gistResult);
12599
13532
  } finally {
12600
- await writeFile6(replayPath, originalContent, "utf-8");
13533
+ await writeFile7(replayPath, originalContent, "utf-8");
12601
13534
  }
12602
13535
  } catch (err) {
12603
13536
  return c.json({ error: getErrorMessage(err) }, 500);
@@ -12606,7 +13539,7 @@ async function startServer(baseDir, opts) {
12606
13539
  app.post("/api/publish/cloud", async (c) => {
12607
13540
  const result = requireSlug(c.req.query("slug"));
12608
13541
  if ("error" in result) return c.json({ error: result.error }, 400);
12609
- const targetDir = join18(baseDir, result.slug);
13542
+ const targetDir = join22(baseDir, result.slug);
12610
13543
  try {
12611
13544
  const body = await c.req.json().catch(() => ({}));
12612
13545
  const cloudResult = await publishCloudWithOverlays(targetDir, {
@@ -12620,18 +13553,18 @@ async function startServer(baseDir, opts) {
12620
13553
  app.post("/api/export/html", async (c) => {
12621
13554
  const result = requireSlug(c.req.query("slug"));
12622
13555
  if ("error" in result) return c.json({ error: result.error }, 400);
12623
- const targetDir = join18(baseDir, result.slug);
13556
+ const targetDir = join22(baseDir, result.slug);
12624
13557
  try {
12625
13558
  const rawSession = await loadSessionFromDisk(baseDir, result.slug);
12626
13559
  const overlaysData = await loadOverlays(baseDir, result.slug);
12627
13560
  const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
12628
- const replayPath = join18(targetDir, "replay.json");
12629
- const originalContent = await readFile19(replayPath, "utf-8");
13561
+ const replayPath = join22(targetDir, "replay.json");
13562
+ const originalContent = await readFile22(replayPath, "utf-8");
12630
13563
  try {
12631
13564
  const outputPath = await generateOutput(targetSession, targetDir);
12632
13565
  return c.json({ path: outputPath });
12633
13566
  } finally {
12634
- await writeFile6(replayPath, originalContent, "utf-8");
13567
+ await writeFile7(replayPath, originalContent, "utf-8");
12635
13568
  }
12636
13569
  } catch (err) {
12637
13570
  return c.json({ error: getErrorMessage(err) }, 500);
@@ -12640,23 +13573,23 @@ async function startServer(baseDir, opts) {
12640
13573
  app.get("/api/export/github/status", async (c) => {
12641
13574
  const result = requireSlug(c.req.query("slug"));
12642
13575
  if ("error" in result) return c.json({ error: result.error }, 400);
12643
- const targetDir = join18(baseDir, result.slug);
13576
+ const targetDir = join22(baseDir, result.slug);
12644
13577
  try {
12645
- const svgPath = join18(targetDir, "session-preview.svg");
12646
- const mdPath = join18(targetDir, "github-summary.md");
12647
- const gifPath = join18(targetDir, "session-preview.gif");
13578
+ const svgPath = join22(targetDir, "session-preview.svg");
13579
+ const mdPath = join22(targetDir, "github-summary.md");
13580
+ const gifPath = join22(targetDir, "session-preview.gif");
12648
13581
  const [svgContent, markdown, gifBuf] = await Promise.all([
12649
- readFile19(svgPath, "utf-8").catch(() => null),
12650
- readFile19(mdPath, "utf-8").catch(() => null),
12651
- readFile19(gifPath).catch(() => null)
13582
+ readFile22(svgPath, "utf-8").catch(() => null),
13583
+ readFile22(mdPath, "utf-8").catch(() => null),
13584
+ readFile22(gifPath).catch(() => null)
12652
13585
  ]);
12653
13586
  if (!svgContent && !markdown && !gifBuf) return c.json({ exists: false });
12654
13587
  const gist = await loadSavedGistInfo(targetDir);
12655
13588
  const gifContent = gifBuf ? gifBuf.toString("base64") : null;
12656
13589
  const [gifMtime, svgMtime, mdMtime] = await Promise.all([
12657
- stat10(gifPath).then((s) => s.mtime.toISOString()).catch(() => null),
12658
- stat10(svgPath).then((s) => s.mtime.toISOString()).catch(() => null),
12659
- stat10(mdPath).then((s) => s.mtime.toISOString()).catch(() => null)
13590
+ stat11(gifPath).then((s) => s.mtime.toISOString()).catch(() => null),
13591
+ stat11(svgPath).then((s) => s.mtime.toISOString()).catch(() => null),
13592
+ stat11(mdPath).then((s) => s.mtime.toISOString()).catch(() => null)
12660
13593
  ]);
12661
13594
  return c.json({
12662
13595
  exists: true,
@@ -12678,7 +13611,7 @@ async function startServer(baseDir, opts) {
12678
13611
  app.post("/api/export/github", async (c) => {
12679
13612
  const result = requireSlug(c.req.query("slug"));
12680
13613
  if ("error" in result) return c.json({ error: result.error }, 400);
12681
- const targetDir = join18(baseDir, result.slug);
13614
+ const targetDir = join22(baseDir, result.slug);
12682
13615
  try {
12683
13616
  const rawSession = await loadSessionFromDisk(baseDir, result.slug);
12684
13617
  const overlaysData = await loadOverlays(baseDir, result.slug);
@@ -12686,15 +13619,15 @@ async function startServer(baseDir, opts) {
12686
13619
  const gist = await loadSavedGistInfo(targetDir);
12687
13620
  const replayUrl = gist?.viewerUrl || void 0;
12688
13621
  const svgContent = generateGitHubSvg(targetSession, { replayUrl });
12689
- const svgFilePath = join18(targetDir, "session-preview.svg");
12690
- await writeFile6(svgFilePath, svgContent, "utf-8");
13622
+ const svgFilePath = join22(targetDir, "session-preview.svg");
13623
+ await writeFile7(svgFilePath, svgContent, "utf-8");
12691
13624
  let gifContent = null;
12692
13625
  let gifFilePath = null;
12693
13626
  let gifWarning;
12694
13627
  try {
12695
13628
  const gifBuffer = await generateGitHubGif(targetSession, { replayUrl });
12696
- gifFilePath = join18(targetDir, "session-preview.gif");
12697
- await writeFile6(gifFilePath, gifBuffer);
13629
+ gifFilePath = join22(targetDir, "session-preview.gif");
13630
+ await writeFile7(gifFilePath, gifBuffer);
12698
13631
  gifContent = gifBuffer.toString("base64");
12699
13632
  } catch (err) {
12700
13633
  gifWarning = `GIF generation failed: ${err instanceof Error ? err.message : String(err)}`;
@@ -12704,8 +13637,8 @@ async function startServer(baseDir, opts) {
12704
13637
  svgPath: "./session-preview.svg",
12705
13638
  gifPath: gifContent ? "./session-preview.gif" : void 0
12706
13639
  });
12707
- const mdFilePath = join18(targetDir, "github-summary.md");
12708
- await writeFile6(mdFilePath, markdown, "utf-8");
13640
+ const mdFilePath = join22(targetDir, "github-summary.md");
13641
+ await writeFile7(mdFilePath, markdown, "utf-8");
12709
13642
  const findings = scanForSecrets(JSON.stringify(targetSession));
12710
13643
  const warnings = findings.map((f) => `[${f.rule}] ${f.match}`);
12711
13644
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -12785,31 +13718,6 @@ async function startServer(baseDir, opts) {
12785
13718
  return c.json({ error: getErrorMessage(err) }, 500);
12786
13719
  }
12787
13720
  });
12788
- app.get("/api/overlays", async (c) => {
12789
- const result = requireSlug(c.req.query("slug"));
12790
- if ("error" in result) return c.json({ error: result.error }, 400);
12791
- const overlays = await loadOverlays(baseDir, result.slug);
12792
- return c.json(overlays);
12793
- });
12794
- app.post("/api/overlays", async (c) => {
12795
- const result = requireSlug(c.req.query("slug"));
12796
- if ("error" in result) return c.json({ error: result.error }, 400);
12797
- let body;
12798
- try {
12799
- body = await c.req.json();
12800
- } catch {
12801
- return c.json({ error: "invalid JSON body" }, 400);
12802
- }
12803
- if (!body || !Array.isArray(body.overlays)) {
12804
- return c.json({ error: "invalid overlays shape" }, 400);
12805
- }
12806
- try {
12807
- await saveOverlays(baseDir, result.slug, body);
12808
- } catch (err) {
12809
- return c.json({ error: `Failed to save overlays: ${getErrorMessage(err)}` }, 500);
12810
- }
12811
- return c.json({ ok: true });
12812
- });
12813
13721
  function fixOriginalValues(overlays, originalSession) {
12814
13722
  for (const overlay of overlays) {
12815
13723
  const scene = originalSession.scenes[overlay.sceneIndex];
@@ -12937,10 +13845,10 @@ async function startServer(baseDir, opts) {
12937
13845
  }
12938
13846
  }
12939
13847
  );
12940
- await new Promise((resolve3) => {
13848
+ await new Promise((resolve4) => {
12941
13849
  process.on("SIGINT", () => {
12942
13850
  console.log(chalk.dim("\n Server stopped.\n"));
12943
- resolve3();
13851
+ resolve4();
12944
13852
  process.exit(0);
12945
13853
  });
12946
13854
  });
@@ -13299,21 +14207,21 @@ function formatDuration2(ms) {
13299
14207
  init_utils();
13300
14208
  init_version();
13301
14209
  async function runGitHubExport(replay, outputDir) {
13302
- const { join: join19 } = await import("path");
13303
- const { writeFile: writeFile7 } = await import("fs/promises");
14210
+ const { join: join23 } = await import("path");
14211
+ const { writeFile: writeFile8 } = await import("fs/promises");
13304
14212
  const savedGist = await loadSavedGistInfo(outputDir);
13305
14213
  const replayUrl = savedGist?.viewerUrl;
13306
14214
  const svgSpinner = ora("Generating animated SVG...").start();
13307
14215
  const svgContent = generateGitHubSvg(replay, { replayUrl });
13308
- const svgFilePath = join19(outputDir, "session-preview.svg");
13309
- await writeFile7(svgFilePath, svgContent, "utf-8");
14216
+ const svgFilePath = join23(outputDir, "session-preview.svg");
14217
+ await writeFile8(svgFilePath, svgContent, "utf-8");
13310
14218
  svgSpinner.succeed(`SVG: ${svgFilePath}`);
13311
14219
  let gifGenerated = false;
13312
14220
  const gifSpinner = ora("Generating animated GIF...").start();
13313
14221
  try {
13314
14222
  const gifBuffer = await generateGitHubGif(replay, { replayUrl });
13315
- const gifFilePath = join19(outputDir, "session-preview.gif");
13316
- await writeFile7(gifFilePath, gifBuffer);
14223
+ const gifFilePath = join23(outputDir, "session-preview.gif");
14224
+ await writeFile8(gifFilePath, gifBuffer);
13317
14225
  const gifSizeKB = Math.round(gifBuffer.length / 1024);
13318
14226
  gifSpinner.succeed(`GIF: ${gifFilePath} (${gifSizeKB} KB)`);
13319
14227
  gifGenerated = true;
@@ -13326,8 +14234,8 @@ async function runGitHubExport(replay, outputDir) {
13326
14234
  svgPath: "./session-preview.svg",
13327
14235
  gifPath: gifGenerated ? "./session-preview.gif" : void 0
13328
14236
  });
13329
- const mdFilePath = join19(outputDir, "github-summary.md");
13330
- await writeFile7(mdFilePath, markdown, "utf-8");
14237
+ const mdFilePath = join23(outputDir, "github-summary.md");
14238
+ await writeFile8(mdFilePath, markdown, "utf-8");
13331
14239
  mdSpinner.succeed(`Markdown: ${mdFilePath}`);
13332
14240
  const redactionsSpinner = ora("Generating redaction report...").start();
13333
14241
  const alreadyRedactedCount = (markdown.match(/\[REDACTED\]/g) || []).length;
@@ -13342,8 +14250,8 @@ async function runGitHubExport(replay, outputDir) {
13342
14250
  "leftoverFindings: regex matches in the final markdown \u2014 review these before sharing."
13343
14251
  ]
13344
14252
  };
13345
- const redactionsPath = join19(outputDir, "redactions.json");
13346
- await writeFile7(redactionsPath, `${JSON.stringify(redactionsReport, null, 2)}
14253
+ const redactionsPath = join23(outputDir, "redactions.json");
14254
+ await writeFile8(redactionsPath, `${JSON.stringify(redactionsReport, null, 2)}
13347
14255
  `, "utf-8");
13348
14256
  const leftoverNote = leftoverFindings.length > 0 ? chalk2.yellow(`(${leftoverFindings.length} leftover finding(s) \u2014 review)`) : chalk2.dim("(no leftover findings)");
13349
14257
  redactionsSpinner.succeed(`Redactions: ${redactionsPath} ${leftoverNote}`);
@@ -13351,7 +14259,7 @@ async function runGitHubExport(replay, outputDir) {
13351
14259
  markdown,
13352
14260
  outputDir,
13353
14261
  svgPath: svgFilePath,
13354
- gifPath: gifGenerated ? join19(outputDir, "session-preview.gif") : void 0,
14262
+ gifPath: gifGenerated ? join23(outputDir, "session-preview.gif") : void 0,
13355
14263
  mdPath: mdFilePath,
13356
14264
  redactionsPath
13357
14265
  };
@@ -13429,8 +14337,8 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
13429
14337
  console.log(chalk2.bold.cyan("\n vibe-replay") + chalk2.dim(` v${CLI_VERSION}
13430
14338
  `));
13431
14339
  const { join: pathJoin } = await import("path");
13432
- const { homedir: homedir17 } = await import("os");
13433
- const replayBaseDir = pathJoin(homedir17(), ".vibe-replay");
14340
+ const { homedir: homedir20 } = await import("os");
14341
+ const replayBaseDir = pathJoin(homedir20(), ".vibe-replay");
13434
14342
  void discoverAllSessions().then(async (sessions) => {
13435
14343
  await writeFileCache(SESSION_DISCOVERY_CACHE_KEY, sessions);
13436
14344
  }).catch(() => {
@@ -13472,14 +14380,14 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
13472
14380
  return;
13473
14381
  }
13474
14382
  if (topChoice === "replays") {
13475
- const { readdir: readdir12, readFile: readFile20 } = await import("fs/promises");
14383
+ const { readdir: readdir13, readFile: readFile23 } = await import("fs/promises");
13476
14384
  const replayEntries = [];
13477
14385
  try {
13478
- const entries = await readdir12(replayBaseDir);
14386
+ const entries = await readdir13(replayBaseDir);
13479
14387
  for (const slug2 of entries) {
13480
14388
  if (slug2.startsWith(".") || slug2 === "cache") continue;
13481
14389
  try {
13482
- const raw = await readFile20(pathJoin(replayBaseDir, slug2, "replay.json"), "utf-8");
14390
+ const raw = await readFile23(pathJoin(replayBaseDir, slug2, "replay.json"), "utf-8");
13483
14391
  const replay2 = JSON.parse(raw);
13484
14392
  const title = replay2.meta?.title || slug2;
13485
14393
  const provider2 = replay2.meta?.provider || "";
@@ -13492,7 +14400,7 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
13492
14400
  minute: "2-digit",
13493
14401
  hour12: false
13494
14402
  }) : "";
13495
- const providerBadge = provider2 === "claude-code" ? chalk2.hex("#D97706")("claude") : provider2 === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : provider2 === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : provider2 === "cursor" ? chalk2.hex("#0096FF")("cursor") : provider2 === "codex" ? chalk2.hex("#B392F0")("codex") : chalk2.yellow(provider2);
14403
+ const providerBadge = provider2 === "claude-code" ? chalk2.hex("#D97706")("claude") : provider2 === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : provider2 === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : provider2 === "cursor" ? chalk2.hex("#0096FF")("cursor") : provider2 === "codex" ? chalk2.hex("#B392F0")("codex") : provider2 === "pi" ? chalk2.hex("#14B8A6")("pi") : chalk2.yellow(provider2);
13496
14404
  replayEntries.push({
13497
14405
  name: `${providerBadge} ${chalk2.dim(`[${time}]`)} ${chalk2.white(title)} ${chalk2.dim(`(${scenes} scenes)`)}`,
13498
14406
  value: slug2,
@@ -13665,10 +14573,10 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
13665
14573
  replay.meta.title = normalizedUserTitle;
13666
14574
  }
13667
14575
  }
13668
- const { join: join19 } = await import("path");
14576
+ const { join: join23 } = await import("path");
13669
14577
  const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
13670
14578
  const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
13671
- const outputDir = join19(home, ".vibe-replay", slug);
14579
+ const outputDir = join23(home, ".vibe-replay", slug);
13672
14580
  const genSpinner = ora("Generating replay...").start();
13673
14581
  const outputPath = await generateOutput(replay, outputDir);
13674
14582
  const { stat: fsStat } = await import("fs/promises");
@@ -13760,7 +14668,7 @@ ${chalk2.bold.green(" Done!")}
13760
14668
  if (target === "local") {
13761
14669
  await publishLocal(outputPath);
13762
14670
  } else if (target === "editor") {
13763
- await startServer(join19(home, ".vibe-replay"), {
14671
+ await startServer(join23(home, ".vibe-replay"), {
13764
14672
  openSlug: slug,
13765
14673
  ...getDevViewerOpts()
13766
14674
  });
@@ -13864,13 +14772,17 @@ ${chalk2.bold.green(" Done!")}
13864
14772
  console.log(chalk2.dim(" File: ") + chalk2.white(outputPath));
13865
14773
  console.log();
13866
14774
  });
13867
- program.command("sessions").description("Search local AI coding sessions (agent-friendly)").option("-q, --query <text>", "Search title, prompt, project, branch, model, or slug").option("--project <text>", "Filter by project path substring").option("-p, --provider <name>", "Filter by provider (claude-code, cursor, codex, ...)").option("-l, --limit <number>", "Maximum sessions to return", (value) => Number(value), 10).option("--scan", "Run richer per-session scan for efficiency metrics").option("--any", "Match any query term instead of requiring all terms").option("--brief", "Include scan-backed session briefs and match evidence").option("--dedupe", "Collapse near-duplicate sessions with the same long prompt/title").option("--json", "Print machine-readable JSON").action(async (opts) => {
14775
+ program.command("sessions").description("Search local AI coding sessions (agent-friendly)").option("-q, --query <text>", "Search title, prompt, project, branch, model, or slug").option("--project <text>", "Filter by project path substring").option(
14776
+ "-P, --provider-filter <name>",
14777
+ "Filter by provider (claude-code, cursor, codex, pi, ...)"
14778
+ ).option("-l, --limit <number>", "Maximum sessions to return", (value) => Number(value), 10).option("--scan", "Run richer per-session scan for efficiency metrics").option("--any", "Match any query term instead of requiring all terms").option("--brief", "Include scan-backed session briefs and match evidence").option("--dedupe", "Collapse near-duplicate sessions with the same long prompt/title").option("--json", "Print machine-readable JSON").action(async (opts, command) => {
13868
14779
  const discoverSpinner = opts.json ? void 0 : ora("Discovering local sessions...").start();
13869
14780
  try {
14781
+ const queryOptions = normalizeSessionsCommandOptions(opts, command);
13870
14782
  const sessions = mergeSameSessions2(await discoverAllSessions());
13871
14783
  discoverSpinner?.succeed(`Found ${sessions.length} sessions`);
13872
14784
  const scanSpinner = opts.scan && !opts.json ? ora("Scanning matching sessions...").start() : void 0;
13873
- const matches = await queryLocalSessions(sessions, opts);
14785
+ const matches = await queryLocalSessions(sessions, queryOptions);
13874
14786
  scanSpinner?.succeed(`Prepared ${matches.length} session result(s)`);
13875
14787
  if (opts.json) {
13876
14788
  console.log(JSON.stringify({ sessions: matches }, null, 2));
@@ -14019,9 +14931,9 @@ authCmd.command("status").description("Show current authentication status").opti
14019
14931
  var VISIBILITIES = ["public", "unlisted", "private"];
14020
14932
  program.command("share").description("Share an existing replay via cloud (unlisted link, expires in 7 days)").argument("[path]", "Path to replay directory or replay.json").option("--visibility <type>", `Visibility: ${VISIBILITIES.join(", ")}`, "unlisted").option("--api-url <url>", `API base URL (default: ${DEFAULT_API_URL})`).action(async (pathArg, opts) => {
14021
14933
  const { existsSync: existsSync2, statSync } = await import("fs");
14022
- const { readFile: readFile20, readdir: readdir12 } = await import("fs/promises");
14023
- const { join: join19, dirname: dirname6, resolve: resolve3 } = await import("path");
14024
- const { homedir: homedir17 } = await import("os");
14934
+ const { readFile: readFile23, readdir: readdir13 } = await import("fs/promises");
14935
+ const { join: join23, dirname: dirname5, resolve: resolve4 } = await import("path");
14936
+ const { homedir: homedir20 } = await import("os");
14025
14937
  if (opts.apiUrl) {
14026
14938
  process.env.VIBE_REPLAY_API_URL = opts.apiUrl.replace(/\/$/, "");
14027
14939
  }
@@ -14040,7 +14952,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
14040
14952
  }
14041
14953
  let outputDir;
14042
14954
  if (pathArg) {
14043
- const abs = resolve3(pathArg);
14955
+ const abs = resolve4(pathArg);
14044
14956
  if (!existsSync2(abs)) {
14045
14957
  console.error(chalk2.red(`
14046
14958
  \u2717 Path not found: ${abs}
@@ -14048,16 +14960,16 @@ program.command("share").description("Share an existing replay via cloud (unlist
14048
14960
  process.exit(1);
14049
14961
  }
14050
14962
  const s = statSync(abs);
14051
- outputDir = s.isDirectory() ? abs : dirname6(abs);
14963
+ outputDir = s.isDirectory() ? abs : dirname5(abs);
14052
14964
  } else {
14053
- const replayBaseDir = join19(homedir17(), ".vibe-replay");
14054
- const entries = await readdir12(replayBaseDir).catch(() => []);
14965
+ const replayBaseDir = join23(homedir20(), ".vibe-replay");
14966
+ const entries = await readdir13(replayBaseDir).catch(() => []);
14055
14967
  const replays = [];
14056
14968
  for (const slug of entries) {
14057
14969
  if (slug.startsWith(".") || slug === "cache") continue;
14058
- const jsonPath2 = join19(replayBaseDir, slug, "replay.json");
14970
+ const jsonPath2 = join23(replayBaseDir, slug, "replay.json");
14059
14971
  try {
14060
- const raw = await readFile20(jsonPath2, "utf-8");
14972
+ const raw = await readFile23(jsonPath2, "utf-8");
14061
14973
  const replay = JSON.parse(raw);
14062
14974
  const title = replay.meta?.title || slug;
14063
14975
  const startTime = replay.meta?.startTime || "";
@@ -14070,7 +14982,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
14070
14982
  }) : "";
14071
14983
  replays.push({
14072
14984
  name: time ? `${chalk2.dim(`[${time}]`)} ${chalk2.white(title)}` : chalk2.white(title),
14073
- value: join19(replayBaseDir, slug),
14985
+ value: join23(replayBaseDir, slug),
14074
14986
  time: startTime
14075
14987
  });
14076
14988
  } catch {
@@ -14086,7 +14998,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
14086
14998
  choices: replays
14087
14999
  });
14088
15000
  }
14089
- const jsonPath = join19(outputDir, "replay.json");
15001
+ const jsonPath = join23(outputDir, "replay.json");
14090
15002
  if (!existsSync2(jsonPath)) {
14091
15003
  console.error(chalk2.red(`
14092
15004
  \u2717 No replay.json found in ${outputDir}
@@ -14109,15 +15021,15 @@ program.command("share").description("Share an existing replay via cloud (unlist
14109
15021
  }
14110
15022
  });
14111
15023
  var LIVE_STALENESS_WARNING_MS = 5 * 6e4;
14112
- program.command("live").description("Watch a running AI coding session live in the browser").option("-p, --provider <name>", "Provider name (default: auto-detect)").option("-s, --session <sessionId>", "Specific session ID to watch").action(async (opts) => {
15024
+ program.command("live").description("Watch a running AI coding session live in the browser").option("-p, --provider <name>", "Provider name (default: auto-detect)").option("-s, --session <sessionId>", "Specific session ID to watch").action(async (opts, command) => {
14113
15025
  const { join: pathJoin } = await import("path");
14114
- const { homedir: homedir17 } = await import("os");
14115
- const replayBaseDir = pathJoin(homedir17(), ".vibe-replay");
15026
+ const { homedir: homedir20 } = await import("os");
15027
+ const replayBaseDir = pathJoin(homedir20(), ".vibe-replay");
14116
15028
  console.log(chalk2.bold.cyan("\n vibe-replay live") + chalk2.dim(` v${CLI_VERSION}
14117
15029
  `));
14118
15030
  let target = null;
14119
15031
  if (opts.session) {
14120
- const providerHint = opts.provider;
15032
+ const providerHint = normalizeCommandProviderOption(opts.provider, command);
14121
15033
  const providers2 = providerHint ? [getProvider(providerHint)].filter((p) => !!p) : getAllProviders();
14122
15034
  for (const provider of providers2) {
14123
15035
  try {
@@ -14139,7 +15051,8 @@ program.command("live").description("Watch a running AI coding session live in t
14139
15051
  const ora2 = (await import("ora")).default;
14140
15052
  const spinner = ora2("Finding the most recent session...").start();
14141
15053
  const all = [];
14142
- const providers2 = opts.provider ? [getProvider(opts.provider)].filter((p) => !!p) : getAllProviders();
15054
+ const providerHint = normalizeCommandProviderOption(opts.provider, command);
15055
+ const providers2 = providerHint ? [getProvider(providerHint)].filter((p) => !!p) : getAllProviders();
14143
15056
  for (const provider of providers2) {
14144
15057
  try {
14145
15058
  const sessions = await provider.discover();
@@ -14175,6 +15088,24 @@ program.command("login", { hidden: true }).description("Log in to vibe-replay (a
14175
15088
  await authCmd.commands.find((c) => c.name() === "login")?.parseAsync(["login", ...process.argv.slice(3)], { from: "user" });
14176
15089
  });
14177
15090
  program.parse();
15091
+ function normalizeSessionsCommandOptions(opts, command) {
15092
+ const provider = opts.providerFilter || normalizeCommandProviderOption(opts.provider, command);
15093
+ return {
15094
+ ...opts,
15095
+ ...provider ? { provider } : {}
15096
+ };
15097
+ }
15098
+ function normalizeCommandProviderOption(provider, command) {
15099
+ if (command.getOptionValueSource("provider") === "cli") {
15100
+ return provider;
15101
+ }
15102
+ const parent = command.parent;
15103
+ if (parent?.getOptionValueSource("provider") === "cli") {
15104
+ const parentProvider = parent.opts().provider;
15105
+ return parentProvider;
15106
+ }
15107
+ return void 0;
15108
+ }
14178
15109
  function formatSessionChoices(sessions, cleanupPeriodDays) {
14179
15110
  const merged = mergeSameSessions2(sessions);
14180
15111
  const byProject = /* @__PURE__ */ new Map();
@@ -14200,7 +15131,7 @@ function formatSessionChoices(sessions, cleanupPeriodDays) {
14200
15131
  });
14201
15132
  const sizeKB = Math.round(s.fileSize / 1024);
14202
15133
  const prompt = s.firstPrompt.replace(/\n/g, " ").slice(0, 50);
14203
- const providerBadge = s.provider === "claude-code" ? chalk2.hex("#D97706")("claude") : s.provider === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : s.provider === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : s.provider === "cursor" ? chalk2.hex("#0096FF")("cursor") : s.provider === "codex" ? chalk2.hex("#B392F0")("codex") : chalk2.yellow(s.provider);
15134
+ const providerBadge = s.provider === "claude-code" ? chalk2.hex("#D97706")("claude") : s.provider === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : s.provider === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : s.provider === "cursor" ? chalk2.hex("#0096FF")("cursor") : s.provider === "codex" ? chalk2.hex("#B392F0")("codex") : s.provider === "pi" ? chalk2.hex("#14B8A6")("pi") : chalk2.yellow(s.provider);
14204
15135
  const titleStr = s.title ? chalk2.white(` "${s.title}"`) : "";
14205
15136
  const fileCount = s.filePaths.length > 1 ? chalk2.dim(` [${s.filePaths.length} parts]`) : "";
14206
15137
  const sqliteBadge = s.hasSqlite ? chalk2.green(" db") : "";