vibe-replay 0.2.3 → 0.2.4

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
@@ -28,60 +28,14 @@ var init_version = __esm({
28
28
  }
29
29
  });
30
30
 
31
- // src/utils.ts
32
- import { homedir as homedir3 } from "os";
33
- function shortenPath(path) {
34
- const home = homedir3();
35
- if (path.startsWith(home)) return `~${path.slice(home.length)}`;
36
- return path;
37
- }
38
- function normalizeTitle(value) {
39
- const cleaned = (value || "").replace(/\s+/g, " ").trim().slice(0, MAX_TITLE_CHARS);
40
- return cleaned || void 0;
41
- }
42
- function extractToolFilePath(input) {
43
- return extractToolFilePaths(input)[0];
44
- }
45
- function extractToolFilePaths(input) {
46
- if (!input) return [];
47
- const plural2 = input.file_paths ?? input.filePaths ?? input.paths;
48
- const paths = Array.isArray(plural2) ? plural2.filter((fp) => typeof fp === "string" && fp.trim().length > 0) : [];
49
- const singular = input.file_path ?? input.filePath ?? input.path ?? input.relativeWorkspacePath;
50
- if (typeof singular === "string" && singular.trim()) paths.unshift(singular);
51
- return [...new Set(paths)];
52
- }
53
- function localDayKey(input) {
54
- if (input == null || input === "") return void 0;
55
- const d = input instanceof Date ? input : new Date(input);
56
- if (Number.isNaN(d.getTime())) return void 0;
57
- const y = d.getFullYear();
58
- const m = String(d.getMonth() + 1).padStart(2, "0");
59
- const day = String(d.getDate()).padStart(2, "0");
60
- return `${y}-${m}-${day}`;
61
- }
62
- var MAX_TITLE_CHARS, FILE_EDIT_TOOLS;
63
- var init_utils = __esm({
64
- "src/utils.ts"() {
65
- "use strict";
66
- MAX_TITLE_CHARS = 120;
67
- FILE_EDIT_TOOLS = /* @__PURE__ */ new Set([
68
- "Edit",
69
- "MultiEdit",
70
- "Write",
71
- "NotebookEdit",
72
- "Delete"
73
- ]);
74
- }
75
- });
76
-
77
31
  // src/overlays.ts
