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/assets/viewer.html +52 -42
- package/dist/index.js +1001 -525
- package/package.json +9 -1
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
|
|
79
|
-
import { join as
|
|
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 = [
|
|
35
|
+
const dirs = [join17(baseDir, slug), resolve2("./vibe-replay", slug)];
|
|
82
36
|
for (const dir of dirs) {
|
|
83
37
|
try {
|
|
84
|
-
const raw = await
|
|
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
|
|
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
|
|
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 =
|
|
247
|
-
const content = await
|
|
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 =
|
|
295
|
-
const originalContent = await
|
|
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
|
|
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(
|
|
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 =
|
|
326
|
-
AUTH_FILE =
|
|
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
|
|
410
|
-
import { homedir as
|
|
411
|
-
import { dirname as dirname4, join as
|
|
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
|
|
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 =
|
|
611
|
-
STORE_PATH =
|
|
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 !==
|
|
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:
|
|
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
|
|
733
|
-
import { homedir as
|
|
734
|
-
import { join as
|
|
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 =
|
|
809
|
+
const home = homedir3();
|
|
739
810
|
for (const file of [
|
|
740
|
-
|
|
741
|
-
|
|
811
|
+
join3(home, ".claude", "settings.local.json"),
|
|
812
|
+
join3(home, ".claude", "settings.json")
|
|
742
813
|
]) {
|
|
743
814
|
try {
|
|
744
|
-
const raw = await
|
|
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
|
|
1621
|
-
import { dirname, join as
|
|
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 =
|
|
1706
|
+
const outputPath = join4(outputDir, "index.html");
|
|
1636
1707
|
await writeFile2(outputPath, finalHtml, "utf-8");
|
|
1637
|
-
const jsonPath =
|
|
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
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
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
|
|
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/
|
|
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
|
|
1747
|
+
import { join as join5 } from "path";
|
|
1677
1748
|
import { createInterface } from "readline";
|
|
1678
|
-
|
|
1679
|
-
|
|
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 =
|
|
1690
|
-
const projStat = await
|
|
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 =
|
|
1702
|
-
const fileStat = await
|
|
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)
|
|
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 && !
|
|
1784
|
-
const cleaned =
|
|
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
|
|
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/
|
|
1854
|
-
import { readdir as readdir2, readFile as
|
|
1855
|
-
import { join as
|
|
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/
|
|
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/
|
|
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
|
|
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 (
|
|
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 && !
|
|
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
|
|
2457
|
-
if (turnModel)
|
|
2458
|
-
if (durationMs !== void 0)
|
|
2459
|
-
if (turnTokens)
|
|
2460
|
-
if (maxContextTokens > 0)
|
|
2461
|
-
stats.push(
|
|
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 =
|
|
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 =
|
|
2482
|
-
const metaContent = await
|
|
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
|
|
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/
|
|
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/
|
|
2780
|
+
// ../provider-claude-code/src/claude-cowork/discover.ts
|
|
2633
2781
|
import { createReadStream as createReadStream2 } from "fs";
|
|
2634
|
-
import { readdir as readdir3, readFile as
|
|
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
|
|
2784
|
+
import { join as join7 } from "path";
|
|
2637
2785
|
import { createInterface as createInterface2 } from "readline";
|
|
2638
|
-
var COWORK_DIR =
|
|
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 =
|
|
2659
|
-
const accountStat = await
|
|
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 =
|
|
2669
|
-
const orgStat = await
|
|
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 =
|
|
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
|
|
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 =
|
|
2704
|
-
const auditStat = await
|
|
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 (
|
|
2773
|
-
const cleaned =
|
|
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/
|
|
2798
|
-
import { readFile as
|
|
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
|
|
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/
|
|
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/
|
|
2866
|
-
import { readdir as readdir4, readFile as
|
|
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
|
|
2869
|
-
var DESKTOP_DIR =
|
|
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 =
|
|
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 =
|
|
2891
|
-
const accountStat = await
|
|
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 =
|
|
2901
|
-
const orgStat = await
|
|
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 =
|
|
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
|
|
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 =
|
|
2927
|
-
const jsonlStat = await
|
|
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/
|
|
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/
|
|
3103
|
+
// ../provider-codex/src/codex/discover.ts
|
|
2952
3104
|
import { createReadStream as createReadStream3 } from "fs";
|
|
2953
|
-
import { readFile as
|
|
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
|
|
3107
|
+
import { basename, join as join9 } from "path";
|
|
2956
3108
|
import { createInterface as createInterface3 } from "readline";
|
|
2957
|
-
init_utils();
|
|
2958
3109
|
|
|
2959
|
-
// src/
|
|
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/
|
|
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(
|
|
3013
|
-
const fileStat = await
|
|
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))
|
|
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
|
|
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(
|
|
3034
|
-
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
|
|
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
|
|
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
|
|
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 =
|
|
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 ||
|
|
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
|
|
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
|
|
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/
|
|
3439
|
+
// ../provider-codex/src/codex/parser.ts
|
|
3283
3440
|
import { readFileSync } from "fs";
|
|
3284
|
-
import { readFile as
|
|
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
|
|
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
|
|
3925
|
-
if (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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
4102
|
+
if (diff > 0 && diff < 12 * 60 * 60 * 1e3) stat13.durationMs = diff;
|
|
3946
4103
|
}
|
|
3947
4104
|
}
|
|
3948
|
-
return
|
|
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/
|
|
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/
|
|
3991
|
-
|
|
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
|
|
4150
|
+
import { basename as basename3, join as join13 } from "path";
|
|
3995
4151
|
|
|
3996
|
-
// src/
|
|
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
|
|
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
|
|
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/
|
|
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/
|
|
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
|
|
4078
|
-
var CURSOR_CHATS_DIR =
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
4682
|
-
const wsStat = await
|
|
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 =
|
|
4693
|
-
const dbStat = await
|
|
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
|
|
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
|
|
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
|
|
5216
|
+
const dirStat = await stat6(current).catch(() => null);
|
|
5062
5217
|
if (dirStat?.isDirectory()) {
|
|
5063
5218
|
if (!deepestExisting) deepestExisting = current;
|
|
5064
|
-
const gitStat = await
|
|
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
|
|
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
|
|
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((
|
|
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((
|
|
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((
|
|
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,
|
|
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((
|
|
6116
|
-
...enrichment.map((
|
|
6270
|
+
...primary.map((stat13) => stat13.turnIndex),
|
|
6271
|
+
...enrichment.map((stat13) => stat13.turnIndex)
|
|
6117
6272
|
);
|
|
6118
|
-
const primaryByIndex = new Map(primary.map((
|
|
6119
|
-
const enrichmentByIndex = new Map(enrichment.map((
|
|
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,
|
|
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((
|
|
6218
|
-
const wallClockDuration = durationsByTurn.get(
|
|
6219
|
-
return wallClockDuration !== void 0 ? { ...
|
|
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,
|
|
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
|
|
6270
|
-
if (
|
|
6271
|
-
delete
|
|
6424
|
+
for (const stat13 of turnStats) {
|
|
6425
|
+
if (stat13.tokenUsage && !hasAnyTokens(stat13.tokenUsage)) {
|
|
6426
|
+
delete stat13.tokenUsage;
|
|
6272
6427
|
}
|
|
6273
|
-
if ((
|
|
6274
|
-
delete
|
|
6428
|
+
if ((stat13.durationMs || 0) <= 0) {
|
|
6429
|
+
delete stat13.durationMs;
|
|
6275
6430
|
}
|
|
6276
|
-
if ((
|
|
6277
|
-
delete
|
|
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
|
-
(
|
|
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,
|
|
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/
|
|
6771
|
+
// ../provider-cursor/src/cursor/sdk-reader.ts
|
|
6617
6772
|
import { execFile as execFile2 } from "child_process";
|
|
6618
|
-
import { readdir as readdir7, readFile as
|
|
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
|
|
6775
|
+
import { join as join12 } from "path";
|
|
6621
6776
|
import { promisify as promisify2 } from "util";
|
|
6622
|
-
var CURSOR_PROJECTS_ROOT =
|
|
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
|
|
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
|
|
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 =
|
|
6977
|
-
const sdkStat = await
|
|
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 =
|
|
6982
|
-
const dbStat = await
|
|
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/
|
|
7016
|
-
var CURSOR_DIR =
|
|
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 =
|
|
7056
|
-
const dirStat = await
|
|
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(
|
|
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
|
|
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 =
|
|
7318
|
+
const candidatePath = join13(current, candidate);
|
|
7162
7319
|
if (!dirNames.has(candidate)) {
|
|
7163
7320
|
if (process.platform !== "win32") continue;
|
|
7164
|
-
const candidateStat = await
|
|
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
|
|
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 =
|
|
7252
|
-
const entryStat = await
|
|
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 =
|
|
7263
|
-
const innerStat = await
|
|
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
|
|
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 =
|
|
7285
|
-
const entryStat = await
|
|
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/
|
|
7309
|
-
import { readdir as readdir9, readFile as
|
|
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
|
|
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
|
-
|
|
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(
|
|
7347
|
-
|
|
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(
|
|
7383
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
7852
|
-
const toolDir =
|
|
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 =
|
|
7882
|
-
const st = await
|
|
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 =
|
|
7890
|
-
const nestedStat = await
|
|
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 =
|
|
7907
|
-
const st = await
|
|
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
|
|
8099
|
+
const content = await readFile14(path, "utf-8").catch(() => "");
|
|
7922
8100
|
const result = content.trim();
|
|
7923
8101
|
if (!result) continue;
|
|
7924
|
-
const st = await
|
|
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/
|
|
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/
|
|
8207
|
+
// ../provider-pi/src/pi/discover.ts
|
|
8030
8208
|
import { createReadStream as createReadStream4 } from "fs";
|
|
8031
|
-
import { readdir as readdir10, stat as
|
|
8032
|
-
import { basename as basename5, join as
|
|
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/
|
|
8036
|
-
import { readFile as
|
|
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
|
|
8216
|
+
import { join as join15 } from "path";
|
|
8039
8217
|
function getPiAgentDir() {
|
|
8040
|
-
return process.env.PI_CODING_AGENT_DIR ||
|
|
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 ||
|
|
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
|
|
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/
|
|
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 =
|
|
8088
|
-
const projectStat = await
|
|
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 =
|
|
8099
|
-
const fileStat = await
|
|
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(
|
|
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/
|
|
8217
|
-
|
|
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
|
|
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/
|
|
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/
|
|
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
|
|
8713
|
-
import { join as
|
|
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 =
|
|
8726
|
-
const content = await
|
|
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
|
|
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(
|
|
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
|
|
8918
|
-
stat as
|
|
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
|
|
8923
|
-
import { join as
|
|
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((
|
|
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
|
-
|
|
9372
|
+
resolve5(parsed.result || stdout);
|
|
9194
9373
|
} catch {
|
|
9195
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
9423
|
+
resolve5(typeof parsed.result === "string" ? parsed.result : stdout);
|
|
9245
9424
|
} catch {
|
|
9246
|
-
|
|
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((
|
|
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
|
-
|
|
10028
|
+
resolve5(null);
|
|
9850
10029
|
return;
|
|
9851
10030
|
}
|
|
9852
10031
|
const first = out.split(/\r?\n/).find((line) => line.trim());
|
|
9853
|
-
|
|
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
|
|
9866
|
-
import { join as
|
|
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 = [
|
|
10047
|
+
const dirs = [join22(baseDir, slug), resolve3("./vibe-replay", slug)];
|
|
9869
10048
|
for (const dir of dirs) {
|
|
9870
10049
|
try {
|
|
9871
|
-
const raw = await
|
|
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(
|
|
9881
|
-
const annPath =
|
|
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(
|
|
9886
|
-
const overlayPath =
|
|
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
|
|
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 =
|
|
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
|
|
10036
|
-
import { homedir as
|
|
10037
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
11240
|
+
resolved = join23(homedir18(), resolved.slice(2));
|
|
11048
11241
|
} else if (resolved === "~") {
|
|
11049
|
-
resolved =
|
|
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 =
|
|
11056
|
-
const memoryDir =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
11761
|
+
const dir = join24(baseDir, ARCHIVE_DIR);
|
|
11567
11762
|
await mkdir6(dir, { recursive: true });
|
|
11568
|
-
await writeFile7(
|
|
11763
|
+
await writeFile7(join24(dir, slug), "");
|
|
11569
11764
|
}
|
|
11570
11765
|
async function unarchiveSlug(baseDir, slug) {
|
|
11571
11766
|
try {
|
|
11572
|
-
await unlink2(
|
|
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 =
|
|
11787
|
+
const replayPath = join24(baseDir, entry, "replay.json");
|
|
11586
11788
|
try {
|
|
11587
|
-
const raw = await
|
|
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(
|
|
11794
|
+
gist = await loadSavedGistInfo(join24(baseDir, entry));
|
|
11593
11795
|
} catch {
|
|
11594
11796
|
}
|
|
11595
|
-
const cloudInfo = await loadSavedCloudInfo(
|
|
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
|
|
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 =
|
|
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 =
|
|
11876
|
+
let replayPath = join24(baseDir, slug, "replay.json");
|
|
11670
11877
|
try {
|
|
11671
|
-
await
|
|
11878
|
+
await stat12(replayPath);
|
|
11672
11879
|
} catch {
|
|
11673
|
-
const fallback =
|
|
11674
|
-
await
|
|
11880
|
+
const fallback = resolve4("./vibe-replay", slug, "replay.json");
|
|
11881
|
+
await stat12(fallback);
|
|
11675
11882
|
replayPath = fallback;
|
|
11676
11883
|
}
|
|
11677
|
-
const raw = await
|
|
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("~\\") ?
|
|
12223
|
+
const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ? join24(home, p.slice(2)) : p;
|
|
11850
12224
|
try {
|
|
11851
|
-
const s = await
|
|
12225
|
+
const s = await stat12(resolved);
|
|
11852
12226
|
projectExistsMap.set(p, s.isDirectory());
|
|
11853
12227
|
if (s.isDirectory()) {
|
|
11854
12228
|
try {
|
|
11855
|
-
await
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
12017
|
-
if (!cached?.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
12190
|
-
const persistedInsights = await
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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((
|
|
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
|
-
|
|
13119
|
+
resolve5();
|
|
12689
13120
|
});
|
|
12690
13121
|
});
|
|
12691
13122
|
});
|
|
12692
13123
|
});
|
|
12693
|
-
|
|
12694
|
-
const cached = await
|
|
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",
|
|
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
|
|
13135
|
+
await writeFileCache2(replaysCacheKey, sessions);
|
|
12703
13136
|
return c.json(sessions);
|
|
12704
|
-
}
|
|
12705
|
-
app.
|
|
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 =
|
|
12721
|
-
await writeFile7(
|
|
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.
|
|
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(
|
|
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
|
-
|
|
12760
|
-
const cached = await
|
|
13198
|
+
const getCachedSourceSessions = async (c) => {
|
|
13199
|
+
const cached = await readSourcesCatalogCache();
|
|
13200
|
+
const staleProviders = await getStaleSourceProviders(cached);
|
|
12761
13201
|
return c.json({
|
|
12762
|
-
sessions: cached?.
|
|
12763
|
-
cachedAt: cached?.
|
|
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/
|
|
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.
|
|
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
|
|
12772
|
-
if (!cached?.
|
|
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 =
|
|
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.
|
|
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.
|
|
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,
|
|
12809
|
-
const previous = await
|
|
13257
|
+
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir20());
|
|
13258
|
+
const previous = await readSourcesCatalogCache();
|
|
12810
13259
|
const result = await buildSourcesResult(
|
|
12811
13260
|
merged,
|
|
12812
13261
|
baseDir,
|
|
12813
|
-
|
|
12814
|
-
previous?.
|
|
13262
|
+
homedir20(),
|
|
13263
|
+
previous?.sessions || [],
|
|
12815
13264
|
cleanupPeriodDays
|
|
12816
13265
|
);
|
|
12817
|
-
await
|
|
13266
|
+
const catalog = await writeDiscoveredSourcesCatalog(result, previous);
|
|
12818
13267
|
enrichCursorStatsInBackground(merged, result);
|
|
12819
|
-
return c.json({
|
|
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
|
|
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,
|
|
12844
|
-
const previous = await
|
|
13300
|
+
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir20());
|
|
13301
|
+
const previous = await readSourcesCatalogCache();
|
|
12845
13302
|
const result = await buildSourcesResult(
|
|
12846
13303
|
merged,
|
|
12847
13304
|
baseDir,
|
|
12848
|
-
|
|
12849
|
-
previous?.
|
|
13305
|
+
homedir20(),
|
|
13306
|
+
previous?.sessions || [],
|
|
12850
13307
|
cleanupPeriodDays
|
|
12851
13308
|
);
|
|
12852
|
-
await
|
|
13309
|
+
const catalog = await writeDiscoveredSourcesCatalog(result, previous);
|
|
12853
13310
|
enrichCursorStatsInBackground(merged, result);
|
|
12854
13311
|
await stream.writeSSE({
|
|
12855
|
-
data: JSON.stringify({
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
13523
|
-
const originalContent = await
|
|
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 =
|
|
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 =
|
|
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 =
|
|
13562
|
-
const originalContent = await
|
|
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 =
|
|
14046
|
+
const targetDir = join24(baseDir, result.slug);
|
|
13577
14047
|
try {
|
|
13578
|
-
const svgPath =
|
|
13579
|
-
const mdPath =
|
|
13580
|
-
const gifPath =
|
|
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
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
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
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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((
|
|
14318
|
+
await new Promise((resolve5) => {
|
|
13849
14319
|
process.on("SIGINT", () => {
|
|
13850
14320
|
console.log(chalk.dim("\n Server stopped.\n"));
|
|
13851
|
-
|
|
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:
|
|
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:
|
|
13973
|
-
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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:
|
|
14341
|
-
const replayBaseDir = pathJoin(
|
|
14812
|
+
const { homedir: homedir21 } = await import("os");
|
|
14813
|
+
const replayBaseDir = pathJoin(homedir21(), ".vibe-replay");
|
|
14342
14814
|
void discoverAllSessions().then(async (sessions) => {
|
|
14343
|
-
await
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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(
|
|
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:
|
|
14935
|
-
const { join:
|
|
14936
|
-
const { homedir:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
15444
|
+
const jsonPath2 = join25(replayBaseDir, slug, "replay.json");
|
|
14971
15445
|
try {
|
|
14972
|
-
const raw = await
|
|
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:
|
|
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 =
|
|
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:
|
|
15027
|
-
const replayBaseDir = pathJoin(
|
|
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 ${
|
|
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", {
|