@runtimescope/collector 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GENCCHYK.js → chunk-MM44DN7Y.js} +400 -61
- package/dist/chunk-MM44DN7Y.js.map +1 -0
- package/dist/index.d.ts +70 -12
- package/dist/index.js +8 -72
- package/dist/index.js.map +1 -1
- package/dist/standalone.js +15 -1
- package/dist/standalone.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-GENCCHYK.js.map +0 -1
|
@@ -50,6 +50,12 @@ var RingBuffer = class {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/store.ts
|
|
53
|
+
function matchesSessionFilter(eventSessionId, filterSessionId) {
|
|
54
|
+
if (filterSessionId.includes(",")) {
|
|
55
|
+
return filterSessionId.split(",").includes(eventSessionId);
|
|
56
|
+
}
|
|
57
|
+
return eventSessionId === filterSessionId;
|
|
58
|
+
}
|
|
53
59
|
var EventStore = class {
|
|
54
60
|
buffer;
|
|
55
61
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -110,7 +116,7 @@ var EventStore = class {
|
|
|
110
116
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
111
117
|
return this.buffer.query((e) => {
|
|
112
118
|
if (e.eventType !== "network") return false;
|
|
113
|
-
if (filter.sessionId && e.sessionId
|
|
119
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
114
120
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
115
121
|
const ne = e;
|
|
116
122
|
if (ne.timestamp < since) return false;
|
|
@@ -125,7 +131,7 @@ var EventStore = class {
|
|
|
125
131
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
126
132
|
return this.buffer.query((e) => {
|
|
127
133
|
if (e.eventType !== "console") return false;
|
|
128
|
-
if (filter.sessionId && e.sessionId
|
|
134
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
129
135
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
130
136
|
const ce = e;
|
|
131
137
|
if (ce.timestamp < since) return false;
|
|
@@ -147,7 +153,7 @@ var EventStore = class {
|
|
|
147
153
|
const typeSet = filter.eventTypes ? new Set(filter.eventTypes) : null;
|
|
148
154
|
return this.buffer.toArray().filter((e) => {
|
|
149
155
|
if (e.timestamp < since) return false;
|
|
150
|
-
if (filter.sessionId && e.sessionId
|
|
156
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
151
157
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
152
158
|
if (typeSet && !typeSet.has(e.eventType)) return false;
|
|
153
159
|
return true;
|
|
@@ -168,16 +174,28 @@ var EventStore = class {
|
|
|
168
174
|
getSessionIdsForProjectId(projectId) {
|
|
169
175
|
return Array.from(this.sessions.values()).filter((s) => s.projectId === projectId).map((s) => s.sessionId);
|
|
170
176
|
}
|
|
177
|
+
/** Re-tag all sessions with oldProjectId to use newProjectId. */
|
|
178
|
+
retagSessions(oldProjectId, newProjectId) {
|
|
179
|
+
for (const [, session] of this.sessions) {
|
|
180
|
+
if (session.projectId === oldProjectId) {
|
|
181
|
+
session.projectId = newProjectId;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
171
185
|
/** Check if an event belongs to the given projectId (via its session). */
|
|
172
186
|
matchesProjectId(sessionId, projectId) {
|
|
173
187
|
const session = this.sessions.get(sessionId);
|
|
174
|
-
|
|
188
|
+
if (!session?.projectId) return false;
|
|
189
|
+
if (projectId.includes(",")) {
|
|
190
|
+
return projectId.split(",").includes(session.projectId);
|
|
191
|
+
}
|
|
192
|
+
return session.projectId === projectId;
|
|
175
193
|
}
|
|
176
194
|
getStateEvents(filter = {}) {
|
|
177
195
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
178
196
|
return this.buffer.query((e) => {
|
|
179
197
|
if (e.eventType !== "state") return false;
|
|
180
|
-
if (filter.sessionId && e.sessionId
|
|
198
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
181
199
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
182
200
|
const se = e;
|
|
183
201
|
if (se.timestamp < since) return false;
|
|
@@ -189,7 +207,7 @@ var EventStore = class {
|
|
|
189
207
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
190
208
|
return this.buffer.query((e) => {
|
|
191
209
|
if (e.eventType !== "render") return false;
|
|
192
|
-
if (filter.sessionId && e.sessionId
|
|
210
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
193
211
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
194
212
|
const re = e;
|
|
195
213
|
if (re.timestamp < since) return false;
|
|
@@ -206,7 +224,7 @@ var EventStore = class {
|
|
|
206
224
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
207
225
|
return this.buffer.query((e) => {
|
|
208
226
|
if (e.eventType !== "performance") return false;
|
|
209
|
-
if (filter.sessionId && e.sessionId
|
|
227
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
210
228
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
211
229
|
const pe = e;
|
|
212
230
|
if (pe.timestamp < since) return false;
|
|
@@ -218,7 +236,7 @@ var EventStore = class {
|
|
|
218
236
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
219
237
|
return this.buffer.query((e) => {
|
|
220
238
|
if (e.eventType !== "database") return false;
|
|
221
|
-
if (filter.sessionId && e.sessionId
|
|
239
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
222
240
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
223
241
|
const de = e;
|
|
224
242
|
if (de.timestamp < since) return false;
|
|
@@ -240,7 +258,7 @@ var EventStore = class {
|
|
|
240
258
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
241
259
|
return this.buffer.query((e) => {
|
|
242
260
|
if (e.eventType !== "custom") return false;
|
|
243
|
-
if (filter.sessionId && e.sessionId
|
|
261
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
244
262
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
245
263
|
const ce = e;
|
|
246
264
|
if (ce.timestamp < since) return false;
|
|
@@ -252,7 +270,7 @@ var EventStore = class {
|
|
|
252
270
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
253
271
|
return this.buffer.query((e) => {
|
|
254
272
|
if (e.eventType !== "ui") return false;
|
|
255
|
-
if (filter.sessionId && e.sessionId
|
|
273
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
256
274
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
257
275
|
const ue = e;
|
|
258
276
|
if (ue.timestamp < since) return false;
|
|
@@ -267,7 +285,7 @@ var EventStore = class {
|
|
|
267
285
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
268
286
|
const results = this.buffer.query((e) => {
|
|
269
287
|
if (e.eventType !== eventType) return false;
|
|
270
|
-
if (filter.sessionId && e.sessionId
|
|
288
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
271
289
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
272
290
|
if (e.timestamp < since) return false;
|
|
273
291
|
if (filter.url) {
|
|
@@ -282,7 +300,7 @@ var EventStore = class {
|
|
|
282
300
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
283
301
|
return this.buffer.query((e) => {
|
|
284
302
|
if (e.eventType !== eventType) return false;
|
|
285
|
-
if (filter.sessionId && e.sessionId
|
|
303
|
+
if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
|
|
286
304
|
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
287
305
|
if (e.timestamp < since) return false;
|
|
288
306
|
if (filter.url) {
|
|
@@ -349,6 +367,18 @@ function getOrCreateProjectId(projectManager, appName) {
|
|
|
349
367
|
projectManager.setProjectIdForApp(appName, projectId);
|
|
350
368
|
return projectId;
|
|
351
369
|
}
|
|
370
|
+
function resolveProjectId(projectManager, appName, pmStore) {
|
|
371
|
+
const fromIndex = projectManager.resolveAppProjectId(appName);
|
|
372
|
+
if (fromIndex) return fromIndex;
|
|
373
|
+
if (pmStore) {
|
|
374
|
+
const fromPm = pmStore.findProjectIdByApp(appName);
|
|
375
|
+
if (fromPm) {
|
|
376
|
+
projectManager.setProjectIdForApp(appName, fromPm);
|
|
377
|
+
return fromPm;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return getOrCreateProjectId(projectManager, appName);
|
|
381
|
+
}
|
|
352
382
|
|
|
353
383
|
// src/sqlite-store.ts
|
|
354
384
|
import { renameSync, existsSync } from "fs";
|
|
@@ -852,6 +882,7 @@ var CollectorServer = class {
|
|
|
852
882
|
pruneTimer = null;
|
|
853
883
|
heartbeatTimer = null;
|
|
854
884
|
tlsConfig = null;
|
|
885
|
+
pmStore = null;
|
|
855
886
|
constructor(options = {}) {
|
|
856
887
|
this.store = new EventStore(options.bufferSize ?? 1e4);
|
|
857
888
|
this.projectManager = options.projectManager ?? null;
|
|
@@ -887,6 +918,10 @@ var CollectorServer = class {
|
|
|
887
918
|
getRateLimiter() {
|
|
888
919
|
return this.rateLimiter;
|
|
889
920
|
}
|
|
921
|
+
/** Set the PmStore for project ID resolution (called after construction when PmStore is available). */
|
|
922
|
+
setPmStore(pmStore) {
|
|
923
|
+
this.pmStore = pmStore;
|
|
924
|
+
}
|
|
890
925
|
onConnect(cb) {
|
|
891
926
|
this.connectCallbacks.push(cb);
|
|
892
927
|
}
|
|
@@ -1071,7 +1106,7 @@ var CollectorServer = class {
|
|
|
1071
1106
|
this.pendingHandshakes.delete(ws);
|
|
1072
1107
|
}
|
|
1073
1108
|
const projectName = payload.appName;
|
|
1074
|
-
const projectId = payload.projectId ?? (this.projectManager ?
|
|
1109
|
+
const projectId = payload.projectId ?? (this.projectManager ? resolveProjectId(this.projectManager, projectName, this.pmStore) : void 0);
|
|
1075
1110
|
this.clients.set(ws, {
|
|
1076
1111
|
sessionId: payload.sessionId,
|
|
1077
1112
|
projectName,
|
|
@@ -1244,6 +1279,7 @@ var DEFAULT_GLOBAL_CONFIG = {
|
|
|
1244
1279
|
};
|
|
1245
1280
|
var ProjectManager = class {
|
|
1246
1281
|
baseDir;
|
|
1282
|
+
appProjectIndex = /* @__PURE__ */ new Map();
|
|
1247
1283
|
constructor(baseDir) {
|
|
1248
1284
|
this.baseDir = baseDir ?? join(homedir(), ".runtimescope");
|
|
1249
1285
|
}
|
|
@@ -1356,6 +1392,60 @@ var ProjectManager = class {
|
|
|
1356
1392
|
}
|
|
1357
1393
|
return null;
|
|
1358
1394
|
}
|
|
1395
|
+
// --- Reverse index: appName → projectId ---
|
|
1396
|
+
/**
|
|
1397
|
+
* Build reverse index: appName -> projectId.
|
|
1398
|
+
* Scans all project configs, PM projects with runtimeApps + runtimeProjectId,
|
|
1399
|
+
* and project-level .runtimescope/config.json files from PM project paths.
|
|
1400
|
+
*/
|
|
1401
|
+
rebuildAppIndex(pmStore) {
|
|
1402
|
+
this.appProjectIndex.clear();
|
|
1403
|
+
for (const name of this.listProjects()) {
|
|
1404
|
+
const config = this.getProjectConfig(name);
|
|
1405
|
+
if (config?.projectId) {
|
|
1406
|
+
this.appProjectIndex.set(name.toLowerCase(), config.projectId);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (pmStore) {
|
|
1410
|
+
for (const p of pmStore.listProjects()) {
|
|
1411
|
+
if (p.runtimeProjectId && p.runtimeApps) {
|
|
1412
|
+
for (const app of p.runtimeApps) {
|
|
1413
|
+
this.appProjectIndex.set(app.toLowerCase(), p.runtimeProjectId);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
if (pmStore) {
|
|
1419
|
+
for (const p of pmStore.listProjects()) {
|
|
1420
|
+
if (p.path) {
|
|
1421
|
+
try {
|
|
1422
|
+
const configPath = join(p.path, ".runtimescope", "config.json");
|
|
1423
|
+
if (existsSync2(configPath)) {
|
|
1424
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
1425
|
+
const config = JSON.parse(content);
|
|
1426
|
+
if (config.projectId) {
|
|
1427
|
+
if (config.appName) {
|
|
1428
|
+
this.appProjectIndex.set(config.appName.toLowerCase(), config.projectId);
|
|
1429
|
+
}
|
|
1430
|
+
if (Array.isArray(config.sdks)) {
|
|
1431
|
+
for (const sdk of config.sdks) {
|
|
1432
|
+
if (sdk.appName) {
|
|
1433
|
+
this.appProjectIndex.set(sdk.appName.toLowerCase(), config.projectId);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
} catch {
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
/** O(1) lookup from the cached index. */
|
|
1446
|
+
resolveAppProjectId(appName) {
|
|
1447
|
+
return this.appProjectIndex.get(appName.toLowerCase()) ?? null;
|
|
1448
|
+
}
|
|
1359
1449
|
// --- Environment variable resolution ---
|
|
1360
1450
|
resolveEnvVars(value) {
|
|
1361
1451
|
return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
@@ -1409,6 +1499,129 @@ var ProjectManager = class {
|
|
|
1409
1499
|
}
|
|
1410
1500
|
};
|
|
1411
1501
|
|
|
1502
|
+
// src/project-config.ts
|
|
1503
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1504
|
+
import { join as join2 } from "path";
|
|
1505
|
+
var DEFAULT_CAPTURE = {
|
|
1506
|
+
network: true,
|
|
1507
|
+
console: true,
|
|
1508
|
+
xhr: true,
|
|
1509
|
+
body: false,
|
|
1510
|
+
performance: true,
|
|
1511
|
+
renders: true,
|
|
1512
|
+
navigation: true,
|
|
1513
|
+
clicks: false,
|
|
1514
|
+
http: false,
|
|
1515
|
+
errors: true,
|
|
1516
|
+
stackTraces: false
|
|
1517
|
+
};
|
|
1518
|
+
function readProjectConfig(projectDir) {
|
|
1519
|
+
const configPath = join2(projectDir, ".runtimescope", "config.json");
|
|
1520
|
+
if (!existsSync3(configPath)) return null;
|
|
1521
|
+
try {
|
|
1522
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
1523
|
+
return JSON.parse(content);
|
|
1524
|
+
} catch {
|
|
1525
|
+
return null;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function writeProjectConfig(projectDir, config) {
|
|
1529
|
+
const dir = join2(projectDir, ".runtimescope");
|
|
1530
|
+
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
1531
|
+
writeFileSync2(join2(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1532
|
+
}
|
|
1533
|
+
function scaffoldProjectConfig(projectDir, opts) {
|
|
1534
|
+
const existing = readProjectConfig(projectDir);
|
|
1535
|
+
if (existing) {
|
|
1536
|
+
if (opts.sdkType) {
|
|
1537
|
+
const alreadyHas = existing.sdks.some((s) => s.type === opts.sdkType);
|
|
1538
|
+
if (!alreadyHas) {
|
|
1539
|
+
existing.sdks.push({
|
|
1540
|
+
type: opts.sdkType,
|
|
1541
|
+
framework: opts.framework,
|
|
1542
|
+
appName: opts.appName !== existing.appName ? opts.appName : void 0
|
|
1543
|
+
});
|
|
1544
|
+
writeProjectConfig(projectDir, existing);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return existing;
|
|
1548
|
+
}
|
|
1549
|
+
const projectId = generateProjectId();
|
|
1550
|
+
const httpPort = process.env.RUNTIMESCOPE_HTTP_PORT ?? "9091";
|
|
1551
|
+
const dsn = `runtimescope://${projectId}@localhost:${httpPort}/${opts.appName}`;
|
|
1552
|
+
const config = {
|
|
1553
|
+
projectId,
|
|
1554
|
+
dsn,
|
|
1555
|
+
appName: opts.appName,
|
|
1556
|
+
description: opts.description,
|
|
1557
|
+
sdks: opts.sdkType ? [{ type: opts.sdkType, framework: opts.framework }] : [],
|
|
1558
|
+
capture: { ...DEFAULT_CAPTURE },
|
|
1559
|
+
category: opts.category
|
|
1560
|
+
};
|
|
1561
|
+
writeProjectConfig(projectDir, config);
|
|
1562
|
+
const gitignorePath = join2(projectDir, ".runtimescope", ".gitignore");
|
|
1563
|
+
if (!existsSync3(gitignorePath)) {
|
|
1564
|
+
writeFileSync2(gitignorePath, "# Keep config.json committed, ignore local state\n*.log\n*.db\n.env\n", "utf-8");
|
|
1565
|
+
}
|
|
1566
|
+
return config;
|
|
1567
|
+
}
|
|
1568
|
+
function resolveProjectAppNames(config) {
|
|
1569
|
+
const names = /* @__PURE__ */ new Set([config.appName]);
|
|
1570
|
+
for (const sdk of config.sdks) {
|
|
1571
|
+
if (sdk.appName) names.add(sdk.appName);
|
|
1572
|
+
}
|
|
1573
|
+
return Array.from(names);
|
|
1574
|
+
}
|
|
1575
|
+
function migrateProjectIds(projectManager, pmStore) {
|
|
1576
|
+
const result = { unified: 0, skipped: 0, details: [] };
|
|
1577
|
+
if (!pmStore) return result;
|
|
1578
|
+
for (const project of pmStore.listProjects()) {
|
|
1579
|
+
if (!project.runtimeApps || project.runtimeApps.length < 2) continue;
|
|
1580
|
+
if (!project.path) {
|
|
1581
|
+
result.skipped++;
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
let canonicalId = null;
|
|
1585
|
+
try {
|
|
1586
|
+
const configPath = join2(project.path, ".runtimescope", "config.json");
|
|
1587
|
+
if (existsSync3(configPath)) {
|
|
1588
|
+
const config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1589
|
+
if (config.projectId) canonicalId = config.projectId;
|
|
1590
|
+
}
|
|
1591
|
+
} catch {
|
|
1592
|
+
}
|
|
1593
|
+
if (!canonicalId && project.runtimeProjectId) {
|
|
1594
|
+
canonicalId = project.runtimeProjectId;
|
|
1595
|
+
}
|
|
1596
|
+
if (!canonicalId) {
|
|
1597
|
+
for (const appName of project.runtimeApps) {
|
|
1598
|
+
const existing = projectManager.getProjectIdForApp(appName);
|
|
1599
|
+
if (existing) {
|
|
1600
|
+
canonicalId = existing;
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (!canonicalId) {
|
|
1606
|
+
result.skipped++;
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
for (const appName of project.runtimeApps) {
|
|
1610
|
+
const current = projectManager.getProjectIdForApp(appName);
|
|
1611
|
+
if (current !== canonicalId) {
|
|
1612
|
+
projectManager.setProjectIdForApp(appName, canonicalId);
|
|
1613
|
+
result.details.push(`Unified ${appName}: ${current ?? "none"} \u2192 ${canonicalId}`);
|
|
1614
|
+
result.unified++;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (project.runtimeProjectId !== canonicalId) {
|
|
1618
|
+
pmStore.updateProject(project.id, { runtimeProjectId: canonicalId });
|
|
1619
|
+
result.details.push(`PM project ${project.name}: runtimeProjectId \u2192 ${canonicalId}`);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return result;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1412
1625
|
// src/auth.ts
|
|
1413
1626
|
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
1414
1627
|
var AuthManager = class {
|
|
@@ -1573,7 +1786,7 @@ var Redactor = class {
|
|
|
1573
1786
|
// src/platform.ts
|
|
1574
1787
|
import { execFileSync, execSync } from "child_process";
|
|
1575
1788
|
import { readlinkSync, readdirSync as readdirSync2 } from "fs";
|
|
1576
|
-
import { join as
|
|
1789
|
+
import { join as join3 } from "path";
|
|
1577
1790
|
var IS_WIN = process.platform === "win32";
|
|
1578
1791
|
var IS_LINUX = process.platform === "linux";
|
|
1579
1792
|
function runFile(cmd, args, timeoutMs = 5e3) {
|
|
@@ -1692,7 +1905,7 @@ function findPidsInDir_linux(dir) {
|
|
|
1692
1905
|
const pid = parseInt(entry, 10);
|
|
1693
1906
|
if (isNaN(pid) || pid <= 1) continue;
|
|
1694
1907
|
try {
|
|
1695
|
-
const cwd = readlinkSync(
|
|
1908
|
+
const cwd = readlinkSync(join3("/proc", entry, "cwd"));
|
|
1696
1909
|
if (cwd.startsWith(dir)) pids.push(pid);
|
|
1697
1910
|
} catch {
|
|
1698
1911
|
}
|
|
@@ -1896,15 +2109,15 @@ var SessionManager = class {
|
|
|
1896
2109
|
// src/http-server.ts
|
|
1897
2110
|
import { createServer } from "http";
|
|
1898
2111
|
import { createServer as createHttpsServer2 } from "https";
|
|
1899
|
-
import { readFileSync as
|
|
2112
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
1900
2113
|
import { resolve, dirname } from "path";
|
|
1901
2114
|
import { fileURLToPath } from "url";
|
|
1902
2115
|
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
1903
2116
|
|
|
1904
2117
|
// src/pm/pm-routes.ts
|
|
1905
2118
|
import { readdir, readFile, writeFile, unlink, mkdir } from "fs/promises";
|
|
1906
|
-
import { existsSync as
|
|
1907
|
-
import { join as
|
|
2119
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2120
|
+
import { join as join4 } from "path";
|
|
1908
2121
|
import { homedir as homedir2 } from "os";
|
|
1909
2122
|
import { spawn, execFileSync as execFileSync2 } from "child_process";
|
|
1910
2123
|
var LOG_RING_SIZE = 500;
|
|
@@ -2041,6 +2254,16 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2041
2254
|
helpers.json(res, { error: err.message }, 400);
|
|
2042
2255
|
}
|
|
2043
2256
|
});
|
|
2257
|
+
route("DELETE", "/api/pm/projects/:id", (_req, res, params) => {
|
|
2258
|
+
const id = params.get("id");
|
|
2259
|
+
const project = pmStore.getProject(id);
|
|
2260
|
+
if (!project) {
|
|
2261
|
+
helpers.json(res, { error: "Project not found" }, 404);
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
pmStore.deleteProject(id);
|
|
2265
|
+
helpers.json(res, { ok: true, deleted: project.name });
|
|
2266
|
+
});
|
|
2044
2267
|
route("GET", "/api/pm/tasks", (_req, res, params) => {
|
|
2045
2268
|
const projectId = params.get("project_id") ?? void 0;
|
|
2046
2269
|
const status = params.get("status") ?? void 0;
|
|
@@ -2216,13 +2439,13 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2216
2439
|
helpers.json(res, { data: [], count: 0 });
|
|
2217
2440
|
return;
|
|
2218
2441
|
}
|
|
2219
|
-
const memoryDir =
|
|
2442
|
+
const memoryDir = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
|
|
2220
2443
|
try {
|
|
2221
2444
|
const files = await readdir(memoryDir);
|
|
2222
2445
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
2223
2446
|
const result = await Promise.all(
|
|
2224
2447
|
mdFiles.map(async (filename) => {
|
|
2225
|
-
const content = await readFile(
|
|
2448
|
+
const content = await readFile(join4(memoryDir, filename), "utf-8");
|
|
2226
2449
|
return { filename, content, sizeBytes: Buffer.byteLength(content) };
|
|
2227
2450
|
})
|
|
2228
2451
|
);
|
|
@@ -2239,7 +2462,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2239
2462
|
helpers.json(res, { error: "Project not found" }, 404);
|
|
2240
2463
|
return;
|
|
2241
2464
|
}
|
|
2242
|
-
const filePath =
|
|
2465
|
+
const filePath = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
|
|
2243
2466
|
try {
|
|
2244
2467
|
const content = await readFile(filePath, "utf-8");
|
|
2245
2468
|
helpers.json(res, { filename, content, sizeBytes: Buffer.byteLength(content) });
|
|
@@ -2262,9 +2485,9 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2262
2485
|
}
|
|
2263
2486
|
try {
|
|
2264
2487
|
const { content } = JSON.parse(body);
|
|
2265
|
-
const memoryDir =
|
|
2488
|
+
const memoryDir = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
|
|
2266
2489
|
await mkdir(memoryDir, { recursive: true });
|
|
2267
|
-
await writeFile(
|
|
2490
|
+
await writeFile(join4(memoryDir, filename), content, "utf-8");
|
|
2268
2491
|
helpers.json(res, { ok: true });
|
|
2269
2492
|
} catch (err) {
|
|
2270
2493
|
helpers.json(res, { error: err.message }, 500);
|
|
@@ -2278,7 +2501,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2278
2501
|
helpers.json(res, { error: "Project not found" }, 404);
|
|
2279
2502
|
return;
|
|
2280
2503
|
}
|
|
2281
|
-
const filePath =
|
|
2504
|
+
const filePath = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
|
|
2282
2505
|
try {
|
|
2283
2506
|
await unlink(filePath);
|
|
2284
2507
|
helpers.json(res, { ok: true });
|
|
@@ -2338,7 +2561,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2338
2561
|
const { content } = JSON.parse(body);
|
|
2339
2562
|
const paths = getRulesPaths(project.claudeProjectKey, project.path);
|
|
2340
2563
|
const filePath = paths[scope];
|
|
2341
|
-
const dir =
|
|
2564
|
+
const dir = join4(filePath, "..");
|
|
2342
2565
|
await mkdir(dir, { recursive: true });
|
|
2343
2566
|
await writeFile(filePath, content, "utf-8");
|
|
2344
2567
|
helpers.json(res, { ok: true });
|
|
@@ -2358,7 +2581,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2358
2581
|
return;
|
|
2359
2582
|
}
|
|
2360
2583
|
try {
|
|
2361
|
-
const pkgPath =
|
|
2584
|
+
const pkgPath = join4(project.path, "package.json");
|
|
2362
2585
|
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
2363
2586
|
const scripts = pkg.scripts ?? {};
|
|
2364
2587
|
const recommended = ["dev", "start", "serve"].find((s) => s in scripts) ?? null;
|
|
@@ -2774,6 +2997,71 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2774
2997
|
res.end(csv);
|
|
2775
2998
|
}
|
|
2776
2999
|
});
|
|
3000
|
+
route("GET", "/api/pm/capex-all", (_req, res, params) => {
|
|
3001
|
+
const category = params.get("category") ?? void 0;
|
|
3002
|
+
const projects = pmStore.listProjects();
|
|
3003
|
+
const filteredProjects = category ? projects.filter((p) => p.category === category) : projects;
|
|
3004
|
+
const allEntries = [];
|
|
3005
|
+
let totalCost = 0;
|
|
3006
|
+
let totalCapitalizable = 0;
|
|
3007
|
+
let totalExpensed = 0;
|
|
3008
|
+
let totalMinutes = 0;
|
|
3009
|
+
let totalConfirmed = 0;
|
|
3010
|
+
let totalUnconfirmed = 0;
|
|
3011
|
+
const byProject = [];
|
|
3012
|
+
for (const project of filteredProjects) {
|
|
3013
|
+
const entries = pmStore.listCapexEntries(project.id);
|
|
3014
|
+
let projCost = 0;
|
|
3015
|
+
let projCap = 0;
|
|
3016
|
+
let projExp = 0;
|
|
3017
|
+
let projMins = 0;
|
|
3018
|
+
let projConfirmed = 0;
|
|
3019
|
+
for (const e of entries) {
|
|
3020
|
+
allEntries.push({ ...e, projectName: project.name });
|
|
3021
|
+
projCost += e.adjustedCostMicrodollars;
|
|
3022
|
+
projMins += e.activeMinutes;
|
|
3023
|
+
if (e.classification === "capitalizable") projCap += e.adjustedCostMicrodollars;
|
|
3024
|
+
else projExp += e.adjustedCostMicrodollars;
|
|
3025
|
+
if (e.confirmed) projConfirmed++;
|
|
3026
|
+
}
|
|
3027
|
+
if (entries.length > 0) {
|
|
3028
|
+
byProject.push({
|
|
3029
|
+
projectId: project.id,
|
|
3030
|
+
projectName: project.name,
|
|
3031
|
+
category: project.category,
|
|
3032
|
+
totalCost: projCost,
|
|
3033
|
+
capitalizable: projCap,
|
|
3034
|
+
expensed: projExp,
|
|
3035
|
+
activeMinutes: projMins,
|
|
3036
|
+
activeHours: +(projMins / 60).toFixed(2),
|
|
3037
|
+
confirmed: projConfirmed,
|
|
3038
|
+
total: entries.length
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
totalCost += projCost;
|
|
3042
|
+
totalCapitalizable += projCap;
|
|
3043
|
+
totalExpensed += projExp;
|
|
3044
|
+
totalMinutes += projMins;
|
|
3045
|
+
totalConfirmed += projConfirmed;
|
|
3046
|
+
totalUnconfirmed += entries.length - projConfirmed;
|
|
3047
|
+
}
|
|
3048
|
+
helpers.json(res, {
|
|
3049
|
+
data: {
|
|
3050
|
+
summary: {
|
|
3051
|
+
totalCost,
|
|
3052
|
+
capitalizable: totalCapitalizable,
|
|
3053
|
+
expensed: totalExpensed,
|
|
3054
|
+
activeMinutes: totalMinutes,
|
|
3055
|
+
activeHours: +(totalMinutes / 60).toFixed(2),
|
|
3056
|
+
confirmed: totalConfirmed,
|
|
3057
|
+
unconfirmed: totalUnconfirmed,
|
|
3058
|
+
projectCount: byProject.length
|
|
3059
|
+
},
|
|
3060
|
+
byProject,
|
|
3061
|
+
entries: allEntries.sort((a, b) => b.createdAt - a.createdAt)
|
|
3062
|
+
}
|
|
3063
|
+
});
|
|
3064
|
+
});
|
|
2777
3065
|
route("GET", "/api/pm/capex-report-all", async (_req, res, params) => {
|
|
2778
3066
|
const startDate = params.get("start_date") ?? void 0;
|
|
2779
3067
|
const endDate = params.get("end_date") ?? void 0;
|
|
@@ -2819,9 +3107,9 @@ function sanitizeFilename(name) {
|
|
|
2819
3107
|
function getRulesPaths(claudeProjectKey, projectPath) {
|
|
2820
3108
|
const home = homedir2();
|
|
2821
3109
|
return {
|
|
2822
|
-
global:
|
|
2823
|
-
project: claudeProjectKey ?
|
|
2824
|
-
local: projectPath ?
|
|
3110
|
+
global: join4(home, ".claude", "CLAUDE.md"),
|
|
3111
|
+
project: claudeProjectKey ? join4(home, ".claude", "projects", claudeProjectKey, "CLAUDE.md") : join4(projectPath ?? "", ".claude", "CLAUDE.md"),
|
|
3112
|
+
local: projectPath ? join4(projectPath, "CLAUDE.md") : join4(home, "CLAUDE.md")
|
|
2825
3113
|
};
|
|
2826
3114
|
}
|
|
2827
3115
|
function execGit(args, cwd) {
|
|
@@ -2866,7 +3154,7 @@ function parseGitStatus(porcelain) {
|
|
|
2866
3154
|
}
|
|
2867
3155
|
async function readRuleFile(filePath) {
|
|
2868
3156
|
try {
|
|
2869
|
-
if (
|
|
3157
|
+
if (existsSync4(filePath)) {
|
|
2870
3158
|
const content = await readFile(filePath, "utf-8");
|
|
2871
3159
|
return { path: filePath, content, exists: true };
|
|
2872
3160
|
}
|
|
@@ -3111,7 +3399,7 @@ var HttpServer = class {
|
|
|
3111
3399
|
return;
|
|
3112
3400
|
}
|
|
3113
3401
|
const payload = parsed;
|
|
3114
|
-
const projectId = typeof payload.projectId === "string" ? payload.projectId : payload.appName && this.projectManager ?
|
|
3402
|
+
const projectId = typeof payload.projectId === "string" ? payload.projectId : payload.appName && this.projectManager ? resolveProjectId(this.projectManager, payload.appName, this.pmStore) : void 0;
|
|
3115
3403
|
if (!payload.sessionId || !Array.isArray(payload.events) || payload.events.length === 0) {
|
|
3116
3404
|
this.json(res, {
|
|
3117
3405
|
error: "Required: sessionId (string), events (non-empty array)",
|
|
@@ -3196,7 +3484,7 @@ var HttpServer = class {
|
|
|
3196
3484
|
// npm installed
|
|
3197
3485
|
];
|
|
3198
3486
|
for (const p of candidates) {
|
|
3199
|
-
if (
|
|
3487
|
+
if (existsSync5(p)) {
|
|
3200
3488
|
this.sdkBundlePath = p;
|
|
3201
3489
|
return p;
|
|
3202
3490
|
}
|
|
@@ -3344,7 +3632,7 @@ var HttpServer = class {
|
|
|
3344
3632
|
if (req.method === "GET" && url.pathname === "/runtimescope.js") {
|
|
3345
3633
|
const sdkPath = this.resolveSdkPath();
|
|
3346
3634
|
if (sdkPath) {
|
|
3347
|
-
const bundle =
|
|
3635
|
+
const bundle = readFileSync4(sdkPath, "utf-8");
|
|
3348
3636
|
res.writeHead(200, {
|
|
3349
3637
|
"Content-Type": "application/javascript",
|
|
3350
3638
|
"Cache-Control": "no-cache"
|
|
@@ -3358,14 +3646,12 @@ var HttpServer = class {
|
|
|
3358
3646
|
}
|
|
3359
3647
|
if (req.method === "GET" && url.pathname === "/snippet") {
|
|
3360
3648
|
const appName = (url.searchParams.get("app") || "my-app").replace(/[^a-zA-Z0-9_-]/g, "");
|
|
3361
|
-
const
|
|
3649
|
+
const projectId = url.searchParams.get("project_id") || "proj_xxx";
|
|
3650
|
+
const dsn = `runtimescope://${projectId}@localhost:${this.activePort}/${appName}`;
|
|
3362
3651
|
const snippet = `<!-- RuntimeScope SDK \u2014 paste before </body> -->
|
|
3363
3652
|
<script src="http://localhost:${this.activePort}/runtimescope.js"></script>
|
|
3364
3653
|
<script>
|
|
3365
|
-
RuntimeScope.init({
|
|
3366
|
-
appName: '${appName}',
|
|
3367
|
-
endpoint: 'ws://localhost:${wsPort}',
|
|
3368
|
-
});
|
|
3654
|
+
RuntimeScope.init({ dsn: '${dsn}' });
|
|
3369
3655
|
</script>`;
|
|
3370
3656
|
res.writeHead(200, {
|
|
3371
3657
|
"Content-Type": "text/plain"
|
|
@@ -3595,6 +3881,13 @@ var PmStore = class {
|
|
|
3595
3881
|
CREATE INDEX IF NOT EXISTS idx_pm_capex_project ON pm_capex_entries(project_id);
|
|
3596
3882
|
CREATE INDEX IF NOT EXISTS idx_pm_capex_period ON pm_capex_entries(period);
|
|
3597
3883
|
CREATE INDEX IF NOT EXISTS idx_pm_capex_confirmed ON pm_capex_entries(confirmed);
|
|
3884
|
+
|
|
3885
|
+
CREATE TABLE IF NOT EXISTS pm_deleted_projects (
|
|
3886
|
+
path TEXT PRIMARY KEY,
|
|
3887
|
+
name TEXT,
|
|
3888
|
+
deleted_at INTEGER NOT NULL
|
|
3889
|
+
);
|
|
3890
|
+
CREATE INDEX IF NOT EXISTS idx_deleted_path ON pm_deleted_projects(path);
|
|
3598
3891
|
`);
|
|
3599
3892
|
}
|
|
3600
3893
|
runMigrations() {
|
|
@@ -3762,6 +4055,11 @@ var PmStore = class {
|
|
|
3762
4055
|
this.updateProject(match.id, updates);
|
|
3763
4056
|
return match.id;
|
|
3764
4057
|
}
|
|
4058
|
+
/** Find a project's runtimeProjectId by checking if appName appears in any project's runtimeApps. */
|
|
4059
|
+
findProjectIdByApp(appName) {
|
|
4060
|
+
const row = this.db.prepare(`SELECT runtime_project_id FROM pm_projects WHERE runtime_apps LIKE ? AND runtime_project_id IS NOT NULL LIMIT 1`).get(`%"${appName}"%`);
|
|
4061
|
+
return row?.runtime_project_id ?? null;
|
|
4062
|
+
}
|
|
3765
4063
|
listCategories() {
|
|
3766
4064
|
const rows = this.db.prepare("SELECT DISTINCT category FROM pm_projects WHERE category IS NOT NULL ORDER BY category ASC").all();
|
|
3767
4065
|
return rows.map((r) => r.category);
|
|
@@ -4521,6 +4819,38 @@ var PmStore = class {
|
|
|
4521
4819
|
};
|
|
4522
4820
|
}
|
|
4523
4821
|
// ============================================================
|
|
4822
|
+
// Deleted Projects Blocklist
|
|
4823
|
+
// ============================================================
|
|
4824
|
+
deleteProject(id) {
|
|
4825
|
+
const project = this.getProject(id);
|
|
4826
|
+
if (!project) return;
|
|
4827
|
+
if (project.path) {
|
|
4828
|
+
this.db.prepare(
|
|
4829
|
+
"INSERT OR REPLACE INTO pm_deleted_projects (path, name, deleted_at) VALUES (?, ?, ?)"
|
|
4830
|
+
).run(project.path, project.name, Date.now());
|
|
4831
|
+
}
|
|
4832
|
+
if (project.claudeProjectKey) {
|
|
4833
|
+
this.db.prepare(
|
|
4834
|
+
"INSERT OR REPLACE INTO pm_deleted_projects (path, name, deleted_at) VALUES (?, ?, ?)"
|
|
4835
|
+
).run(project.claudeProjectKey, project.name, Date.now());
|
|
4836
|
+
}
|
|
4837
|
+
this.db.prepare("DELETE FROM pm_capex_entries WHERE project_id = ?").run(id);
|
|
4838
|
+
this.db.prepare("DELETE FROM pm_notes WHERE project_id = ?").run(id);
|
|
4839
|
+
this.db.prepare("DELETE FROM pm_tasks WHERE project_id = ?").run(id);
|
|
4840
|
+
this.db.prepare("DELETE FROM pm_sessions WHERE project_id = ?").run(id);
|
|
4841
|
+
this.db.prepare("DELETE FROM pm_projects WHERE id = ?").run(id);
|
|
4842
|
+
}
|
|
4843
|
+
isDeletedPath(path) {
|
|
4844
|
+
const row = this.db.prepare("SELECT 1 FROM pm_deleted_projects WHERE path = ?").get(path);
|
|
4845
|
+
return !!row;
|
|
4846
|
+
}
|
|
4847
|
+
recoverProject(path) {
|
|
4848
|
+
this.db.prepare("DELETE FROM pm_deleted_projects WHERE path = ?").run(path);
|
|
4849
|
+
}
|
|
4850
|
+
listDeletedProjects() {
|
|
4851
|
+
return this.db.prepare("SELECT path, name, deleted_at as deletedAt FROM pm_deleted_projects ORDER BY deleted_at DESC").all();
|
|
4852
|
+
}
|
|
4853
|
+
// ============================================================
|
|
4524
4854
|
// Cleanup
|
|
4525
4855
|
// ============================================================
|
|
4526
4856
|
close() {
|
|
@@ -4733,13 +5063,13 @@ async function parseSessionJsonl(jsonlPath, sessionId, projectId) {
|
|
|
4733
5063
|
|
|
4734
5064
|
// src/pm/project-discovery.ts
|
|
4735
5065
|
import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
4736
|
-
import { join as
|
|
4737
|
-
import { existsSync as
|
|
5066
|
+
import { join as join5, basename as basename2 } from "path";
|
|
5067
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4738
5068
|
import { homedir as homedir3 } from "os";
|
|
4739
5069
|
var LOG_PREFIX = "[RuntimeScope PM]";
|
|
4740
5070
|
async function detectSdkInstalled(projectPath) {
|
|
4741
5071
|
try {
|
|
4742
|
-
const pkgPath =
|
|
5072
|
+
const pkgPath = join5(projectPath, "package.json");
|
|
4743
5073
|
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
4744
5074
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4745
5075
|
if ("@runtimescope/sdk" in allDeps || "@runtimescope/server-sdk" in allDeps) {
|
|
@@ -4749,13 +5079,13 @@ async function detectSdkInstalled(projectPath) {
|
|
|
4749
5079
|
const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages ?? [];
|
|
4750
5080
|
for (const ws of workspaces) {
|
|
4751
5081
|
const wsBase = ws.replace(/\/?\*$/, "");
|
|
4752
|
-
const wsDir =
|
|
5082
|
+
const wsDir = join5(projectPath, wsBase);
|
|
4753
5083
|
try {
|
|
4754
5084
|
const entries = await readdir2(wsDir, { withFileTypes: true });
|
|
4755
5085
|
for (const entry of entries) {
|
|
4756
5086
|
if (!entry.isDirectory()) continue;
|
|
4757
5087
|
try {
|
|
4758
|
-
const wsPkg = JSON.parse(await readFile2(
|
|
5088
|
+
const wsPkg = JSON.parse(await readFile2(join5(wsDir, entry.name, "package.json"), "utf-8"));
|
|
4759
5089
|
const wsDeps = { ...wsPkg.dependencies, ...wsPkg.devDependencies };
|
|
4760
5090
|
if ("@runtimescope/sdk" in wsDeps || "@runtimescope/server-sdk" in wsDeps) {
|
|
4761
5091
|
return true;
|
|
@@ -4770,7 +5100,7 @@ async function detectSdkInstalled(projectPath) {
|
|
|
4770
5100
|
} catch {
|
|
4771
5101
|
}
|
|
4772
5102
|
try {
|
|
4773
|
-
await stat2(
|
|
5103
|
+
await stat2(join5(projectPath, "node_modules", "@runtimescope"));
|
|
4774
5104
|
return true;
|
|
4775
5105
|
} catch {
|
|
4776
5106
|
return false;
|
|
@@ -4801,7 +5131,7 @@ function slugifyPath(fsPath) {
|
|
|
4801
5131
|
}
|
|
4802
5132
|
function decodeClaudeKey(key) {
|
|
4803
5133
|
const naive = "/" + key.slice(1).replace(/-/g, "/");
|
|
4804
|
-
if (
|
|
5134
|
+
if (existsSync6(naive)) return naive;
|
|
4805
5135
|
const parts = key.slice(1).split("-");
|
|
4806
5136
|
return resolvePathSegments(parts);
|
|
4807
5137
|
}
|
|
@@ -4809,16 +5139,16 @@ function resolvePathSegments(parts) {
|
|
|
4809
5139
|
if (parts.length === 0) return null;
|
|
4810
5140
|
function tryResolve(prefix, remaining) {
|
|
4811
5141
|
if (remaining.length === 0) {
|
|
4812
|
-
return
|
|
5142
|
+
return existsSync6(prefix) ? prefix : null;
|
|
4813
5143
|
}
|
|
4814
5144
|
for (let count = remaining.length; count >= 1; count--) {
|
|
4815
5145
|
const segment = remaining.slice(0, count).join("-");
|
|
4816
|
-
const candidate =
|
|
5146
|
+
const candidate = join5(prefix, segment);
|
|
4817
5147
|
if (count === remaining.length) {
|
|
4818
|
-
if (
|
|
5148
|
+
if (existsSync6(candidate)) return candidate;
|
|
4819
5149
|
} else {
|
|
4820
5150
|
try {
|
|
4821
|
-
if (
|
|
5151
|
+
if (existsSync6(candidate)) {
|
|
4822
5152
|
const result = tryResolve(candidate, remaining.slice(count));
|
|
4823
5153
|
if (result) return result;
|
|
4824
5154
|
}
|
|
@@ -4834,13 +5164,14 @@ function toPeriod(timestampMs) {
|
|
|
4834
5164
|
const d = new Date(timestampMs);
|
|
4835
5165
|
const year = d.getFullYear();
|
|
4836
5166
|
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
4837
|
-
|
|
5167
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
5168
|
+
return `${year}-${month}-${day}`;
|
|
4838
5169
|
}
|
|
4839
5170
|
var ProjectDiscovery = class {
|
|
4840
5171
|
constructor(pmStore, projectManager, claudeBaseDir) {
|
|
4841
5172
|
this.pmStore = pmStore;
|
|
4842
5173
|
this.projectManager = projectManager;
|
|
4843
|
-
this.claudeBaseDir = claudeBaseDir ??
|
|
5174
|
+
this.claudeBaseDir = claudeBaseDir ?? join5(homedir3(), ".claude");
|
|
4844
5175
|
}
|
|
4845
5176
|
claudeBaseDir;
|
|
4846
5177
|
/**
|
|
@@ -4873,7 +5204,7 @@ var ProjectDiscovery = class {
|
|
|
4873
5204
|
sessionsUpdated: 0,
|
|
4874
5205
|
errors: []
|
|
4875
5206
|
};
|
|
4876
|
-
const projectsDir =
|
|
5207
|
+
const projectsDir = join5(this.claudeBaseDir, "projects");
|
|
4877
5208
|
try {
|
|
4878
5209
|
await stat2(projectsDir);
|
|
4879
5210
|
} catch {
|
|
@@ -4940,6 +5271,7 @@ var ProjectDiscovery = class {
|
|
|
4940
5271
|
await this.pmStore.upsertProject(updated);
|
|
4941
5272
|
result.projectsUpdated = (result.projectsUpdated ?? 0) + 1;
|
|
4942
5273
|
} else {
|
|
5274
|
+
if (this.pmStore.isDeletedPath(sourcePath)) continue;
|
|
4943
5275
|
const fsPath = projectDir;
|
|
4944
5276
|
const project = {
|
|
4945
5277
|
id,
|
|
@@ -4985,10 +5317,10 @@ var ProjectDiscovery = class {
|
|
|
4985
5317
|
if (!project.claudeProjectKey) {
|
|
4986
5318
|
return 0;
|
|
4987
5319
|
}
|
|
4988
|
-
const projectDir =
|
|
5320
|
+
const projectDir = join5(this.claudeBaseDir, "projects", project.claudeProjectKey);
|
|
4989
5321
|
let sessionsIndexed = 0;
|
|
4990
5322
|
try {
|
|
4991
|
-
const indexPath =
|
|
5323
|
+
const indexPath = join5(projectDir, "sessions-index.json");
|
|
4992
5324
|
let indexEntries = null;
|
|
4993
5325
|
try {
|
|
4994
5326
|
const indexContent = await readFile2(indexPath, "utf-8");
|
|
@@ -5001,7 +5333,7 @@ var ProjectDiscovery = class {
|
|
|
5001
5333
|
for (const jsonlFile of jsonlFiles) {
|
|
5002
5334
|
try {
|
|
5003
5335
|
const sessionId = jsonlFile.replace(".jsonl", "");
|
|
5004
|
-
const jsonlPath =
|
|
5336
|
+
const jsonlPath = join5(projectDir, jsonlFile);
|
|
5005
5337
|
const fileStat = await stat2(jsonlPath);
|
|
5006
5338
|
const fileSize = fileStat.size;
|
|
5007
5339
|
const existingSession = await this.pmStore.getSession(sessionId);
|
|
@@ -5036,7 +5368,7 @@ var ProjectDiscovery = class {
|
|
|
5036
5368
|
* Process a single Claude project directory key.
|
|
5037
5369
|
*/
|
|
5038
5370
|
async processClaudeProject(key, result) {
|
|
5039
|
-
const projectDir =
|
|
5371
|
+
const projectDir = join5(this.claudeBaseDir, "projects", key);
|
|
5040
5372
|
let fsPath = decodeClaudeKey(key);
|
|
5041
5373
|
if (!fsPath) {
|
|
5042
5374
|
fsPath = await this.resolvePathFromIndex(projectDir);
|
|
@@ -5062,6 +5394,8 @@ var ProjectDiscovery = class {
|
|
|
5062
5394
|
await this.pmStore.upsertProject(updated);
|
|
5063
5395
|
result.projectsUpdated = (result.projectsUpdated ?? 0) + 1;
|
|
5064
5396
|
} else {
|
|
5397
|
+
if (fsPath && this.pmStore.isDeletedPath(fsPath)) return;
|
|
5398
|
+
if (this.pmStore.isDeletedPath(key)) return;
|
|
5065
5399
|
const project = {
|
|
5066
5400
|
id,
|
|
5067
5401
|
name,
|
|
@@ -5088,7 +5422,7 @@ var ProjectDiscovery = class {
|
|
|
5088
5422
|
*/
|
|
5089
5423
|
async resolvePathFromIndex(projectDir) {
|
|
5090
5424
|
try {
|
|
5091
|
-
const indexPath =
|
|
5425
|
+
const indexPath = join5(projectDir, "sessions-index.json");
|
|
5092
5426
|
const content = await readFile2(indexPath, "utf-8");
|
|
5093
5427
|
const index = JSON.parse(content);
|
|
5094
5428
|
const entry = index.entries?.find((e) => e.projectPath);
|
|
@@ -5103,11 +5437,11 @@ var ProjectDiscovery = class {
|
|
|
5103
5437
|
*/
|
|
5104
5438
|
async indexSessionsForClaudeProject(projectId, claudeKey) {
|
|
5105
5439
|
const counts = { discovered: 0, updated: 0 };
|
|
5106
|
-
const projectDir =
|
|
5440
|
+
const projectDir = join5(this.claudeBaseDir, "projects", claudeKey);
|
|
5107
5441
|
try {
|
|
5108
5442
|
let indexEntries = null;
|
|
5109
5443
|
try {
|
|
5110
|
-
const indexPath =
|
|
5444
|
+
const indexPath = join5(projectDir, "sessions-index.json");
|
|
5111
5445
|
const indexContent = await readFile2(indexPath, "utf-8");
|
|
5112
5446
|
const index = JSON.parse(indexContent);
|
|
5113
5447
|
indexEntries = index.entries ?? [];
|
|
@@ -5118,7 +5452,7 @@ var ProjectDiscovery = class {
|
|
|
5118
5452
|
for (const jsonlFile of jsonlFiles) {
|
|
5119
5453
|
try {
|
|
5120
5454
|
const sessionId = jsonlFile.replace(".jsonl", "");
|
|
5121
|
-
const jsonlPath =
|
|
5455
|
+
const jsonlPath = join5(projectDir, jsonlFile);
|
|
5122
5456
|
const fileStat = await stat2(jsonlPath);
|
|
5123
5457
|
const fileSize = fileStat.size;
|
|
5124
5458
|
const existingSession = await this.pmStore.getSession(sessionId);
|
|
@@ -5308,6 +5642,11 @@ export {
|
|
|
5308
5642
|
resolveTlsConfig,
|
|
5309
5643
|
CollectorServer,
|
|
5310
5644
|
ProjectManager,
|
|
5645
|
+
readProjectConfig,
|
|
5646
|
+
writeProjectConfig,
|
|
5647
|
+
scaffoldProjectConfig,
|
|
5648
|
+
resolveProjectAppNames,
|
|
5649
|
+
migrateProjectIds,
|
|
5311
5650
|
AuthManager,
|
|
5312
5651
|
generateApiKey,
|
|
5313
5652
|
BUILT_IN_RULES,
|
|
@@ -5326,4 +5665,4 @@ export {
|
|
|
5326
5665
|
parseSessionJsonl,
|
|
5327
5666
|
ProjectDiscovery
|
|
5328
5667
|
};
|
|
5329
|
-
//# sourceMappingURL=chunk-
|
|
5668
|
+
//# sourceMappingURL=chunk-MM44DN7Y.js.map
|