78
- import { readFile as readFile16 } from "fs/promises";
79
- import { join as join16, resolve } from "path";
32
+ import { readFile as readFile17 } from "fs/promises";
33
+ import { join as join17, resolve as resolve2 } from "path";
80
34
  async function loadOverlays(baseDir, slug) {
81
- const dirs = [join16(baseDir, slug), resolve("./vibe-replay", slug)];
35
+ const dirs = [join17(baseDir, slug), resolve2("./vibe-replay", slug)];
82
36
  for (const dir of dirs) {
83
37
  try {
84
- const raw = await readFile16(join16(dir, "overlays.json"), "utf-8");
38
+ const raw = await readFile17(join17(dir, "overlays.json"), "utf-8");
85
39
  const parsed = JSON.parse(raw);
86
40
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.overlays)) {
87
41
  return parsed;
@@ -140,9 +94,9 @@ __export(cloud_exports, {
140
94
  saveAuthTokenSync: () => saveAuthTokenSync
141
95
  });
142
96
  import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
143
- import { mkdir as mkdir3, readFile as readFile17, unlink, writeFile as writeFile3 } from "fs/promises";
97
+ import { mkdir as mkdir3, readFile as readFile18, unlink, writeFile as writeFile3 } from "fs/promises";
144
98
  import { homedir as homedir14 } from "os";
145
- import { basename as basename7, dirname as dirname3, join as join17 } from "path";
99
+ import { basename as basename7, dirname as dirname3, join as join18 } from "path";
146
100
  function authOrigin(url) {
147
101
  try {
148
102
  return new URL(url).origin;
@@ -243,8 +197,8 @@ async function publishCloud(outputDir, opts) {
243
197
  if (!auth) {
244
198
  throw new Error("Not logged in. Run `vibe-replay auth login` first.");
245
199
  }
246
- const jsonPath = join17(outputDir, "replay.json");
247
- const content = await readFile17(jsonPath, "utf-8");
200
+ const jsonPath = join18(outputDir, "replay.json");
201
+ const content = await readFile18(jsonPath, "utf-8");
248
202
  const replay = JSON.parse(content);
249
203
  const sizeBytes = Buffer.byteLength(content, "utf-8");
250
204
  const MAX_SIZE = 10 * 1024 * 1024;
@@ -291,8 +245,8 @@ async function publishCloudWithOverlays(outputDir, opts) {
291
245
  if (overlays.overlays.length === 0) {
292
246
  return publishCloud(outputDir, opts);
293
247
  }
294
- const replayPath = join17(outputDir, "replay.json");
295
- const originalContent = await readFile17(replayPath, "utf-8");
248
+ const replayPath = join18(outputDir, "replay.json");
249
+ const originalContent = await readFile18(replayPath, "utf-8");
296
250
  const session = JSON.parse(originalContent);
297
251
  const merged = sessionWithEffectiveContent(session, overlays);
298
252
  await writeFile3(replayPath, JSON.stringify(merged), "utf-8");
@@ -304,7 +258,7 @@ async function publishCloudWithOverlays(outputDir, opts) {
304
258
  }
305
259
  async function loadSavedCloudInfo(outputDir) {
306
260
  try {
307
- const raw = await readFile17(join17(outputDir, CLOUD_META_FILE), "utf-8");
261
+ const raw = await readFile18(join18(outputDir, CLOUD_META_FILE), "utf-8");
308
262
  const parsed = JSON.parse(raw);
309
263
  if (!parsed?.id || !parsed?.url) return void 0;
310
264
  return parsed;
@@ -313,7 +267,7 @@ async function loadSavedCloudInfo(outputDir) {
313
267
  }
314
268
  }
315
269
  async function saveCloudInfo(outputDir, info) {
316
- await writeFile3(join17(outputDir, CLOUD_META_FILE), JSON.stringify(info, null, 2), "utf-8");
270
+ await writeFile3(join18(outputDir, CLOUD_META_FILE), JSON.stringify(info, null, 2), "utf-8");
317
271
  }
318
272
  var CLOUD_META_FILE, DEFAULT_API_URL, AUTH_DIR, AUTH_FILE;
319
273
  var init_cloud = __esm({
@@ -322,8 +276,8 @@ var init_cloud = __esm({
322
276
  init_overlays();
323
277
  CLOUD_META_FILE = ".vibe-replay-cloud.json";
324
278
  DEFAULT_API_URL = "https://vibe-replay.com";
325
- AUTH_DIR = join17(homedir14(), ".config", "vibe-replay");
326
- AUTH_FILE = join17(AUTH_DIR, "auth.json");
279
+ AUTH_DIR = join18(homedir14(), ".config", "vibe-replay");
280
+ AUTH_FILE = join18(AUTH_DIR, "auth.json");
327
281
  }
328
282
  });
329
283
 
@@ -396,6 +350,54 @@ var init_machine_id = __esm({
396
350
  }
397
351
  });
398
352
 
353
+ // src/utils.ts
354
+ import { readFile as readFile20 } from "fs/promises";
355
+ import { homedir as homedir15 } from "os";
356
+ import { join as join20 } from "path";
357
+ function shortenPath2(path) {
358
+ const home = homedir15();
359
+ if (path.startsWith(home)) return `~${path.slice(home.length)}`;
360
+ return path;
361
+ }
362
+ function normalizeTitle(value) {
363
+ const cleaned = (value || "").replace(/\s+/g, " ").trim().slice(0, MAX_TITLE_CHARS);
364
+ return cleaned || void 0;
365
+ }
366
+ function extractToolFilePath(input) {
367
+ return extractToolFilePaths(input)[0];
368
+ }
369
+ function extractToolFilePaths(input) {
370
+ if (!input) return [];
371
+ const plural2 = input.file_paths ?? input.filePaths ?? input.paths;
372
+ const paths = Array.isArray(plural2) ? plural2.filter((fp) => typeof fp === "string" && fp.trim().length > 0) : [];
373
+ const singular = input.file_path ?? input.filePath ?? input.path ?? input.relativeWorkspacePath;
374
+ if (typeof singular === "string" && singular.trim()) paths.unshift(singular);
375
+ return [...new Set(paths)];
376
+ }
377
+ function localDayKey(input) {
378
+ if (input == null || input === "") return void 0;
379
+ const d = input instanceof Date ? input : new Date(input);
380
+ if (Number.isNaN(d.getTime())) return void 0;
381
+ const y = d.getFullYear();
382
+ const m = String(d.getMonth() + 1).padStart(2, "0");
383
+ const day = String(d.getDate()).padStart(2, "0");
384
+ return `${y}-${m}-${day}`;
385
+ }
386
+ var MAX_TITLE_CHARS, FILE_EDIT_TOOLS;
387
+ var init_utils = __esm({
388
+ "src/utils.ts"() {
389
+ "use strict";
390
+ MAX_TITLE_CHARS = 120;
391
+ FILE_EDIT_TOOLS = /* @__PURE__ */ new Set([
392
+ "Edit",
393
+ "MultiEdit",
394
+ "Write",
395
+ "NotebookEdit",
396
+ "Delete"
397
+ ]);
398
+ }
399
+ });
400
+
399
401
  // src/insights.ts
400
402
  var insights_exports = {};
401
403
  __export(insights_exports, {
@@ -406,12 +408,12 @@ __export(insights_exports, {
406
408
  scanResultToInsight: () => scanResultToInsight,
407
409
  writeInsightsStore: () => writeInsightsStore
408
410
  });
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";
411
+ import { mkdir as mkdir4, readFile as readFile21, rename, writeFile as writeFile5 } from "fs/promises";
412
+ import { homedir as homedir16 } from "os";
413
+ import { dirname as dirname4, join as join21 } from "path";
412
414
  async function readInsightsStore() {
413
415
  try {
414
- const raw = await readFile19(STORE_PATH, "utf-8");
416
+ const raw = await readFile21(STORE_PATH, "utf-8");
415
417
  const parsed = JSON.parse(raw);
416
418
  return migrateInsightsStore(parsed);
417
419
  } catch {
@@ -607,8 +609,8 @@ var init_insights = __esm({
607
609
  init_machine_id();
608
610
  init_utils();
609
611
  init_version();
610
- INSIGHTS_DIR = join19(homedir15(), ".vibe-replay", "insights");
611
- STORE_PATH = join19(INSIGHTS_DIR, "store.json");
612
+ INSIGHTS_DIR = join21(homedir16(), ".vibe-replay", "insights");
613
+ STORE_PATH = join21(INSIGHTS_DIR, "store.json");
612
614
  }
613
615
  });
614
616
 
@@ -619,26 +621,32 @@ import chalk2 from "chalk";
619
621
  import { program } from "commander";
620
622
  import ora from "ora";
621
623
 
622
- // src/cache.ts
623
- init_version();
624
+ // ../provider-core/src/cache.ts
624
625
  import { mkdir, readFile, writeFile } from "fs/promises";
625
626
  import { homedir } from "os";
626
627
  import { join } from "path";
627
628
  var CACHE_ENVELOPE_VERSION = 1;
628
629
  var CACHE_DIR = join(homedir(), ".vibe-replay", "cache");
630
+ var configuredAppVersion;
629
631
  function isFileCacheDisabled() {
630
632
  return process.env.VIBE_REPLAY_DISABLE_FILE_CACHE === "1";
631
633
  }
634
+ function setFileCacheAppVersion(appVersion) {
635
+ configuredAppVersion = appVersion;
636
+ }
637
+ function currentAppVersion(options) {
638
+ return options?.appVersion || configuredAppVersion || process.env.npm_package_version || "0.0.0";
639
+ }
632
640
  function toCachePath(key) {
633
641
  const safeKey = key.replace(/[^a-zA-Z0-9._-]/g, "_");
634
642
  return join(CACHE_DIR, `${safeKey}.json`);
635
643
  }
636
- async function readFileCache(key) {
644
+ async function readFileCache(key, options) {
637
645
  if (isFileCacheDisabled()) return null;
638
646
  try {
639
647
  const raw = await readFile(toCachePath(key), "utf-8");
640
648
  const parsed = JSON.parse(raw);
641
- if (parsed.envelopeVersion !== CACHE_ENVELOPE_VERSION || parsed.appVersion !== CLI_VERSION || typeof parsed.updatedAt !== "string" || !("data" in parsed)) {
649
+ if (parsed.envelopeVersion !== CACHE_ENVELOPE_VERSION || parsed.appVersion !== currentAppVersion(options) || typeof parsed.updatedAt !== "string" || !("data" in parsed)) {
642
650
  return null;
643
651
  }
644
652
  return { updatedAt: parsed.updatedAt, data: parsed.data };
@@ -646,13 +654,13 @@ async function readFileCache(key) {
646
654
  return null;
647
655
  }
648
656
  }
649
- async function writeFileCache(key, data) {
657
+ async function writeFileCache(key, data, options) {
650
658
  if (isFileCacheDisabled()) return;
651
659
  try {
652
660
  await mkdir(CACHE_DIR, { recursive: true });
653
661
  const payload = {
654
662
  envelopeVersion: CACHE_ENVELOPE_VERSION,
655
- appVersion: CLI_VERSION,
663
+ appVersion: currentAppVersion(options),
656
664
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
657
665
  data
658
666
  };
@@ -661,6 +669,69 @@ async function writeFileCache(key, data) {
661
669
  }
662
670
  }
663
671
 
672
+ // ../provider-core/src/utils.ts
673
+ import { readFile as readFile2, stat } from "fs/promises";
674
+ import { homedir as homedir2 } from "os";
675
+ import { isAbsolute, join as join2, resolve } from "path";
676
+ function shortenPath(path) {
677
+ const home = homedir2();
678
+ if (path.startsWith(home)) return `~${path.slice(home.length)}`;
679
+ return path;
680
+ }
681
+ async function readGitRepo(projectDir) {
682
+ if (!projectDir.trim()) return void 0;
683
+ try {
684
+ const resolved = projectDir.startsWith("~") ? join2(homedir2(), projectDir.slice(1)) : projectDir;
685
+ const gitPath = join2(resolved, ".git");
686
+ let configPath = join2(gitPath, "config");
687
+ const gitStat = await stat(gitPath).catch(() => null);
688
+ if (gitStat?.isFile()) {
689
+ const gitFile = await readFile2(gitPath, "utf-8");
690
+ const gitdirMatch = gitFile.match(/^gitdir:\s*(.+)$/m);
691
+ if (!gitdirMatch) return void 0;
692
+ const gitdir = gitdirMatch[1].trim();
693
+ const absoluteGitdir = isAbsolute(gitdir) ? gitdir : resolve(resolved, gitdir);
694
+ const commonDir = await readFile2(join2(absoluteGitdir, "commondir"), "utf-8").catch(
695
+ () => "../.."
696
+ );
697
+ configPath = join2(resolve(absoluteGitdir, commonDir.trim()), "config");
698
+ }
699
+ const config = await readFile2(configPath, "utf-8");
700
+ const match = config.match(/\[remote "origin"\][^[]*?url\s*=\s*(.+)/);
701
+ if (!match) return void 0;
702
+ return normalizeGitUrl(match[1].trim());
703
+ } catch {
704
+ return void 0;
705
+ }
706
+ }
707
+ function normalizeGitUrl(url) {
708
+ const trimmed = url.trim();
709
+ const scpMatch = trimmed.match(/:([^/][^:]+?)(?:\.git)?\s*$/);
710
+ if (!trimmed.startsWith("http") && !trimmed.startsWith("ssh://") && scpMatch) {
711
+ const path = scpMatch[1];
712
+ const parts = path.split("/");
713
+ if (parts.length >= 2) return `${parts[0]}/${parts[1]}`;
714
+ }
715
+ try {
716
+ const parsed = new URL(trimmed);
717
+ const parts = parsed.pathname.replace(/^\//, "").replace(/\.git$/, "").split("/");
718
+ if (parts.length >= 2) {
719
+ return `${parts[0]}/${parts[1]}`;
720
+ }
721
+ } catch {
722
+ }
723
+ return void 0;
724
+ }
725
+
726
+ // src/cache.ts
727
+ init_version();
728
+ async function readFileCache2(key) {
729
+ return readFileCache(key, { appVersion: CLI_VERSION });
730
+ }
731
+ async function writeFileCache2(key, data) {
732
+ return writeFileCache(key, data, { appVersion: CLI_VERSION });
733
+ }
734
+
664
735
  // src/clean-prompt.ts
665
736
  function isSystemGeneratedMessage(text) {
666
737
  return text.startsWith("[Request interrupted by user") || text.startsWith("<command-name>") || text.startsWith("<command-message>") || text.startsWith("<local-command-caveat>") || text.startsWith("<local-command-stdout>") || text.startsWith("<task-notification>") || text.startsWith("<bash-input>") || text.startsWith("<bash-stdout>");
@@ -729,19 +800,19 @@ function stripTerminalTranscriptNoise(text) {
729
800
  }
730
801
 
731
802
  // src/cleanup-warning.ts
732
- import { readFile as readFile2 } from "fs/promises";
733
- import { homedir as homedir2 } from "os";
734
- import { join as join2 } from "path";
803
+ import { readFile as readFile3 } from "fs/promises";
804
+ import { homedir as homedir3 } from "os";
805
+ import { join as join3 } from "path";
735
806
  var DEFAULT_CLEANUP_PERIOD_DAYS = 30;
736
807
  var WARNING_THRESHOLD_DAYS = 7;
737
808
  async function getClaudeCodeCleanupPeriod() {
738
- const home = homedir2();
809
+ const home = homedir3();
739
810
  for (const file of [
740
- join2(home, ".claude", "settings.local.json"),
741
- join2(home, ".claude", "settings.json")
811
+ join3(home, ".claude", "settings.local.json"),
812
+ join3(home, ".claude", "settings.json")
742
813
  ]) {
743
814
  try {
744
- const raw = await readFile2(file, "utf-8");
815
+ const raw = await readFile3(file, "utf-8");
745
816
  const settings = JSON.parse(raw);
746
817
  if (typeof settings.cleanupPeriodDays === "number") {
747
818
  return settings.cleanupPeriodDays;
@@ -1617,8 +1688,8 @@ async function generateGitHubGif(session, opts = {}) {
1617
1688
  }
1618
1689
 
1619
1690
  // src/generator.ts
1620
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
1621
- import { dirname, join as join3 } from "path";
1691
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
1692
+ import { dirname, join as join4 } from "path";
1622
1693
  import { fileURLToPath } from "url";
1623
1694
  var __dirname = dirname(fileURLToPath(import.meta.url));
1624
1695
  async function generateOutput(session, outputDir) {
@@ -1632,21 +1703,21 @@ async function generateOutput(session, outputDir) {
1632
1703
  "<title>vibe-replay</title>",
1633
1704
  `<title>${escapeHtml(title)} \u2014 vibe-replay</title>`
1634
1705
  );
1635
- const outputPath = join3(outputDir, "index.html");
1706
+ const outputPath = join4(outputDir, "index.html");
1636
1707
  await writeFile2(outputPath, finalHtml, "utf-8");
1637
- const jsonPath = join3(outputDir, "replay.json");
1708
+ const jsonPath = join4(outputDir, "replay.json");
1638
1709
  await writeFile2(jsonPath, JSON.stringify(session), "utf-8");
1639
1710
  return outputPath;
1640
1711
  }
1641
1712
  async function loadViewerHtml() {
1642
1713
  const assetsPaths = [
1643
- join3(__dirname, "..", "assets", "viewer.html"),
1644
- join3(__dirname, "assets", "viewer.html"),
1645
- join3(__dirname, "..", "..", "assets", "viewer.html")
1714
+ join4(__dirname, "..", "assets", "viewer.html"),
1715
+ join4(__dirname, "assets", "viewer.html"),
1716
+ join4(__dirname, "..", "..", "assets", "viewer.html")
1646
1717
  ];
1647
1718
  for (const p of assetsPaths) {
1648
1719
  try {
1649
- return await readFile3(p, "utf-8");
1720
+ return await readFile4(p, "utf-8");
1650
1721
  } catch (err) {
1651
1722
  if (err instanceof Error && "code" in err && err.code !== "ENOENT")
1652
1723
  throw err;
@@ -1669,14 +1740,82 @@ function injectDataScript(html, scriptTag) {
1669
1740
  ${html.slice(headIdx)}`;
1670
1741
  }
1671
1742
 
1672
- // src/providers/claude-code/discover.ts
1743
+ // ../provider-claude-code/src/claude-code/discover.ts
1673
1744
  import { createReadStream } from "fs";
1674
- import { readdir, stat } from "fs/promises";
1745
+ import { readdir, stat as stat2 } from "fs/promises";
1675
1746
  import { homedir as homedir4 } from "os";
1676
- import { join as join4 } from "path";
1747
+ import { join as join5 } from "path";
1677
1748
  import { createInterface } from "readline";
1678
- init_utils();
1679
- var CLAUDE_DIR = join4(homedir4(), ".claude", "projects");
1749
+
1750
+ // ../provider-core/src/clean-prompt.ts
1751
+ function isSystemGeneratedMessage2(text) {
1752
+ return text.startsWith("[Request interrupted by user") || text.startsWith("<command-name>") || text.startsWith("<command-message>") || text.startsWith("<local-command-caveat>") || text.startsWith("<local-command-stdout>") || text.startsWith("<task-notification>") || text.startsWith("<bash-input>") || text.startsWith("<bash-stdout>");
1753
+ }
1754
+ function previewPrompt2(text) {
1755
+ return cleanPromptText2(text).slice(0, 200);
1756
+ }
1757
+ function cleanPromptText2(text) {
1758
+ if (typeof text !== "string") return "";
1759
+ let cleaned = text.trim();
1760
+ if (!cleaned) return "";
1761
+ if (looksLikeConversationSummary2(cleaned)) return "";
1762
+ const hadTerminalNoise = /Last login:|gh auth status|github\.com|credential\.helper=|branch\./i.test(cleaned);
1763
+ cleaned = stripSlackPreamble2(cleaned);
1764
+ cleaned = stripTerminalTranscriptNoise2(cleaned);
1765
+ if (/<attached_files>|<code_selection\b/i.test(cleaned)) {
1766
+ return "";
1767
+ }
1768
+ cleaned = cleaned.replace(/<\/?[a-z][^>]*>/gi, "");
1769
+ cleaned = cleaned.replace(/^\s*\d+\|\s*/gm, "");
1770
+ cleaned = cleaned.replace(
1771
+ /Caveat:\s*The messages below were generated by the user while running local commands\.[^.]*/g,
1772
+ ""
1773
+ );
1774
+ cleaned = cleaned.replace(/DO NOT respond to these messages[^.]*/g, "");
1775
+ cleaned = cleaned.replace(/^\/\w+\s*/g, "");
1776
+ cleaned = cleaned.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
1777
+ if (!cleaned || looksLikeConversationSummary2(cleaned)) return "";
1778
+ if (hadTerminalNoise && /^[a-z0-9._:-]{1,12}$/i.test(cleaned)) return "";
1779
+ return cleaned;
1780
+ }
1781
+ function looksLikeConversationSummary2(text) {
1782
+ const normalized = text.trim();
1783
+ return /^\[Previous conversation summary\]:/i.test(normalized) || /^Summary:\s*1\.\s*Primary Request and Intent:/i.test(normalized) || // Cursor sometimes stores a truncated mid-sentence summary fragment instead
1784
+ // of the real first prompt; suppress that artifact in previews.
1785
+ /^and merge infrastructure was built for human-paced output/i.test(normalized);
1786
+ }
1787
+ function stripSlackPreamble2(text) {
1788
+ const lines = text.split("\n");
1789
+ const trimmed = [...lines];
1790
+ const removedSpeaker = /^\S.*\[\d{1,2}:\d{2}\s?(?:AM|PM)\]$/i.test(trimmed[0] || "");
1791
+ if (removedSpeaker) {
1792
+ trimmed.shift();
1793
+ }
1794
+ if (removedSpeaker && /^(?:hello|hi)\b/i.test((trimmed[0] || "").trim())) {
1795
+ trimmed.shift();
1796
+ }
1797
+ return trimmed.join("\n").trim();
1798
+ }
1799
+ function stripTerminalTranscriptNoise2(text) {
1800
+ const lines = text.split("\n");
1801
+ const kept = lines.filter((line) => {
1802
+ const trimmed = line.trim();
1803
+ if (!trimmed) return true;
1804
+ if (/^Last login:/i.test(trimmed)) return false;
1805
+ if (/^(?:➜|❯)\s+/.test(trimmed)) return false;
1806
+ if (/^github(?:\.com|\.rbx\.com)?$/i.test(trimmed)) return false;
1807
+ if (/^[✓✔]/.test(trimmed)) return false;
1808
+ if (/^-\s+(?:Active account|Git operations protocol|Token|Token scopes):/i.test(trimmed))
1809
+ return false;
1810
+ if (/^(?:credential|init|user|filter|alias|core|remote|branch)\.[^=]+=/i.test(trimmed) || /^branch\.[^.]+\./i.test(trimmed))
1811
+ return false;
1812
+ return true;
1813
+ });
1814
+ return kept.join("\n").trim();
1815
+ }
1816
+
1817
+ // ../provider-claude-code/src/claude-code/discover.ts
1818
+ var CLAUDE_DIR = join5(homedir4(), ".claude", "projects");
1680
1819
  async function discoverClaudeCodeSessions() {
1681
1820
  const sessions = [];
1682
1821
  let projectDirs;
@@ -1686,8 +1825,8 @@ async function discoverClaudeCodeSessions() {
1686
1825
  return sessions;
1687
1826
  }
1688
1827
  for (const projDir of projectDirs) {
1689
- const projPath = join4(CLAUDE_DIR, projDir);
1690
- const projStat = await stat(projPath).catch(() => null);
1828
+ const projPath = join5(CLAUDE_DIR, projDir);
1829
+ const projStat = await stat2(projPath).catch(() => null);
1691
1830
  if (!projStat?.isDirectory()) continue;
1692
1831
  const project = decodeProjectDir(projDir);
1693
1832
  let files;
@@ -1696,13 +1835,22 @@ async function discoverClaudeCodeSessions() {
1696
1835
  } catch {
1697
1836
  continue;
1698
1837
  }
1838
+ let gitRepo;
1839
+ let gitRepoResolved = false;
1699
1840
  for (const file of files) {
1700
1841
  if (!file.endsWith(".jsonl")) continue;
1701
- const filePath = join4(projPath, file);
1702
- const fileStat = await stat(filePath).catch(() => null);
1842
+ const filePath = join5(projPath, file);
1843
+ const fileStat = await stat2(filePath).catch(() => null);
1703
1844
  if (!fileStat) continue;
1704
1845
  const info = await extractSessionInfo(filePath, fileStat.size, project);
1705
- if (info) sessions.push(info);
1846
+ if (info) {
1847
+ if (!gitRepoResolved && info.cwd) {
1848
+ gitRepo = await readGitRepo(info.cwd);
1849
+ gitRepoResolved = true;
1850
+ }
1851
+ info.gitRepo = gitRepo;
1852
+ sessions.push(info);
1853
+ }
1706
1854
  }
1707
1855
  }
1708
1856
  sessions.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
@@ -1780,8 +1928,8 @@ async function extractSessionInfo(filePath, fileSize, project) {
1780
1928
  if (prompts.length < MAX_PROMPTS && obj.type === "user" && obj.message?.role === "user") {
1781
1929
  const contentField = obj.message.content;
1782
1930
  const text = typeof contentField === "string" ? contentField : Array.isArray(contentField) ? contentField.filter((b) => b.type === "text").map((b) => b.text ?? "").join("") : "";
1783
- if (text && !isSystemGeneratedMessage(text)) {
1784
- const cleaned = cleanPromptText(text);
1931
+ if (text && !isSystemGeneratedMessage2(text)) {
1932
+ const cleaned = cleanPromptText2(text);
1785
1933
  if (cleaned.length >= 10) prompts.push(cleaned.slice(0, 200));
1786
1934
  }
1787
1935
  }
@@ -1821,7 +1969,7 @@ async function extractSessionInfo(filePath, fileSize, project) {
1821
1969
  }
1822
1970
  }
1823
1971
  if (!timestamp) {
1824
- const fileStat2 = await stat(filePath).catch(() => null);
1972
+ const fileStat2 = await stat2(filePath).catch(() => null);
1825
1973
  if (fileStat2) timestamp = fileStat2.mtime.toISOString();
1826
1974
  else return null;
1827
1975
  }
@@ -1850,11 +1998,11 @@ async function extractSessionInfo(filePath, fileSize, project) {
1850
1998
  };
1851
1999
  }
1852
2000
 
1853
- // src/providers/claude-code/parser.ts
1854
- import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
1855
- import { join as join5 } from "path";
2001
+ // ../provider-claude-code/src/claude-code/parser.ts
2002
+ import { readdir as readdir2, readFile as readFile5 } from "fs/promises";
2003
+ import { join as join6 } from "path";
1856
2004
 
1857
- // src/duration.ts
2005
+ // ../provider-core/src/duration.ts
1858
2006
  var DEFAULT_MAX_GAP_MS = 5 * 60 * 1e3;
1859
2007
  function estimateActiveDuration(timestamps, maxGapMs = DEFAULT_MAX_GAP_MS) {
1860
2008
  if (timestamps.length < 2) return void 0;
@@ -1868,7 +2016,7 @@ function estimateActiveDuration(timestamps, maxGapMs = DEFAULT_MAX_GAP_MS) {
1868
2016
  return active > 0 ? active : void 0;
1869
2017
  }
1870
2018
 
1871
- // src/providers/warnings.ts
2019
+ // ../provider-contract/src/warnings.ts
1872
2020
  function addParseWarning(warnings, warning) {
1873
2021
  const existing = warnings.find(
1874
2022
  (w) => w.kind === warning.kind && w.source === warning.source && w.message === warning.message
@@ -1892,12 +2040,12 @@ function compactWarningSample(value, max = 160) {
1892
2040
  return `${compacted.slice(0, max)}...`;
1893
2041
  }
1894
2042
 
1895
- // src/providers/claude-code/parser.ts
2043
+ // ../provider-claude-code/src/claude-code/parser.ts
1896
2044
  async function parseClaudeCodeSession(filePaths) {
1897
2045
  const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
1898
2046
  const allLines = [];
1899
2047
  for (const fp of paths) {
1900
- const content = await readFile4(fp, "utf-8");
2048
+ const content = await readFile5(fp, "utf-8");
1901
2049
  allLines.push(...content.split("\n"));
1902
2050
  }
1903
2051
  return parseClaudeCodeLines(allLines, { subagentsSourcePath: paths[0] });
@@ -2098,7 +2246,7 @@ async function parseClaudeCodeLines(lines, options = {}) {
2098
2246
  continue;
2099
2247
  }
2100
2248
  if (role === "user" && typeof msgContent === "string") {
2101
- if (isSystemGeneratedMessage(msgContent)) continue;
2249
+ if (isSystemGeneratedMessage2(msgContent)) continue;
2102
2250
  const isCompaction = obj.isCompactSummary || msgContent.startsWith("This session is being continued from a previous conversation");
2103
2251
  userTurns.push({
2104
2252
  role: "user",
@@ -2135,7 +2283,7 @@ async function parseClaudeCodeLines(lines, options = {}) {
2135
2283
  }
2136
2284
  }
2137
2285
  const combinedText = textParts.join("").trim();
2138
- if (!isToolSearchResponse && !isSystemGeneratedMessage(combinedText) && (textParts.length > 0 || userImages.length > 0)) {
2286
+ if (!isToolSearchResponse && !isSystemGeneratedMessage2(combinedText) && (textParts.length > 0 || userImages.length > 0)) {
2139
2287
  const blocks = textParts.map(
2140
2288
  (t) => ({ type: "text", text: t })
2141
2289
  );
@@ -2453,19 +2601,19 @@ function buildTurnStats(finalTurns, usageByMsgId, turnDurations) {
2453
2601
  } else if (group.startTimestamp && group.endTimestamp) {
2454
2602
  durationMs = estimateActiveDuration([group.startTimestamp, group.endTimestamp]);
2455
2603
  }
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);
2604
+ const stat13 = { turnIndex: i };
2605
+ if (turnModel) stat13.model = turnModel;
2606
+ if (durationMs !== void 0) stat13.durationMs = durationMs;
2607
+ if (turnTokens) stat13.tokenUsage = turnTokens;
2608
+ if (maxContextTokens > 0) stat13.contextTokens = maxContextTokens;
2609
+ stats.push(stat13);
2462
2610
  }
2463
2611
  return stats;
2464
2612
  }
2465
2613
  async function readSubagents(mainFilePath, usageByMsgId, parseWarnings) {
2466
2614
  const result = /* @__PURE__ */ new Map();
2467
2615
  const sessionDir = mainFilePath.replace(/\.jsonl$/, "");
2468
- const subagentsDir = join5(sessionDir, "subagents");
2616
+ const subagentsDir = join6(sessionDir, "subagents");
2469
2617
  let files;
2470
2618
  try {
2471
2619
  files = await readdir2(subagentsDir);
@@ -2478,8 +2626,8 @@ async function readSubagents(mainFilePath, usageByMsgId, parseWarnings) {
2478
2626
  let agentType = "unknown";
2479
2627
  let description;
2480
2628
  try {
2481
- const metaPath = join5(subagentsDir, file.replace(/\.jsonl$/, ".meta.json"));
2482
- const metaContent = await readFile4(metaPath, "utf-8");
2629
+ const metaPath = join6(subagentsDir, file.replace(/\.jsonl$/, ".meta.json"));
2630
+ const metaContent = await readFile5(metaPath, "utf-8");
2483
2631
  const meta = JSON.parse(metaContent);
2484
2632
  agentType = meta.agentType || "unknown";
2485
2633
  description = meta.description;
@@ -2487,7 +2635,7 @@ async function readSubagents(mainFilePath, usageByMsgId, parseWarnings) {
2487
2635
  }
2488
2636
  let content;
2489
2637
  try {
2490
- content = await readFile4(join5(subagentsDir, file), "utf-8");
2638
+ content = await readFile5(join6(subagentsDir, file), "utf-8");
2491
2639
  } catch {
2492
2640
  continue;
2493
2641
  }
@@ -2621,7 +2769,7 @@ async function readSubagents(mainFilePath, usageByMsgId, parseWarnings) {
2621
2769
  return result;
2622
2770
  }
2623
2771
 
2624
- // src/providers/claude-code/index.ts
2772
+ // ../provider-claude-code/src/claude-code/index.ts
2625
2773
  var claudeCodeProvider = {
2626
2774
  name: "claude-code",
2627
2775
  displayName: "Claude Code",
@@ -2629,13 +2777,13 @@ var claudeCodeProvider = {
2629
2777
  parse: parseClaudeCodeSession
2630
2778
  };
2631
2779
 
2632
- // src/providers/claude-cowork/discover.ts
2780
+ // ../provider-claude-code/src/claude-cowork/discover.ts
2633
2781
  import { createReadStream as createReadStream2 } from "fs";
2634
- import { readdir as readdir3, readFile as readFile5, stat as stat2 } from "fs/promises";
2782
+ import { readdir as readdir3, readFile as readFile6, stat as stat3 } from "fs/promises";
2635
2783
  import { homedir as homedir5 } from "os";
2636
- import { join as join6 } from "path";
2784
+ import { join as join7 } from "path";
2637
2785
  import { createInterface as createInterface2 } from "readline";
2638
- var COWORK_DIR = join6(
2786
+ var COWORK_DIR = join7(
2639
2787
  homedir5(),
2640
2788
  "Library",
2641
2789
  "Application Support",
@@ -2655,8 +2803,8 @@ async function discoverCoworkFromDir(coworkDir) {
2655
2803
  return sessions;
2656
2804
  }
2657
2805
  for (const accountId of accountDirs) {
2658
- const accountPath = join6(coworkDir, accountId);
2659
- const accountStat = await stat2(accountPath).catch(() => null);
2806
+ const accountPath = join7(coworkDir, accountId);
2807
+ const accountStat = await stat3(accountPath).catch(() => null);
2660
2808
  if (!accountStat?.isDirectory()) continue;
2661
2809
  let orgDirs;
2662
2810
  try {
@@ -2665,8 +2813,8 @@ async function discoverCoworkFromDir(coworkDir) {
2665
2813
  continue;
2666
2814
  }
2667
2815
  for (const orgId of orgDirs) {
2668
- const orgPath = join6(accountPath, orgId);
2669
- const orgStat = await stat2(orgPath).catch(() => null);
2816
+ const orgPath = join7(accountPath, orgId);
2817
+ const orgStat = await stat3(orgPath).catch(() => null);
2670
2818
  if (!orgStat?.isDirectory()) continue;
2671
2819
  let entries;
2672
2820
  try {
@@ -2676,7 +2824,7 @@ async function discoverCoworkFromDir(coworkDir) {
2676
2824
  }
2677
2825
  for (const entry of entries) {
2678
2826
  if (!entry.startsWith("local_") || !entry.endsWith(".json")) continue;
2679
- const jsonPath = join6(orgPath, entry);
2827
+ const jsonPath = join7(orgPath, entry);
2680
2828
  const info = await extractCoworkSessionInfo(jsonPath);
2681
2829
  if (info) sessions.push(info);
2682
2830
  }
@@ -2688,7 +2836,7 @@ async function discoverCoworkFromDir(coworkDir) {
2688
2836
  async function extractCoworkSessionInfo(jsonPath) {
2689
2837
  let raw;
2690
2838
  try {
2691
- raw = await readFile5(jsonPath, "utf-8");
2839
+ raw = await readFile6(jsonPath, "utf-8");
2692
2840
  } catch {
2693
2841
  return null;
2694
2842
  }
@@ -2700,8 +2848,8 @@ async function extractCoworkSessionInfo(jsonPath) {
2700
2848
  }
2701
2849
  if (!meta.sessionId) return null;
2702
2850
  const dir = jsonPath.replace(/\.json$/, "");
2703
- const auditPath = join6(dir, "audit.jsonl");
2704
- const auditStat = await stat2(auditPath).catch(() => null);
2851
+ const auditPath = join7(dir, "audit.jsonl");
2852
+ const auditStat = await stat3(auditPath).catch(() => null);
2705
2853
  if (!auditStat || auditStat.size === 0) return null;
2706
2854
  const sessionId = meta.sessionId.replace(/^local_/, "");
2707
2855
  const toolUseRe = /"type"\s*:\s*"tool_use"/g;
@@ -2738,6 +2886,7 @@ async function extractCoworkSessionInfo(jsonPath) {
2738
2886
  const model = meta.model ? meta.model.replace(/\[[^\]]*\]$/, "") : void 0;
2739
2887
  const fsDetectedFiles = Array.isArray(meta.fsDetectedFiles) ? meta.fsDetectedFiles.filter((file) => typeof file === "string") : void 0;
2740
2888
  const project = "Cowork";
2889
+ const gitRepo = meta.cwd ? await readGitRepo(meta.cwd) : void 0;
2741
2890
  return {
2742
2891
  provider: "claude-cowork",
2743
2892
  sessionId,
@@ -2746,6 +2895,7 @@ async function extractCoworkSessionInfo(jsonPath) {
2746
2895
  project,
2747
2896
  cwd: meta.cwd || "",
2748
2897
  version: "",
2898
+ gitRepo,
2749
2899
  timestamp,
2750
2900
  lineCount,
2751
2901
  fileSize,
@@ -2769,8 +2919,8 @@ function collectPromptsFromLines(lines, initialMessage) {
2769
2919
  const MAX = 2;
2770
2920
  const pushCleaned = (text) => {
2771
2921
  if (!text) return;
2772
- if (isSystemGeneratedMessage(text)) return;
2773
- const cleaned = previewPrompt(text);
2922
+ if (isSystemGeneratedMessage2(text)) return;
2923
+ const cleaned = previewPrompt2(text);
2774
2924
  if (cleaned.length < 10) return;
2775
2925
  if (prompts.includes(cleaned)) return;
2776
2926
  prompts.push(cleaned);
@@ -2794,8 +2944,8 @@ function collectPromptsFromLines(lines, initialMessage) {
2794
2944
  return prompts;
2795
2945
  }
2796
2946
 
2797
- // src/providers/claude-cowork/parser.ts
2798
- import { readFile as readFile6 } from "fs/promises";
2947
+ // ../provider-claude-code/src/claude-cowork/parser.ts
2948
+ import { readFile as readFile7 } from "fs/promises";
2799
2949
  function normalizeCoworkLine(line, onMalformedJson) {
2800
2950
  let obj;
2801
2951
  try {
@@ -2823,7 +2973,7 @@ async function parseClaudeCoworkSession(filePaths, sessionInfo) {
2823
2973
  const normalized = [];
2824
2974
  const parseWarnings = [];
2825
2975
  for (const fp of paths) {
2826
- const content = await readFile6(fp, "utf-8");
2976
+ const content = await readFile7(fp, "utf-8");
2827
2977
  const lines = content.split("\n");
2828
2978
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
2829
2979
  const raw = lines[lineIndex];
@@ -2854,7 +3004,7 @@ async function parseClaudeCoworkSession(filePaths, sessionInfo) {
2854
3004
  return result;
2855
3005
  }
2856
3006
 
2857
- // src/providers/claude-cowork/index.ts
3007
+ // ../provider-claude-code/src/claude-cowork/index.ts
2858
3008
  var claudeCoworkProvider = {
2859
3009
  name: "claude-cowork",
2860
3010
  displayName: "Claude Cowork",
@@ -2862,18 +3012,18 @@ var claudeCoworkProvider = {
2862
3012
  parse: parseClaudeCoworkSession
2863
3013
  };
2864
3014
 
2865
- // src/providers/claude-desktop/discover.ts
2866
- import { readdir as readdir4, readFile as readFile7, stat as stat3 } from "fs/promises";
3015
+ // ../provider-claude-code/src/claude-desktop/discover.ts
3016
+ import { readdir as readdir4, readFile as readFile8, stat as stat4 } from "fs/promises";
2867
3017
  import { homedir as homedir6 } from "os";
2868
- import { join as join7 } from "path";
2869
- var DESKTOP_DIR = join7(
3018
+ import { join as join8 } from "path";
3019
+ var DESKTOP_DIR = join8(
2870
3020
  homedir6(),
2871
3021
  "Library",
2872
3022
  "Application Support",
2873
3023
  "Claude",
2874
3024
  "claude-code-sessions"
2875
3025
  );
2876
- var DEFAULT_CLAUDE_PROJECTS_DIR = join7(homedir6(), ".claude", "projects");
3026
+ var DEFAULT_CLAUDE_PROJECTS_DIR = join8(homedir6(), ".claude", "projects");
2877
3027
  async function discoverClaudeDesktopSessions() {
2878
3028
  if (process.platform !== "darwin") return [];
2879
3029
  return discoverFromDir(DESKTOP_DIR, DEFAULT_CLAUDE_PROJECTS_DIR);
@@ -2887,8 +3037,8 @@ async function discoverFromDir(desktopDir, claudeProjectsDir) {
2887
3037
  return sessions;
2888
3038
  }
2889
3039
  for (const accountId of accountDirs) {
2890
- const accountPath = join7(desktopDir, accountId);
2891
- const accountStat = await stat3(accountPath).catch(() => null);
3040
+ const accountPath = join8(desktopDir, accountId);
3041
+ const accountStat = await stat4(accountPath).catch(() => null);
2892
3042
  if (!accountStat?.isDirectory()) continue;
2893
3043
  let orgDirs;
2894
3044
  try {
@@ -2897,8 +3047,8 @@ async function discoverFromDir(desktopDir, claudeProjectsDir) {
2897
3047
  continue;
2898
3048
  }
2899
3049
  for (const orgId of orgDirs) {
2900
- const orgPath = join7(accountPath, orgId);
2901
- const orgStat = await stat3(orgPath).catch(() => null);
3050
+ const orgPath = join8(accountPath, orgId);
3051
+ const orgStat = await stat4(orgPath).catch(() => null);
2902
3052
  if (!orgStat?.isDirectory()) continue;
2903
3053
  let files;
2904
3054
  try {
@@ -2908,7 +3058,7 @@ async function discoverFromDir(desktopDir, claudeProjectsDir) {
2908
3058
  }
2909
3059
  for (const file of files) {
2910
3060
  if (!file.startsWith("local_") || !file.endsWith(".json")) continue;
2911
- const jsonPath = join7(orgPath, file);
3061
+ const jsonPath = join8(orgPath, file);
2912
3062
  const info = await extractDesktopSessionInfo(jsonPath, claudeProjectsDir);
2913
3063
  if (info) sessions.push(info);
2914
3064
  }
@@ -2919,28 +3069,30 @@ async function discoverFromDir(desktopDir, claudeProjectsDir) {
2919
3069
  }
2920
3070
  async function extractDesktopSessionInfo(jsonPath, claudeProjectsDir = DEFAULT_CLAUDE_PROJECTS_DIR) {
2921
3071
  try {
2922
- const content = await readFile7(jsonPath, "utf-8");
3072
+ const content = await readFile8(jsonPath, "utf-8");
2923
3073
  const desktop = JSON.parse(content);
2924
3074
  if (!desktop.cliSessionId || !desktop.cwd) return null;
2925
3075
  const encodedCwd = desktop.cwd.replace(/\/+$/, "").replace(/\//g, "-");
2926
- const jsonlPath = join7(claudeProjectsDir, encodedCwd, `${desktop.cliSessionId}.jsonl`);
2927
- const jsonlStat = await stat3(jsonlPath).catch(() => null);
3076
+ const jsonlPath = join8(claudeProjectsDir, encodedCwd, `${desktop.cliSessionId}.jsonl`);
3077
+ const jsonlStat = await stat4(jsonlPath).catch(() => null);
2928
3078
  if (!jsonlStat) return null;
2929
3079
  const info = await extractSessionInfo(jsonlPath, jsonlStat.size, desktop.cwd);
2930
3080
  if (!info) return null;
3081
+ const gitRepo = await readGitRepo(desktop.cwd);
2931
3082
  return {
2932
3083
  ...info,
2933
3084
  provider: "claude-desktop",
2934
3085
  title: desktop.title || info.title,
2935
3086
  model: desktop.model || info.model,
2936
- timestamp: new Date(desktop.lastActivityAt).toISOString()
3087
+ timestamp: new Date(desktop.lastActivityAt).toISOString(),
3088
+ gitRepo
2937
3089
  };
2938
3090
  } catch {
2939
3091
  return null;
2940
3092
  }
2941
3093
  }
2942
3094
 
2943
- // src/providers/claude-desktop/index.ts
3095
+ // ../provider-claude-code/src/claude-desktop/index.ts
2944
3096
  var claudeDesktopProvider = {
2945
3097
  name: "claude-desktop",
2946
3098
  displayName: "Claude Desktop",
@@ -2948,15 +3100,14 @@ var claudeDesktopProvider = {
2948
3100
  parse: parseClaudeCodeSession
2949
3101
  };
2950
3102
 
2951
- // src/providers/codex/discover.ts
3103
+ // ../provider-codex/src/codex/discover.ts
2952
3104
  import { createReadStream as createReadStream3 } from "fs";
2953
- import { readFile as readFile8, readdir as readdir5, stat as stat4 } from "fs/promises";
3105
+ import { readFile as readFile9, readdir as readdir5, stat as stat5 } from "fs/promises";
2954
3106
  import { homedir as homedir7 } from "os";
2955
- import { basename, join as join8 } from "path";
3107
+ import { basename, join as join9 } from "path";
2956
3108
  import { createInterface as createInterface3 } from "readline";
2957
- init_utils();
2958
3109
 
2959
- // src/providers/codex/constants.ts
3110
+ // ../provider-codex/src/codex/constants.ts
2960
3111
  var USER_MESSAGE_BEGIN = "## My request for Codex:";
2961
3112
  var CODEX_CONTEXT_TAGS = [
2962
3113
  "environment_context",
@@ -3000,7 +3151,7 @@ function codexStripTwoPass(text) {
3000
3151
  return result;
3001
3152
  }
3002
3153
 
3003
- // src/providers/codex/discover.ts
3154
+ // ../provider-codex/src/codex/discover.ts
3004
3155
  var STATE_DB_FILENAME = "state_5.sqlite";
3005
3156
  async function discoverCodexSessions() {
3006
3157
  const byId = /* @__PURE__ */ new Map();
@@ -3009,31 +3160,37 @@ async function discoverCodexSessions() {
3009
3160
  const info = await sessionInfoFromThreadRow(row);
3010
3161
  if (info) byId.set(info.sessionId, info);
3011
3162
  }
3012
- for (const filePath of await findRolloutFiles(join8(codexHome, "sessions"))) {
3013
- const fileStat = await stat4(filePath).catch(() => null);
3163
+ for (const filePath of await findRolloutFiles(join9(codexHome, "sessions"))) {
3164
+ const fileStat = await stat5(filePath).catch(() => null);
3014
3165
  if (!fileStat?.isFile()) continue;
3015
3166
  const info = await extractCodexSessionInfo(filePath, fileStat.size);
3016
- if (info && !byId.has(info.sessionId)) byId.set(info.sessionId, info);
3167
+ if (info && !byId.has(info.sessionId)) {
3168
+ if (info.cwd) info.gitRepo = await readGitRepo(info.cwd);
3169
+ byId.set(info.sessionId, info);
3170
+ }
3017
3171
  }
3018
3172
  return [...byId.values()].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
3019
3173
  }
3020
3174
  async function sessionInfoFromThreadRow(row) {
3021
3175
  if (!row.id || !row.rollout_path) return null;
3022
- const fileStat = await stat4(row.rollout_path).catch(() => null);
3176
+ const fileStat = await stat5(row.rollout_path).catch(() => null);
3023
3177
  if (!fileStat?.isFile()) return null;
3024
3178
  const extracted = await extractCodexSessionInfo(row.rollout_path, fileStat.size);
3025
3179
  const rowFirstPrompt = row.first_user_message ? normalizeDiscoveredUserMessage(row.first_user_message) : "";
3026
3180
  const firstPrompt = rowFirstPrompt || extracted?.firstPrompt || "";
3027
3181
  if (!firstPrompt) return extracted;
3182
+ const cwd = row.cwd || extracted?.cwd || "";
3183
+ const gitRepo = await readGitRepo(cwd);
3028
3184
  return {
3029
3185
  provider: "codex",
3030
3186
  sessionId: row.id,
3031
3187
  slug: row.id.slice(0, 8),
3032
3188
  title: row.title || extracted?.title,
3033
- project: shortenPath(row.cwd || extracted?.cwd || ""),
3034
- cwd: row.cwd || extracted?.cwd || "",
3189
+ project: shortenPath(cwd),
3190
+ cwd,
3035
3191
  version: row.cli_version || extracted?.version || "",
3036
3192
  gitBranch: row.git_branch || extracted?.gitBranch,
3193
+ gitRepo,
3037
3194
  timestamp: toIsoFlexible(row.updated_at_ms || row.updated_at) || extracted?.timestamp || fileStat.mtime.toISOString(),
3038
3195
  lineCount: extracted?.lineCount || 0,
3039
3196
  fileSize: fileStat.size,
@@ -3144,7 +3301,7 @@ async function extractCodexSessionInfo(filePath, fileSize) {
3144
3301
  cwd,
3145
3302
  version: version2,
3146
3303
  gitBranch,
3147
- timestamp: timestamp || await stat4(filePath).then((s) => s.mtime.toISOString()),
3304
+ timestamp: timestamp || await stat5(filePath).then((s) => s.mtime.toISOString()),
3148
3305
  lineCount,
3149
3306
  fileSize,
3150
3307
  filePath,
@@ -3160,12 +3317,12 @@ async function extractCodexSessionInfo(filePath, fileSize) {
3160
3317
  }
3161
3318
  async function readThreadRowsFromStateDb() {
3162
3319
  const stateDbPath = getStateDbPath();
3163
- const dbStat = await stat4(stateDbPath).catch(() => null);
3320
+ const dbStat = await stat5(stateDbPath).catch(() => null);
3164
3321
  if (!dbStat?.isFile() || dbStat.size < 1024) return [];
3165
3322
  try {
3166
3323
  const mod = await import("sql.js");
3167
3324
  const SQL = await mod.default();
3168
- const dbBuffer = await readFile8(stateDbPath);
3325
+ const dbBuffer = await readFile9(stateDbPath);
3169
3326
  const db = new SQL.Database(dbBuffer);
3170
3327
  try {
3171
3328
  const result = db.exec(`
@@ -3202,7 +3359,7 @@ async function findRolloutFiles(dir) {
3202
3359
  return;
3203
3360
  }
3204
3361
  for (const entry of entries) {
3205
- const fp = join8(current, entry.name);
3362
+ const fp = join9(current, entry.name);
3206
3363
  if (entry.isDirectory()) await walk(fp);
3207
3364
  else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
3208
3365
  out.push(fp);
@@ -3213,11 +3370,11 @@ async function findRolloutFiles(dir) {
3213
3370
  return out;
3214
3371
  }
3215
3372
  function getCodexHome() {
3216
- return process.env.CODEX_HOME || join8(homedir7(), ".codex");
3373
+ return process.env.CODEX_HOME || join9(homedir7(), ".codex");
3217
3374
  }
3218
3375
  function getStateDbPath() {
3219
3376
  const sqliteHome = process.env.CODEX_SQLITE_HOME || getCodexHome();
3220
- return join8(sqliteHome, STATE_DB_FILENAME);
3377
+ return join9(sqliteHome, STATE_DB_FILENAME);
3221
3378
  }
3222
3379
  function toIsoFlexible(value) {
3223
3380
  if (!value) return void 0;
@@ -3229,7 +3386,7 @@ function isEditTool(name) {
3229
3386
  return !!name && ["apply_patch", "edit", "write_file"].includes(name);
3230
3387
  }
3231
3388
  function normalizeDiscoveredUserMessage(text) {
3232
- return cleanPromptText(codexStripTwoPass(text));
3389
+ return cleanPromptText2(codexStripTwoPass(text));
3233
3390
  }
3234
3391
  function recordDiscoveredPrompt(promptSeen, prompts, timestamp, text, imageKey) {
3235
3392
  const hasImages = imageKey.length > 0;
@@ -3279,9 +3436,9 @@ function imageDedupeKey(image) {
3279
3436
  return `${image.slice(0, 64)}:${image.length}:${image.slice(-32)}`;
3280
3437
  }
3281
3438
 
3282
- // src/providers/codex/parser.ts
3439
+ // ../provider-codex/src/codex/parser.ts
3283
3440
  import { readFileSync } from "fs";
3284
- import { readFile as readFile9 } from "fs/promises";
3441
+ import { readFile as readFile10 } from "fs/promises";
3285
3442
  import { extname } from "path";
3286
3443
  function asCodexTokenInfo(value) {
3287
3444
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
@@ -3294,7 +3451,7 @@ async function parseCodexSession(filePaths, sessionInfo) {
3294
3451
  const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
3295
3452
  const lines = [];
3296
3453
  for (const fp of paths) {
3297
- const content = await readFile9(fp, "utf-8");
3454
+ const content = await readFile10(fp, "utf-8");
3298
3455
  lines.push(...content.split("\n"));
3299
3456
  }
3300
3457
  return parseCodexLines(lines, sessionInfo, paths);
@@ -3921,31 +4078,31 @@ function buildCodexTurnStats(turns, snapshots, taskDurations, model) {
3921
4078
  const usable = snapshots.filter((s) => s.last);
3922
4079
  return userTurns.map((turn, i) => {
3923
4080
  const usage = usable[i]?.last;
3924
- const stat12 = { turnIndex: i };
3925
- if (model) stat12.model = model;
4081
+ const stat13 = { turnIndex: i };
4082
+ if (model) stat13.model = model;
3926
4083
  if (usage) {
3927
4084
  const input = usage.input_tokens || 0;
3928
4085
  const cached = usage.cached_input_tokens || 0;
3929
- stat12.tokenUsage = {
4086
+ stat13.tokenUsage = {
3930
4087
  inputTokens: Math.max(0, input - cached),
3931
4088
  outputTokens: usage.output_tokens || 0,
3932
4089
  cacheCreationTokens: 0,
3933
4090
  cacheReadTokens: cached
3934
4091
  };
3935
- stat12.contextTokens = input;
4092
+ stat13.contextTokens = input;
3936
4093
  }
3937
4094
  const nextUser = userTurns[i + 1]?.timestamp;
3938
4095
  const taskDuration = taskDurations[i];
3939
4096
  if (typeof taskDuration === "number" && taskDuration > 0) {
3940
- stat12.durationMs = taskDuration;
4097
+ stat13.durationMs = taskDuration;
3941
4098
  } else if (turn.timestamp) {
3942
4099
  const end = nextUser || usable[i]?.timestamp;
3943
4100
  if (end) {
3944
4101
  const diff = Date.parse(end) - Date.parse(turn.timestamp);
3945
- if (diff > 0 && diff < 12 * 60 * 60 * 1e3) stat12.durationMs = diff;
4102
+ if (diff > 0 && diff < 12 * 60 * 60 * 1e3) stat13.durationMs = diff;
3946
4103
  }
3947
4104
  }
3948
- return stat12;
4105
+ return stat13;
3949
4106
  });
3950
4107
  }
3951
4108
  function pushUnique(items, value) {
@@ -3979,7 +4136,7 @@ function mimeTypeForPath(path) {
3979
4136
  }
3980
4137
  }
3981
4138
 
3982
- // src/providers/codex/index.ts
4139
+ // ../provider-codex/src/codex/index.ts
3983
4140
  var codexProvider = {
3984
4141
  name: "codex",
3985
4142
  displayName: "Codex",
@@ -3987,22 +4144,20 @@ var codexProvider = {
3987
4144
  parse: parseCodexSession
3988
4145
  };
3989
4146
 
3990
- // src/providers/cursor/discover.ts
3991
- init_utils();
3992
- import { readdir as readdir8, readFile as readFile12, stat as stat7 } from "fs/promises";
4147
+ // ../provider-cursor/src/cursor/discover.ts
4148
+ import { readdir as readdir8, readFile as readFile13, stat as stat8 } from "fs/promises";
3993
4149
  import { homedir as homedir11 } from "os";
3994
- import { basename as basename3, join as join12 } from "path";
4150
+ import { basename as basename3, join as join13 } from "path";
3995
4151
 
3996
- // src/providers/cursor/sqlite-reader.ts
4152
+ // ../provider-cursor/src/cursor/sqlite-reader.ts
3997
4153
  import { createHash as createHash2 } from "crypto";
3998
4154
  import { execFile } from "child_process";
3999
- import { readdir as readdir6, readFile as readFile10, stat as stat5 } from "fs/promises";
4155
+ import { readdir as readdir6, readFile as readFile11, stat as stat6 } from "fs/promises";
4000
4156
  import { homedir as homedir9 } from "os";
4001
- import { basename as basename2, dirname as dirname2, join as join10, posix } from "path";
4157
+ import { basename as basename2, dirname as dirname2, join as join11, posix } from "path";
4002
4158
  import { promisify } from "util";
4003
- init_utils();
4004
4159
 
4005
- // src/providers/cursor/sanitize.ts
4160
+ // ../provider-cursor/src/cursor/sanitize.ts
4006
4161
  function internalPlanningHeadingBreakRe() {
4007
4162
  return /\n{2,}\*\*([^*\n]{3,120})\*\*\n{2,}/g;
4008
4163
  }
@@ -4071,11 +4226,11 @@ function looksLikeInternalPlanningBody(text) {
4071
4226
  );
4072
4227
  }
4073
4228
 
4074
- // src/providers/cursor/sqlite-io.ts
4229
+ // ../provider-cursor/src/cursor/sqlite-io.ts
4075
4230
  import { createHash } from "crypto";
4076
4231
  import { homedir as homedir8 } from "os";
4077
- import { join as join9 } from "path";
4078
- var CURSOR_CHATS_DIR = join9(homedir8(), ".cursor", "chats");
4232
+ import { join as join10 } from "path";
4233
+ var CURSOR_CHATS_DIR = join10(homedir8(), ".cursor", "chats");
4079
4234
  function createRetryableInit(factory) {
4080
4235
  let promise = null;
4081
4236
  return async () => {
@@ -4092,7 +4247,7 @@ function workspaceHash(absolutePath) {
4092
4247
  return createHash("md5").update(absolutePath).digest("hex");
4093
4248
  }
4094
4249
 
4095
- // src/providers/cursor/tool-mapping.ts
4250
+ // ../provider-cursor/src/cursor/tool-mapping.ts
4096
4251
  function parseJson(raw) {
4097
4252
  if (typeof raw !== "string") return null;
4098
4253
  try {
@@ -4385,7 +4540,7 @@ function mapToolArgs(toolName, args, resultText = "") {
4385
4540
  return argsObj;
4386
4541
  }
4387
4542
 
4388
- // src/providers/cursor/sqlite-reader.ts
4543
+ // ../provider-cursor/src/cursor/sqlite-reader.ts
4389
4544
  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
4545
  var MIN_STORE_DB_SIZE = 8192;
4391
4546
  var MAX_CURSOR_REQUEST_CONTEXT_ROWS = 500;
@@ -4412,7 +4567,7 @@ var resolvedProjectRootCache = /* @__PURE__ */ new Map();
4412
4567
  var GLOBAL_STATE_DISCOVERY_CACHE_PREFIX = "cursor-global-state-discovery-v4";
4413
4568
  function globalStateDbCandidates() {
4414
4569
  const candidates = [
4415
- join10(
4570
+ join11(
4416
4571
  homedir9(),
4417
4572
  "Library",
4418
4573
  "Application Support",
@@ -4421,23 +4576,23 @@ function globalStateDbCandidates() {
4421
4576
  "globalStorage",
4422
4577
  "state.vscdb"
4423
4578
  ),
4424
- join10(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb")
4579
+ join11(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb")
4425
4580
  ];
4426
4581
  const appData = process.env.APPDATA;
4427
4582
  if (appData) {
4428
- candidates.push(join10(appData, "Cursor", "User", "globalStorage", "state.vscdb"));
4583
+ candidates.push(join11(appData, "Cursor", "User", "globalStorage", "state.vscdb"));
4429
4584
  }
4430
4585
  return [...new Set(candidates)];
4431
4586
  }
4432
4587
  async function findGlobalStateDb() {
4433
4588
  for (const candidate of globalStateDbCandidates()) {
4434
- const s = await stat5(candidate).catch(() => null);
4589
+ const s = await stat6(candidate).catch(() => null);
4435
4590
  if (s?.isFile() && s.size >= MIN_STORE_DB_SIZE) return candidate;
4436
4591
  }
4437
4592
  return null;
4438
4593
  }
4439
4594
  async function globalStateWalFingerprint(dbPath) {
4440
- const walStat = await stat5(`${dbPath}-wal`).catch(() => null);
4595
+ const walStat = await stat6(`${dbPath}-wal`).catch(() => null);
4441
4596
  return {
4442
4597
  walSize: walStat?.isFile() ? walStat.size : 0,
4443
4598
  walMtimeMs: walStat?.isFile() ? walStat.mtimeMs : 0
@@ -4623,7 +4778,7 @@ async function queryGlobalStateTextValue(globalStateDb, key) {
4623
4778
  async function openGlobalStateDb() {
4624
4779
  const dbPath = await findGlobalStateDb();
4625
4780
  if (!dbPath) return null;
4626
- const dbStat = await stat5(dbPath).catch(() => null);
4781
+ const dbStat = await stat6(dbPath).catch(() => null);
4627
4782
  if (!dbStat?.isFile() || dbStat.size < MIN_STORE_DB_SIZE) return null;
4628
4783
  const walFingerprint = await globalStateWalFingerprint(dbPath);
4629
4784
  if (cachedGlobalStateDb && cachedGlobalStateDb.dbPath === dbPath && cachedGlobalStateDb.size === dbStat.size && cachedGlobalStateDb.mtimeMs === dbStat.mtimeMs && cachedGlobalStateDb.walSize === walFingerprint.walSize && cachedGlobalStateDb.walMtimeMs === walFingerprint.walMtimeMs) {
@@ -4646,7 +4801,7 @@ async function openGlobalStateDb() {
4646
4801
  } catch {
4647
4802
  return null;
4648
4803
  }
4649
- const dbBuffer = await readFile10(dbPath).catch(() => null);
4804
+ const dbBuffer = await readFile11(dbPath).catch(() => null);
4650
4805
  if (!dbBuffer) return null;
4651
4806
  const db = new SQL.Database(dbBuffer);
4652
4807
  closeCachedSqlJsDb();
@@ -4678,8 +4833,8 @@ async function buildStoreDbIndex() {
4678
4833
  return index;
4679
4834
  }
4680
4835
  for (const wsHash of workspaceDirs) {
4681
- const wsDir = join10(CURSOR_CHATS_DIR, wsHash);
4682
- const wsStat = await stat5(wsDir).catch(() => null);
4836
+ const wsDir = join11(CURSOR_CHATS_DIR, wsHash);
4837
+ const wsStat = await stat6(wsDir).catch(() => null);
4683
4838
  if (!wsStat?.isDirectory()) continue;
4684
4839
  let sessions;
4685
4840
  try {
@@ -4689,8 +4844,8 @@ async function buildStoreDbIndex() {
4689
4844
  }
4690
4845
  for (const sessionId of sessions) {
4691
4846
  if (!SESSION_ID_RE.test(sessionId)) continue;
4692
- const dbPath = join10(wsDir, sessionId, "store.db");
4693
- const dbStat = await stat5(dbPath).catch(() => null);
4847
+ const dbPath = join11(wsDir, sessionId, "store.db");
4848
+ const dbStat = await stat6(dbPath).catch(() => null);
4694
4849
  if (!dbStat?.isFile() || dbStat.size < MIN_STORE_DB_SIZE) continue;
4695
4850
  index.set(sessionId, {
4696
4851
  dbPath,
@@ -4715,7 +4870,7 @@ async function findStoreDb(sessionId) {
4715
4870
  return refreshed.get(sessionId)?.dbPath || null;
4716
4871
  }
4717
4872
  async function existingPath(path) {
4718
- const s = await stat5(path).catch(() => null);
4873
+ const s = await stat6(path).catch(() => null);
4719
4874
  return s ? path : null;
4720
4875
  }
4721
4876
  async function resolveCursorLiveWatchPaths(sessionId) {
@@ -5054,14 +5209,14 @@ async function resolveProjectRootFromPath(rawPath) {
5054
5209
  if (cached) return cached;
5055
5210
  const resolving = (async () => {
5056
5211
  let current = candidate;
5057
- const initial = await stat5(current).catch(() => null);
5212
+ const initial = await stat6(current).catch(() => null);
5058
5213
  if (initial?.isFile()) current = dirname2(current);
5059
5214
  let deepestExisting = null;
5060
5215
  while (current && current !== "/") {
5061
- const dirStat = await stat5(current).catch(() => null);
5216
+ const dirStat = await stat6(current).catch(() => null);
5062
5217
  if (dirStat?.isDirectory()) {
5063
5218
  if (!deepestExisting) deepestExisting = current;
5064
- const gitStat = await stat5(join10(current, ".git")).catch(() => null);
5219
+ const gitStat = await stat6(join11(current, ".git")).catch(() => null);
5065
5220
  if (gitStat) return current;
5066
5221
  }
5067
5222
  const parent = dirname2(current);
@@ -5184,7 +5339,7 @@ async function readStoreDbMeta(dbPath) {
5184
5339
  } catch {
5185
5340
  return null;
5186
5341
  }
5187
- const dbBuffer = await readFile10(dbPath).catch(() => null);
5342
+ const dbBuffer = await readFile11(dbPath).catch(() => null);
5188
5343
  if (!dbBuffer) return null;
5189
5344
  const db = new SQL.Database(dbBuffer);
5190
5345
  try {
@@ -5490,7 +5645,7 @@ async function parseCursorStoreDb(sessionId, workspacePath = "") {
5490
5645
  } catch {
5491
5646
  return null;
5492
5647
  }
5493
- const dbBuffer = await readFile10(dbPath);
5648
+ const dbBuffer = await readFile11(dbPath);
5494
5649
  const db = new SQL.Database(dbBuffer);
5495
5650
  try {
5496
5651
  const metaRows = db.exec("SELECT value FROM meta WHERE key = '0'");
@@ -5607,7 +5762,7 @@ function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
5607
5762
  const { turns, turnStats, totalDurationMs } = messagesToTurns(messages);
5608
5763
  const slug = sessionId.slice(0, 8);
5609
5764
  const title = metaJson.name || firstUserTextSnippet(turns);
5610
- const hasDurationStats = turnStats.some((stat12) => (stat12.durationMs || 0) > 0);
5765
+ const hasDurationStats = turnStats.some((stat13) => (stat13.durationMs || 0) > 0);
5611
5766
  const notes = [];
5612
5767
  if (hasDurationStats) {
5613
5768
  notes.push("Per-turn duration is estimated from Cursor tool execution metadata.");
@@ -5620,7 +5775,7 @@ function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
5620
5775
  slug,
5621
5776
  title,
5622
5777
  cwd: workspacePath,
5623
- model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((stat12) => typeof stat12.model === "string" && stat12.model)?.model,
5778
+ model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((stat13) => typeof stat13.model === "string" && stat13.model)?.model,
5624
5779
  startTime: metaJson.createdAt ? new Date(metaJson.createdAt).toISOString() : void 0,
5625
5780
  ...totalDurationMs !== void 0 ? { totalDurationMs } : {},
5626
5781
  ...turnStats.length > 0 ? { turnStats } : {},
@@ -6022,13 +6177,13 @@ function mergeUniqueStrings(...groups) {
6022
6177
  }
6023
6178
  function mergeCursorParseResults(primary, enrichment) {
6024
6179
  const mergedModel = chooseMergedCursorModel(primary.model, enrichment.model);
6025
- const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((stat12) => stat12.durationMs !== void 0) === true);
6180
+ const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((stat13) => stat13.durationMs !== void 0) === true);
6026
6181
  const mergedTurnStats = mergeTurnStats(primary.turnStats, enrichment.turnStats, {
6027
6182
  preferEnrichmentDuration
6028
6183
  });
6029
6184
  const mergedTokenUsage = primary.tokenUsage || enrichment.tokenUsage;
6030
6185
  const mergedTokenUsageByModel = primary.tokenUsageByModel || enrichment.tokenUsageByModel;
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);
6186
+ const mergedTotalDurationMs = (preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || primary.totalDurationMs || (!preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || (mergedTurnStats && mergedTurnStats.length > 0 ? mergedTurnStats.reduce((sum, stat13) => sum + (stat13.durationMs || 0), 0) || void 0 : void 0);
6032
6187
  const mergedDuration = mergedTotalDurationMs !== primary.totalDurationMs;
6033
6188
  const mergedTokens = !primary.tokenUsage && !!enrichment.tokenUsage || !primary.tokenUsageByModel && !!enrichment.tokenUsageByModel;
6034
6189
  const supplements = mergeUniqueStrings(
@@ -6112,11 +6267,11 @@ function mergeTurnStats(primary, enrichment, options) {
6112
6267
  if (!primary?.length) return enrichment;
6113
6268
  if (!enrichment?.length) return primary;
6114
6269
  const maxTurnIndex = Math.max(
6115
- ...primary.map((stat12) => stat12.turnIndex),
6116
- ...enrichment.map((stat12) => stat12.turnIndex)
6270
+ ...primary.map((stat13) => stat13.turnIndex),
6271
+ ...enrichment.map((stat13) => stat13.turnIndex)
6117
6272
  );
6118
- const primaryByIndex = new Map(primary.map((stat12) => [stat12.turnIndex, stat12]));
6119
- const enrichmentByIndex = new Map(enrichment.map((stat12) => [stat12.turnIndex, stat12]));
6273
+ const primaryByIndex = new Map(primary.map((stat13) => [stat13.turnIndex, stat13]));
6274
+ const enrichmentByIndex = new Map(enrichment.map((stat13) => [stat13.turnIndex, stat13]));
6120
6275
  const merged = [];
6121
6276
  for (let turnIndex = 0; turnIndex <= maxTurnIndex; turnIndex++) {
6122
6277
  const current = primaryByIndex.get(turnIndex);
@@ -6210,17 +6365,17 @@ function applyGlobalStateWallClockDurations(entries, turnStats) {
6210
6365
  if (durationsByTurn.size === 0) {
6211
6366
  return {
6212
6367
  turnStats,
6213
- totalDurationMs: turnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0,
6368
+ totalDurationMs: turnStats.reduce((sum, stat13) => sum + (stat13.durationMs || 0), 0) || void 0,
6214
6369
  usedWallClock: false
6215
6370
  };
6216
6371
  }
6217
- const mergedTurnStats = turnStats.map((stat12) => {
6218
- const wallClockDuration = durationsByTurn.get(stat12.turnIndex);
6219
- return wallClockDuration !== void 0 ? { ...stat12, durationMs: wallClockDuration } : stat12;
6372
+ const mergedTurnStats = turnStats.map((stat13) => {
6373
+ const wallClockDuration = durationsByTurn.get(stat13.turnIndex);
6374
+ return wallClockDuration !== void 0 ? { ...stat13, durationMs: wallClockDuration } : stat13;
6220
6375
  });
6221
6376
  return {
6222
6377
  turnStats: mergedTurnStats,
6223
- totalDurationMs: mergedTurnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0,
6378
+ totalDurationMs: mergedTurnStats.reduce((sum, stat13) => sum + (stat13.durationMs || 0), 0) || void 0,
6224
6379
  usedWallClock: true
6225
6380
  };
6226
6381
  }
@@ -6266,15 +6421,15 @@ function buildGlobalStateMetrics(entries, fallbackModel, sessionTokenUsage) {
6266
6421
  current.durationMs = (current.durationMs || 0) + bubbleDurationMs;
6267
6422
  }
6268
6423
  }
6269
- for (const stat12 of turnStats) {
6270
- if (stat12.tokenUsage && !hasAnyTokens(stat12.tokenUsage)) {
6271
- delete stat12.tokenUsage;
6424
+ for (const stat13 of turnStats) {
6425
+ if (stat13.tokenUsage && !hasAnyTokens(stat13.tokenUsage)) {
6426
+ delete stat13.tokenUsage;
6272
6427
  }
6273
- if ((stat12.durationMs || 0) <= 0) {
6274
- delete stat12.durationMs;
6428
+ if ((stat13.durationMs || 0) <= 0) {
6429
+ delete stat13.durationMs;
6275
6430
  }
6276
- if ((stat12.contextTokens || 0) <= 0) {
6277
- delete stat12.contextTokens;
6431
+ if ((stat13.contextTokens || 0) <= 0) {
6432
+ delete stat13.contextTokens;
6278
6433
  }
6279
6434
  }
6280
6435
  const {
@@ -6422,7 +6577,7 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
6422
6577
  }
6423
6578
  const hasDetailedTurnStats = Boolean(
6424
6579
  metrics.turnStats?.some(
6425
- (stat12) => !!stat12.durationMs || !!stat12.contextTokens || !!stat12.tokenUsage
6580
+ (stat13) => !!stat13.durationMs || !!stat13.contextTokens || !!stat13.tokenUsage
6426
6581
  )
6427
6582
  );
6428
6583
  if (!hasDetailedTurnStats) {
@@ -6525,7 +6680,7 @@ function messagesToTurns(messages) {
6525
6680
  }
6526
6681
  }
6527
6682
  const turnStats = buildStoreTurnStats(turns);
6528
- const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0 : void 0;
6683
+ const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum, stat13) => sum + (stat13.durationMs || 0), 0) || void 0 : void 0;
6529
6684
  return { turns, turnStats, totalDurationMs };
6530
6685
  }
6531
6686
  var CURSOR_SYSTEM_CONTEXT_RE = /^<(?:user_info|system_reminder|agent_transcripts|rules|git_status)>/;
@@ -6613,13 +6768,13 @@ function extractModel(msg) {
6613
6768
  return void 0;
6614
6769
  }
6615
6770
 
6616
- // src/providers/cursor/sdk-reader.ts
6771
+ // ../provider-cursor/src/cursor/sdk-reader.ts
6617
6772
  import { execFile as execFile2 } from "child_process";
6618
- import { readdir as readdir7, readFile as readFile11, stat as stat6 } from "fs/promises";
6773
+ import { readdir as readdir7, readFile as readFile12, stat as stat7 } from "fs/promises";
6619
6774
  import { homedir as homedir10 } from "os";
6620
- import { join as join11 } from "path";
6775
+ import { join as join12 } from "path";
6621
6776
  import { promisify as promisify2 } from "util";
6622
- var CURSOR_PROJECTS_ROOT = join11(homedir10(), ".cursor", "projects");
6777
+ var CURSOR_PROJECTS_ROOT = join12(homedir10(), ".cursor", "projects");
6623
6778
  var SDK_STORE_SUBDIR = "sdk-agent-store";
6624
6779
  var SDK_INDEX_DB_BASENAME = "index.db";
6625
6780
  var MIN_INDEX_DB_SIZE = 1024;
@@ -6902,7 +7057,7 @@ function applySdkEnrichmentToTurns(turns, enrichment) {
6902
7057
  return { turns, toolCallsEnriched, assistantTurnsModelTagged };
6903
7058
  }
6904
7059
  async function openIndexDb(dbPath) {
6905
- const st = await stat6(dbPath).catch(() => null);
7060
+ const st = await stat7(dbPath).catch(() => null);
6906
7061
  if (!st?.isFile() || st.size < MIN_INDEX_DB_SIZE) return null;
6907
7062
  if (await canUseSqliteCliFor(dbPath)) {
6908
7063
  return { dbPath, backend: "sqlite-cli" };
@@ -6914,7 +7069,7 @@ async function openIndexDb(dbPath) {
6914
7069
  return null;
6915
7070
  }
6916
7071
  if (st.size > MAX_SQLJS_DB_BYTES) return null;
6917
- const buffer = await readFile11(dbPath).catch(() => null);
7072
+ const buffer = await readFile12(dbPath).catch(() => null);
6918
7073
  if (!buffer) return null;
6919
7074
  const db = new SQL.Database(buffer);
6920
7075
  return { dbPath, backend: "sqljs", db };
@@ -6973,13 +7128,13 @@ async function listSdkIndexDbPaths(projectsRoot) {
6973
7128
  const perProjectPaths = await Promise.all(
6974
7129
  projects.map(async (project) => {
6975
7130
  const out = [];
6976
- const sdkRoot = join11(projectsRoot, project, SDK_STORE_SUBDIR);
6977
- const sdkStat = await stat6(sdkRoot).catch(() => null);
7131
+ const sdkRoot = join12(projectsRoot, project, SDK_STORE_SUBDIR);
7132
+ const sdkStat = await stat7(sdkRoot).catch(() => null);
6978
7133
  if (!sdkStat?.isDirectory()) return out;
6979
7134
  const projectHashes = await readdir7(sdkRoot).catch(() => []);
6980
7135
  for (const hash of projectHashes) {
6981
- const dbPath = join11(sdkRoot, hash, SDK_INDEX_DB_BASENAME);
6982
- const dbStat = await stat6(dbPath).catch(() => null);
7136
+ const dbPath = join12(sdkRoot, hash, SDK_INDEX_DB_BASENAME);
7137
+ const dbStat = await stat7(dbPath).catch(() => null);
6983
7138
  if (!dbStat?.isFile() || dbStat.size < MIN_INDEX_DB_SIZE) continue;
6984
7139
  out.push(dbPath);
6985
7140
  }
@@ -7012,8 +7167,8 @@ function safeJsonStringify(value) {
7012
7167
  }
7013
7168
  }
7014
7169
 
7015
- // src/providers/cursor/discover.ts
7016
- var CURSOR_DIR = join12(homedir11(), ".cursor", "projects");
7170
+ // ../provider-cursor/src/cursor/discover.ts
7171
+ var CURSOR_DIR = join13(homedir11(), ".cursor", "projects");
7017
7172
  var PROJECT_DISCOVERY_CONCURRENCY = 6;
7018
7173
  var TRANSCRIPT_INFO_CONCURRENCY = 6;
7019
7174
  var ENTRY_STAT_CONCURRENCY = 32;
@@ -7052,14 +7207,15 @@ async function discoverCursorSessions() {
7052
7207
  return sessions;
7053
7208
  }
7054
7209
  async function discoverProjectSessions(projDir) {
7055
- const transcriptsDir = join12(CURSOR_DIR, projDir, "agent-transcripts");
7056
- const dirStat = await stat7(transcriptsDir).catch(() => null);
7210
+ const transcriptsDir = join13(CURSOR_DIR, projDir, "agent-transcripts");
7211
+ const dirStat = await stat8(transcriptsDir).catch(() => null);
7057
7212
  if (!dirStat?.isDirectory()) return [];
7058
7213
  const project = await decodeProjectDir2(projDir);
7214
+ const gitRepo = await readGitRepo(project);
7059
7215
  const transcriptEntries = await collectTranscriptEntries(transcriptsDir);
7060
7216
  if (transcriptEntries.length === 0) return [];
7061
7217
  transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7062
- const toolEntries = await collectToolEntries(join12(CURSOR_DIR, projDir, "agent-tools"));
7218
+ const toolEntries = await collectToolEntries(join13(CURSOR_DIR, projDir, "agent-tools"));
7063
7219
  toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
7064
7220
  const jobs = [];
7065
7221
  let toolStart = 0;
@@ -7089,6 +7245,7 @@ async function discoverProjectSessions(projDir) {
7089
7245
  if (!info) return null;
7090
7246
  info.workspacePath = project;
7091
7247
  info.hasSqlite = false;
7248
+ info.gitRepo = gitRepo;
7092
7249
  return info;
7093
7250
  });
7094
7251
  return infos.filter((info) => info !== null);
@@ -7148,7 +7305,7 @@ async function decodeWindowsProjectDir(encoded) {
7148
7305
  }
7149
7306
  async function resolveEncodedProjectParts(parts, idx, current) {
7150
7307
  if (idx >= parts.length) {
7151
- const currentStat = await stat7(current).catch(() => null);
7308
+ const currentStat = await stat8(current).catch(() => null);
7152
7309
  return currentStat?.isDirectory() ? current : null;
7153
7310
  }
7154
7311
  const entries = await readdir8(current, { withFileTypes: true }).catch(() => []);
@@ -7158,10 +7315,10 @@ async function resolveEncodedProjectParts(parts, idx, current) {
7158
7315
  );
7159
7316
  for (let end = idx + 1; end <= parts.length; end++) {
7160
7317
  const candidate = parts.slice(idx, end).join("-");
7161
- const candidatePath = join12(current, candidate);
7318
+ const candidatePath = join13(current, candidate);
7162
7319
  if (!dirNames.has(candidate)) {
7163
7320
  if (process.platform !== "win32") continue;
7164
- const candidateStat = await stat7(candidatePath).catch(() => null);
7321
+ const candidateStat = await stat8(candidatePath).catch(() => null);
7165
7322
  if (!candidateStat?.isDirectory()) continue;
7166
7323
  }
7167
7324
  const resolved = await resolveEncodedProjectParts(parts, end, candidatePath);
@@ -7171,7 +7328,7 @@ async function resolveEncodedProjectParts(parts, idx, current) {
7171
7328
  }
7172
7329
  async function extractSessionInfo2(filePath, fileSize, mtimeMs, project, toolPaths) {
7173
7330
  try {
7174
- const content = await readFile12(filePath, "utf-8");
7331
+ const content = await readFile13(filePath, "utf-8");
7175
7332
  const sessionId = basename3(filePath, ".jsonl");
7176
7333
  let firstPrompt = "";
7177
7334
  let promptCount = 0;
@@ -7248,8 +7405,8 @@ async function collectTranscriptEntries(transcriptsDir) {
7248
7405
  return [];
7249
7406
  }
7250
7407
  const transcripts = await mapLimit(entries, ENTRY_STAT_CONCURRENCY, async (entry) => {
7251
- const entryPath = join12(transcriptsDir, entry);
7252
- const entryStat = await stat7(entryPath).catch(() => null);
7408
+ const entryPath = join13(transcriptsDir, entry);
7409
+ const entryStat = await stat8(entryPath).catch(() => null);
7253
7410
  if (!entryStat) return null;
7254
7411
  if (entry.endsWith(".jsonl") && entryStat.isFile()) {
7255
7412
  return {
@@ -7259,8 +7416,8 @@ async function collectTranscriptEntries(transcriptsDir) {
7259
7416
  };
7260
7417
  }
7261
7418
  if (!entryStat.isDirectory()) return null;
7262
- const innerPath = join12(entryPath, `${entry}.jsonl`);
7263
- const innerStat = await stat7(innerPath).catch(() => null);
7419
+ const innerPath = join13(entryPath, `${entry}.jsonl`);
7420
+ const innerStat = await stat8(innerPath).catch(() => null);
7264
7421
  if (!innerStat?.isFile()) return null;
7265
7422
  return {
7266
7423
  path: innerPath,
@@ -7271,7 +7428,7 @@ async function collectTranscriptEntries(transcriptsDir) {
7271
7428
  return transcripts.filter((entry) => entry !== null);
7272
7429
  }
7273
7430
  async function collectToolEntries(toolDir) {
7274
- const dirStat = await stat7(toolDir).catch(() => null);
7431
+ const dirStat = await stat8(toolDir).catch(() => null);
7275
7432
  if (!dirStat?.isDirectory()) return [];
7276
7433
  let entries;
7277
7434
  try {
@@ -7281,8 +7438,8 @@ async function collectToolEntries(toolDir) {
7281
7438
  }
7282
7439
  const toolFiles = entries.filter((entry) => entry.endsWith(".txt"));
7283
7440
  const tools = await mapLimit(toolFiles, ENTRY_STAT_CONCURRENCY, async (entry) => {
7284
- const entryPath = join12(toolDir, entry);
7285
- const entryStat = await stat7(entryPath).catch(() => null);
7441
+ const entryPath = join13(toolDir, entry);
7442
+ const entryStat = await stat8(entryPath).catch(() => null);
7286
7443
  if (!entryStat?.isFile()) return null;
7287
7444
  return { path: entryPath, mtimeMs: entryStat.mtimeMs };
7288
7445
  });
@@ -7305,10 +7462,20 @@ async function mapLimit(items, concurrency, worker) {
7305
7462
  return results;
7306
7463
  }
7307
7464
 
7308
- // src/providers/cursor/parser.ts
7309
- import { readdir as readdir9, readFile as readFile13, stat as stat8 } from "fs/promises";
7465
+ // ../provider-cursor/src/cursor/parser.ts
7466
+ import { readdir as readdir9, readFile as readFile14, stat as stat9 } from "fs/promises";
7310
7467
  import { homedir as homedir12 } from "os";
7311
- import { basename as basename4, extname as extname2, join as join13 } from "path";
7468
+ import { basename as basename4, extname as extname2, join as join14 } from "path";
7469
+ var defaultDependencies = {
7470
+ isSystemContextText,
7471
+ mapCursorToolName,
7472
+ mapToolArgs,
7473
+ parseCursorSqlite
7474
+ };
7475
+ function createCursorParser(deps = {}) {
7476
+ const resolved = { ...defaultDependencies, ...deps };
7477
+ return (filePaths, sessionInfo) => parseCursorSessionWithDependencies(filePaths, sessionInfo, resolved);
7478
+ }
7312
7479
  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;
7313
7480
  function toErrorMessage(err) {
7314
7481
  if (err instanceof Error && err.message) return err.message;
@@ -7319,7 +7486,8 @@ function compactErrorMessage(err, max = 180) {
7319
7486
  if (cleaned.length <= max) return cleaned;
7320
7487
  return `${cleaned.slice(0, max)}...`;
7321
7488
  }
7322
- async function parseCursorSession(filePaths, sessionInfo) {
7489
+ var parseCursorSession = createCursorParser();
7490
+ async function parseCursorSessionWithDependencies(filePaths, sessionInfo, deps) {
7323
7491
  const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
7324
7492
  const transcriptPaths = paths.filter((p) => p.endsWith(".jsonl"));
7325
7493
  const explicitToolPaths = paths.filter((p) => p.endsWith(".txt"));
@@ -7333,7 +7501,7 @@ async function parseCursorSession(filePaths, sessionInfo) {
7333
7501
  let sqliteResult = null;
7334
7502
  try {
7335
7503
  const preferredWorkspacePath2 = sessionInfo?.workspacePath || sessionInfo?.cwd || "";
7336
- sqliteResult = await parseCursorSqlite(preferredWorkspacePath2, sqliteSessionId);
7504
+ sqliteResult = await deps.parseCursorSqlite(preferredWorkspacePath2, sqliteSessionId);
7337
7505
  } catch (err) {
7338
7506
  sqliteError = compactErrorMessage(err);
7339
7507
  sqliteFallbackNote = `cursor SQLite parse failed (${sqliteError}); fell back to JSONL transcript`;
@@ -7343,9 +7511,14 @@ async function parseCursorSession(filePaths, sessionInfo) {
7343
7511
  if (transcriptPaths.length > 0) {
7344
7512
  const thinkingBefore = countThinkingBlocks(sqliteResult.turns);
7345
7513
  const userImagesBefore = countUserImages(sqliteResult.turns);
7346
- const jsonlThinking = await parseCursorJsonl(transcriptPaths, [], {
7347
- inferToolPaths: false
7348
- });
7514
+ const jsonlThinking = await parseCursorJsonl(
7515
+ transcriptPaths,
7516
+ [],
7517
+ {
7518
+ inferToolPaths: false
7519
+ },
7520
+ deps
7521
+ );
7349
7522
  sqliteResult.parseWarnings = mergeParseWarnings(
7350
7523
  sqliteResult.parseWarnings,
7351
7524
  jsonlThinking.parseWarnings
@@ -7379,9 +7552,14 @@ async function parseCursorSession(filePaths, sessionInfo) {
7379
7552
  }
7380
7553
  throw new Error("Cursor parse requires at least one transcript .jsonl path");
7381
7554
  }
7382
- const jsonlResult = await parseCursorJsonl(transcriptPaths, explicitToolPaths, {
7383
- inferToolPaths: true
7384
- });
7555
+ const jsonlResult = await parseCursorJsonl(
7556
+ transcriptPaths,
7557
+ explicitToolPaths,
7558
+ {
7559
+ inferToolPaths: true
7560
+ },
7561
+ deps
7562
+ );
7385
7563
  const preferredWorkspacePath = sessionInfo?.workspacePath || sessionInfo?.cwd || "";
7386
7564
  if (!jsonlResult.cwd && preferredWorkspacePath) {
7387
7565
  jsonlResult.cwd = preferredWorkspacePath;
@@ -7488,7 +7666,7 @@ function withNote(info, note) {
7488
7666
  if (!notes.includes(note)) notes.push(note);
7489
7667
  return { ...info, notes };
7490
7668
  }
7491
- async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
7669
+ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options, deps) {
7492
7670
  const allTurns = [];
7493
7671
  let syntheticToolId = 0;
7494
7672
  const toolResults = /* @__PURE__ */ new Map();
@@ -7498,7 +7676,7 @@ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
7498
7676
  const sortedTranscriptPaths = await sortByMtime(transcriptPaths);
7499
7677
  const sessionId = basename4(sortedTranscriptPaths[sortedTranscriptPaths.length - 1], ".jsonl");
7500
7678
  for (const filePath of sortedTranscriptPaths) {
7501
- const content = await readFile13(filePath, "utf-8");
7679
+ const content = await readFile14(filePath, "utf-8");
7502
7680
  const lines = content.split("\n");
7503
7681
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
7504
7682
  const line = lines[lineIndex];
@@ -7536,7 +7714,7 @@ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
7536
7714
  }
7537
7715
  } else if (block.type === "text" && block.text) {
7538
7716
  let text = stripUserQueryWrapper(block.text);
7539
- if (role === "user" && isSystemContextText(text)) continue;
7717
+ if (role === "user" && deps.isSystemContextText(text)) continue;
7540
7718
  const extracted = extractImageFilePathsFromText(text);
7541
7719
  text = normalizeImagePlaceholderLines(extracted.cleanedText);
7542
7720
  for (const imagePath of extracted.paths) imageFilePaths.add(imagePath);
@@ -7605,14 +7783,14 @@ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
7605
7783
  if (thinking) blocks.push({ type: "thinking", thinking });
7606
7784
  } else if (block.type === "tool_use") {
7607
7785
  const rawName = typeof block.name === "string" && block.name.trim() ? block.name.trim() : "Tool";
7608
- const name = mapCursorToolName(rawName);
7786
+ const name = deps.mapCursorToolName(rawName);
7609
7787
  blocks.push({
7610
7788
  type: "tool_use",
7611
7789
  id: typeof block.id === "string" && block.id.trim() ? block.id : `cursor-inline-${syntheticToolId++}`,
7612
7790
  name,
7613
7791
  // Keep the raw name here because Cursor raw tools carry different arg
7614
7792
  // schemas even when they map to the same canonical replay tool.
7615
- input: mapToolArgs(rawName, block.input)
7793
+ input: deps.mapToolArgs(rawName, block.input)
7616
7794
  });
7617
7795
  }
7618
7796
  }
@@ -7628,7 +7806,7 @@ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
7628
7806
  const toolPaths = explicitToolPaths.length > 0 ? await sortByMtime(explicitToolPaths) : options.inferToolPaths ? await inferToolPaths(sortedTranscriptPaths) : [];
7629
7807
  const toolEvents = await loadToolEvents(toolPaths);
7630
7808
  attachToolEvents(allTurns, toolEvents);
7631
- attachToolResults(allTurns, toolResults, toolErrors, toolImages);
7809
+ attachToolResults(allTurns, toolResults, toolErrors, toolImages, deps);
7632
7810
  const slug = sessionId.slice(0, 8);
7633
7811
  const firstUser = allTurns.find((t) => t.role === "user");
7634
7812
  const firstBlock = firstUser?.blocks[0];
@@ -7664,7 +7842,7 @@ function normalizeImagePlaceholderLines(text) {
7664
7842
  }
7665
7843
  function resolveImagePath(pathValue) {
7666
7844
  if (pathValue.startsWith("~/") || pathValue.startsWith("~\\")) {
7667
- return join13(homedir12(), pathValue.slice(2));
7845
+ return join14(homedir12(), pathValue.slice(2));
7668
7846
  }
7669
7847
  return pathValue;
7670
7848
  }
@@ -7693,7 +7871,7 @@ function extractImageFilePathsFromText(text) {
7693
7871
  }
7694
7872
  async function readImageFileAsDataUrl(pathValue) {
7695
7873
  try {
7696
- const data = await readFile13(pathValue);
7874
+ const data = await readFile14(pathValue);
7697
7875
  return `data:${imageMediaType(pathValue)};base64,${data.toString("base64")}`;
7698
7876
  } catch {
7699
7877
  return null;
@@ -7755,7 +7933,7 @@ function extractToolResultImages(block) {
7755
7933
  return `data:${mediaType};base64,${source.data}`;
7756
7934
  }).filter((value) => typeof value === "string");
7757
7935
  }
7758
- function attachToolResults(turns, toolResults, toolErrors, toolImages) {
7936
+ function attachToolResults(turns, toolResults, toolErrors, toolImages, deps) {
7759
7937
  for (const turn of turns) {
7760
7938
  if (turn.role !== "assistant") continue;
7761
7939
  for (const block of turn.blocks) {
@@ -7763,7 +7941,7 @@ function attachToolResults(turns, toolResults, toolErrors, toolImages) {
7763
7941
  const result = toolResults.get(block.id);
7764
7942
  if (result !== void 0) {
7765
7943
  block._result = result.result;
7766
- block.input = mapToolArgs(block.name, block.input, result.result);
7944
+ block.input = deps.mapToolArgs(block.name, block.input, result.result);
7767
7945
  const durationMs = durationBetween(turn.timestamp, result.timestamp);
7768
7946
  if (durationMs !== void 0) block._durationMs = durationMs;
7769
7947
  }
@@ -7830,7 +8008,7 @@ function mergeJsonlSupplementsIntoCursorTurns(primaryTurns, jsonlTurns) {
7830
8008
  async function sortByMtime(paths) {
7831
8009
  const entries = [];
7832
8010
  for (const path of paths) {
7833
- const st = await stat8(path).catch(() => null);
8011
+ const st = await stat9(path).catch(() => null);
7834
8012
  if (!st?.isFile()) continue;
7835
8013
  entries.push({ path, mtimeMs: st.mtimeMs });
7836
8014
  }
@@ -7848,8 +8026,8 @@ async function inferToolPaths(transcriptPaths) {
7848
8026
  projects.get(projectRoot)?.add(transcriptPath);
7849
8027
  }
7850
8028
  for (const [projectRoot, selectedPaths] of projects.entries()) {
7851
- const transcriptsDir = join13(projectRoot, "agent-transcripts");
7852
- const toolDir = join13(projectRoot, "agent-tools");
8029
+ const transcriptsDir = join14(projectRoot, "agent-transcripts");
8030
+ const toolDir = join14(projectRoot, "agent-tools");
7853
8031
  const transcriptEntries = await collectTranscriptEntries2(transcriptsDir);
7854
8032
  const toolEntries = await collectToolEntries2(toolDir);
7855
8033
  if (transcriptEntries.length === 0 || toolEntries.length === 0) continue;
@@ -7878,16 +8056,16 @@ async function collectTranscriptEntries2(transcriptsDir) {
7878
8056
  }
7879
8057
  const transcripts = [];
7880
8058
  for (const entry of entries) {
7881
- const entryPath = join13(transcriptsDir, entry);
7882
- const st = await stat8(entryPath).catch(() => null);
8059
+ const entryPath = join14(transcriptsDir, entry);
8060
+ const st = await stat9(entryPath).catch(() => null);
7883
8061
  if (!st) continue;
7884
8062
  if (entry.endsWith(".jsonl") && st.isFile()) {
7885
8063
  transcripts.push({ path: entryPath, mtimeMs: st.mtimeMs });
7886
8064
  continue;
7887
8065
  }
7888
8066
  if (!st.isDirectory()) continue;
7889
- const nested = join13(entryPath, `${entry}.jsonl`);
7890
- const nestedStat = await stat8(nested).catch(() => null);
8067
+ const nested = join14(entryPath, `${entry}.jsonl`);
8068
+ const nestedStat = await stat9(nested).catch(() => null);
7891
8069
  if (!nestedStat?.isFile()) continue;
7892
8070
  transcripts.push({ path: nested, mtimeMs: nestedStat.mtimeMs });
7893
8071
  }
@@ -7903,8 +8081,8 @@ async function collectToolEntries2(toolDir) {
7903
8081
  const tools = [];
7904
8082
  for (const entry of entries) {
7905
8083
  if (!entry.endsWith(".txt")) continue;
7906
- const entryPath = join13(toolDir, entry);
7907
- const st = await stat8(entryPath).catch(() => null);
8084
+ const entryPath = join14(toolDir, entry);
8085
+ const st = await stat9(entryPath).catch(() => null);
7908
8086
  if (!st?.isFile()) continue;
7909
8087
  tools.push({ path: entryPath, mtimeMs: st.mtimeMs });
7910
8088
  }
@@ -7918,10 +8096,10 @@ function getCursorProjectRoot(transcriptPath) {
7918
8096
  async function loadToolEvents(toolPaths) {
7919
8097
  const events = [];
7920
8098
  for (const path of toolPaths) {
7921
- const content = await readFile13(path, "utf-8").catch(() => "");
8099
+ const content = await readFile14(path, "utf-8").catch(() => "");
7922
8100
  const result = content.trim();
7923
8101
  if (!result) continue;
7924
- const st = await stat8(path).catch(() => null);
8102
+ const st = await stat9(path).catch(() => null);
7925
8103
  const id = basename4(path, extname2(path));
7926
8104
  events.push({
7927
8105
  id,
@@ -8018,7 +8196,7 @@ function splitToolMarker(text) {
8018
8196
  return void 0;
8019
8197
  }
8020
8198
 
8021
- // src/providers/cursor/index.ts
8199
+ // ../provider-cursor/src/cursor/index.ts
8022
8200
  var cursorProvider = {
8023
8201
  name: "cursor",
8024
8202
  displayName: "Cursor",
@@ -8026,27 +8204,27 @@ var cursorProvider = {
8026
8204
  parse: (filePaths, sessionInfo) => parseCursorSession(filePaths, sessionInfo)
8027
8205
  };
8028
8206
 
8029
- // src/providers/pi/discover.ts
8207
+ // ../provider-pi/src/pi/discover.ts
8030
8208
  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";
8209
+ import { readdir as readdir10, stat as stat10 } from "fs/promises";
8210
+ import { basename as basename5, join as join16 } from "path";
8033
8211
  import { createInterface as createInterface4 } from "readline";
8034
8212
 
8035
- // src/providers/pi/config.ts
8036
- import { readFile as readFile14 } from "fs/promises";
8213
+ // ../provider-pi/src/pi/config.ts
8214
+ import { readFile as readFile15 } from "fs/promises";
8037
8215
  import { homedir as homedir13 } from "os";
8038
- import { join as join14 } from "path";
8216
+ import { join as join15 } from "path";
8039
8217
  function getPiAgentDir() {
8040
- return process.env.PI_CODING_AGENT_DIR || join14(homedir13(), ".pi", "agent");
8218
+ return process.env.PI_CODING_AGENT_DIR || join15(homedir13(), ".pi", "agent");
8041
8219
  }
8042
8220
  function getPiSessionsDir() {
8043
- return process.env.PI_CODING_AGENT_SESSION_DIR || join14(getPiAgentDir(), "sessions");
8221
+ return process.env.PI_CODING_AGENT_SESSION_DIR || join15(getPiAgentDir(), "sessions");
8044
8222
  }
8045
8223
  async function readPiModelContextWindows() {
8046
8224
  const result = /* @__PURE__ */ new Map();
8047
8225
  let raw;
8048
8226
  try {
8049
- raw = await readFile14(join14(getPiAgentDir(), "models.json"), "utf-8");
8227
+ raw = await readFile15(join15(getPiAgentDir(), "models.json"), "utf-8");
8050
8228
  } catch {
8051
8229
  return result;
8052
8230
  }
@@ -8072,7 +8250,7 @@ function collectModelContextWindows(value, result) {
8072
8250
  for (const nested of Object.values(obj)) collectModelContextWindows(nested, result);
8073
8251
  }
8074
8252
 
8075
- // src/providers/pi/discover.ts
8253
+ // ../provider-pi/src/pi/discover.ts
8076
8254
  var PROMPT_SCAN_LIMIT = 2;
8077
8255
  async function discoverPiSessions() {
8078
8256
  const sessionsRoot = getPiSessionsDir();
@@ -8084,8 +8262,8 @@ async function discoverPiSessions() {
8084
8262
  return sessions;
8085
8263
  }
8086
8264
  for (const projectDir of projectDirs) {
8087
- const projectPath = join15(sessionsRoot, projectDir);
8088
- const projectStat = await stat9(projectPath).catch(() => null);
8265
+ const projectPath = join16(sessionsRoot, projectDir);
8266
+ const projectStat = await stat10(projectPath).catch(() => null);
8089
8267
  if (!projectStat?.isDirectory()) continue;
8090
8268
  let files;
8091
8269
  try {
@@ -8095,8 +8273,8 @@ async function discoverPiSessions() {
8095
8273
  }
8096
8274
  for (const file of files) {
8097
8275
  if (!file.endsWith(".jsonl")) continue;
8098
- const filePath = join15(projectPath, file);
8099
- const fileStat = await stat9(filePath).catch(() => null);
8276
+ const filePath = join16(projectPath, file);
8277
+ const fileStat = await stat10(filePath).catch(() => null);
8100
8278
  if (!fileStat) continue;
8101
8279
  const info = await extractPiSessionInfo(filePath, fileStat.size, projectDir);
8102
8280
  if (info) sessions.push(info);
@@ -8154,7 +8332,7 @@ async function extractPiSessionInfo(filePath, fileSize, encodedProjectDir) {
8154
8332
  if (text) {
8155
8333
  promptCount++;
8156
8334
  if (prompts.length < PROMPT_SCAN_LIMIT) {
8157
- prompts.push(cleanPromptText(text).slice(0, 200));
8335
+ prompts.push(cleanPromptText2(text).slice(0, 200));
8158
8336
  }
8159
8337
  }
8160
8338
  } else if (message.role === "assistant") {
@@ -8175,6 +8353,7 @@ async function extractPiSessionInfo(filePath, fileSize, encodedProjectDir) {
8175
8353
  }
8176
8354
  if (!header || prompts.length === 0) return null;
8177
8355
  const cwd = header.cwd || decodeProjectDir3(encodedProjectDir);
8356
+ const gitRepo = await readGitRepo(cwd);
8178
8357
  const slug = basename5(filePath, ".jsonl").replace(/^\d{4}-\d{2}-\d{2}T[^_]+_/, "");
8179
8358
  const fallbackTimestamp = timestamp || header.timestamp || (/* @__PURE__ */ new Date()).toISOString();
8180
8359
  return {
@@ -8185,6 +8364,7 @@ async function extractPiSessionInfo(filePath, fileSize, encodedProjectDir) {
8185
8364
  project: cwd,
8186
8365
  cwd,
8187
8366
  version: String(header.version || 1),
8367
+ gitRepo,
8188
8368
  timestamp: fallbackTimestamp,
8189
8369
  lineCount,
8190
8370
  fileSize,
@@ -8213,15 +8393,14 @@ function isEditTool2(name) {
8213
8393
  return name === "edit" || name === "write" || name === "Edit" || name === "Write";
8214
8394
  }
8215
8395
 
8216
- // src/providers/pi/parser.ts
8217
- init_utils();
8218
- import { readFile as readFile15 } from "fs/promises";
8396
+ // ../provider-pi/src/pi/parser.ts
8397
+ import { readFile as readFile16 } from "fs/promises";
8219
8398
  import { basename as basename6 } from "path";
8220
8399
  async function parsePiSession(filePaths, sessionInfo) {
8221
8400
  const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
8222
8401
  const allLines = [];
8223
8402
  for (const fp of paths) {
8224
- const content = await readFile15(fp, "utf-8");
8403
+ const content = await readFile16(fp, "utf-8");
8225
8404
  allLines.push(...content.split("\n"));
8226
8405
  }
8227
8406
  return parsePiLines(allLines, {
@@ -8655,7 +8834,7 @@ function estimateActiveDuration2(timestamps) {
8655
8834
  return total || void 0;
8656
8835
  }
8657
8836
 
8658
- // src/providers/pi/index.ts
8837
+ // ../provider-pi/src/pi/index.ts
8659
8838
  var piProvider = {
8660
8839
  name: "pi",
8661
8840
  displayName: "Pi",
@@ -8663,7 +8842,7 @@ var piProvider = {
8663
8842
  parse: parsePiSession
8664
8843
  };
8665
8844
 
8666
- // src/providers/index.ts
8845
+ // ../providers-default/src/index.ts
8667
8846
  var providers = [
8668
8847
  claudeCoworkProvider,
8669
8848
  claudeDesktopProvider,
@@ -8709,8 +8888,8 @@ init_cloud();
8709
8888
  // src/publishers/gist.ts
8710
8889
  init_cloud();
8711
8890
  import { createHash as createHash3 } from "crypto";
8712
- import { readFile as readFile18, writeFile as writeFile4 } from "fs/promises";
8713
- import { join as join18 } from "path";
8891
+ import { readFile as readFile19, writeFile as writeFile4 } from "fs/promises";
8892
+ import { join as join19 } from "path";
8714
8893
  var GIST_META_FILE = ".vibe-replay-gist.json";
8715
8894
  function checkPublishStatus() {
8716
8895
  const auth = loadAuthToken();
@@ -8722,8 +8901,8 @@ async function publishGist(outputDir, title, opts) {
8722
8901
  if (!auth) {
8723
8902
  throw new Error("Not logged in. Run `vibe-replay auth login` first.");
8724
8903
  }
8725
- const jsonPath = join18(outputDir, "replay.json");
8726
- const content = await readFile18(jsonPath, "utf-8");
8904
+ const jsonPath = join19(outputDir, "replay.json");
8905
+ const content = await readFile19(jsonPath, "utf-8");
8727
8906
  const overwrite = opts?.overwrite;
8728
8907
  const filename = overwrite?.filename || `${sanitizeFilename(title)}.json`;
8729
8908
  const description = `vibe-replay: ${title}`;
@@ -8787,7 +8966,7 @@ async function publishGist(outputDir, title, opts) {
8787
8966
  }
8788
8967
  async function loadSavedGistInfo(outputDir) {
8789
8968
  try {
8790
- const raw = await readFile18(join18(outputDir, GIST_META_FILE), "utf-8");
8969
+ const raw = await readFile19(join19(outputDir, GIST_META_FILE), "utf-8");
8791
8970
  const parsed = JSON.parse(raw);
8792
8971
  if (!parsed?.gistId || !parsed?.filename) return void 0;
8793
8972
  return parsed;
@@ -8796,7 +8975,7 @@ async function loadSavedGistInfo(outputDir) {
8796
8975
  }
8797
8976
  }
8798
8977
  async function saveGistInfo(outputDir, info) {
8799
- await writeFile4(join18(outputDir, GIST_META_FILE), JSON.stringify(info, null, 2), "utf-8");
8978
+ await writeFile4(join19(outputDir, GIST_META_FILE), JSON.stringify(info, null, 2), "utf-8");
8800
8979
  }
8801
8980
  function sanitizeFilename(s) {
8802
8981
  return s.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").slice(0, 60);
@@ -8914,13 +9093,13 @@ import {
8914
9093
  mkdir as mkdir6,
8915
9094
  open as fsOpen,
8916
9095
  readdir as readdir12,
8917
- readFile as readFile22,
8918
- stat as stat11,
9096
+ readFile as readFile24,
9097
+ stat as stat12,
8919
9098
  unlink as unlink2,
8920
9099
  writeFile as writeFile7
8921
9100
  } from "fs/promises";
8922
- import { homedir as homedir19 } from "os";
8923
- import { join as join22, resolve as resolve3 } from "path";
9101
+ import { homedir as homedir20 } from "os";
9102
+ import { join as join24, resolve as resolve4 } from "path";
8924
9103
  import { promisify as promisify3 } from "util";
8925
9104
  import { serve } from "@hono/node-server";
8926
9105
  import chalk from "chalk";
@@ -9176,7 +9355,7 @@ async function executeFeedback(prompt, tool) {
9176
9355
  return runOpencode(prompt, tool.command);
9177
9356
  }
9178
9357
  function runClaude(prompt, cmd) {
9179
- return new Promise((resolve4, reject) => {
9358
+ return new Promise((resolve5, reject) => {
9180
9359
  const proc = spawnTool(cmd, ["-p", "--output-format", "json"], {
9181
9360
  env: { ...process.env, NO_COLOR: "1" },
9182
9361
  timeout: 6e5,
@@ -9190,9 +9369,9 @@ function runClaude(prompt, cmd) {
9190
9369
  if (code === 0) {
9191
9370
  try {
9192
9371
  const parsed = JSON.parse(stdout);
9193
- resolve4(parsed.result || stdout);
9372
+ resolve5(parsed.result || stdout);
9194
9373
  } catch {
9195
- resolve4(stdout);
9374
+ resolve5(stdout);
9196
9375
  }
9197
9376
  } else {
9198
9377
  reject(new Error(`claude exited ${code}: ${stderr.slice(0, 500)}`));
@@ -9204,7 +9383,7 @@ function runClaude(prompt, cmd) {
9204
9383
  });
9205
9384
  }
9206
9385
  function runOpencode(prompt, cmd) {
9207
- return new Promise((resolve4, reject) => {
9386
+ return new Promise((resolve5, reject) => {
9208
9387
  const proc = spawnTool(cmd, ["run"], {
9209
9388
  env: { ...process.env, NO_COLOR: "1", TERM: "dumb" },
9210
9389
  timeout: 6e5,
@@ -9216,7 +9395,7 @@ function runOpencode(prompt, cmd) {
9216
9395
  proc.stderr.on("data", (d) => stderr += d.toString());
9217
9396
  proc.on("close", (code) => {
9218
9397
  if (code === 0) {
9219
- resolve4(stripAnsi(stdout));
9398
+ resolve5(stripAnsi(stdout));
9220
9399
  } else {
9221
9400
  reject(new Error(`opencode exited ${code}: ${stripAnsi(stderr).slice(0, 500)}`));
9222
9401
  }
@@ -9227,7 +9406,7 @@ function runOpencode(prompt, cmd) {
9227
9406
  });
9228
9407
  }
9229
9408
  function runAgent(prompt, cmd) {
9230
- return new Promise((resolve4, reject) => {
9409
+ return new Promise((resolve5, reject) => {
9231
9410
  const proc = spawnTool(cmd, ["-p", "--output-format", "json", "--mode", "ask", "--trust"], {
9232
9411
  env: { ...process.env, NO_COLOR: "1" },
9233
9412
  timeout: 6e5,
@@ -9241,9 +9420,9 @@ function runAgent(prompt, cmd) {
9241
9420
  if (code === 0) {
9242
9421
  try {
9243
9422
  const parsed = JSON.parse(stdout);
9244
- resolve4(typeof parsed.result === "string" ? parsed.result : stdout);
9423
+ resolve5(typeof parsed.result === "string" ? parsed.result : stdout);
9245
9424
  } catch {
9246
- resolve4(stdout);
9425
+ resolve5(stdout);
9247
9426
  }
9248
9427
  } else {
9249
9428
  reject(new Error(`agent exited ${code}: ${stderr.slice(0, 500)}`));
@@ -9839,18 +10018,18 @@ function stripAnsi(str) {
9839
10018
  return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\][^\x07]*\x07/g, "");
9840
10019
  }
9841
10020
  function locateCommand(cmd) {
9842
- return new Promise((resolve4, reject) => {
10021
+ return new Promise((resolve5, reject) => {
9843
10022
  const locator = IS_WINDOWS ? "where" : "which";
9844
10023
  const proc = spawn(locator, [cmd], { timeout: 5e3 });
9845
10024
  let out = "";
9846
10025
  proc.stdout.on("data", (d) => out += d.toString());
9847
10026
  proc.on("close", (code) => {
9848
10027
  if (code !== 0) {
9849
- resolve4(null);
10028
+ resolve5(null);
9850
10029
  return;
9851
10030
  }
9852
10031
  const first = out.split(/\r?\n/).find((line) => line.trim());
9853
- resolve4(first ? first.trim() : null);
10032
+ resolve5(first ? first.trim() : null);
9854
10033
  });
9855
10034
  proc.on("error", reject);
9856
10035
  });
@@ -9862,13 +10041,13 @@ init_overlays();
9862
10041
  init_cloud();
9863
10042
 
9864
10043
  // 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";
10044
+ import { mkdir as mkdir5, readFile as readFile22, writeFile as writeFile6 } from "fs/promises";
10045
+ import { join as join22, resolve as resolve3 } from "path";
9867
10046
  async function loadAnnotations(baseDir, slug) {
9868
- const dirs = [join20(baseDir, slug), resolve2("./vibe-replay", slug)];
10047
+ const dirs = [join22(baseDir, slug), resolve3("./vibe-replay", slug)];
9869
10048
  for (const dir of dirs) {
9870
10049
  try {
9871
- const raw = await readFile20(join20(dir, "annotations.json"), "utf-8");
10050
+ const raw = await readFile22(join22(dir, "annotations.json"), "utf-8");
9872
10051
  const anns = JSON.parse(raw);
9873
10052
  if (Array.isArray(anns)) return anns;
9874
10053
  } catch {
@@ -9877,13 +10056,13 @@ async function loadAnnotations(baseDir, slug) {
9877
10056
  return [];
9878
10057
  }
9879
10058
  async function saveAnnotations(baseDir, slug, annotations) {
9880
- await mkdir5(join20(baseDir, slug), { recursive: true });
9881
- const annPath = join20(baseDir, slug, "annotations.json");
10059
+ await mkdir5(join22(baseDir, slug), { recursive: true });
10060
+ const annPath = join22(baseDir, slug, "annotations.json");
9882
10061
  await writeFile6(annPath, JSON.stringify(annotations, null, 2), "utf-8");
9883
10062
  }
9884
10063
  async function saveOverlays(baseDir, slug, overlays) {
9885
- await mkdir5(join20(baseDir, slug), { recursive: true });
9886
- const overlayPath = join20(baseDir, slug, "overlays.json");
10064
+ await mkdir5(join22(baseDir, slug), { recursive: true });
10065
+ const overlayPath = join22(baseDir, slug, "overlays.json");
9887
10066
  await writeFile6(overlayPath, JSON.stringify(overlays, null, 2), "utf-8");
9888
10067
  }
9889
10068
 
@@ -9891,7 +10070,7 @@ async function saveOverlays(baseDir, slug, overlays) {
9891
10070
  init_overlays();
9892
10071
 
9893
10072
  // src/server-core.ts
9894
- import { homedir as homedir16 } from "os";
10073
+ import { homedir as homedir17 } from "os";
9895
10074
  import { basename as basename8 } from "path";
9896
10075
  function safeSlug(raw) {
9897
10076
  if (!raw) return null;
@@ -9910,7 +10089,7 @@ function getErrorMessage(err) {
9910
10089
  return "Unknown error";
9911
10090
  }
9912
10091
  function normalizeProjectPath(project) {
9913
- const home = homedir16();
10092
+ const home = homedir17();
9914
10093
  return project.startsWith(home) ? `~${project.slice(home.length)}` : project;
9915
10094
  }
9916
10095
  var MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST = 90;
@@ -10032,11 +10211,25 @@ function registerSessionAssetRoutes(app, deps) {
10032
10211
  }
10033
10212
 
10034
10213
  // src/scanner.ts
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";
10214
+ import { readdir as readdir11, readFile as readFile23, stat as stat11 } from "fs/promises";
10215
+ import { homedir as homedir18 } from "os";
10216
+ import { join as join23 } from "path";
10217
+
10218
+ // src/duration.ts
10219
+ var DEFAULT_MAX_GAP_MS2 = 5 * 60 * 1e3;
10220
+ function estimateActiveDuration3(timestamps, maxGapMs = DEFAULT_MAX_GAP_MS2) {
10221
+ if (timestamps.length < 2) return void 0;
10222
+ const sorted = timestamps.map((t) => Date.parse(t)).filter(Number.isFinite).sort((a, b) => a - b);
10223
+ if (sorted.length < 2) return void 0;
10224
+ let active = 0;
10225
+ for (let i = 1; i < sorted.length; i++) {
10226
+ const gap = sorted[i] - sorted[i - 1];
10227
+ active += Math.min(gap, maxGapMs);
10228
+ }
10229
+ return active > 0 ? active : void 0;
10230
+ }
10038
10231
 
10039
- // src/pricing.ts
10232
+ // ../replay-core/src/pricing.ts
10040
10233
  var MODEL_PRICING = {
10041
10234
  "gpt-5.5": {
10042
10235
  inputRate: 5,
@@ -10245,7 +10438,7 @@ async function scanSession(input) {
10245
10438
  for (const filePath of input.filePaths) {
10246
10439
  let content;
10247
10440
  try {
10248
- content = await readFile21(filePath, "utf-8");
10441
+ content = await readFile23(filePath, "utf-8");
10249
10442
  } catch {
10250
10443
  continue;
10251
10444
  }
@@ -10354,7 +10547,7 @@ async function scanSession(input) {
10354
10547
  const fp = extractToolFilePath(block.input);
10355
10548
  if (fp) {
10356
10549
  editCount++;
10357
- const short = shortenPath(fp);
10550
+ const short = shortenPath2(fp);
10358
10551
  fileEditCounts.set(short, (fileEditCounts.get(short) || 0) + 1);
10359
10552
  }
10360
10553
  }
@@ -10367,7 +10560,7 @@ async function scanSession(input) {
10367
10560
  if (input.filePaths.length > 0) {
10368
10561
  const mainFile = input.filePaths[0];
10369
10562
  const sessionDir = mainFile.replace(/\.jsonl$/, "");
10370
- const subagentsDir = join21(sessionDir, "subagents");
10563
+ const subagentsDir = join23(sessionDir, "subagents");
10371
10564
  try {
10372
10565
  const files = await readdir11(subagentsDir);
10373
10566
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
@@ -10375,7 +10568,7 @@ async function scanSession(input) {
10375
10568
  for (const saFile of jsonlFiles) {
10376
10569
  let saContent;
10377
10570
  try {
10378
- saContent = await readFile21(join21(subagentsDir, saFile), "utf-8");
10571
+ saContent = await readFile23(join23(subagentsDir, saFile), "utf-8");
10379
10572
  } catch {
10380
10573
  continue;
10381
10574
  }
@@ -10395,7 +10588,7 @@ async function scanSession(input) {
10395
10588
  const fp = extractToolFilePath(block.input);
10396
10589
  if (fp) {
10397
10590
  editCount++;
10398
- const short = shortenPath(fp);
10591
+ const short = shortenPath2(fp);
10399
10592
  fileEditCounts.set(short, (fileEditCounts.get(short) || 0) + 1);
10400
10593
  }
10401
10594
  }
@@ -10444,7 +10637,7 @@ async function scanSession(input) {
10444
10637
  }
10445
10638
  let durationMs = totalDurationMs || void 0;
10446
10639
  if (!durationMs) {
10447
- durationMs = estimateActiveDuration(allTimestamps);
10640
+ durationMs = estimateActiveDuration3(allTimestamps);
10448
10641
  }
10449
10642
  const gitBranch = gitBranches.length > 0 ? gitBranches[gitBranches.length - 1] : void 0;
10450
10643
  return {
@@ -10604,7 +10797,7 @@ function buildScanResultFromParsed(input, parsed) {
10604
10797
  const saPath = extractToolFilePath(saScene.input);
10605
10798
  if (!saPath) continue;
10606
10799
  editCount++;
10607
- const short = shortenPath(saPath);
10800
+ const short = shortenPath2(saPath);
10608
10801
  fileEditCounts.set(short, (fileEditCounts.get(short) || 0) + 1);
10609
10802
  }
10610
10803
  }
@@ -10613,7 +10806,7 @@ function buildScanResultFromParsed(input, parsed) {
10613
10806
  if (rawPaths.length === 0) continue;
10614
10807
  editCount++;
10615
10808
  for (const rawPath of rawPaths) {
10616
- const short = shortenPath(rawPath);
10809
+ const short = shortenPath2(rawPath);
10617
10810
  fileEditCounts.set(short, (fileEditCounts.get(short) || 0) + 1);
10618
10811
  }
10619
10812
  }
@@ -10674,20 +10867,20 @@ function estimateParsedCost(parsed) {
10674
10867
  var SCAN_CACHE_KEY = "session-scans-v1";
10675
10868
  var SCAN_CONCURRENCY = 4;
10676
10869
  async function readScanCache() {
10677
- const cached = await readFileCache(SCAN_CACHE_KEY);
10870
+ const cached = await readFileCache2(SCAN_CACHE_KEY);
10678
10871
  if (!cached) return null;
10679
10872
  if (cached.data.scannerVersion !== SCANNER_VERSION) return null;
10680
10873
  return cached.data;
10681
10874
  }
10682
10875
  async function writeScanCache(data) {
10683
- await writeFileCache(SCAN_CACHE_KEY, data);
10876
+ await writeFileCache2(SCAN_CACHE_KEY, data);
10684
10877
  }
10685
10878
  async function getFileMeta(filePaths) {
10686
10879
  let totalSize = 0;
10687
10880
  let maxMtime = 0;
10688
10881
  for (const fp of filePaths) {
10689
10882
  try {
10690
- const s = await stat10(fp);
10883
+ const s = await stat11(fp);
10691
10884
  totalSize += s.size;
10692
10885
  if (s.mtimeMs > maxMtime) maxMtime = s.mtimeMs;
10693
10886
  } catch {
@@ -11040,24 +11233,24 @@ function buildAggregateDataQuality(scans) {
11040
11233
  }
11041
11234
  return notes.length > 0 ? { notes } : void 0;
11042
11235
  }
11043
- var CLAUDE_DIR2 = join21(homedir17(), ".claude", "projects");
11236
+ var CLAUDE_DIR2 = join23(homedir18(), ".claude", "projects");
11044
11237
  function encodeProjectDir(project) {
11045
11238
  let resolved = project;
11046
11239
  if (resolved.startsWith("~/")) {
11047
- resolved = join21(homedir17(), resolved.slice(2));
11240
+ resolved = join23(homedir18(), resolved.slice(2));
11048
11241
  } else if (resolved === "~") {
11049
- resolved = homedir17();
11242
+ resolved = homedir18();
11050
11243
  }
11051
11244
  return resolved.replace(/\//g, "-");
11052
11245
  }
11053
11246
  async function readProjectMemory(project) {
11054
11247
  const encoded = encodeProjectDir(project);
11055
- const projectDir = join21(CLAUDE_DIR2, encoded);
11056
- const memoryDir = join21(projectDir, "memory");
11248
+ const projectDir = join23(CLAUDE_DIR2, encoded);
11249
+ const memoryDir = join23(projectDir, "memory");
11057
11250
  const memoryFiles = [];
11058
11251
  let claudeMd;
11059
11252
  try {
11060
- const content = await readFile21(join21(projectDir, "CLAUDE.md"), "utf-8");
11253
+ const content = await readFile23(join23(projectDir, "CLAUDE.md"), "utf-8");
11061
11254
  if (content.trim()) claudeMd = content.slice(0, 5e3);
11062
11255
  } catch {
11063
11256
  }
@@ -11066,7 +11259,7 @@ async function readProjectMemory(project) {
11066
11259
  for (const file of files) {
11067
11260
  if (!file.endsWith(".md") || file === "MEMORY.md") continue;
11068
11261
  try {
11069
- const content = await readFile21(join21(memoryDir, file), "utf-8");
11262
+ const content = await readFile23(join23(memoryDir, file), "utf-8");
11070
11263
  const fm = parseFrontmatter(content);
11071
11264
  memoryFiles.push({
11072
11265
  name: fm.name || file.replace(/\.md$/, ""),
@@ -11106,18 +11299,18 @@ function extractMetaText(content) {
11106
11299
  return "";
11107
11300
  }
11108
11301
 
11109
- // src/transform.ts
11110
- import { homedir as homedir18 } from "os";
11302
+ // ../replay-core/src/transform.ts
11303
+ import { homedir as homedir19 } from "os";
11111
11304
 
11112
- // src/utils/tokenEstimate.ts
11305
+ // ../replay-core/src/utils/tokenEstimate.ts
11113
11306
  function estimateTokens(s) {
11114
11307
  if (!s) return 0;
11115
11308
  if (!s.trim()) return 0;
11116
11309
  return Math.max(1, Math.round(s.length / 4));
11117
11310
  }
11118
11311
 
11119
- // src/transform.ts
11120
- var HOME = homedir18();
11312
+ // ../replay-core/src/transform.ts
11313
+ var HOME = homedir19();
11121
11314
  function redactPath(s) {
11122
11315
  if (!HOME) return s;
11123
11316
  return s.replaceAll(HOME, "~");
@@ -11270,6 +11463,8 @@ function transformToReplay(parsed, provider, project, options) {
11270
11463
  compactions: parsed.compactions,
11271
11464
  ...parsed.subAgentSummary && parsed.subAgentSummary.length > 0 ? { subAgentSummary: parsed.subAgentSummary } : syntheticSubAgentSummary.length > 0 ? { subAgentSummary: syntheticSubAgentSummary } : {},
11272
11465
  ...parsed.gitBranch ? { gitBranch: parsed.gitBranch } : {},
11466
+ // Provider metadata wins over caller fallback when both are present.
11467
+ ...parsed.gitRepo || options?.gitRepo ? { gitRepo: parsed.gitRepo || options?.gitRepo } : {},
11273
11468
  ...parsed.gitBranches ? { gitBranches: parsed.gitBranches } : {},
11274
11469
  ...parsed.entrypoint ? { entrypoint: parsed.entrypoint } : {},
11275
11470
  ...parsed.permissionMode ? { permissionMode: parsed.permissionMode } : {},
@@ -11556,23 +11751,30 @@ init_version();
11556
11751
  var ARCHIVE_DIR = ".archive";
11557
11752
  async function getArchivedSlugs(baseDir) {
11558
11753
  try {
11559
- const entries = await readdir12(join22(baseDir, ARCHIVE_DIR));
11754
+ const entries = await readdir12(join24(baseDir, ARCHIVE_DIR));
11560
11755
  return new Set(entries);
11561
11756
  } catch {
11562
11757
  return /* @__PURE__ */ new Set();
11563
11758
  }
11564
11759
  }
11565
11760
  async function archiveSlug(baseDir, slug) {
11566
- const dir = join22(baseDir, ARCHIVE_DIR);
11761
+ const dir = join24(baseDir, ARCHIVE_DIR);
11567
11762
  await mkdir6(dir, { recursive: true });
11568
- await writeFile7(join22(dir, slug), "");
11763
+ await writeFile7(join24(dir, slug), "");
11569
11764
  }
11570
11765
  async function unarchiveSlug(baseDir, slug) {
11571
11766
  try {
11572
- await unlink2(join22(baseDir, ARCHIVE_DIR, slug));
11767
+ await unlink2(join24(baseDir, ARCHIVE_DIR, slug));
11573
11768
  } catch {
11574
11769
  }
11575
11770
  }
11771
+ var replayGitRepoByProjectCache = /* @__PURE__ */ new Map();
11772
+ async function readReplayGitRepo(project) {
11773
+ if (!replayGitRepoByProjectCache.has(project)) {
11774
+ replayGitRepoByProjectCache.set(project, await readGitRepo(project));
11775
+ }
11776
+ return replayGitRepoByProjectCache.get(project);
11777
+ }
11576
11778
  async function scanSessionsFromDir(baseDir) {
11577
11779
  const results = [];
11578
11780
  let entries;
@@ -11582,22 +11784,26 @@ async function scanSessionsFromDir(baseDir) {
11582
11784
  return results;
11583
11785
  }
11584
11786
  for (const entry of entries) {
11585
- const replayPath = join22(baseDir, entry, "replay.json");
11787
+ const replayPath = join24(baseDir, entry, "replay.json");
11586
11788
  try {
11587
- const raw = await readFile22(replayPath, "utf-8");
11789
+ const raw = await readFile24(replayPath, "utf-8");
11588
11790
  const session = JSON.parse(raw);
11589
11791
  const annotationCount = (await loadAnnotations(baseDir, entry)).length;
11590
11792
  let gist;
11591
11793
  try {
11592
- gist = await loadSavedGistInfo(join22(baseDir, entry));
11794
+ gist = await loadSavedGistInfo(join24(baseDir, entry));
11593
11795
  } catch {
11594
11796
  }
11595
- const cloudInfo = await loadSavedCloudInfo(join22(baseDir, entry));
11797
+ const cloudInfo = await loadSavedCloudInfo(join24(baseDir, entry));
11596
11798
  const userPrompts = (session.scenes || []).filter((sc) => sc.type === "user-prompt").map((sc) => previewPrompt(sc.content)).filter((m) => m.length >= 10);
11597
11799
  const firstMessage = userPrompts[0] || void 0;
11598
11800
  const messages = userPrompts.length > 0 ? userPrompts.slice(0, 2) : void 0;
11599
11801
  const generatorVersion = session.meta.generator?.version;
11600
11802
  const replayOutdated = generatorVersion ? generatorVersion !== CLI_VERSION : false;
11803
+ let gitRepo = session.meta.gitRepo;
11804
+ if (!gitRepo && session.meta.project) {
11805
+ gitRepo = await readReplayGitRepo(session.meta.project);
11806
+ }
11601
11807
  results.push({
11602
11808
  slug: entry,
11603
11809
  baseDir,
@@ -11605,6 +11811,7 @@ async function scanSessionsFromDir(baseDir) {
11605
11811
  title: session.meta.title,
11606
11812
  provider: session.meta.provider,
11607
11813
  model: session.meta.model,
11814
+ gitRepo,
11608
11815
  project: session.meta.project,
11609
11816
  startTime: session.meta.startTime,
11610
11817
  endTime: session.meta.endTime,
@@ -11620,7 +11827,7 @@ async function scanSessionsFromDir(baseDir) {
11620
11827
  let outdated = false;
11621
11828
  if (gist?.contentHash) {
11622
11829
  try {
11623
- const content = await readFile22(replayPath, "utf-8");
11830
+ const content = await readFile24(replayPath, "utf-8");
11624
11831
  const currentHash = createHash5("sha256").update(content).digest("hex").slice(0, 16);
11625
11832
  outdated = currentHash !== gist?.contentHash;
11626
11833
  } catch {
@@ -11647,7 +11854,7 @@ async function scanSessionsFromDir(baseDir) {
11647
11854
  }
11648
11855
  async function scanSessions(baseDir) {
11649
11856
  const dirs = [baseDir];
11650
- const cwdLocal = resolve3("./vibe-replay");
11857
+ const cwdLocal = resolve4("./vibe-replay");
11651
11858
  if (cwdLocal !== baseDir) {
11652
11859
  dirs.push(cwdLocal);
11653
11860
  }
@@ -11666,15 +11873,15 @@ async function scanSessions(baseDir) {
11666
11873
  return allResults;
11667
11874
  }
11668
11875
  async function loadSessionFromDisk(baseDir, slug) {
11669
- let replayPath = join22(baseDir, slug, "replay.json");
11876
+ let replayPath = join24(baseDir, slug, "replay.json");
11670
11877
  try {
11671
- await stat11(replayPath);
11878
+ await stat12(replayPath);
11672
11879
  } catch {
11673
- const fallback = resolve3("./vibe-replay", slug, "replay.json");
11674
- await stat11(fallback);
11880
+ const fallback = resolve4("./vibe-replay", slug, "replay.json");
11881
+ await stat12(fallback);
11675
11882
  replayPath = fallback;
11676
11883
  }
11677
- const raw = await readFile22(replayPath, "utf-8");
11884
+ const raw = await readFile24(replayPath, "utf-8");
11678
11885
  const session = JSON.parse(raw);
11679
11886
  const annotations = await loadAnnotations(baseDir, slug);
11680
11887
  if (annotations.length > 0) {
@@ -11682,6 +11889,173 @@ async function loadSessionFromDisk(baseDir, slug) {
11682
11889
  }
11683
11890
  return session;
11684
11891
  }
11892
+ function isSourceSessionCatalogCache(value) {
11893
+ return !!value && typeof value === "object" && value.schemaVersion === 1 && typeof value.discoveredAt === "string" && Array.isArray(value.sessions);
11894
+ }
11895
+ function normalizeSourceSessionCatalogCache(cached) {
11896
+ if (!cached) return null;
11897
+ if (Array.isArray(cached.data)) {
11898
+ return {
11899
+ sessions: cached.data,
11900
+ cachedAt: cached.updatedAt,
11901
+ discoveredAt: cached.updatedAt,
11902
+ updatedAt: cached.updatedAt,
11903
+ legacy: true
11904
+ };
11905
+ }
11906
+ if (isSourceSessionCatalogCache(cached.data)) {
11907
+ return {
11908
+ sessions: cached.data.sessions,
11909
+ cachedAt: cached.updatedAt,
11910
+ discoveredAt: cached.data.discoveredAt || cached.updatedAt,
11911
+ updatedAt: cached.data.updatedAt || cached.updatedAt,
11912
+ providerStates: cached.data.providerStates,
11913
+ legacy: false
11914
+ };
11915
+ }
11916
+ return null;
11917
+ }
11918
+ function buildProviderDiscoveryStates(sources, discoveredAt) {
11919
+ const byProvider = /* @__PURE__ */ new Map();
11920
+ for (const source of sources) {
11921
+ const bucket = byProvider.get(source.provider) || [];
11922
+ bucket.push(source);
11923
+ byProvider.set(source.provider, bucket);
11924
+ }
11925
+ const states = {};
11926
+ for (const [provider, providerSources] of byProvider) {
11927
+ states[provider] = {
11928
+ provider,
11929
+ discoveredAt,
11930
+ sessionCount: providerSources.length
11931
+ };
11932
+ }
11933
+ return states;
11934
+ }
11935
+ function buildSourceSessionCatalogCache(sessions, discoveredAt, previous) {
11936
+ const providerStates = {
11937
+ ...previous?.providerStates,
11938
+ ...buildProviderDiscoveryStates(sessions, discoveredAt)
11939
+ };
11940
+ return {
11941
+ schemaVersion: 1,
11942
+ discoveredAt,
11943
+ updatedAt: discoveredAt,
11944
+ providerStates,
11945
+ sessions
11946
+ };
11947
+ }
11948
+ async function probeSourceRecordsFreshness(sources, provider) {
11949
+ const probe = {
11950
+ provider,
11951
+ sessionsRoot: provider,
11952
+ fileCount: 0
11953
+ };
11954
+ const paths = new Set(
11955
+ sources.filter((source) => source.provider === provider).flatMap((source) => source.filePaths || []).filter(
11956
+ (filePath) => typeof filePath === "string" && filePath.length > 0
11957
+ )
11958
+ );
11959
+ for (const filePath of paths) {
11960
+ const fileStat = await stat12(filePath).catch(() => null);
11961
+ if (!fileStat) continue;
11962
+ probe.fileCount += 1;
11963
+ if (probe.newestSourceMtimeMs == null || fileStat.mtimeMs > probe.newestSourceMtimeMs) {
11964
+ probe.newestSourceMtimeMs = fileStat.mtimeMs;
11965
+ probe.newestSourcePath = filePath;
11966
+ }
11967
+ }
11968
+ return probe;
11969
+ }
11970
+ function mergeSourceCatalogSessionUpdates(current, updates) {
11971
+ if (current.length === 0) return updates;
11972
+ const bySessionId = /* @__PURE__ */ new Map();
11973
+ const byKey = /* @__PURE__ */ new Map();
11974
+ for (const update of updates) {
11975
+ byKey.set(sourceSessionKey(update.provider, update.project, update.slug), update);
11976
+ if (typeof update.sessionId === "string" && update.sessionId) {
11977
+ bySessionId.set(update.sessionId, update);
11978
+ }
11979
+ }
11980
+ return current.map((session) => {
11981
+ const byId = session.sessionId ? bySessionId.get(session.sessionId) : void 0;
11982
+ const update = (byId?.provider === session.provider ? byId : void 0) ?? byKey.get(sourceSessionKey(session.provider, session.project, session.slug));
11983
+ return update ? { ...session, ...update } : session;
11984
+ });
11985
+ }
11986
+ function updateSourceSessionCatalogSessions(catalog, sessions, updatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
11987
+ return {
11988
+ schemaVersion: 1,
11989
+ discoveredAt: catalog.discoveredAt || catalog.cachedAt || updatedAt,
11990
+ updatedAt,
11991
+ providerStates: catalog.providerStates,
11992
+ sessions
11993
+ };
11994
+ }
11995
+ async function walkJsonlFreshness(root, provider) {
11996
+ const probe = {
11997
+ provider,
11998
+ sessionsRoot: root,
11999
+ fileCount: 0
12000
+ };
12001
+ async function walk(dir) {
12002
+ let entries;
12003
+ try {
12004
+ entries = await readdir12(dir, { withFileTypes: true });
12005
+ } catch {
12006
+ return;
12007
+ }
12008
+ await Promise.all(
12009
+ entries.map(async (entry) => {
12010
+ const entryPath = join24(dir, entry.name);
12011
+ if (entry.isDirectory()) {
12012
+ await walk(entryPath);
12013
+ return;
12014
+ }
12015
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) return;
12016
+ const fileStat = await stat12(entryPath).catch(() => null);
12017
+ if (!fileStat) return;
12018
+ probe.fileCount += 1;
12019
+ if (probe.newestSourceMtimeMs == null || fileStat.mtimeMs > probe.newestSourceMtimeMs) {
12020
+ probe.newestSourceMtimeMs = fileStat.mtimeMs;
12021
+ probe.newestSourcePath = entryPath;
12022
+ }
12023
+ })
12024
+ );
12025
+ }
12026
+ await walk(root);
12027
+ return probe;
12028
+ }
12029
+ async function probePiSourceFreshness() {
12030
+ return walkJsonlFreshness(getPiSessionsDir(), "pi");
12031
+ }
12032
+ function sourceProviderFingerprint(probe) {
12033
+ return `${probe.fileCount}:${probe.newestSourcePath || ""}`;
12034
+ }
12035
+ async function getStaleSourceProviders(catalog) {
12036
+ if (!catalog) return [];
12037
+ const staleProviders = [];
12038
+ const piProbe = await probePiSourceFreshness();
12039
+ const piFingerprint = sourceProviderFingerprint(piProbe);
12040
+ const previousPiFingerprint = catalog.providerStates?.pi?.fingerprint;
12041
+ const previousPiSessionCount = catalog.providerStates?.pi?.sessionCount;
12042
+ const cachedPiSessionCount = catalog.sessions.filter(
12043
+ (session) => session.provider === "pi"
12044
+ ).length;
12045
+ if (cachedPiSessionCount > 0 && typeof previousPiSessionCount === "number" && previousPiSessionCount !== cachedPiSessionCount) {
12046
+ staleProviders.push("pi");
12047
+ }
12048
+ if (previousPiFingerprint) {
12049
+ if (piFingerprint !== previousPiFingerprint) staleProviders.push("pi");
12050
+ return [...new Set(staleProviders)];
12051
+ }
12052
+ const piDiscoveredAt = catalog.providerStates?.pi?.discoveredAt || catalog.discoveredAt || catalog.cachedAt;
12053
+ const piDiscoveredMs = piDiscoveredAt ? Date.parse(piDiscoveredAt) : Number.NaN;
12054
+ if (piProbe.fileCount > 0 && piProbe.newestSourceMtimeMs != null && (catalog.legacy || !Number.isFinite(piDiscoveredMs) || piProbe.newestSourceMtimeMs > piDiscoveredMs)) {
12055
+ staleProviders.push("pi");
12056
+ }
12057
+ return [...new Set(staleProviders)];
12058
+ }
11685
12059
  function sourceSessionKey(provider, project, slug) {
11686
12060
  return `${provider}::${project}::${slug}`;
11687
12061
  }
@@ -11846,13 +12220,13 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
11846
12220
  const projectExistsMap = /* @__PURE__ */ new Map();
11847
12221
  const projectIsGitMap = /* @__PURE__ */ new Map();
11848
12222
  for (const p of uniqueProjects) {
11849
- const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ? join22(home, p.slice(2)) : p;
12223
+ const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ? join24(home, p.slice(2)) : p;
11850
12224
  try {
11851
- const s = await stat11(resolved);
12225
+ const s = await stat12(resolved);
11852
12226
  projectExistsMap.set(p, s.isDirectory());
11853
12227
  if (s.isDirectory()) {
11854
12228
  try {
11855
- await stat11(join22(resolved, ".git"));
12229
+ await stat12(join24(resolved, ".git"));
11856
12230
  projectIsGitMap.set(p, true);
11857
12231
  } catch {
11858
12232
  projectIsGitMap.set(p, false);
@@ -11896,6 +12270,7 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
11896
12270
  hasSqlite: s.hasSqlite,
11897
12271
  hasSdk: s.hasSdk,
11898
12272
  gitBranch: s.gitBranch,
12273
+ gitRepo: s.gitRepo,
11899
12274
  model: s.model,
11900
12275
  durationMsEst: s.durationMsEst,
11901
12276
  editCountEst: s.editCountEst,
@@ -11916,6 +12291,7 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
11916
12291
  title: replay.title,
11917
12292
  provider: replay.provider,
11918
12293
  model: replay.model,
12294
+ gitRepo: replay.gitRepo,
11919
12295
  project: replay.project,
11920
12296
  startTime: replay.startTime,
11921
12297
  endTime: replay.endTime,
@@ -11932,7 +12308,7 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
11932
12308
  });
11933
12309
  }
11934
12310
  async function readClaudeSessionState(sessionId) {
11935
- const sessionsDir = join22(homedir19(), ".claude", "sessions");
12311
+ const sessionsDir = join24(homedir20(), ".claude", "sessions");
11936
12312
  let files;
11937
12313
  try {
11938
12314
  files = await readdir12(sessionsDir);
@@ -11943,7 +12319,7 @@ async function readClaudeSessionState(sessionId) {
11943
12319
  if (!file.endsWith(".json")) continue;
11944
12320
  let data;
11945
12321
  try {
11946
- const content = await readFile22(join22(sessionsDir, file), "utf-8");
12322
+ const content = await readFile24(join24(sessionsDir, file), "utf-8");
11947
12323
  data = JSON.parse(content);
11948
12324
  } catch {
11949
12325
  continue;
@@ -12002,10 +12378,48 @@ async function startServer(baseDir, opts) {
12002
12378
  const replaysCacheKey = `dashboard-replays-v1-${cacheKeySuffix}`;
12003
12379
  const scanResultsCacheKey = `dashboard-scan-results-v1-${cacheKeySuffix}`;
12004
12380
  const insightsCacheKey = `dashboard-insights-v1-${cacheKeySuffix}`;
12381
+ const readSourcesCatalogCache = async () => normalizeSourceSessionCatalogCache(
12382
+ await readFileCache2(sourcesCacheKey)
12383
+ );
12384
+ const writeDiscoveredSourcesCatalog = async (sessions, previous) => {
12385
+ const discoveredAt = (/* @__PURE__ */ new Date()).toISOString();
12386
+ const catalog = buildSourceSessionCatalogCache(sessions, discoveredAt, previous);
12387
+ try {
12388
+ const piProbe = await probeSourceRecordsFreshness(sessions, "pi");
12389
+ const piSessionCount = sessions.filter((session) => session.provider === "pi").length;
12390
+ catalog.providerStates = {
12391
+ ...catalog.providerStates,
12392
+ pi: {
12393
+ ...catalog.providerStates?.pi || {
12394
+ provider: "pi",
12395
+ discoveredAt
12396
+ },
12397
+ provider: "pi",
12398
+ discoveredAt,
12399
+ sessionCount: piSessionCount,
12400
+ newestSourceMtimeMs: piProbe.newestSourceMtimeMs,
12401
+ newestSourcePath: piProbe.newestSourcePath,
12402
+ fingerprint: sourceProviderFingerprint(piProbe)
12403
+ }
12404
+ };
12405
+ } catch {
12406
+ }
12407
+ await writeFileCache2(sourcesCacheKey, catalog);
12408
+ return catalog;
12409
+ };
12410
+ const writeUpdatedSourcesCatalog = async (previous, sessions) => {
12411
+ await writeFileCache2(
12412
+ sourcesCacheKey,
12413
+ updateSourceSessionCatalogSessions(
12414
+ previous,
12415
+ mergeSourceCatalogSessionUpdates(previous.sessions, sessions)
12416
+ )
12417
+ );
12418
+ };
12005
12419
  const refreshReplaysCache = async () => {
12006
12420
  try {
12007
12421
  const sessions = await scanSessions(baseDir);
12008
- await writeFileCache(replaysCacheKey, sessions);
12422
+ await writeFileCache2(replaysCacheKey, sessions);
12009
12423
  return sessions;
12010
12424
  } catch {
12011
12425
  return null;
@@ -12013,11 +12427,11 @@ async function startServer(baseDir, opts) {
12013
12427
  };
12014
12428
  const syncSourcesCacheWithReplays = async (replays) => {
12015
12429
  try {
12016
- const cached = await readFileCache(sourcesCacheKey);
12017
- if (!cached?.data?.length) return;
12430
+ const cached = await readSourcesCatalogCache();
12431
+ if (!cached?.sessions.length) return;
12018
12432
  const { bySlug, bySessionId } = buildReplayMaps(replays);
12019
12433
  let changed = false;
12020
- const updated = cached.data.map((s) => {
12434
+ const updated = cached.sessions.map((s) => {
12021
12435
  const replay = bySlug.get(s.slug) || (s.sessionId ? bySessionId.get(s.sessionId) : void 0);
12022
12436
  const hadReplay = !!s.existingReplay;
12023
12437
  const hasReplay = !!replay;
@@ -12048,7 +12462,7 @@ async function startServer(baseDir, opts) {
12048
12462
  };
12049
12463
  });
12050
12464
  if (changed) {
12051
- await writeFileCache(sourcesCacheKey, updated);
12465
+ await writeUpdatedSourcesCatalog(cached, updated);
12052
12466
  }
12053
12467
  } catch {
12054
12468
  }
@@ -12155,12 +12569,28 @@ async function startServer(baseDir, opts) {
12155
12569
  processed: sourcesEnrichmentStatus.processed + 1
12156
12570
  };
12157
12571
  if (changed && sourcesEnrichmentStatus.processed % 5 === 0) {
12158
- await writeFileCache(sourcesCacheKey, enrichedSources);
12572
+ const cached = await readSourcesCatalogCache();
12573
+ await writeUpdatedSourcesCatalog(
12574
+ cached || {
12575
+ sessions: baseSources,
12576
+ discoveredAt: sourcesEnrichmentStatus.startedAt,
12577
+ cachedAt: sourcesEnrichmentStatus.startedAt
12578
+ },
12579
+ enrichedSources
12580
+ );
12159
12581
  }
12160
12582
  }
12161
12583
  }
12162
12584
  if (changed) {
12163
- await writeFileCache(sourcesCacheKey, enrichedSources);
12585
+ const cached = await readSourcesCatalogCache();
12586
+ await writeUpdatedSourcesCatalog(
12587
+ cached || {
12588
+ sessions: baseSources,
12589
+ discoveredAt: sourcesEnrichmentStatus.startedAt,
12590
+ cachedAt: sourcesEnrichmentStatus.startedAt
12591
+ },
12592
+ enrichedSources
12593
+ );
12164
12594
  }
12165
12595
  sourcesEnrichmentStatus = {
12166
12596
  ...sourcesEnrichmentStatus,
@@ -12186,8 +12616,8 @@ async function startServer(baseDir, opts) {
12186
12616
  }
12187
12617
  });
12188
12618
  };
12189
- const persistedScanResults = await readFileCache(scanResultsCacheKey);
12190
- const persistedInsights = await readFileCache(insightsCacheKey);
12619
+ const persistedScanResults = await readFileCache2(scanResultsCacheKey);
12620
+ const persistedInsights = await readFileCache2(insightsCacheKey);
12191
12621
  let scanState = {
12192
12622
  running: false,
12193
12623
  scanned: persistedScanResults?.data.length || 0,
@@ -12309,7 +12739,7 @@ async function startServer(baseDir, opts) {
12309
12739
  projectInsights: projects,
12310
12740
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
12311
12741
  };
12312
- await writeFileCache(insightsCacheKey, {
12742
+ await writeFileCache2(insightsCacheKey, {
12313
12743
  userInsights: insightsCache.userInsights,
12314
12744
  projectInsights: [...insightsCache.projectInsights.entries()],
12315
12745
  computedAt: insightsCache.computedAt
@@ -12337,7 +12767,7 @@ async function startServer(baseDir, opts) {
12337
12767
  allSessions.push(...sessions);
12338
12768
  }
12339
12769
  const merged = mergeSameSessions(allSessions);
12340
- const home = homedir19();
12770
+ const home = homedir20();
12341
12771
  for (const s of merged) {
12342
12772
  if (s.project.startsWith(home)) {
12343
12773
  s.project = `~${s.project.slice(home.length)}`;
@@ -12385,7 +12815,7 @@ async function startServer(baseDir, opts) {
12385
12815
  startedAt: scanState.startedAt,
12386
12816
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
12387
12817
  };
12388
- await writeFileCache(scanResultsCacheKey, results);
12818
+ await writeFileCache2(scanResultsCacheKey, results);
12389
12819
  persistInsightsFromScan(results).then(() => autoSyncInsights()).catch(() => {
12390
12820
  });
12391
12821
  precomputeInsightsCache(results).catch(() => {
@@ -12451,7 +12881,7 @@ async function startServer(baseDir, opts) {
12451
12881
  await sendError(`Session not found: ${sessionId}`);
12452
12882
  return;
12453
12883
  }
12454
- const home = homedir19();
12884
+ const home = homedir20();
12455
12885
  const projectFor = (info) => info.project.startsWith(home) ? `~${info.project.slice(home.length)}` : info.project;
12456
12886
  const watchers = [];
12457
12887
  const watchedPaths = /* @__PURE__ */ new Set();
@@ -12519,13 +12949,13 @@ async function startServer(baseDir, opts) {
12519
12949
  const cached = jsonlTail.get(filePath);
12520
12950
  let size;
12521
12951
  try {
12522
- size = (await stat11(filePath)).size;
12952
+ size = (await stat12(filePath)).size;
12523
12953
  } catch {
12524
12954
  jsonlTail.delete(filePath);
12525
12955
  return cached?.lines ?? [];
12526
12956
  }
12527
12957
  if (!cached || size < cached.offset) {
12528
- const content = await readFile22(filePath);
12958
+ const content = await readFile24(filePath);
12529
12959
  const { lines, partial } = splitDecodedLines(content);
12530
12960
  jsonlTail.set(filePath, { offset: content.length, partial, lines });
12531
12961
  return lines;
@@ -12592,7 +13022,8 @@ async function startServer(baseDir, opts) {
12592
13022
  name: "vibe-replay",
12593
13023
  version: CLI_VERSION,
12594
13024
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12595
- }
13025
+ },
13026
+ gitRepo: info.gitRepo
12596
13027
  });
12597
13028
  if (isClaudeProvider) {
12598
13029
  lastLiveState = await readClaudeSessionState(sessionId);
@@ -12671,7 +13102,7 @@ async function startServer(baseDir, opts) {
12671
13102
  if (aborted) return;
12672
13103
  scheduleRebuild(0);
12673
13104
  }, RESOLVE_REFRESH_INTERVAL_MS);
12674
- await new Promise((resolve4) => {
13105
+ await new Promise((resolve5) => {
12675
13106
  stream.onAbort(() => {
12676
13107
  aborted = true;
12677
13108
  if (pendingTimer) clearTimeout(pendingTimer);
@@ -12685,24 +13116,28 @@ async function startServer(baseDir, opts) {
12685
13116
  } catch {
12686
13117
  }
12687
13118
  }
12688
- resolve4();
13119
+ resolve5();
12689
13120
  });
12690
13121
  });
12691
13122
  });
12692
13123
  });
12693
- app.get("/api/sessions/cached", async (c) => {
12694
- const cached = await readFileCache(replaysCacheKey);
13124
+ const getCachedReplays = async (c) => {
13125
+ const cached = await readFileCache2(replaysCacheKey);
12695
13126
  return c.json({
12696
13127
  sessions: cached?.data || [],
12697
13128
  cachedAt: cached?.updatedAt
12698
13129
  });
12699
- });
12700
- app.get("/api/sessions", async (c) => {
13130
+ };
13131
+ app.get("/api/sessions/cached", getCachedReplays);
13132
+ app.get("/api/replays/cached", getCachedReplays);
13133
+ const getReplays = async (c) => {
12701
13134
  const sessions = await scanSessions(baseDir);
12702
- await writeFileCache(replaysCacheKey, sessions);
13135
+ await writeFileCache2(replaysCacheKey, sessions);
12703
13136
  return c.json(sessions);
12704
- });
12705
- app.patch("/api/sessions/:slug", async (c) => {
13137
+ };
13138
+ app.get("/api/sessions", getReplays);
13139
+ app.get("/api/replays", getReplays);
13140
+ const patchReplay = async (c) => {
12706
13141
  const slug = safeSlug(c.req.param("slug"));
12707
13142
  if (!slug) return c.json({ error: "invalid slug" }, 400);
12708
13143
  let body;
@@ -12717,8 +13152,8 @@ async function startServer(baseDir, opts) {
12717
13152
  try {
12718
13153
  const target = await loadSessionFromDisk(baseDir, slug);
12719
13154
  target.meta.title = normalizeTitle(body.title);
12720
- const targetDir = join22(baseDir, slug);
12721
- await writeFile7(join22(targetDir, "replay.json"), JSON.stringify(target), "utf-8");
13155
+ const targetDir = join24(baseDir, slug);
13156
+ await writeFile7(join24(targetDir, "replay.json"), JSON.stringify(target), "utf-8");
12722
13157
  await generateOutput(target, targetDir);
12723
13158
  const updatedReplays = await refreshReplaysCache();
12724
13159
  if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
@@ -12726,20 +13161,24 @@ async function startServer(baseDir, opts) {
12726
13161
  } catch (err) {
12727
13162
  return c.json({ error: getErrorMessage(err) }, 500);
12728
13163
  }
12729
- });
12730
- app.delete("/api/sessions/:slug", async (c) => {
13164
+ };
13165
+ app.patch("/api/sessions/:slug", patchReplay);
13166
+ app.patch("/api/replays/:slug", patchReplay);
13167
+ const deleteReplay = async (c) => {
12731
13168
  const slug = safeSlug(c.req.param("slug"));
12732
13169
  if (!slug) return c.json({ error: "invalid slug" }, 400);
12733
13170
  try {
12734
13171
  const { rm } = await import("fs/promises");
12735
- await rm(join22(baseDir, slug), { recursive: true });
13172
+ await rm(join24(baseDir, slug), { recursive: true });
12736
13173
  const updatedReplays = await refreshReplaysCache();
12737
13174
  if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
12738
13175
  return c.json({ ok: true });
12739
13176
  } catch (err) {
12740
13177
  return c.json({ error: getErrorMessage(err) }, 500);
12741
13178
  }
12742
- });
13179
+ };
13180
+ app.delete("/api/sessions/:slug", deleteReplay);
13181
+ app.delete("/api/replays/:slug", deleteReplay);
12743
13182
  app.get("/api/archived", async (c) => {
12744
13183
  const slugs = await getArchivedSlugs(baseDir);
12745
13184
  return c.json({ slugs: [...slugs] });
@@ -12756,25 +13195,33 @@ async function startServer(baseDir, opts) {
12756
13195
  await unarchiveSlug(baseDir, slug);
12757
13196
  return c.json({ ok: true });
12758
13197
  });
12759
- app.get("/api/sources/cached", async (c) => {
12760
- const cached = await readFileCache(sourcesCacheKey);
13198
+ const getCachedSourceSessions = async (c) => {
13199
+ const cached = await readSourcesCatalogCache();
13200
+ const staleProviders = await getStaleSourceProviders(cached);
12761
13201
  return c.json({
12762
- sessions: cached?.data || [],
12763
- cachedAt: cached?.updatedAt
13202
+ sessions: cached?.sessions || [],
13203
+ cachedAt: cached?.cachedAt,
13204
+ discoveredAt: cached?.discoveredAt,
13205
+ stale: staleProviders.length > 0,
13206
+ staleProviders
12764
13207
  });
12765
- });
12766
- app.get("/api/sources/enrichment-status", async (c) => {
13208
+ };
13209
+ app.get("/api/sources/cached", getCachedSourceSessions);
13210
+ app.get("/api/source-sessions/cached", getCachedSourceSessions);
13211
+ const getSourceSessionsEnrichmentStatus = async (c) => {
12767
13212
  return c.json(sourcesEnrichmentStatus);
12768
- });
12769
- app.post("/api/sources/enrich", async (c) => {
13213
+ };
13214
+ app.get("/api/sources/enrichment-status", getSourceSessionsEnrichmentStatus);
13215
+ app.get("/api/source-sessions/enrichment-status", getSourceSessionsEnrichmentStatus);
13216
+ const postSourceSessionsEnrich = async (c) => {
12770
13217
  const hints = enrichmentHintsFromBody(await c.req.json().catch(() => void 0));
12771
- const cached = await readFileCache(sourcesCacheKey);
12772
- if (!cached?.data?.length) {
13218
+ const cached = await readSourcesCatalogCache();
13219
+ if (!cached?.sessions.length) {
12773
13220
  return c.json({ ok: false, message: "No sources cache available" }, 404);
12774
13221
  }
12775
13222
  const cursorProvider2 = getProvider("cursor");
12776
13223
  if (!cursorProvider2) return c.json({ ok: false, message: "Cursor provider unavailable" }, 404);
12777
- const home = homedir19();
13224
+ const home = homedir20();
12778
13225
  let cursorSessions = lastDiscoveredMergedSessions.filter(
12779
13226
  (session) => session.provider === "cursor"
12780
13227
  );
@@ -12789,14 +13236,16 @@ async function startServer(baseDir, opts) {
12789
13236
  ];
12790
13237
  }
12791
13238
  const wasRunning = sourcesEnrichmentStatus.running;
12792
- enrichCursorStatsInBackground(cursorSessions, cached.data, hints);
13239
+ enrichCursorStatsInBackground(cursorSessions, cached.sessions, hints);
12793
13240
  return c.json({
12794
13241
  ok: true,
12795
13242
  running: sourcesEnrichmentStatus.running,
12796
13243
  queued: wasRunning
12797
13244
  });
12798
- });
12799
- app.get("/api/sources", async (c) => {
13245
+ };
13246
+ app.post("/api/sources/enrich", postSourceSessionsEnrich);
13247
+ app.post("/api/source-sessions/enrich", postSourceSessionsEnrich);
13248
+ const getSourceSessions = async (c) => {
12800
13249
  try {
12801
13250
  const providers2 = getAllProviders();
12802
13251
  const allSessions = [];
@@ -12805,23 +13254,31 @@ async function startServer(baseDir, opts) {
12805
13254
  allSessions.push(...sessions);
12806
13255
  }
12807
13256
  const merged = mergeSameSessions(allSessions);
12808
- lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir19());
12809
- const previous = await readFileCache(sourcesCacheKey);
13257
+ lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir20());
13258
+ const previous = await readSourcesCatalogCache();
12810
13259
  const result = await buildSourcesResult(
12811
13260
  merged,
12812
13261
  baseDir,
12813
- homedir19(),
12814
- previous?.data || [],
13262
+ homedir20(),
13263
+ previous?.sessions || [],
12815
13264
  cleanupPeriodDays
12816
13265
  );
12817
- await writeFileCache(sourcesCacheKey, result);
13266
+ const catalog = await writeDiscoveredSourcesCatalog(result, previous);
12818
13267
  enrichCursorStatsInBackground(merged, result);
12819
- return c.json({ sessions: result, cleanupPeriodDays });
13268
+ return c.json({
13269
+ sessions: result,
13270
+ cleanupPeriodDays,
13271
+ discoveredAt: catalog.discoveredAt,
13272
+ stale: false,
13273
+ staleProviders: []
13274
+ });
12820
13275
  } catch (err) {
12821
13276
  return c.json({ error: getErrorMessage(err) }, 500);
12822
13277
  }
12823
- });
12824
- app.get("/api/sources/stream", (c) => {
13278
+ };
13279
+ app.get("/api/sources", getSourceSessions);
13280
+ app.get("/api/source-sessions", getSourceSessions);
13281
+ const streamSourceSessions = (c) => {
12825
13282
  return streamSSE(c, async (stream) => {
12826
13283
  try {
12827
13284
  const providers2 = getAllProviders();
@@ -12840,19 +13297,26 @@ async function startServer(baseDir, opts) {
12840
13297
  }
12841
13298
  }
12842
13299
  const merged = mergeSameSessions(allSessions);
12843
- lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir19());
12844
- const previous = await readFileCache(sourcesCacheKey);
13300
+ lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir20());
13301
+ const previous = await readSourcesCatalogCache();
12845
13302
  const result = await buildSourcesResult(
12846
13303
  merged,
12847
13304
  baseDir,
12848
- homedir19(),
12849
- previous?.data || [],
13305
+ homedir20(),
13306
+ previous?.sessions || [],
12850
13307
  cleanupPeriodDays
12851
13308
  );
12852
- await writeFileCache(sourcesCacheKey, result);
13309
+ const catalog = await writeDiscoveredSourcesCatalog(result, previous);
12853
13310
  enrichCursorStatsInBackground(merged, result);
12854
13311
  await stream.writeSSE({
12855
- data: JSON.stringify({ type: "complete", sessions: result, cleanupPeriodDays })
13312
+ data: JSON.stringify({
13313
+ type: "complete",
13314
+ sessions: result,
13315
+ cleanupPeriodDays,
13316
+ discoveredAt: catalog.discoveredAt,
13317
+ stale: false,
13318
+ staleProviders: []
13319
+ })
12856
13320
  });
12857
13321
  } catch (err) {
12858
13322
  await stream.writeSSE({
@@ -12860,7 +13324,9 @@ async function startServer(baseDir, opts) {
12860
13324
  });
12861
13325
  }
12862
13326
  });
12863
- });
13327
+ };
13328
+ app.get("/api/sources/stream", streamSourceSessions);
13329
+ app.get("/api/source-sessions/stream", streamSourceSessions);
12864
13330
  app.post("/api/generate", async (c) => {
12865
13331
  try {
12866
13332
  const body = await c.req.json();
@@ -12880,15 +13346,17 @@ async function startServer(baseDir, opts) {
12880
13346
  return c.json({ error: "title must be a string" }, 400);
12881
13347
  }
12882
13348
  const parsed = await provider.parse(resolved.value.paths, resolved.value.sessionInfo);
12883
- const home = homedir19();
13349
+ const home = homedir20();
12884
13350
  const rawProject = body.sessionProject || parsed.cwd;
12885
13351
  const project = rawProject.startsWith(home) ? `~${rawProject.slice(home.length)}` : rawProject;
13352
+ const gitRepo = resolved.value.sessionInfo?.gitRepo || await readGitRepo(rawProject);
12886
13353
  const replay = transformToReplay(parsed, body.provider, project, {
12887
13354
  generator: {
12888
13355
  name: "vibe-replay",
12889
13356
  version: CLI_VERSION,
12890
13357
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12891
- }
13358
+ },
13359
+ gitRepo
12892
13360
  });
12893
13361
  if (typeof body.title === "string") {
12894
13362
  const normalizedCustomTitle = normalizeTitle(body.title);
@@ -12898,7 +13366,7 @@ async function startServer(baseDir, opts) {
12898
13366
  }
12899
13367
  const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
12900
13368
  const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
12901
- const outputDir = join22(baseDir, slug);
13369
+ const outputDir = join24(baseDir, slug);
12902
13370
  await generateOutput(replay, outputDir);
12903
13371
  const updatedReplays = await refreshReplaysCache();
12904
13372
  if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
@@ -12941,7 +13409,7 @@ async function startServer(baseDir, opts) {
12941
13409
  for (const slug of entries) {
12942
13410
  if (slug.startsWith(".") || slug === "cache") continue;
12943
13411
  try {
12944
- const replayPath = join22(replaysDir, slug, "replay.json");
13412
+ const replayPath = join24(replaysDir, slug, "replay.json");
12945
13413
  const raw = await readF(replayPath, "utf-8").catch(() => null);
12946
13414
  if (!raw) continue;
12947
13415
  const oldReplay = JSON.parse(raw);
@@ -12963,17 +13431,18 @@ async function startServer(baseDir, opts) {
12963
13431
  }
12964
13432
  const paths = [...sessionInfo.filePaths, ...sessionInfo.toolPaths || []];
12965
13433
  const parsed = await provider.parse(paths, sessionInfo);
12966
- const home = homedir19();
13434
+ const home = homedir20();
12967
13435
  const project = sessionInfo.project.startsWith(home) ? `~${sessionInfo.project.slice(home.length)}` : sessionInfo.project;
12968
13436
  const replay = transformToReplay(parsed, providerName, project, {
12969
13437
  generator: {
12970
13438
  name: "vibe-replay",
12971
13439
  version: CLI_VERSION,
12972
13440
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12973
- }
13441
+ },
13442
+ gitRepo: sessionInfo.gitRepo
12974
13443
  });
12975
13444
  if (oldReplay.meta?.title) replay.meta.title = oldReplay.meta.title;
12976
- const outputDir = join22(replaysDir, slug);
13445
+ const outputDir = join24(replaysDir, slug);
12977
13446
  await generateOutput(replay, outputDir);
12978
13447
  results.push({ slug, status: "regenerated", scenes: replay.scenes.length });
12979
13448
  } catch (err) {
@@ -13014,7 +13483,8 @@ async function startServer(baseDir, opts) {
13014
13483
  results: scanState.results,
13015
13484
  running: scanState.running,
13016
13485
  scanned: scanState.scanned,
13017
- total: scanState.total
13486
+ total: scanState.total,
13487
+ finishedAt: scanState.finishedAt
13018
13488
  });
13019
13489
  });
13020
13490
  app.get("/api/insights", async (c) => {
@@ -13459,7 +13929,7 @@ async function startServer(baseDir, opts) {
13459
13929
  app.get("/api/gist-info", async (c) => {
13460
13930
  const result = requireSlug(c.req.query("slug"));
13461
13931
  if ("error" in result) return c.json({ error: result.error }, 400);
13462
- const targetDir = join22(baseDir, result.slug);
13932
+ const targetDir = join24(baseDir, result.slug);
13463
13933
  const gist = await loadSavedGistInfo(targetDir);
13464
13934
  if (!gist) return c.json({ gist: null });
13465
13935
  return c.json({ gist });
@@ -13467,7 +13937,7 @@ async function startServer(baseDir, opts) {
13467
13937
  app.delete("/api/gist-info", async (c) => {
13468
13938
  const result = requireSlug(c.req.query("slug"));
13469
13939
  if ("error" in result) return c.json({ error: result.error }, 400);
13470
- const metaPath = join22(baseDir, result.slug, ".vibe-replay-gist.json");
13940
+ const metaPath = join24(baseDir, result.slug, ".vibe-replay-gist.json");
13471
13941
  await unlink2(metaPath).catch(() => {
13472
13942
  });
13473
13943
  return c.json({ ok: true });
@@ -13475,7 +13945,7 @@ async function startServer(baseDir, opts) {
13475
13945
  app.get("/api/cloud-info", async (c) => {
13476
13946
  const result = requireSlug(c.req.query("slug"));
13477
13947
  if ("error" in result) return c.json({ error: result.error }, 400);
13478
- const targetDir = join22(baseDir, result.slug);
13948
+ const targetDir = join24(baseDir, result.slug);
13479
13949
  const cloud = await loadSavedCloudInfo(targetDir);
13480
13950
  if (!cloud) return c.json({ cloud: null });
13481
13951
  return c.json({ cloud });
@@ -13483,10 +13953,10 @@ async function startServer(baseDir, opts) {
13483
13953
  app.post("/api/cloud-info", async (c) => {
13484
13954
  const result = requireSlug(c.req.query("slug"));
13485
13955
  if ("error" in result) return c.json({ error: result.error }, 400);
13486
- const targetDir = join22(baseDir, result.slug);
13956
+ const targetDir = join24(baseDir, result.slug);
13487
13957
  const body = await c.req.json();
13488
13958
  if (!body.id || !body.url) return c.json({ error: "Missing id/url" }, 400);
13489
- const metaPath = join22(targetDir, ".vibe-replay-cloud.json");
13959
+ const metaPath = join24(targetDir, ".vibe-replay-cloud.json");
13490
13960
  await writeFile7(
13491
13961
  metaPath,
13492
13962
  JSON.stringify(
@@ -13506,7 +13976,7 @@ async function startServer(baseDir, opts) {
13506
13976
  app.delete("/api/cloud-info", async (c) => {
13507
13977
  const result = requireSlug(c.req.query("slug"));
13508
13978
  if ("error" in result) return c.json({ error: result.error }, 400);
13509
- const metaPath = join22(baseDir, result.slug, ".vibe-replay-cloud.json");
13979
+ const metaPath = join24(baseDir, result.slug, ".vibe-replay-cloud.json");
13510
13980
  await unlink2(metaPath).catch(() => {
13511
13981
  });
13512
13982
  return c.json({ ok: true });
@@ -13514,13 +13984,13 @@ async function startServer(baseDir, opts) {
13514
13984
  app.post("/api/publish/gist", async (c) => {
13515
13985
  const result = requireSlug(c.req.query("slug"));
13516
13986
  if ("error" in result) return c.json({ error: result.error }, 400);
13517
- const targetDir = join22(baseDir, result.slug);
13987
+ const targetDir = join24(baseDir, result.slug);
13518
13988
  try {
13519
13989
  const rawSession = await loadSessionFromDisk(baseDir, result.slug);
13520
13990
  const overlaysData = await loadOverlays(baseDir, result.slug);
13521
13991
  const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
13522
- const replayPath = join22(targetDir, "replay.json");
13523
- const originalContent = await readFile22(replayPath, "utf-8");
13992
+ const replayPath = join24(targetDir, "replay.json");
13993
+ const originalContent = await readFile24(replayPath, "utf-8");
13524
13994
  await writeFile7(replayPath, JSON.stringify(targetSession), "utf-8");
13525
13995
  try {
13526
13996
  const title = targetSession.meta.title || targetSession.meta.slug;
@@ -13539,7 +14009,7 @@ async function startServer(baseDir, opts) {
13539
14009
  app.post("/api/publish/cloud", async (c) => {
13540
14010
  const result = requireSlug(c.req.query("slug"));
13541
14011
  if ("error" in result) return c.json({ error: result.error }, 400);
13542
- const targetDir = join22(baseDir, result.slug);
14012
+ const targetDir = join24(baseDir, result.slug);
13543
14013
  try {
13544
14014
  const body = await c.req.json().catch(() => ({}));
13545
14015
  const cloudResult = await publishCloudWithOverlays(targetDir, {
@@ -13553,13 +14023,13 @@ async function startServer(baseDir, opts) {
13553
14023
  app.post("/api/export/html", async (c) => {
13554
14024
  const result = requireSlug(c.req.query("slug"));
13555
14025
  if ("error" in result) return c.json({ error: result.error }, 400);
13556
- const targetDir = join22(baseDir, result.slug);
14026
+ const targetDir = join24(baseDir, result.slug);
13557
14027
  try {
13558
14028
  const rawSession = await loadSessionFromDisk(baseDir, result.slug);
13559
14029
  const overlaysData = await loadOverlays(baseDir, result.slug);
13560
14030
  const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
13561
- const replayPath = join22(targetDir, "replay.json");
13562
- const originalContent = await readFile22(replayPath, "utf-8");
14031
+ const replayPath = join24(targetDir, "replay.json");
14032
+ const originalContent = await readFile24(replayPath, "utf-8");
13563
14033
  try {
13564
14034
  const outputPath = await generateOutput(targetSession, targetDir);
13565
14035
  return c.json({ path: outputPath });
@@ -13573,23 +14043,23 @@ async function startServer(baseDir, opts) {
13573
14043
  app.get("/api/export/github/status", async (c) => {
13574
14044
  const result = requireSlug(c.req.query("slug"));
13575
14045
  if ("error" in result) return c.json({ error: result.error }, 400);
13576
- const targetDir = join22(baseDir, result.slug);
14046
+ const targetDir = join24(baseDir, result.slug);
13577
14047
  try {
13578
- const svgPath = join22(targetDir, "session-preview.svg");
13579
- const mdPath = join22(targetDir, "github-summary.md");
13580
- const gifPath = join22(targetDir, "session-preview.gif");
14048
+ const svgPath = join24(targetDir, "session-preview.svg");
14049
+ const mdPath = join24(targetDir, "github-summary.md");
14050
+ const gifPath = join24(targetDir, "session-preview.gif");
13581
14051
  const [svgContent, markdown, gifBuf] = await Promise.all([
13582
- readFile22(svgPath, "utf-8").catch(() => null),
13583
- readFile22(mdPath, "utf-8").catch(() => null),
13584
- readFile22(gifPath).catch(() => null)
14052
+ readFile24(svgPath, "utf-8").catch(() => null),
14053
+ readFile24(mdPath, "utf-8").catch(() => null),
14054
+ readFile24(gifPath).catch(() => null)
13585
14055
  ]);
13586
14056
  if (!svgContent && !markdown && !gifBuf) return c.json({ exists: false });
13587
14057
  const gist = await loadSavedGistInfo(targetDir);
13588
14058
  const gifContent = gifBuf ? gifBuf.toString("base64") : null;
13589
14059
  const [gifMtime, svgMtime, mdMtime] = await Promise.all([
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)
14060
+ stat12(gifPath).then((s) => s.mtime.toISOString()).catch(() => null),
14061
+ stat12(svgPath).then((s) => s.mtime.toISOString()).catch(() => null),
14062
+ stat12(mdPath).then((s) => s.mtime.toISOString()).catch(() => null)
13593
14063
  ]);
13594
14064
  return c.json({
13595
14065
  exists: true,
@@ -13611,7 +14081,7 @@ async function startServer(baseDir, opts) {
13611
14081
  app.post("/api/export/github", async (c) => {
13612
14082
  const result = requireSlug(c.req.query("slug"));
13613
14083
  if ("error" in result) return c.json({ error: result.error }, 400);
13614
- const targetDir = join22(baseDir, result.slug);
14084
+ const targetDir = join24(baseDir, result.slug);
13615
14085
  try {
13616
14086
  const rawSession = await loadSessionFromDisk(baseDir, result.slug);
13617
14087
  const overlaysData = await loadOverlays(baseDir, result.slug);
@@ -13619,14 +14089,14 @@ async function startServer(baseDir, opts) {
13619
14089
  const gist = await loadSavedGistInfo(targetDir);
13620
14090
  const replayUrl = gist?.viewerUrl || void 0;
13621
14091
  const svgContent = generateGitHubSvg(targetSession, { replayUrl });
13622
- const svgFilePath = join22(targetDir, "session-preview.svg");
14092
+ const svgFilePath = join24(targetDir, "session-preview.svg");
13623
14093
  await writeFile7(svgFilePath, svgContent, "utf-8");
13624
14094
  let gifContent = null;
13625
14095
  let gifFilePath = null;
13626
14096
  let gifWarning;
13627
14097
  try {
13628
14098
  const gifBuffer = await generateGitHubGif(targetSession, { replayUrl });
13629
- gifFilePath = join22(targetDir, "session-preview.gif");
14099
+ gifFilePath = join24(targetDir, "session-preview.gif");
13630
14100
  await writeFile7(gifFilePath, gifBuffer);
13631
14101
  gifContent = gifBuffer.toString("base64");
13632
14102
  } catch (err) {
@@ -13637,7 +14107,7 @@ async function startServer(baseDir, opts) {
13637
14107
  svgPath: "./session-preview.svg",
13638
14108
  gifPath: gifContent ? "./session-preview.gif" : void 0
13639
14109
  });
13640
- const mdFilePath = join22(targetDir, "github-summary.md");
14110
+ const mdFilePath = join24(targetDir, "github-summary.md");
13641
14111
  await writeFile7(mdFilePath, markdown, "utf-8");
13642
14112
  const findings = scanForSecrets(JSON.stringify(targetSession));
13643
14113
  const warnings = findings.map((f) => `[${f.rule}] ${f.match}`);
@@ -13845,10 +14315,10 @@ async function startServer(baseDir, opts) {
13845
14315
  }
13846
14316
  }
13847
14317
  );
13848
- await new Promise((resolve4) => {
14318
+ await new Promise((resolve5) => {
13849
14319
  process.on("SIGINT", () => {
13850
14320
  console.log(chalk.dim("\n Server stopped.\n"));
13851
- resolve4();
14321
+ resolve5();
13852
14322
  process.exit(0);
13853
14323
  });
13854
14324
  });
@@ -13914,7 +14384,7 @@ function scanInputFromSession(session) {
13914
14384
  return {
13915
14385
  sessionId: session.sessionId,
13916
14386
  provider: session.provider,
13917
- project: shortenPath(session.project),
14387
+ project: shortenPath2(session.project),
13918
14388
  slug: session.slug,
13919
14389
  filePaths: session.filePaths,
13920
14390
  toolPaths: session.toolPaths,
@@ -13969,10 +14439,11 @@ function sessionInfoToMatch(session, terms = [], precomputedScore) {
13969
14439
  sessionId: session.sessionId,
13970
14440
  slug: session.slug,
13971
14441
  title: cleanPromptText(session.title || "") || void 0,
13972
- project: shortenPath(session.project),
13973
- cwd: shortenPath(session.cwd),
14442
+ project: shortenPath2(session.project),
14443
+ cwd: shortenPath2(session.cwd),
13974
14444
  timestamp: session.timestamp,
13975
14445
  gitBranch: session.gitBranch,
14446
+ gitRepo: session.gitRepo,
13976
14447
  model: session.model,
13977
14448
  firstPrompt: cleanPromptText(session.firstPrompt),
13978
14449
  prompts: session.prompts?.map(cleanPromptText).filter(Boolean),
@@ -14206,21 +14677,22 @@ function formatDuration2(ms) {
14206
14677
  // src/index.ts
14207
14678
  init_utils();
14208
14679
  init_version();
14680
+ setFileCacheAppVersion(CLI_VERSION);
14209
14681
  async function runGitHubExport(replay, outputDir) {
14210
- const { join: join23 } = await import("path");
14682
+ const { join: join25 } = await import("path");
14211
14683
  const { writeFile: writeFile8 } = await import("fs/promises");
14212
14684
  const savedGist = await loadSavedGistInfo(outputDir);
14213
14685
  const replayUrl = savedGist?.viewerUrl;
14214
14686
  const svgSpinner = ora("Generating animated SVG...").start();
14215
14687
  const svgContent = generateGitHubSvg(replay, { replayUrl });
14216
- const svgFilePath = join23(outputDir, "session-preview.svg");
14688
+ const svgFilePath = join25(outputDir, "session-preview.svg");
14217
14689
  await writeFile8(svgFilePath, svgContent, "utf-8");
14218
14690
  svgSpinner.succeed(`SVG: ${svgFilePath}`);
14219
14691
  let gifGenerated = false;
14220
14692
  const gifSpinner = ora("Generating animated GIF...").start();
14221
14693
  try {
14222
14694
  const gifBuffer = await generateGitHubGif(replay, { replayUrl });
14223
- const gifFilePath = join23(outputDir, "session-preview.gif");
14695
+ const gifFilePath = join25(outputDir, "session-preview.gif");
14224
14696
  await writeFile8(gifFilePath, gifBuffer);
14225
14697
  const gifSizeKB = Math.round(gifBuffer.length / 1024);
14226
14698
  gifSpinner.succeed(`GIF: ${gifFilePath} (${gifSizeKB} KB)`);
@@ -14234,7 +14706,7 @@ async function runGitHubExport(replay, outputDir) {
14234
14706
  svgPath: "./session-preview.svg",
14235
14707
  gifPath: gifGenerated ? "./session-preview.gif" : void 0
14236
14708
  });
14237
- const mdFilePath = join23(outputDir, "github-summary.md");
14709
+ const mdFilePath = join25(outputDir, "github-summary.md");
14238
14710
  await writeFile8(mdFilePath, markdown, "utf-8");
14239
14711
  mdSpinner.succeed(`Markdown: ${mdFilePath}`);
14240
14712
  const redactionsSpinner = ora("Generating redaction report...").start();
@@ -14250,7 +14722,7 @@ async function runGitHubExport(replay, outputDir) {
14250
14722
  "leftoverFindings: regex matches in the final markdown \u2014 review these before sharing."
14251
14723
  ]
14252
14724
  };
14253
- const redactionsPath = join23(outputDir, "redactions.json");
14725
+ const redactionsPath = join25(outputDir, "redactions.json");
14254
14726
  await writeFile8(redactionsPath, `${JSON.stringify(redactionsReport, null, 2)}
14255
14727
  `, "utf-8");
14256
14728
  const leftoverNote = leftoverFindings.length > 0 ? chalk2.yellow(`(${leftoverFindings.length} leftover finding(s) \u2014 review)`) : chalk2.dim("(no leftover findings)");
@@ -14259,7 +14731,7 @@ async function runGitHubExport(replay, outputDir) {
14259
14731
  markdown,
14260
14732
  outputDir,
14261
14733
  svgPath: svgFilePath,
14262
- gifPath: gifGenerated ? join23(outputDir, "session-preview.gif") : void 0,
14734
+ gifPath: gifGenerated ? join25(outputDir, "session-preview.gif") : void 0,
14263
14735
  mdPath: mdFilePath,
14264
14736
  redactionsPath
14265
14737
  };
@@ -14337,10 +14809,10 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
14337
14809
  console.log(chalk2.bold.cyan("\n vibe-replay") + chalk2.dim(` v${CLI_VERSION}
14338
14810
  `));
14339
14811
  const { join: pathJoin } = await import("path");
14340
- const { homedir: homedir20 } = await import("os");
14341
- const replayBaseDir = pathJoin(homedir20(), ".vibe-replay");
14812
+ const { homedir: homedir21 } = await import("os");
14813
+ const replayBaseDir = pathJoin(homedir21(), ".vibe-replay");
14342
14814
  void discoverAllSessions().then(async (sessions) => {
14343
- await writeFileCache(SESSION_DISCOVERY_CACHE_KEY, sessions);
14815
+ await writeFileCache2(SESSION_DISCOVERY_CACHE_KEY, sessions);
14344
14816
  }).catch(() => {
14345
14817
  });
14346
14818
  if (opts.dashboard) {
@@ -14380,14 +14852,14 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
14380
14852
  return;
14381
14853
  }
14382
14854
  if (topChoice === "replays") {
14383
- const { readdir: readdir13, readFile: readFile23 } = await import("fs/promises");
14855
+ const { readdir: readdir13, readFile: readFile25 } = await import("fs/promises");
14384
14856
  const replayEntries = [];
14385
14857
  try {
14386
14858
  const entries = await readdir13(replayBaseDir);
14387
14859
  for (const slug2 of entries) {
14388
14860
  if (slug2.startsWith(".") || slug2 === "cache") continue;
14389
14861
  try {
14390
- const raw = await readFile23(pathJoin(replayBaseDir, slug2, "replay.json"), "utf-8");
14862
+ const raw = await readFile25(pathJoin(replayBaseDir, slug2, "replay.json"), "utf-8");
14391
14863
  const replay2 = JSON.parse(raw);
14392
14864
  const title = replay2.meta?.title || slug2;
14393
14865
  const provider2 = replay2.meta?.provider || "";
@@ -14430,19 +14902,19 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
14430
14902
  return;
14431
14903
  }
14432
14904
  let displayedSessions = [];
14433
- const cached = await readFileCache(SESSION_DISCOVERY_CACHE_KEY);
14905
+ const cached = await readFileCache2(SESSION_DISCOVERY_CACHE_KEY);
14434
14906
  const hasStaleCache = !!(cached && cached.data.length > 0);
14435
14907
  if (hasStaleCache && cached) {
14436
14908
  displayedSessions = cached.data.slice().sort((a, b) => b.timestamp.localeCompare(a.timestamp));
14437
14909
  discoverAllSessions().then(async (freshSessions) => {
14438
- await writeFileCache(SESSION_DISCOVERY_CACHE_KEY, freshSessions);
14910
+ await writeFileCache2(SESSION_DISCOVERY_CACHE_KEY, freshSessions);
14439
14911
  }).catch(() => {
14440
14912
  });
14441
14913
  } else {
14442
14914
  const spinner2 = ora("Scanning sessions...").start();
14443
14915
  try {
14444
14916
  displayedSessions = await discoverAllSessions();
14445
- await writeFileCache(SESSION_DISCOVERY_CACHE_KEY, displayedSessions);
14917
+ await writeFileCache2(SESSION_DISCOVERY_CACHE_KEY, displayedSessions);
14446
14918
  } finally {
14447
14919
  spinner2.stop();
14448
14920
  }
@@ -14509,7 +14981,7 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
14509
14981
  const spinner2 = ora("Refreshing sessions...").start();
14510
14982
  try {
14511
14983
  displayedSessions = await discoverAllSessions();
14512
- await writeFileCache(SESSION_DISCOVERY_CACHE_KEY, displayedSessions);
14984
+ await writeFileCache2(SESSION_DISCOVERY_CACHE_KEY, displayedSessions);
14513
14985
  spinner2.succeed(`Found ${displayedSessions.length} sessions`);
14514
14986
  } catch {
14515
14987
  spinner2.fail("Refresh failed, using previous list");
@@ -14539,12 +15011,14 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
14539
15011
  spinner.text = "Transforming to replay...";
14540
15012
  const rawProject = sessionInfo?.project || parsed.cwd;
14541
15013
  const project = rawProject.startsWith(home) ? `~${rawProject.slice(home.length)}` : rawProject;
15014
+ const gitRepo = sessionInfo?.gitRepo || await readGitRepo(rawProject);
14542
15015
  replay = transformToReplay(parsed, providerName, project, {
14543
15016
  generator: {
14544
15017
  name: "vibe-replay",
14545
15018
  version: CLI_VERSION,
14546
15019
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
14547
- }
15020
+ },
15021
+ gitRepo
14548
15022
  });
14549
15023
  const thinkingStr = replay.meta.stats.thinkingBlocks ? `, ${replay.meta.stats.thinkingBlocks} thinking` : "";
14550
15024
  const sourceStr = replay.meta.dataSource ? chalk2.dim(` [${replay.meta.dataSource}]`) : "";
@@ -14573,10 +15047,10 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
14573
15047
  replay.meta.title = normalizedUserTitle;
14574
15048
  }
14575
15049
  }
14576
- const { join: join23 } = await import("path");
15050
+ const { join: join25 } = await import("path");
14577
15051
  const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
14578
15052
  const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
14579
- const outputDir = join23(home, ".vibe-replay", slug);
15053
+ const outputDir = join25(home, ".vibe-replay", slug);
14580
15054
  const genSpinner = ora("Generating replay...").start();
14581
15055
  const outputPath = await generateOutput(replay, outputDir);
14582
15056
  const { stat: fsStat } = await import("fs/promises");
@@ -14668,7 +15142,7 @@ ${chalk2.bold.green(" Done!")}
14668
15142
  if (target === "local") {
14669
15143
  await publishLocal(outputPath);
14670
15144
  } else if (target === "editor") {
14671
- await startServer(join23(home, ".vibe-replay"), {
15145
+ await startServer(join25(home, ".vibe-replay"), {
14672
15146
  openSlug: slug,
14673
15147
  ...getDevViewerOpts()
14674
15148
  });
@@ -14931,9 +15405,9 @@ authCmd.command("status").description("Show current authentication status").opti
14931
15405
  var VISIBILITIES = ["public", "unlisted", "private"];
14932
15406
  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) => {
14933
15407
  const { existsSync: existsSync2, statSync } = await import("fs");
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");
15408
+ const { readFile: readFile25, readdir: readdir13 } = await import("fs/promises");
15409
+ const { join: join25, dirname: dirname5, resolve: resolve5 } = await import("path");
15410
+ const { homedir: homedir21 } = await import("os");
14937
15411
  if (opts.apiUrl) {
14938
15412
  process.env.VIBE_REPLAY_API_URL = opts.apiUrl.replace(/\/$/, "");
14939
15413
  }
@@ -14952,7 +15426,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
14952
15426
  }
14953
15427
  let outputDir;
14954
15428
  if (pathArg) {
14955
- const abs = resolve4(pathArg);
15429
+ const abs = resolve5(pathArg);
14956
15430
  if (!existsSync2(abs)) {
14957
15431
  console.error(chalk2.red(`
14958
15432
  \u2717 Path not found: ${abs}
@@ -14962,14 +15436,14 @@ program.command("share").description("Share an existing replay via cloud (unlist
14962
15436
  const s = statSync(abs);
14963
15437
  outputDir = s.isDirectory() ? abs : dirname5(abs);
14964
15438
  } else {
14965
- const replayBaseDir = join23(homedir20(), ".vibe-replay");
15439
+ const replayBaseDir = join25(homedir21(), ".vibe-replay");
14966
15440
  const entries = await readdir13(replayBaseDir).catch(() => []);
14967
15441
  const replays = [];
14968
15442
  for (const slug of entries) {
14969
15443
  if (slug.startsWith(".") || slug === "cache") continue;
14970
- const jsonPath2 = join23(replayBaseDir, slug, "replay.json");
15444
+ const jsonPath2 = join25(replayBaseDir, slug, "replay.json");
14971
15445
  try {
14972
- const raw = await readFile23(jsonPath2, "utf-8");
15446
+ const raw = await readFile25(jsonPath2, "utf-8");
14973
15447
  const replay = JSON.parse(raw);
14974
15448
  const title = replay.meta?.title || slug;
14975
15449
  const startTime = replay.meta?.startTime || "";
@@ -14982,7 +15456,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
14982
15456
  }) : "";
14983
15457
  replays.push({
14984
15458
  name: time ? `${chalk2.dim(`[${time}]`)} ${chalk2.white(title)}` : chalk2.white(title),
14985
- value: join23(replayBaseDir, slug),
15459
+ value: join25(replayBaseDir, slug),
14986
15460
  time: startTime
14987
15461
  });
14988
15462
  } catch {
@@ -14998,7 +15472,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
14998
15472
  choices: replays
14999
15473
  });
15000
15474
  }
15001
- const jsonPath = join23(outputDir, "replay.json");
15475
+ const jsonPath = join25(outputDir, "replay.json");
15002
15476
  if (!existsSync2(jsonPath)) {
15003
15477
  console.error(chalk2.red(`
15004
15478
  \u2717 No replay.json found in ${outputDir}
@@ -15023,8 +15497,8 @@ program.command("share").description("Share an existing replay via cloud (unlist
15023
15497
  var LIVE_STALENESS_WARNING_MS = 5 * 6e4;
15024
15498
  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) => {
15025
15499
  const { join: pathJoin } = await import("path");
15026
- const { homedir: homedir20 } = await import("os");
15027
- const replayBaseDir = pathJoin(homedir20(), ".vibe-replay");
15500
+ const { homedir: homedir21 } = await import("os");
15501
+ const replayBaseDir = pathJoin(homedir21(), ".vibe-replay");
15028
15502
  console.log(chalk2.bold.cyan("\n vibe-replay live") + chalk2.dim(` v${CLI_VERSION}
15029
15503
  `));
15030
15504
  let target = null;
@@ -15118,8 +15592,10 @@ function formatSessionChoices(sessions, cleanupPeriodDays) {
15118
15592
  const projectEntries = [...byProject.entries()];
15119
15593
  for (let pi = 0; pi < projectEntries.length; pi++) {
15120
15594
  const [project, projectSessions] = projectEntries[pi];
15595
+ const gitRepo = projectSessions[0]?.gitRepo;
15596
+ const heading = gitRepo ? `${project} ${chalk2.dim(`(${gitRepo})`)}` : project;
15121
15597
  if (pi > 0) choices.push(new Separator(""));
15122
- choices.push(new Separator(chalk2.bold.white(` \u2500\u2500\u2500 ${project} \u2500\u2500\u2500`)));
15598
+ choices.push(new Separator(chalk2.bold.white(` \u2500\u2500\u2500 ${heading} \u2500\u2500\u2500`)));
15123
15599
  for (const s of projectSessions) {
15124
15600
  const date = new Date(s.timestamp);
15125
15601
  const timeStr = date.toLocaleString("en-US", {