@runtimescope/collector 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TUFSIGGJ.js → chunk-HZWALDZM.js} +452 -65
- package/dist/chunk-HZWALDZM.js.map +1 -0
- package/dist/index.d.ts +99 -8
- package/dist/index.js +22 -64
- package/dist/index.js.map +1 -1
- package/dist/standalone.js +11 -4
- package/dist/standalone.js.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-TUFSIGGJ.js.map +0 -1
|
@@ -93,7 +93,8 @@ var EventStore = class {
|
|
|
93
93
|
connectedAt: se.connectedAt,
|
|
94
94
|
sdkVersion: se.sdkVersion,
|
|
95
95
|
eventCount: 0,
|
|
96
|
-
isConnected: true
|
|
96
|
+
isConnected: true,
|
|
97
|
+
projectId: se.projectId
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
100
|
const session = this.sessions.get(event.sessionId);
|
|
@@ -113,6 +114,7 @@ var EventStore = class {
|
|
|
113
114
|
return this.buffer.query((e) => {
|
|
114
115
|
if (e.eventType !== "network") return false;
|
|
115
116
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
117
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
116
118
|
const ne = e;
|
|
117
119
|
if (ne.timestamp < since) return false;
|
|
118
120
|
if (filter.urlPattern && !ne.url.includes(filter.urlPattern)) return false;
|
|
@@ -127,6 +129,7 @@ var EventStore = class {
|
|
|
127
129
|
return this.buffer.query((e) => {
|
|
128
130
|
if (e.eventType !== "console") return false;
|
|
129
131
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
132
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
130
133
|
const ce = e;
|
|
131
134
|
if (ce.timestamp < since) return false;
|
|
132
135
|
if (filter.level && ce.level !== filter.level) return false;
|
|
@@ -148,26 +151,37 @@ var EventStore = class {
|
|
|
148
151
|
return this.buffer.toArray().filter((e) => {
|
|
149
152
|
if (e.timestamp < since) return false;
|
|
150
153
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
154
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
151
155
|
if (typeSet && !typeSet.has(e.eventType)) return false;
|
|
152
156
|
return true;
|
|
153
157
|
});
|
|
154
158
|
}
|
|
155
|
-
getAllEvents(sinceSeconds, sessionId) {
|
|
159
|
+
getAllEvents(sinceSeconds, sessionId, projectId) {
|
|
156
160
|
const since = sinceSeconds ? Date.now() - sinceSeconds * 1e3 : 0;
|
|
157
161
|
return this.buffer.toArray().filter((e) => {
|
|
158
162
|
if (e.timestamp < since) return false;
|
|
159
163
|
if (sessionId && e.sessionId !== sessionId) return false;
|
|
164
|
+
if (projectId && !this.matchesProjectId(e.sessionId, projectId)) return false;
|
|
160
165
|
return true;
|
|
161
166
|
});
|
|
162
167
|
}
|
|
163
168
|
getSessionIdsForProject(appName) {
|
|
164
169
|
return Array.from(this.sessions.values()).filter((s) => s.appName === appName).map((s) => s.sessionId);
|
|
165
170
|
}
|
|
171
|
+
getSessionIdsForProjectId(projectId) {
|
|
172
|
+
return Array.from(this.sessions.values()).filter((s) => s.projectId === projectId).map((s) => s.sessionId);
|
|
173
|
+
}
|
|
174
|
+
/** Check if an event belongs to the given projectId (via its session). */
|
|
175
|
+
matchesProjectId(sessionId, projectId) {
|
|
176
|
+
const session = this.sessions.get(sessionId);
|
|
177
|
+
return session?.projectId === projectId;
|
|
178
|
+
}
|
|
166
179
|
getStateEvents(filter = {}) {
|
|
167
180
|
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
168
181
|
return this.buffer.query((e) => {
|
|
169
182
|
if (e.eventType !== "state") return false;
|
|
170
183
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
184
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
171
185
|
const se = e;
|
|
172
186
|
if (se.timestamp < since) return false;
|
|
173
187
|
if (filter.storeId && se.storeId !== filter.storeId) return false;
|
|
@@ -179,6 +193,7 @@ var EventStore = class {
|
|
|
179
193
|
return this.buffer.query((e) => {
|
|
180
194
|
if (e.eventType !== "render") return false;
|
|
181
195
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
196
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
182
197
|
const re = e;
|
|
183
198
|
if (re.timestamp < since) return false;
|
|
184
199
|
if (filter.componentName) {
|
|
@@ -195,6 +210,7 @@ var EventStore = class {
|
|
|
195
210
|
return this.buffer.query((e) => {
|
|
196
211
|
if (e.eventType !== "performance") return false;
|
|
197
212
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
213
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
198
214
|
const pe = e;
|
|
199
215
|
if (pe.timestamp < since) return false;
|
|
200
216
|
if (filter.metricName && pe.metricName !== filter.metricName) return false;
|
|
@@ -206,6 +222,7 @@ var EventStore = class {
|
|
|
206
222
|
return this.buffer.query((e) => {
|
|
207
223
|
if (e.eventType !== "database") return false;
|
|
208
224
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
225
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
209
226
|
const de = e;
|
|
210
227
|
if (de.timestamp < since) return false;
|
|
211
228
|
if (filter.table) {
|
|
@@ -227,12 +244,25 @@ var EventStore = class {
|
|
|
227
244
|
return this.buffer.query((e) => {
|
|
228
245
|
if (e.eventType !== "custom") return false;
|
|
229
246
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
247
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
230
248
|
const ce = e;
|
|
231
249
|
if (ce.timestamp < since) return false;
|
|
232
250
|
if (filter.name && ce.name !== filter.name) return false;
|
|
233
251
|
return true;
|
|
234
252
|
});
|
|
235
253
|
}
|
|
254
|
+
getUIInteractions(filter = {}) {
|
|
255
|
+
const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
|
|
256
|
+
return this.buffer.query((e) => {
|
|
257
|
+
if (e.eventType !== "ui") return false;
|
|
258
|
+
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
259
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
260
|
+
const ue = e;
|
|
261
|
+
if (ue.timestamp < since) return false;
|
|
262
|
+
if (filter.action && ue.action !== filter.action) return false;
|
|
263
|
+
return true;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
236
266
|
// ============================================================
|
|
237
267
|
// Recon event queries — returns the most recent event of each type
|
|
238
268
|
// ============================================================
|
|
@@ -241,6 +271,7 @@ var EventStore = class {
|
|
|
241
271
|
const results = this.buffer.query((e) => {
|
|
242
272
|
if (e.eventType !== eventType) return false;
|
|
243
273
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
274
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
244
275
|
if (e.timestamp < since) return false;
|
|
245
276
|
if (filter.url) {
|
|
246
277
|
const re = e;
|
|
@@ -255,6 +286,7 @@ var EventStore = class {
|
|
|
255
286
|
return this.buffer.query((e) => {
|
|
256
287
|
if (e.eventType !== eventType) return false;
|
|
257
288
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
289
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
258
290
|
if (e.timestamp < since) return false;
|
|
259
291
|
if (filter.url) {
|
|
260
292
|
const re = e;
|
|
@@ -295,9 +327,43 @@ var EventStore = class {
|
|
|
295
327
|
}
|
|
296
328
|
};
|
|
297
329
|
|
|
330
|
+
// src/project-id.ts
|
|
331
|
+
import { randomBytes } from "crypto";
|
|
332
|
+
var PROJECT_ID_PREFIX = "proj_";
|
|
333
|
+
var PROJECT_ID_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
334
|
+
var PROJECT_ID_LENGTH = 12;
|
|
335
|
+
var PROJECT_ID_REGEX = /^proj_[a-z0-9]{12}$/;
|
|
336
|
+
function generateProjectId() {
|
|
337
|
+
const bytes = randomBytes(PROJECT_ID_LENGTH);
|
|
338
|
+
let id = PROJECT_ID_PREFIX;
|
|
339
|
+
for (let i = 0; i < PROJECT_ID_LENGTH; i++) {
|
|
340
|
+
id += PROJECT_ID_CHARS[bytes[i] % PROJECT_ID_CHARS.length];
|
|
341
|
+
}
|
|
342
|
+
return id;
|
|
343
|
+
}
|
|
344
|
+
function isValidProjectId(id) {
|
|
345
|
+
return PROJECT_ID_REGEX.test(id);
|
|
346
|
+
}
|
|
347
|
+
function getOrCreateProjectId(projectManager, appName) {
|
|
348
|
+
const existing = projectManager.getProjectIdForApp(appName);
|
|
349
|
+
if (existing) return existing;
|
|
350
|
+
const projectId = generateProjectId();
|
|
351
|
+
projectManager.ensureProjectDir(appName);
|
|
352
|
+
projectManager.setProjectIdForApp(appName, projectId);
|
|
353
|
+
return projectId;
|
|
354
|
+
}
|
|
355
|
+
|
|
298
356
|
// src/sqlite-store.ts
|
|
299
|
-
import Database from "better-sqlite3";
|
|
300
357
|
import { renameSync, existsSync } from "fs";
|
|
358
|
+
import { createRequire } from "module";
|
|
359
|
+
var DatabaseConstructor;
|
|
360
|
+
function getDatabase() {
|
|
361
|
+
if (!DatabaseConstructor) {
|
|
362
|
+
const require2 = createRequire(import.meta.url);
|
|
363
|
+
DatabaseConstructor = require2("better-sqlite3");
|
|
364
|
+
}
|
|
365
|
+
return DatabaseConstructor;
|
|
366
|
+
}
|
|
301
367
|
var SqliteStore = class _SqliteStore {
|
|
302
368
|
db;
|
|
303
369
|
writeBuffer = [];
|
|
@@ -316,8 +382,9 @@ var SqliteStore = class _SqliteStore {
|
|
|
316
382
|
this.flushTimer = setInterval(() => this.flush(), flushInterval);
|
|
317
383
|
}
|
|
318
384
|
openDatabase(options) {
|
|
385
|
+
const Db = getDatabase();
|
|
319
386
|
try {
|
|
320
|
-
const db = new
|
|
387
|
+
const db = new Db(options.dbPath);
|
|
321
388
|
if (options.walMode !== false) {
|
|
322
389
|
db.pragma("journal_mode = WAL");
|
|
323
390
|
}
|
|
@@ -347,7 +414,7 @@ var SqliteStore = class _SqliteStore {
|
|
|
347
414
|
}
|
|
348
415
|
} catch {
|
|
349
416
|
}
|
|
350
|
-
const db = new
|
|
417
|
+
const db = new Db(options.dbPath);
|
|
351
418
|
if (options.walMode !== false) {
|
|
352
419
|
db.pragma("journal_mode = WAL");
|
|
353
420
|
}
|
|
@@ -643,14 +710,14 @@ var SqliteStore = class _SqliteStore {
|
|
|
643
710
|
};
|
|
644
711
|
|
|
645
712
|
// src/sqlite-check.ts
|
|
646
|
-
import { createRequire } from "module";
|
|
713
|
+
import { createRequire as createRequire2 } from "module";
|
|
647
714
|
var _checked = false;
|
|
648
715
|
var _available = false;
|
|
649
716
|
function isSqliteAvailable() {
|
|
650
717
|
if (_checked) return _available;
|
|
651
718
|
_checked = true;
|
|
652
719
|
try {
|
|
653
|
-
const require2 =
|
|
720
|
+
const require2 = createRequire2(import.meta.url);
|
|
654
721
|
require2("better-sqlite3");
|
|
655
722
|
_available = true;
|
|
656
723
|
} catch {
|
|
@@ -975,7 +1042,7 @@ var CollectorServer = class {
|
|
|
975
1042
|
console.error(`[RuntimeScope] Session ${clientInfo.sessionId} disconnected`);
|
|
976
1043
|
for (const cb of this.disconnectCallbacks) {
|
|
977
1044
|
try {
|
|
978
|
-
cb(clientInfo.sessionId, clientInfo.projectName);
|
|
1045
|
+
cb(clientInfo.sessionId, clientInfo.projectName, clientInfo.projectId);
|
|
979
1046
|
} catch {
|
|
980
1047
|
}
|
|
981
1048
|
}
|
|
@@ -1007,9 +1074,11 @@ var CollectorServer = class {
|
|
|
1007
1074
|
this.pendingHandshakes.delete(ws);
|
|
1008
1075
|
}
|
|
1009
1076
|
const projectName = payload.appName;
|
|
1077
|
+
const projectId = payload.projectId ?? (this.projectManager ? getOrCreateProjectId(this.projectManager, projectName) : void 0);
|
|
1010
1078
|
this.clients.set(ws, {
|
|
1011
1079
|
sessionId: payload.sessionId,
|
|
1012
|
-
projectName
|
|
1080
|
+
projectName,
|
|
1081
|
+
projectId
|
|
1013
1082
|
});
|
|
1014
1083
|
const sqliteStore = this.ensureSqliteStore(projectName);
|
|
1015
1084
|
if (sqliteStore) {
|
|
@@ -1030,6 +1099,7 @@ var CollectorServer = class {
|
|
|
1030
1099
|
timestamp: msg.timestamp,
|
|
1031
1100
|
eventType: "session",
|
|
1032
1101
|
appName: payload.appName,
|
|
1102
|
+
projectId,
|
|
1033
1103
|
connectedAt: msg.timestamp,
|
|
1034
1104
|
sdkVersion: payload.sdkVersion
|
|
1035
1105
|
});
|
|
@@ -1038,7 +1108,7 @@ var CollectorServer = class {
|
|
|
1038
1108
|
);
|
|
1039
1109
|
for (const cb of this.connectCallbacks) {
|
|
1040
1110
|
try {
|
|
1041
|
-
cb(payload.sessionId, projectName);
|
|
1111
|
+
cb(payload.sessionId, projectName, projectId);
|
|
1042
1112
|
} catch {
|
|
1043
1113
|
}
|
|
1044
1114
|
}
|
|
@@ -1097,7 +1167,7 @@ var CollectorServer = class {
|
|
|
1097
1167
|
getConnectedSessions() {
|
|
1098
1168
|
const sessions = [];
|
|
1099
1169
|
for (const [, info] of this.clients) {
|
|
1100
|
-
sessions.push({ sessionId: info.sessionId, projectName: info.projectName });
|
|
1170
|
+
sessions.push({ sessionId: info.sessionId, projectName: info.projectName, projectId: info.projectId });
|
|
1101
1171
|
}
|
|
1102
1172
|
return sessions;
|
|
1103
1173
|
}
|
|
@@ -1266,6 +1336,29 @@ var ProjectManager = class {
|
|
|
1266
1336
|
projectExists(projectName) {
|
|
1267
1337
|
return existsSync2(this.getProjectDir(projectName));
|
|
1268
1338
|
}
|
|
1339
|
+
// --- Project ID helpers ---
|
|
1340
|
+
/** Look up the stored projectId for an appName. Returns null if none set. */
|
|
1341
|
+
getProjectIdForApp(appName) {
|
|
1342
|
+
const config = this.getProjectConfig(appName);
|
|
1343
|
+
return config?.projectId ?? null;
|
|
1344
|
+
}
|
|
1345
|
+
/** Persist a projectId for an appName in its project config. */
|
|
1346
|
+
setProjectIdForApp(appName, projectId) {
|
|
1347
|
+
this.ensureProjectDir(appName);
|
|
1348
|
+
const config = this.getProjectConfig(appName);
|
|
1349
|
+
if (config) {
|
|
1350
|
+
config.projectId = projectId;
|
|
1351
|
+
this.saveProjectConfig(appName, config);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
/** Resolve a projectId to an appName by scanning all project configs. Returns null if not found. */
|
|
1355
|
+
getAppForProjectId(projectId) {
|
|
1356
|
+
for (const name of this.listProjects()) {
|
|
1357
|
+
const config = this.getProjectConfig(name);
|
|
1358
|
+
if (config?.projectId === projectId) return name;
|
|
1359
|
+
}
|
|
1360
|
+
return null;
|
|
1361
|
+
}
|
|
1269
1362
|
// --- Environment variable resolution ---
|
|
1270
1363
|
resolveEnvVars(value) {
|
|
1271
1364
|
return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
@@ -1320,7 +1413,7 @@ var ProjectManager = class {
|
|
|
1320
1413
|
};
|
|
1321
1414
|
|
|
1322
1415
|
// src/auth.ts
|
|
1323
|
-
import { randomBytes, timingSafeEqual } from "crypto";
|
|
1416
|
+
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
1324
1417
|
var AuthManager = class {
|
|
1325
1418
|
keys = /* @__PURE__ */ new Map();
|
|
1326
1419
|
enabled;
|
|
@@ -1367,7 +1460,7 @@ var AuthManager = class {
|
|
|
1367
1460
|
};
|
|
1368
1461
|
function generateApiKey(label, project) {
|
|
1369
1462
|
return {
|
|
1370
|
-
key:
|
|
1463
|
+
key: randomBytes2(32).toString("hex"),
|
|
1371
1464
|
label,
|
|
1372
1465
|
project,
|
|
1373
1466
|
createdAt: Date.now()
|
|
@@ -1480,6 +1573,198 @@ var Redactor = class {
|
|
|
1480
1573
|
}
|
|
1481
1574
|
};
|
|
1482
1575
|
|
|
1576
|
+
// src/platform.ts
|
|
1577
|
+
import { execFileSync, execSync } from "child_process";
|
|
1578
|
+
import { readlinkSync, readdirSync as readdirSync2 } from "fs";
|
|
1579
|
+
import { join as join2 } from "path";
|
|
1580
|
+
var IS_WIN = process.platform === "win32";
|
|
1581
|
+
var IS_LINUX = process.platform === "linux";
|
|
1582
|
+
function runFile(cmd, args, timeoutMs = 5e3) {
|
|
1583
|
+
try {
|
|
1584
|
+
return execFileSync(cmd, args, { encoding: "utf-8", timeout: timeoutMs }).trim();
|
|
1585
|
+
} catch {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
function runShell(cmd, timeoutMs = 5e3) {
|
|
1590
|
+
try {
|
|
1591
|
+
return execSync(cmd, { encoding: "utf-8", timeout: timeoutMs }).trim();
|
|
1592
|
+
} catch {
|
|
1593
|
+
return null;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
function getPidsOnPort_unix(port) {
|
|
1597
|
+
const lsof = runFile("lsof", ["-ti", `:${port}`], 5e3);
|
|
1598
|
+
if (lsof) {
|
|
1599
|
+
return lsof.split("\n").map(Number).filter((n) => n > 0);
|
|
1600
|
+
}
|
|
1601
|
+
if (IS_LINUX) {
|
|
1602
|
+
const ss = runShell(`ss -tlnp 2>/dev/null | grep ':${port} '`);
|
|
1603
|
+
if (ss) {
|
|
1604
|
+
const pids = [];
|
|
1605
|
+
for (const match of ss.matchAll(/pid=(\d+)/g)) {
|
|
1606
|
+
pids.push(parseInt(match[1], 10));
|
|
1607
|
+
}
|
|
1608
|
+
return [...new Set(pids)];
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
return [];
|
|
1612
|
+
}
|
|
1613
|
+
function getPidsOnPort_win(port) {
|
|
1614
|
+
const out = runShell(`netstat -ano | findstr :${port} | findstr LISTENING`);
|
|
1615
|
+
if (!out) return [];
|
|
1616
|
+
const pids = [];
|
|
1617
|
+
for (const line of out.split("\n")) {
|
|
1618
|
+
const parts = line.trim().split(/\s+/);
|
|
1619
|
+
const pid = parseInt(parts[parts.length - 1], 10);
|
|
1620
|
+
if (pid > 0) pids.push(pid);
|
|
1621
|
+
}
|
|
1622
|
+
return [...new Set(pids)];
|
|
1623
|
+
}
|
|
1624
|
+
function getPidsOnPort(port) {
|
|
1625
|
+
return IS_WIN ? getPidsOnPort_win(port) : getPidsOnPort_unix(port);
|
|
1626
|
+
}
|
|
1627
|
+
function getListenPorts_unix(pid) {
|
|
1628
|
+
const lsof = runShell(`lsof -nP -p ${pid} 2>/dev/null | grep LISTEN`, 3e3);
|
|
1629
|
+
if (lsof) {
|
|
1630
|
+
const ports = [];
|
|
1631
|
+
for (const line of lsof.split("\n")) {
|
|
1632
|
+
const match = line.match(/:(\d+)\s+\(LISTEN\)/);
|
|
1633
|
+
if (match) ports.push(parseInt(match[1], 10));
|
|
1634
|
+
}
|
|
1635
|
+
return [...new Set(ports)];
|
|
1636
|
+
}
|
|
1637
|
+
if (IS_LINUX) {
|
|
1638
|
+
const ss = runShell(`ss -tlnp 2>/dev/null | grep 'pid=${pid},'`, 3e3);
|
|
1639
|
+
if (ss) {
|
|
1640
|
+
const ports = [];
|
|
1641
|
+
for (const match of ss.matchAll(/:(\d+)\s/g)) {
|
|
1642
|
+
ports.push(parseInt(match[1], 10));
|
|
1643
|
+
}
|
|
1644
|
+
return [...new Set(ports)];
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
return [];
|
|
1648
|
+
}
|
|
1649
|
+
function getListenPorts_win(pid) {
|
|
1650
|
+
const out = runShell(`netstat -ano | findstr ${pid} | findstr LISTENING`);
|
|
1651
|
+
if (!out) return [];
|
|
1652
|
+
const ports = [];
|
|
1653
|
+
for (const line of out.split("\n")) {
|
|
1654
|
+
const match = line.match(/:(\d+)\s/);
|
|
1655
|
+
if (match) {
|
|
1656
|
+
const port = parseInt(match[1], 10);
|
|
1657
|
+
const linePid = parseInt(line.trim().split(/\s+/).pop(), 10);
|
|
1658
|
+
if (linePid === pid) ports.push(port);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return [...new Set(ports)];
|
|
1662
|
+
}
|
|
1663
|
+
function getListenPorts(pid) {
|
|
1664
|
+
return IS_WIN ? getListenPorts_win(pid) : getListenPorts_unix(pid);
|
|
1665
|
+
}
|
|
1666
|
+
function getProcessCwd_mac(pid) {
|
|
1667
|
+
const out = runShell(`lsof -p ${pid} 2>/dev/null | grep cwd`, 3e3);
|
|
1668
|
+
if (!out) return void 0;
|
|
1669
|
+
const match = out.match(/cwd\s+\w+\s+\w+\s+\d+\w?\s+\d+\s+\d+\s+\d+\s+(.+)/);
|
|
1670
|
+
return match?.[1]?.trim();
|
|
1671
|
+
}
|
|
1672
|
+
function getProcessCwd_linux(pid) {
|
|
1673
|
+
try {
|
|
1674
|
+
return readlinkSync(`/proc/${pid}/cwd`);
|
|
1675
|
+
} catch {
|
|
1676
|
+
return getProcessCwd_mac(pid);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
function getProcessCwd(pid) {
|
|
1680
|
+
if (IS_WIN) return void 0;
|
|
1681
|
+
if (IS_LINUX) return getProcessCwd_linux(pid);
|
|
1682
|
+
return getProcessCwd_mac(pid);
|
|
1683
|
+
}
|
|
1684
|
+
function findPidsInDir_lsof(dir) {
|
|
1685
|
+
const out = runShell(`lsof -t +D "${dir}" 2>/dev/null | head -20`);
|
|
1686
|
+
if (!out) return [];
|
|
1687
|
+
return out.split("\n").map(Number).filter((n) => n > 0);
|
|
1688
|
+
}
|
|
1689
|
+
function findPidsInDir_linux(dir) {
|
|
1690
|
+
const lsofResult = findPidsInDir_lsof(dir);
|
|
1691
|
+
if (lsofResult.length > 0) return lsofResult;
|
|
1692
|
+
try {
|
|
1693
|
+
const pids = [];
|
|
1694
|
+
for (const entry of readdirSync2("/proc")) {
|
|
1695
|
+
const pid = parseInt(entry, 10);
|
|
1696
|
+
if (isNaN(pid) || pid <= 1) continue;
|
|
1697
|
+
try {
|
|
1698
|
+
const cwd = readlinkSync(join2("/proc", entry, "cwd"));
|
|
1699
|
+
if (cwd.startsWith(dir)) pids.push(pid);
|
|
1700
|
+
} catch {
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return pids;
|
|
1704
|
+
} catch {
|
|
1705
|
+
return [];
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
function findPidsInDirectory(dir) {
|
|
1709
|
+
if (IS_WIN) return [];
|
|
1710
|
+
if (IS_LINUX) return findPidsInDir_linux(dir);
|
|
1711
|
+
return findPidsInDir_lsof(dir);
|
|
1712
|
+
}
|
|
1713
|
+
function parseProcessList_unix() {
|
|
1714
|
+
const output = runFile("ps", ["aux"]);
|
|
1715
|
+
if (!output) return [];
|
|
1716
|
+
const lines = output.split("\n").slice(1);
|
|
1717
|
+
const results = [];
|
|
1718
|
+
for (const line of lines) {
|
|
1719
|
+
const parts = line.trim().split(/\s+/);
|
|
1720
|
+
if (parts.length < 11) continue;
|
|
1721
|
+
const pid = parseInt(parts[1], 10);
|
|
1722
|
+
const cpu = parseFloat(parts[2]);
|
|
1723
|
+
const mem = parseFloat(parts[3]);
|
|
1724
|
+
const command = parts.slice(10).join(" ");
|
|
1725
|
+
if (isNaN(pid)) continue;
|
|
1726
|
+
results.push({ pid, cpu, mem, command });
|
|
1727
|
+
}
|
|
1728
|
+
return results;
|
|
1729
|
+
}
|
|
1730
|
+
function parseProcessList_win() {
|
|
1731
|
+
const output = runFile("tasklist", ["/FO", "CSV", "/NH"], 1e4);
|
|
1732
|
+
if (!output) return [];
|
|
1733
|
+
const results = [];
|
|
1734
|
+
for (const line of output.split("\n")) {
|
|
1735
|
+
const parts = line.match(/"([^"]*)"/g);
|
|
1736
|
+
if (!parts || parts.length < 5) continue;
|
|
1737
|
+
const pid = parseInt(parts[1].replace(/"/g, ""), 10);
|
|
1738
|
+
const command = parts[0].replace(/"/g, "");
|
|
1739
|
+
const memStr = parts[4].replace(/"/g, "").replace(/[, K]/gi, "");
|
|
1740
|
+
const mem = parseInt(memStr, 10) / 1024;
|
|
1741
|
+
if (isNaN(pid)) continue;
|
|
1742
|
+
results.push({ pid, cpu: 0, mem, command });
|
|
1743
|
+
}
|
|
1744
|
+
return results;
|
|
1745
|
+
}
|
|
1746
|
+
function parseProcessList() {
|
|
1747
|
+
return IS_WIN ? parseProcessList_win() : parseProcessList_unix();
|
|
1748
|
+
}
|
|
1749
|
+
function getProcessMemoryMB_unix(pid) {
|
|
1750
|
+
const out = runFile("ps", ["-o", "rss=", "-p", String(pid)], 2e3);
|
|
1751
|
+
if (!out) return 0;
|
|
1752
|
+
const rss = parseInt(out, 10);
|
|
1753
|
+
return isNaN(rss) ? 0 : rss / 1024;
|
|
1754
|
+
}
|
|
1755
|
+
function getProcessMemoryMB_win(pid) {
|
|
1756
|
+
const out = runFile("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], 3e3);
|
|
1757
|
+
if (!out) return 0;
|
|
1758
|
+
const parts = out.match(/"([^"]*)"/g);
|
|
1759
|
+
if (!parts || parts.length < 5) return 0;
|
|
1760
|
+
const memStr = parts[4].replace(/"/g, "").replace(/[, K]/gi, "");
|
|
1761
|
+
const kb = parseInt(memStr, 10);
|
|
1762
|
+
return isNaN(kb) ? 0 : kb / 1024;
|
|
1763
|
+
}
|
|
1764
|
+
function getProcessMemoryMB(pid) {
|
|
1765
|
+
return IS_WIN ? getProcessMemoryMB_win(pid) : getProcessMemoryMB_unix(pid);
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1483
1768
|
// src/session-manager.ts
|
|
1484
1769
|
var SessionManager = class {
|
|
1485
1770
|
projectManager;
|
|
@@ -1622,9 +1907,9 @@ import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
|
1622
1907
|
// src/pm/pm-routes.ts
|
|
1623
1908
|
import { readdir, readFile, writeFile, unlink, mkdir } from "fs/promises";
|
|
1624
1909
|
import { existsSync as existsSync3 } from "fs";
|
|
1625
|
-
import { join as
|
|
1910
|
+
import { join as join3 } from "path";
|
|
1626
1911
|
import { homedir as homedir2 } from "os";
|
|
1627
|
-
import { spawn,
|
|
1912
|
+
import { spawn, execFileSync as execFileSync2 } from "child_process";
|
|
1628
1913
|
var LOG_RING_SIZE = 500;
|
|
1629
1914
|
var managedProcesses = /* @__PURE__ */ new Map();
|
|
1630
1915
|
function pushLog(mp, stream, line) {
|
|
@@ -1934,13 +2219,13 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
1934
2219
|
helpers.json(res, { data: [], count: 0 });
|
|
1935
2220
|
return;
|
|
1936
2221
|
}
|
|
1937
|
-
const memoryDir =
|
|
2222
|
+
const memoryDir = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
|
|
1938
2223
|
try {
|
|
1939
2224
|
const files = await readdir(memoryDir);
|
|
1940
2225
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
1941
2226
|
const result = await Promise.all(
|
|
1942
2227
|
mdFiles.map(async (filename) => {
|
|
1943
|
-
const content = await readFile(
|
|
2228
|
+
const content = await readFile(join3(memoryDir, filename), "utf-8");
|
|
1944
2229
|
return { filename, content, sizeBytes: Buffer.byteLength(content) };
|
|
1945
2230
|
})
|
|
1946
2231
|
);
|
|
@@ -1957,7 +2242,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
1957
2242
|
helpers.json(res, { error: "Project not found" }, 404);
|
|
1958
2243
|
return;
|
|
1959
2244
|
}
|
|
1960
|
-
const filePath =
|
|
2245
|
+
const filePath = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
|
|
1961
2246
|
try {
|
|
1962
2247
|
const content = await readFile(filePath, "utf-8");
|
|
1963
2248
|
helpers.json(res, { filename, content, sizeBytes: Buffer.byteLength(content) });
|
|
@@ -1980,9 +2265,9 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
1980
2265
|
}
|
|
1981
2266
|
try {
|
|
1982
2267
|
const { content } = JSON.parse(body);
|
|
1983
|
-
const memoryDir =
|
|
2268
|
+
const memoryDir = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
|
|
1984
2269
|
await mkdir(memoryDir, { recursive: true });
|
|
1985
|
-
await writeFile(
|
|
2270
|
+
await writeFile(join3(memoryDir, filename), content, "utf-8");
|
|
1986
2271
|
helpers.json(res, { ok: true });
|
|
1987
2272
|
} catch (err) {
|
|
1988
2273
|
helpers.json(res, { error: err.message }, 500);
|
|
@@ -1996,7 +2281,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
1996
2281
|
helpers.json(res, { error: "Project not found" }, 404);
|
|
1997
2282
|
return;
|
|
1998
2283
|
}
|
|
1999
|
-
const filePath =
|
|
2284
|
+
const filePath = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
|
|
2000
2285
|
try {
|
|
2001
2286
|
await unlink(filePath);
|
|
2002
2287
|
helpers.json(res, { ok: true });
|
|
@@ -2056,7 +2341,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2056
2341
|
const { content } = JSON.parse(body);
|
|
2057
2342
|
const paths = getRulesPaths(project.claudeProjectKey, project.path);
|
|
2058
2343
|
const filePath = paths[scope];
|
|
2059
|
-
const dir =
|
|
2344
|
+
const dir = join3(filePath, "..");
|
|
2060
2345
|
await mkdir(dir, { recursive: true });
|
|
2061
2346
|
await writeFile(filePath, content, "utf-8");
|
|
2062
2347
|
helpers.json(res, { ok: true });
|
|
@@ -2076,7 +2361,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2076
2361
|
return;
|
|
2077
2362
|
}
|
|
2078
2363
|
try {
|
|
2079
|
-
const pkgPath =
|
|
2364
|
+
const pkgPath = join3(project.path, "package.json");
|
|
2080
2365
|
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
2081
2366
|
const scripts = pkg.scripts ?? {};
|
|
2082
2367
|
const recommended = ["dev", "start", "serve"].find((s) => s in scripts) ?? null;
|
|
@@ -2246,15 +2531,8 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2246
2531
|
}
|
|
2247
2532
|
managedProcesses.delete(id);
|
|
2248
2533
|
} else if (project.path) {
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
`lsof -t +D "${project.path}" 2>/dev/null | head -5`,
|
|
2252
|
-
{ encoding: "utf-8", timeout: 5e3 }
|
|
2253
|
-
).trim();
|
|
2254
|
-
const pids = output.split("\n").filter(Boolean).map(Number).filter((n) => n > 1 && n !== process.pid);
|
|
2255
|
-
if (pids.length > 0) pid = pids[0];
|
|
2256
|
-
} catch {
|
|
2257
|
-
}
|
|
2534
|
+
const pids = findPidsInDirectory(project.path).filter((n) => n > 1 && n !== process.pid);
|
|
2535
|
+
if (pids.length > 0) pid = pids[0];
|
|
2258
2536
|
}
|
|
2259
2537
|
if (!pid) {
|
|
2260
2538
|
helpers.json(res, { error: "No running dev server found for this project" }, 404);
|
|
@@ -2509,17 +2787,17 @@ function sanitizeFilename(name) {
|
|
|
2509
2787
|
function getRulesPaths(claudeProjectKey, projectPath) {
|
|
2510
2788
|
const home = homedir2();
|
|
2511
2789
|
return {
|
|
2512
|
-
global:
|
|
2513
|
-
project: claudeProjectKey ?
|
|
2514
|
-
local: projectPath ?
|
|
2790
|
+
global: join3(home, ".claude", "CLAUDE.md"),
|
|
2791
|
+
project: claudeProjectKey ? join3(home, ".claude", "projects", claudeProjectKey, "CLAUDE.md") : join3(projectPath ?? "", ".claude", "CLAUDE.md"),
|
|
2792
|
+
local: projectPath ? join3(projectPath, "CLAUDE.md") : join3(home, "CLAUDE.md")
|
|
2515
2793
|
};
|
|
2516
2794
|
}
|
|
2517
2795
|
function execGit(args, cwd) {
|
|
2518
|
-
return
|
|
2796
|
+
return execFileSync2("git", args, { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 10 * 1024 * 1024 });
|
|
2519
2797
|
}
|
|
2520
2798
|
function isGitRepo(path) {
|
|
2521
2799
|
try {
|
|
2522
|
-
|
|
2800
|
+
execFileSync2("git", ["rev-parse", "--git-dir"], { cwd: path, encoding: "utf-8", timeout: 3e3 });
|
|
2523
2801
|
return true;
|
|
2524
2802
|
} catch {
|
|
2525
2803
|
return false;
|
|
@@ -2582,6 +2860,8 @@ var HttpServer = class {
|
|
|
2582
2860
|
activePort = 9091;
|
|
2583
2861
|
startedAt = Date.now();
|
|
2584
2862
|
connectedSessionsGetter = null;
|
|
2863
|
+
pmStore = null;
|
|
2864
|
+
projectManager = null;
|
|
2585
2865
|
constructor(store, processMonitor, options) {
|
|
2586
2866
|
this.store = store;
|
|
2587
2867
|
this.processMonitor = processMonitor ?? null;
|
|
@@ -2589,6 +2869,8 @@ var HttpServer = class {
|
|
|
2589
2869
|
this.allowedOrigins = options?.allowedOrigins ?? null;
|
|
2590
2870
|
this.rateLimiter = options?.rateLimiter ?? null;
|
|
2591
2871
|
this.connectedSessionsGetter = options?.getConnectedSessions ?? null;
|
|
2872
|
+
this.pmStore = options?.pmStore ?? null;
|
|
2873
|
+
this.projectManager = options?.projectManager ?? null;
|
|
2592
2874
|
this.registerRoutes();
|
|
2593
2875
|
if (options?.pmStore && options?.discovery) {
|
|
2594
2876
|
this.pmRouter = createPmRouter(options.pmStore, options.discovery, {
|
|
@@ -2620,12 +2902,14 @@ var HttpServer = class {
|
|
|
2620
2902
|
existing.sessions.push(s.sessionId);
|
|
2621
2903
|
existing.eventCount += s.eventCount;
|
|
2622
2904
|
if (s.isConnected) existing.isConnected = true;
|
|
2905
|
+
if (!existing.projectId && s.projectId) existing.projectId = s.projectId;
|
|
2623
2906
|
} else {
|
|
2624
2907
|
projectMap.set(s.appName, {
|
|
2625
2908
|
appName: s.appName,
|
|
2626
2909
|
sessions: [s.sessionId],
|
|
2627
2910
|
isConnected: s.isConnected,
|
|
2628
|
-
eventCount: s.eventCount
|
|
2911
|
+
eventCount: s.eventCount,
|
|
2912
|
+
projectId: s.projectId
|
|
2629
2913
|
});
|
|
2630
2914
|
}
|
|
2631
2915
|
}
|
|
@@ -2695,7 +2979,8 @@ var HttpServer = class {
|
|
|
2695
2979
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2696
2980
|
urlPattern: params.get("url_pattern") ?? void 0,
|
|
2697
2981
|
method: params.get("method") ?? void 0,
|
|
2698
|
-
sessionId: params.get("session_id") ?? void 0
|
|
2982
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
2983
|
+
projectId: params.get("project_id") ?? void 0
|
|
2699
2984
|
});
|
|
2700
2985
|
this.json(res, { data: events, count: events.length });
|
|
2701
2986
|
});
|
|
@@ -2704,7 +2989,8 @@ var HttpServer = class {
|
|
|
2704
2989
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2705
2990
|
level: params.get("level") ?? void 0,
|
|
2706
2991
|
search: params.get("search") ?? void 0,
|
|
2707
|
-
sessionId: params.get("session_id") ?? void 0
|
|
2992
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
2993
|
+
projectId: params.get("project_id") ?? void 0
|
|
2708
2994
|
});
|
|
2709
2995
|
this.json(res, { data: events, count: events.length });
|
|
2710
2996
|
});
|
|
@@ -2712,7 +2998,8 @@ var HttpServer = class {
|
|
|
2712
2998
|
const events = this.store.getStateEvents({
|
|
2713
2999
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2714
3000
|
storeId: params.get("store_id") ?? void 0,
|
|
2715
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3001
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3002
|
+
projectId: params.get("project_id") ?? void 0
|
|
2716
3003
|
});
|
|
2717
3004
|
this.json(res, { data: events, count: events.length });
|
|
2718
3005
|
});
|
|
@@ -2720,7 +3007,8 @@ var HttpServer = class {
|
|
|
2720
3007
|
const events = this.store.getRenderEvents({
|
|
2721
3008
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2722
3009
|
componentName: params.get("component") ?? void 0,
|
|
2723
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3010
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3011
|
+
projectId: params.get("project_id") ?? void 0
|
|
2724
3012
|
});
|
|
2725
3013
|
this.json(res, { data: events, count: events.length });
|
|
2726
3014
|
});
|
|
@@ -2728,7 +3016,8 @@ var HttpServer = class {
|
|
|
2728
3016
|
const events = this.store.getPerformanceMetrics({
|
|
2729
3017
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2730
3018
|
metricName: params.get("metric") ?? void 0,
|
|
2731
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3019
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3020
|
+
projectId: params.get("project_id") ?? void 0
|
|
2732
3021
|
});
|
|
2733
3022
|
this.json(res, { data: events, count: events.length });
|
|
2734
3023
|
});
|
|
@@ -2738,7 +3027,8 @@ var HttpServer = class {
|
|
|
2738
3027
|
table: params.get("table") ?? void 0,
|
|
2739
3028
|
minDurationMs: numParam(params, "min_duration_ms"),
|
|
2740
3029
|
search: params.get("search") ?? void 0,
|
|
2741
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3030
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3031
|
+
projectId: params.get("project_id") ?? void 0
|
|
2742
3032
|
});
|
|
2743
3033
|
this.json(res, { data: events, count: events.length });
|
|
2744
3034
|
});
|
|
@@ -2747,7 +3037,8 @@ var HttpServer = class {
|
|
|
2747
3037
|
const events = this.store.getEventTimeline({
|
|
2748
3038
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2749
3039
|
eventTypes,
|
|
2750
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3040
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3041
|
+
projectId: params.get("project_id") ?? void 0
|
|
2751
3042
|
});
|
|
2752
3043
|
this.json(res, { data: events, count: events.length });
|
|
2753
3044
|
});
|
|
@@ -2755,7 +3046,18 @@ var HttpServer = class {
|
|
|
2755
3046
|
const events = this.store.getCustomEvents({
|
|
2756
3047
|
name: params.get("name") ?? void 0,
|
|
2757
3048
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2758
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3049
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3050
|
+
projectId: params.get("project_id") ?? void 0
|
|
3051
|
+
});
|
|
3052
|
+
this.json(res, { data: events, count: events.length });
|
|
3053
|
+
});
|
|
3054
|
+
this.routes.set("GET /api/events/ui", (_req, res, params) => {
|
|
3055
|
+
const action = params.get("action");
|
|
3056
|
+
const events = this.store.getUIInteractions({
|
|
3057
|
+
action: action ?? void 0,
|
|
3058
|
+
sinceSeconds: numParam(params, "since_seconds"),
|
|
3059
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3060
|
+
projectId: params.get("project_id") ?? void 0
|
|
2759
3061
|
});
|
|
2760
3062
|
this.json(res, { data: events, count: events.length });
|
|
2761
3063
|
});
|
|
@@ -2777,6 +3079,7 @@ var HttpServer = class {
|
|
|
2777
3079
|
return;
|
|
2778
3080
|
}
|
|
2779
3081
|
const payload = parsed;
|
|
3082
|
+
const projectId = typeof payload.projectId === "string" ? payload.projectId : payload.appName && this.projectManager ? getOrCreateProjectId(this.projectManager, payload.appName) : void 0;
|
|
2780
3083
|
if (!payload.sessionId || !Array.isArray(payload.events) || payload.events.length === 0) {
|
|
2781
3084
|
this.json(res, {
|
|
2782
3085
|
error: "Required: sessionId (string), events (non-empty array)",
|
|
@@ -2793,9 +3096,16 @@ var HttpServer = class {
|
|
|
2793
3096
|
timestamp: Date.now(),
|
|
2794
3097
|
eventType: "session",
|
|
2795
3098
|
appName: payload.appName,
|
|
3099
|
+
projectId,
|
|
2796
3100
|
connectedAt: Date.now(),
|
|
2797
3101
|
sdkVersion: payload.sdkVersion ?? "http"
|
|
2798
3102
|
});
|
|
3103
|
+
if (this.pmStore) {
|
|
3104
|
+
try {
|
|
3105
|
+
this.pmStore.autoLinkApp(payload.appName, projectId);
|
|
3106
|
+
} catch {
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
2799
3109
|
}
|
|
2800
3110
|
const VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
2801
3111
|
"network",
|
|
@@ -2806,6 +3116,9 @@ var HttpServer = class {
|
|
|
2806
3116
|
"dom_snapshot",
|
|
2807
3117
|
"performance",
|
|
2808
3118
|
"database",
|
|
3119
|
+
"custom",
|
|
3120
|
+
"navigation",
|
|
3121
|
+
"ui",
|
|
2809
3122
|
"recon_metadata",
|
|
2810
3123
|
"recon_design_tokens",
|
|
2811
3124
|
"recon_fonts",
|
|
@@ -3124,11 +3437,11 @@ function numParam(params, key) {
|
|
|
3124
3437
|
}
|
|
3125
3438
|
|
|
3126
3439
|
// src/pm/pm-store.ts
|
|
3127
|
-
import
|
|
3440
|
+
import Database from "better-sqlite3";
|
|
3128
3441
|
var PmStore = class {
|
|
3129
3442
|
db;
|
|
3130
3443
|
constructor(options) {
|
|
3131
|
-
this.db = new
|
|
3444
|
+
this.db = new Database(options.dbPath);
|
|
3132
3445
|
if (options.walMode !== false) {
|
|
3133
3446
|
this.db.pragma("journal_mode = WAL");
|
|
3134
3447
|
}
|
|
@@ -3265,6 +3578,10 @@ var PmStore = class {
|
|
|
3265
3578
|
this.db.exec("ALTER TABLE pm_projects ADD COLUMN runtime_apps TEXT DEFAULT NULL");
|
|
3266
3579
|
} catch {
|
|
3267
3580
|
}
|
|
3581
|
+
try {
|
|
3582
|
+
this.db.exec("ALTER TABLE pm_projects ADD COLUMN runtime_project_id TEXT DEFAULT NULL");
|
|
3583
|
+
} catch {
|
|
3584
|
+
}
|
|
3268
3585
|
}
|
|
3269
3586
|
// ============================================================
|
|
3270
3587
|
// Projects
|
|
@@ -3350,6 +3667,10 @@ var PmStore = class {
|
|
|
3350
3667
|
sets.push("runtimescope_project = ?");
|
|
3351
3668
|
params.push(updates.runtimescopeProject ?? null);
|
|
3352
3669
|
}
|
|
3670
|
+
if (updates.runtimeProjectId !== void 0) {
|
|
3671
|
+
sets.push("runtime_project_id = ?");
|
|
3672
|
+
params.push(updates.runtimeProjectId ?? null);
|
|
3673
|
+
}
|
|
3353
3674
|
if (updates.metadata !== void 0) {
|
|
3354
3675
|
sets.push("metadata = ?");
|
|
3355
3676
|
params.push(JSON.stringify(updates.metadata));
|
|
@@ -3360,6 +3681,55 @@ var PmStore = class {
|
|
|
3360
3681
|
params.push(id);
|
|
3361
3682
|
this.db.prepare(`UPDATE pm_projects SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
3362
3683
|
}
|
|
3684
|
+
/**
|
|
3685
|
+
* Auto-link an SDK appName to a PM project.
|
|
3686
|
+
* Matches by: exact name, directory basename, runtimescopeProject, or existing runtimeApps.
|
|
3687
|
+
* Returns the project ID if linked, null if no match found.
|
|
3688
|
+
*/
|
|
3689
|
+
autoLinkApp(appName, projectId) {
|
|
3690
|
+
const projects = this.listProjects();
|
|
3691
|
+
const appLower = appName.toLowerCase();
|
|
3692
|
+
if (projectId) {
|
|
3693
|
+
const byProjectId = projects.find((p) => p.runtimeProjectId === projectId);
|
|
3694
|
+
if (byProjectId) {
|
|
3695
|
+
const apps2 = byProjectId.runtimeApps ?? [];
|
|
3696
|
+
if (!apps2.some((a) => a.toLowerCase() === appLower)) {
|
|
3697
|
+
apps2.push(appName);
|
|
3698
|
+
this.updateProject(byProjectId.id, { runtimeApps: apps2 });
|
|
3699
|
+
}
|
|
3700
|
+
return byProjectId.id;
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
const alreadyLinked = projects.find(
|
|
3704
|
+
(p) => p.runtimeApps?.some((a) => a.toLowerCase() === appLower)
|
|
3705
|
+
);
|
|
3706
|
+
if (alreadyLinked) {
|
|
3707
|
+
if (projectId && !alreadyLinked.runtimeProjectId) {
|
|
3708
|
+
this.updateProject(alreadyLinked.id, { runtimeProjectId: projectId });
|
|
3709
|
+
}
|
|
3710
|
+
return alreadyLinked.id;
|
|
3711
|
+
}
|
|
3712
|
+
const match = projects.find((p) => {
|
|
3713
|
+
if (p.name.toLowerCase() === appLower) return true;
|
|
3714
|
+
if (p.path) {
|
|
3715
|
+
const basename3 = p.path.replace(/\/+$/, "").split("/").pop()?.toLowerCase();
|
|
3716
|
+
if (basename3 === appLower) return true;
|
|
3717
|
+
}
|
|
3718
|
+
if (p.runtimescopeProject?.toLowerCase() === appLower) return true;
|
|
3719
|
+
return false;
|
|
3720
|
+
});
|
|
3721
|
+
if (!match) return null;
|
|
3722
|
+
const apps = match.runtimeApps ?? [];
|
|
3723
|
+
if (!apps.some((a) => a.toLowerCase() === appLower)) {
|
|
3724
|
+
apps.push(appName);
|
|
3725
|
+
}
|
|
3726
|
+
const updates = { runtimeApps: apps };
|
|
3727
|
+
if (projectId && !match.runtimeProjectId) {
|
|
3728
|
+
updates.runtimeProjectId = projectId;
|
|
3729
|
+
}
|
|
3730
|
+
this.updateProject(match.id, updates);
|
|
3731
|
+
return match.id;
|
|
3732
|
+
}
|
|
3363
3733
|
listCategories() {
|
|
3364
3734
|
const rows = this.db.prepare("SELECT DISTINCT category FROM pm_projects WHERE category IS NOT NULL ORDER BY category ASC").all();
|
|
3365
3735
|
return rows.map((r) => r.category);
|
|
@@ -3371,6 +3741,7 @@ var PmStore = class {
|
|
|
3371
3741
|
path: row.path ?? void 0,
|
|
3372
3742
|
claudeProjectKey: row.claude_project_key ?? void 0,
|
|
3373
3743
|
runtimescopeProject: row.runtimescope_project ?? void 0,
|
|
3744
|
+
runtimeProjectId: row.runtime_project_id ?? void 0,
|
|
3374
3745
|
runtimeApps: row.runtime_apps ? JSON.parse(row.runtime_apps) : void 0,
|
|
3375
3746
|
phase: row.phase,
|
|
3376
3747
|
managementAuthorized: row.management_authorized === 1,
|
|
@@ -3668,6 +4039,7 @@ var PmStore = class {
|
|
|
3668
4039
|
p.category,
|
|
3669
4040
|
p.sdk_installed,
|
|
3670
4041
|
p.runtimescope_project,
|
|
4042
|
+
p.runtime_apps,
|
|
3671
4043
|
COUNT(s.id) as session_count,
|
|
3672
4044
|
COALESCE(SUM(s.cost_microdollars), 0) as total_cost,
|
|
3673
4045
|
COALESCE(SUM(s.active_minutes), 0) as total_active_minutes,
|
|
@@ -4206,13 +4578,13 @@ async function parseSessionJsonl(jsonlPath, sessionId, projectId) {
|
|
|
4206
4578
|
|
|
4207
4579
|
// src/pm/project-discovery.ts
|
|
4208
4580
|
import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
4209
|
-
import { join as
|
|
4581
|
+
import { join as join4, basename as basename2 } from "path";
|
|
4210
4582
|
import { existsSync as existsSync5 } from "fs";
|
|
4211
4583
|
import { homedir as homedir3 } from "os";
|
|
4212
4584
|
var LOG_PREFIX = "[RuntimeScope PM]";
|
|
4213
4585
|
async function detectSdkInstalled(projectPath) {
|
|
4214
4586
|
try {
|
|
4215
|
-
const pkgPath =
|
|
4587
|
+
const pkgPath = join4(projectPath, "package.json");
|
|
4216
4588
|
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
4217
4589
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4218
4590
|
if ("@runtimescope/sdk" in allDeps || "@runtimescope/server-sdk" in allDeps) {
|
|
@@ -4222,13 +4594,13 @@ async function detectSdkInstalled(projectPath) {
|
|
|
4222
4594
|
const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages ?? [];
|
|
4223
4595
|
for (const ws of workspaces) {
|
|
4224
4596
|
const wsBase = ws.replace(/\/?\*$/, "");
|
|
4225
|
-
const wsDir =
|
|
4597
|
+
const wsDir = join4(projectPath, wsBase);
|
|
4226
4598
|
try {
|
|
4227
4599
|
const entries = await readdir2(wsDir, { withFileTypes: true });
|
|
4228
4600
|
for (const entry of entries) {
|
|
4229
4601
|
if (!entry.isDirectory()) continue;
|
|
4230
4602
|
try {
|
|
4231
|
-
const wsPkg = JSON.parse(await readFile2(
|
|
4603
|
+
const wsPkg = JSON.parse(await readFile2(join4(wsDir, entry.name, "package.json"), "utf-8"));
|
|
4232
4604
|
const wsDeps = { ...wsPkg.dependencies, ...wsPkg.devDependencies };
|
|
4233
4605
|
if ("@runtimescope/sdk" in wsDeps || "@runtimescope/server-sdk" in wsDeps) {
|
|
4234
4606
|
return true;
|
|
@@ -4243,7 +4615,7 @@ async function detectSdkInstalled(projectPath) {
|
|
|
4243
4615
|
} catch {
|
|
4244
4616
|
}
|
|
4245
4617
|
try {
|
|
4246
|
-
await stat2(
|
|
4618
|
+
await stat2(join4(projectPath, "node_modules", "@runtimescope"));
|
|
4247
4619
|
return true;
|
|
4248
4620
|
} catch {
|
|
4249
4621
|
return false;
|
|
@@ -4286,7 +4658,7 @@ function resolvePathSegments(parts) {
|
|
|
4286
4658
|
}
|
|
4287
4659
|
for (let count = remaining.length; count >= 1; count--) {
|
|
4288
4660
|
const segment = remaining.slice(0, count).join("-");
|
|
4289
|
-
const candidate =
|
|
4661
|
+
const candidate = join4(prefix, segment);
|
|
4290
4662
|
if (count === remaining.length) {
|
|
4291
4663
|
if (existsSync5(candidate)) return candidate;
|
|
4292
4664
|
} else {
|
|
@@ -4313,7 +4685,7 @@ var ProjectDiscovery = class {
|
|
|
4313
4685
|
constructor(pmStore, projectManager, claudeBaseDir) {
|
|
4314
4686
|
this.pmStore = pmStore;
|
|
4315
4687
|
this.projectManager = projectManager;
|
|
4316
|
-
this.claudeBaseDir = claudeBaseDir ??
|
|
4688
|
+
this.claudeBaseDir = claudeBaseDir ?? join4(homedir3(), ".claude");
|
|
4317
4689
|
}
|
|
4318
4690
|
claudeBaseDir;
|
|
4319
4691
|
/**
|
|
@@ -4346,7 +4718,7 @@ var ProjectDiscovery = class {
|
|
|
4346
4718
|
sessionsUpdated: 0,
|
|
4347
4719
|
errors: []
|
|
4348
4720
|
};
|
|
4349
|
-
const projectsDir =
|
|
4721
|
+
const projectsDir = join4(this.claudeBaseDir, "projects");
|
|
4350
4722
|
try {
|
|
4351
4723
|
await stat2(projectsDir);
|
|
4352
4724
|
} catch {
|
|
@@ -4399,9 +4771,14 @@ var ProjectDiscovery = class {
|
|
|
4399
4771
|
const sourcePath = existing?.path ?? projectDir;
|
|
4400
4772
|
const sdkInstalled = await detectSdkInstalled(sourcePath);
|
|
4401
4773
|
if (existing) {
|
|
4774
|
+
const apps = existing.runtimeApps ?? [];
|
|
4775
|
+
if (!apps.some((a) => a.toLowerCase() === projectName.toLowerCase())) {
|
|
4776
|
+
apps.push(projectName);
|
|
4777
|
+
}
|
|
4402
4778
|
const updated = {
|
|
4403
4779
|
...existing,
|
|
4404
4780
|
runtimescopeProject: projectName,
|
|
4781
|
+
runtimeApps: apps,
|
|
4405
4782
|
sdkInstalled: sdkInstalled || existing.sdkInstalled,
|
|
4406
4783
|
updatedAt: now
|
|
4407
4784
|
};
|
|
@@ -4414,6 +4791,7 @@ var ProjectDiscovery = class {
|
|
|
4414
4791
|
name: projectName,
|
|
4415
4792
|
path: fsPath,
|
|
4416
4793
|
runtimescopeProject: projectName,
|
|
4794
|
+
runtimeApps: [projectName],
|
|
4417
4795
|
phase: "application_development",
|
|
4418
4796
|
managementAuthorized: false,
|
|
4419
4797
|
probableToComplete: true,
|
|
@@ -4452,10 +4830,10 @@ var ProjectDiscovery = class {
|
|
|
4452
4830
|
if (!project.claudeProjectKey) {
|
|
4453
4831
|
return 0;
|
|
4454
4832
|
}
|
|
4455
|
-
const projectDir =
|
|
4833
|
+
const projectDir = join4(this.claudeBaseDir, "projects", project.claudeProjectKey);
|
|
4456
4834
|
let sessionsIndexed = 0;
|
|
4457
4835
|
try {
|
|
4458
|
-
const indexPath =
|
|
4836
|
+
const indexPath = join4(projectDir, "sessions-index.json");
|
|
4459
4837
|
let indexEntries = null;
|
|
4460
4838
|
try {
|
|
4461
4839
|
const indexContent = await readFile2(indexPath, "utf-8");
|
|
@@ -4468,7 +4846,7 @@ var ProjectDiscovery = class {
|
|
|
4468
4846
|
for (const jsonlFile of jsonlFiles) {
|
|
4469
4847
|
try {
|
|
4470
4848
|
const sessionId = jsonlFile.replace(".jsonl", "");
|
|
4471
|
-
const jsonlPath =
|
|
4849
|
+
const jsonlPath = join4(projectDir, jsonlFile);
|
|
4472
4850
|
const fileStat = await stat2(jsonlPath);
|
|
4473
4851
|
const fileSize = fileStat.size;
|
|
4474
4852
|
const existingSession = await this.pmStore.getSession(sessionId);
|
|
@@ -4503,7 +4881,7 @@ var ProjectDiscovery = class {
|
|
|
4503
4881
|
* Process a single Claude project directory key.
|
|
4504
4882
|
*/
|
|
4505
4883
|
async processClaudeProject(key, result) {
|
|
4506
|
-
const projectDir =
|
|
4884
|
+
const projectDir = join4(this.claudeBaseDir, "projects", key);
|
|
4507
4885
|
let fsPath = decodeClaudeKey(key);
|
|
4508
4886
|
if (!fsPath) {
|
|
4509
4887
|
fsPath = await this.resolvePathFromIndex(projectDir);
|
|
@@ -4555,7 +4933,7 @@ var ProjectDiscovery = class {
|
|
|
4555
4933
|
*/
|
|
4556
4934
|
async resolvePathFromIndex(projectDir) {
|
|
4557
4935
|
try {
|
|
4558
|
-
const indexPath =
|
|
4936
|
+
const indexPath = join4(projectDir, "sessions-index.json");
|
|
4559
4937
|
const content = await readFile2(indexPath, "utf-8");
|
|
4560
4938
|
const index = JSON.parse(content);
|
|
4561
4939
|
const entry = index.entries?.find((e) => e.projectPath);
|
|
@@ -4570,11 +4948,11 @@ var ProjectDiscovery = class {
|
|
|
4570
4948
|
*/
|
|
4571
4949
|
async indexSessionsForClaudeProject(projectId, claudeKey) {
|
|
4572
4950
|
const counts = { discovered: 0, updated: 0 };
|
|
4573
|
-
const projectDir =
|
|
4951
|
+
const projectDir = join4(this.claudeBaseDir, "projects", claudeKey);
|
|
4574
4952
|
try {
|
|
4575
4953
|
let indexEntries = null;
|
|
4576
4954
|
try {
|
|
4577
|
-
const indexPath =
|
|
4955
|
+
const indexPath = join4(projectDir, "sessions-index.json");
|
|
4578
4956
|
const indexContent = await readFile2(indexPath, "utf-8");
|
|
4579
4957
|
const index = JSON.parse(indexContent);
|
|
4580
4958
|
indexEntries = index.entries ?? [];
|
|
@@ -4585,7 +4963,7 @@ var ProjectDiscovery = class {
|
|
|
4585
4963
|
for (const jsonlFile of jsonlFiles) {
|
|
4586
4964
|
try {
|
|
4587
4965
|
const sessionId = jsonlFile.replace(".jsonl", "");
|
|
4588
|
-
const jsonlPath =
|
|
4966
|
+
const jsonlPath = join4(projectDir, jsonlFile);
|
|
4589
4967
|
const fileStat = await stat2(jsonlPath);
|
|
4590
4968
|
const fileSize = fileStat.size;
|
|
4591
4969
|
const existingSession = await this.pmStore.getSession(sessionId);
|
|
@@ -4766,6 +5144,9 @@ export {
|
|
|
4766
5144
|
__require,
|
|
4767
5145
|
RingBuffer,
|
|
4768
5146
|
EventStore,
|
|
5147
|
+
generateProjectId,
|
|
5148
|
+
isValidProjectId,
|
|
5149
|
+
getOrCreateProjectId,
|
|
4769
5150
|
SqliteStore,
|
|
4770
5151
|
isSqliteAvailable,
|
|
4771
5152
|
SessionRateLimiter,
|
|
@@ -4777,6 +5158,12 @@ export {
|
|
|
4777
5158
|
generateApiKey,
|
|
4778
5159
|
BUILT_IN_RULES,
|
|
4779
5160
|
Redactor,
|
|
5161
|
+
getPidsOnPort,
|
|
5162
|
+
getListenPorts,
|
|
5163
|
+
getProcessCwd,
|
|
5164
|
+
findPidsInDirectory,
|
|
5165
|
+
parseProcessList,
|
|
5166
|
+
getProcessMemoryMB,
|
|
4780
5167
|
SessionManager,
|
|
4781
5168
|
HttpServer,
|
|
4782
5169
|
PmStore,
|
|
@@ -4785,4 +5172,4 @@ export {
|
|
|
4785
5172
|
parseSessionJsonl,
|
|
4786
5173
|
ProjectDiscovery
|
|
4787
5174
|
};
|
|
4788
|
-
//# sourceMappingURL=chunk-
|
|
5175
|
+
//# sourceMappingURL=chunk-HZWALDZM.js.map
|