@runtimescope/collector 0.8.0 → 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-BKRGXAJB.js → chunk-HZWALDZM.js} +414 -59
- package/dist/chunk-HZWALDZM.js.map +1 -0
- package/dist/index.d.ts +71 -4
- 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 +1 -1
- package/dist/chunk-BKRGXAJB.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,6 +244,7 @@ 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;
|
|
@@ -238,6 +256,7 @@ var EventStore = class {
|
|
|
238
256
|
return this.buffer.query((e) => {
|
|
239
257
|
if (e.eventType !== "ui") return false;
|
|
240
258
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
259
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
241
260
|
const ue = e;
|
|
242
261
|
if (ue.timestamp < since) return false;
|
|
243
262
|
if (filter.action && ue.action !== filter.action) return false;
|
|
@@ -252,6 +271,7 @@ var EventStore = class {
|
|
|
252
271
|
const results = this.buffer.query((e) => {
|
|
253
272
|
if (e.eventType !== eventType) return false;
|
|
254
273
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
274
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
255
275
|
if (e.timestamp < since) return false;
|
|
256
276
|
if (filter.url) {
|
|
257
277
|
const re = e;
|
|
@@ -266,6 +286,7 @@ var EventStore = class {
|
|
|
266
286
|
return this.buffer.query((e) => {
|
|
267
287
|
if (e.eventType !== eventType) return false;
|
|
268
288
|
if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
|
|
289
|
+
if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
|
|
269
290
|
if (e.timestamp < since) return false;
|
|
270
291
|
if (filter.url) {
|
|
271
292
|
const re = e;
|
|
@@ -306,6 +327,32 @@ var EventStore = class {
|
|
|
306
327
|
}
|
|
307
328
|
};
|
|
308
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
|
+
|
|
309
356
|
// src/sqlite-store.ts
|
|
310
357
|
import { renameSync, existsSync } from "fs";
|
|
311
358
|
import { createRequire } from "module";
|
|
@@ -995,7 +1042,7 @@ var CollectorServer = class {
|
|
|
995
1042
|
console.error(`[RuntimeScope] Session ${clientInfo.sessionId} disconnected`);
|
|
996
1043
|
for (const cb of this.disconnectCallbacks) {
|
|
997
1044
|
try {
|
|
998
|
-
cb(clientInfo.sessionId, clientInfo.projectName);
|
|
1045
|
+
cb(clientInfo.sessionId, clientInfo.projectName, clientInfo.projectId);
|
|
999
1046
|
} catch {
|
|
1000
1047
|
}
|
|
1001
1048
|
}
|
|
@@ -1027,9 +1074,11 @@ var CollectorServer = class {
|
|
|
1027
1074
|
this.pendingHandshakes.delete(ws);
|
|
1028
1075
|
}
|
|
1029
1076
|
const projectName = payload.appName;
|
|
1077
|
+
const projectId = payload.projectId ?? (this.projectManager ? getOrCreateProjectId(this.projectManager, projectName) : void 0);
|
|
1030
1078
|
this.clients.set(ws, {
|
|
1031
1079
|
sessionId: payload.sessionId,
|
|
1032
|
-
projectName
|
|
1080
|
+
projectName,
|
|
1081
|
+
projectId
|
|
1033
1082
|
});
|
|
1034
1083
|
const sqliteStore = this.ensureSqliteStore(projectName);
|
|
1035
1084
|
if (sqliteStore) {
|
|
@@ -1050,6 +1099,7 @@ var CollectorServer = class {
|
|
|
1050
1099
|
timestamp: msg.timestamp,
|
|
1051
1100
|
eventType: "session",
|
|
1052
1101
|
appName: payload.appName,
|
|
1102
|
+
projectId,
|
|
1053
1103
|
connectedAt: msg.timestamp,
|
|
1054
1104
|
sdkVersion: payload.sdkVersion
|
|
1055
1105
|
});
|
|
@@ -1058,7 +1108,7 @@ var CollectorServer = class {
|
|
|
1058
1108
|
);
|
|
1059
1109
|
for (const cb of this.connectCallbacks) {
|
|
1060
1110
|
try {
|
|
1061
|
-
cb(payload.sessionId, projectName);
|
|
1111
|
+
cb(payload.sessionId, projectName, projectId);
|
|
1062
1112
|
} catch {
|
|
1063
1113
|
}
|
|
1064
1114
|
}
|
|
@@ -1117,7 +1167,7 @@ var CollectorServer = class {
|
|
|
1117
1167
|
getConnectedSessions() {
|
|
1118
1168
|
const sessions = [];
|
|
1119
1169
|
for (const [, info] of this.clients) {
|
|
1120
|
-
sessions.push({ sessionId: info.sessionId, projectName: info.projectName });
|
|
1170
|
+
sessions.push({ sessionId: info.sessionId, projectName: info.projectName, projectId: info.projectId });
|
|
1121
1171
|
}
|
|
1122
1172
|
return sessions;
|
|
1123
1173
|
}
|
|
@@ -1286,6 +1336,29 @@ var ProjectManager = class {
|
|
|
1286
1336
|
projectExists(projectName) {
|
|
1287
1337
|
return existsSync2(this.getProjectDir(projectName));
|
|
1288
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
|
+
}
|
|
1289
1362
|
// --- Environment variable resolution ---
|
|
1290
1363
|
resolveEnvVars(value) {
|
|
1291
1364
|
return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
@@ -1340,7 +1413,7 @@ var ProjectManager = class {
|
|
|
1340
1413
|
};
|
|
1341
1414
|
|
|
1342
1415
|
// src/auth.ts
|
|
1343
|
-
import { randomBytes, timingSafeEqual } from "crypto";
|
|
1416
|
+
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
1344
1417
|
var AuthManager = class {
|
|
1345
1418
|
keys = /* @__PURE__ */ new Map();
|
|
1346
1419
|
enabled;
|
|
@@ -1387,7 +1460,7 @@ var AuthManager = class {
|
|
|
1387
1460
|
};
|
|
1388
1461
|
function generateApiKey(label, project) {
|
|
1389
1462
|
return {
|
|
1390
|
-
key:
|
|
1463
|
+
key: randomBytes2(32).toString("hex"),
|
|
1391
1464
|
label,
|
|
1392
1465
|
project,
|
|
1393
1466
|
createdAt: Date.now()
|
|
@@ -1500,6 +1573,198 @@ var Redactor = class {
|
|
|
1500
1573
|
}
|
|
1501
1574
|
};
|
|
1502
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
|
+
|
|
1503
1768
|
// src/session-manager.ts
|
|
1504
1769
|
var SessionManager = class {
|
|
1505
1770
|
projectManager;
|
|
@@ -1642,9 +1907,9 @@ import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
|
1642
1907
|
// src/pm/pm-routes.ts
|
|
1643
1908
|
import { readdir, readFile, writeFile, unlink, mkdir } from "fs/promises";
|
|
1644
1909
|
import { existsSync as existsSync3 } from "fs";
|
|
1645
|
-
import { join as
|
|
1910
|
+
import { join as join3 } from "path";
|
|
1646
1911
|
import { homedir as homedir2 } from "os";
|
|
1647
|
-
import { spawn,
|
|
1912
|
+
import { spawn, execFileSync as execFileSync2 } from "child_process";
|
|
1648
1913
|
var LOG_RING_SIZE = 500;
|
|
1649
1914
|
var managedProcesses = /* @__PURE__ */ new Map();
|
|
1650
1915
|
function pushLog(mp, stream, line) {
|
|
@@ -1954,13 +2219,13 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
1954
2219
|
helpers.json(res, { data: [], count: 0 });
|
|
1955
2220
|
return;
|
|
1956
2221
|
}
|
|
1957
|
-
const memoryDir =
|
|
2222
|
+
const memoryDir = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
|
|
1958
2223
|
try {
|
|
1959
2224
|
const files = await readdir(memoryDir);
|
|
1960
2225
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
1961
2226
|
const result = await Promise.all(
|
|
1962
2227
|
mdFiles.map(async (filename) => {
|
|
1963
|
-
const content = await readFile(
|
|
2228
|
+
const content = await readFile(join3(memoryDir, filename), "utf-8");
|
|
1964
2229
|
return { filename, content, sizeBytes: Buffer.byteLength(content) };
|
|
1965
2230
|
})
|
|
1966
2231
|
);
|
|
@@ -1977,7 +2242,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
1977
2242
|
helpers.json(res, { error: "Project not found" }, 404);
|
|
1978
2243
|
return;
|
|
1979
2244
|
}
|
|
1980
|
-
const filePath =
|
|
2245
|
+
const filePath = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
|
|
1981
2246
|
try {
|
|
1982
2247
|
const content = await readFile(filePath, "utf-8");
|
|
1983
2248
|
helpers.json(res, { filename, content, sizeBytes: Buffer.byteLength(content) });
|
|
@@ -2000,9 +2265,9 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2000
2265
|
}
|
|
2001
2266
|
try {
|
|
2002
2267
|
const { content } = JSON.parse(body);
|
|
2003
|
-
const memoryDir =
|
|
2268
|
+
const memoryDir = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
|
|
2004
2269
|
await mkdir(memoryDir, { recursive: true });
|
|
2005
|
-
await writeFile(
|
|
2270
|
+
await writeFile(join3(memoryDir, filename), content, "utf-8");
|
|
2006
2271
|
helpers.json(res, { ok: true });
|
|
2007
2272
|
} catch (err) {
|
|
2008
2273
|
helpers.json(res, { error: err.message }, 500);
|
|
@@ -2016,7 +2281,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2016
2281
|
helpers.json(res, { error: "Project not found" }, 404);
|
|
2017
2282
|
return;
|
|
2018
2283
|
}
|
|
2019
|
-
const filePath =
|
|
2284
|
+
const filePath = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
|
|
2020
2285
|
try {
|
|
2021
2286
|
await unlink(filePath);
|
|
2022
2287
|
helpers.json(res, { ok: true });
|
|
@@ -2076,7 +2341,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2076
2341
|
const { content } = JSON.parse(body);
|
|
2077
2342
|
const paths = getRulesPaths(project.claudeProjectKey, project.path);
|
|
2078
2343
|
const filePath = paths[scope];
|
|
2079
|
-
const dir =
|
|
2344
|
+
const dir = join3(filePath, "..");
|
|
2080
2345
|
await mkdir(dir, { recursive: true });
|
|
2081
2346
|
await writeFile(filePath, content, "utf-8");
|
|
2082
2347
|
helpers.json(res, { ok: true });
|
|
@@ -2096,7 +2361,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2096
2361
|
return;
|
|
2097
2362
|
}
|
|
2098
2363
|
try {
|
|
2099
|
-
const pkgPath =
|
|
2364
|
+
const pkgPath = join3(project.path, "package.json");
|
|
2100
2365
|
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
2101
2366
|
const scripts = pkg.scripts ?? {};
|
|
2102
2367
|
const recommended = ["dev", "start", "serve"].find((s) => s in scripts) ?? null;
|
|
@@ -2266,15 +2531,8 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
|
|
|
2266
2531
|
}
|
|
2267
2532
|
managedProcesses.delete(id);
|
|
2268
2533
|
} else if (project.path) {
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
`lsof -t +D "${project.path}" 2>/dev/null | head -5`,
|
|
2272
|
-
{ encoding: "utf-8", timeout: 5e3 }
|
|
2273
|
-
).trim();
|
|
2274
|
-
const pids = output.split("\n").filter(Boolean).map(Number).filter((n) => n > 1 && n !== process.pid);
|
|
2275
|
-
if (pids.length > 0) pid = pids[0];
|
|
2276
|
-
} catch {
|
|
2277
|
-
}
|
|
2534
|
+
const pids = findPidsInDirectory(project.path).filter((n) => n > 1 && n !== process.pid);
|
|
2535
|
+
if (pids.length > 0) pid = pids[0];
|
|
2278
2536
|
}
|
|
2279
2537
|
if (!pid) {
|
|
2280
2538
|
helpers.json(res, { error: "No running dev server found for this project" }, 404);
|
|
@@ -2529,17 +2787,17 @@ function sanitizeFilename(name) {
|
|
|
2529
2787
|
function getRulesPaths(claudeProjectKey, projectPath) {
|
|
2530
2788
|
const home = homedir2();
|
|
2531
2789
|
return {
|
|
2532
|
-
global:
|
|
2533
|
-
project: claudeProjectKey ?
|
|
2534
|
-
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")
|
|
2535
2793
|
};
|
|
2536
2794
|
}
|
|
2537
2795
|
function execGit(args, cwd) {
|
|
2538
|
-
return
|
|
2796
|
+
return execFileSync2("git", args, { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 10 * 1024 * 1024 });
|
|
2539
2797
|
}
|
|
2540
2798
|
function isGitRepo(path) {
|
|
2541
2799
|
try {
|
|
2542
|
-
|
|
2800
|
+
execFileSync2("git", ["rev-parse", "--git-dir"], { cwd: path, encoding: "utf-8", timeout: 3e3 });
|
|
2543
2801
|
return true;
|
|
2544
2802
|
} catch {
|
|
2545
2803
|
return false;
|
|
@@ -2602,6 +2860,8 @@ var HttpServer = class {
|
|
|
2602
2860
|
activePort = 9091;
|
|
2603
2861
|
startedAt = Date.now();
|
|
2604
2862
|
connectedSessionsGetter = null;
|
|
2863
|
+
pmStore = null;
|
|
2864
|
+
projectManager = null;
|
|
2605
2865
|
constructor(store, processMonitor, options) {
|
|
2606
2866
|
this.store = store;
|
|
2607
2867
|
this.processMonitor = processMonitor ?? null;
|
|
@@ -2609,6 +2869,8 @@ var HttpServer = class {
|
|
|
2609
2869
|
this.allowedOrigins = options?.allowedOrigins ?? null;
|
|
2610
2870
|
this.rateLimiter = options?.rateLimiter ?? null;
|
|
2611
2871
|
this.connectedSessionsGetter = options?.getConnectedSessions ?? null;
|
|
2872
|
+
this.pmStore = options?.pmStore ?? null;
|
|
2873
|
+
this.projectManager = options?.projectManager ?? null;
|
|
2612
2874
|
this.registerRoutes();
|
|
2613
2875
|
if (options?.pmStore && options?.discovery) {
|
|
2614
2876
|
this.pmRouter = createPmRouter(options.pmStore, options.discovery, {
|
|
@@ -2640,12 +2902,14 @@ var HttpServer = class {
|
|
|
2640
2902
|
existing.sessions.push(s.sessionId);
|
|
2641
2903
|
existing.eventCount += s.eventCount;
|
|
2642
2904
|
if (s.isConnected) existing.isConnected = true;
|
|
2905
|
+
if (!existing.projectId && s.projectId) existing.projectId = s.projectId;
|
|
2643
2906
|
} else {
|
|
2644
2907
|
projectMap.set(s.appName, {
|
|
2645
2908
|
appName: s.appName,
|
|
2646
2909
|
sessions: [s.sessionId],
|
|
2647
2910
|
isConnected: s.isConnected,
|
|
2648
|
-
eventCount: s.eventCount
|
|
2911
|
+
eventCount: s.eventCount,
|
|
2912
|
+
projectId: s.projectId
|
|
2649
2913
|
});
|
|
2650
2914
|
}
|
|
2651
2915
|
}
|
|
@@ -2715,7 +2979,8 @@ var HttpServer = class {
|
|
|
2715
2979
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2716
2980
|
urlPattern: params.get("url_pattern") ?? void 0,
|
|
2717
2981
|
method: params.get("method") ?? void 0,
|
|
2718
|
-
sessionId: params.get("session_id") ?? void 0
|
|
2982
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
2983
|
+
projectId: params.get("project_id") ?? void 0
|
|
2719
2984
|
});
|
|
2720
2985
|
this.json(res, { data: events, count: events.length });
|
|
2721
2986
|
});
|
|
@@ -2724,7 +2989,8 @@ var HttpServer = class {
|
|
|
2724
2989
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2725
2990
|
level: params.get("level") ?? void 0,
|
|
2726
2991
|
search: params.get("search") ?? void 0,
|
|
2727
|
-
sessionId: params.get("session_id") ?? void 0
|
|
2992
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
2993
|
+
projectId: params.get("project_id") ?? void 0
|
|
2728
2994
|
});
|
|
2729
2995
|
this.json(res, { data: events, count: events.length });
|
|
2730
2996
|
});
|
|
@@ -2732,7 +2998,8 @@ var HttpServer = class {
|
|
|
2732
2998
|
const events = this.store.getStateEvents({
|
|
2733
2999
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2734
3000
|
storeId: params.get("store_id") ?? void 0,
|
|
2735
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3001
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3002
|
+
projectId: params.get("project_id") ?? void 0
|
|
2736
3003
|
});
|
|
2737
3004
|
this.json(res, { data: events, count: events.length });
|
|
2738
3005
|
});
|
|
@@ -2740,7 +3007,8 @@ var HttpServer = class {
|
|
|
2740
3007
|
const events = this.store.getRenderEvents({
|
|
2741
3008
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2742
3009
|
componentName: params.get("component") ?? void 0,
|
|
2743
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3010
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3011
|
+
projectId: params.get("project_id") ?? void 0
|
|
2744
3012
|
});
|
|
2745
3013
|
this.json(res, { data: events, count: events.length });
|
|
2746
3014
|
});
|
|
@@ -2748,7 +3016,8 @@ var HttpServer = class {
|
|
|
2748
3016
|
const events = this.store.getPerformanceMetrics({
|
|
2749
3017
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2750
3018
|
metricName: params.get("metric") ?? void 0,
|
|
2751
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3019
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3020
|
+
projectId: params.get("project_id") ?? void 0
|
|
2752
3021
|
});
|
|
2753
3022
|
this.json(res, { data: events, count: events.length });
|
|
2754
3023
|
});
|
|
@@ -2758,7 +3027,8 @@ var HttpServer = class {
|
|
|
2758
3027
|
table: params.get("table") ?? void 0,
|
|
2759
3028
|
minDurationMs: numParam(params, "min_duration_ms"),
|
|
2760
3029
|
search: params.get("search") ?? void 0,
|
|
2761
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3030
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3031
|
+
projectId: params.get("project_id") ?? void 0
|
|
2762
3032
|
});
|
|
2763
3033
|
this.json(res, { data: events, count: events.length });
|
|
2764
3034
|
});
|
|
@@ -2767,7 +3037,8 @@ var HttpServer = class {
|
|
|
2767
3037
|
const events = this.store.getEventTimeline({
|
|
2768
3038
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2769
3039
|
eventTypes,
|
|
2770
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3040
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3041
|
+
projectId: params.get("project_id") ?? void 0
|
|
2771
3042
|
});
|
|
2772
3043
|
this.json(res, { data: events, count: events.length });
|
|
2773
3044
|
});
|
|
@@ -2775,7 +3046,8 @@ var HttpServer = class {
|
|
|
2775
3046
|
const events = this.store.getCustomEvents({
|
|
2776
3047
|
name: params.get("name") ?? void 0,
|
|
2777
3048
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2778
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3049
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3050
|
+
projectId: params.get("project_id") ?? void 0
|
|
2779
3051
|
});
|
|
2780
3052
|
this.json(res, { data: events, count: events.length });
|
|
2781
3053
|
});
|
|
@@ -2784,7 +3056,8 @@ var HttpServer = class {
|
|
|
2784
3056
|
const events = this.store.getUIInteractions({
|
|
2785
3057
|
action: action ?? void 0,
|
|
2786
3058
|
sinceSeconds: numParam(params, "since_seconds"),
|
|
2787
|
-
sessionId: params.get("session_id") ?? void 0
|
|
3059
|
+
sessionId: params.get("session_id") ?? void 0,
|
|
3060
|
+
projectId: params.get("project_id") ?? void 0
|
|
2788
3061
|
});
|
|
2789
3062
|
this.json(res, { data: events, count: events.length });
|
|
2790
3063
|
});
|
|
@@ -2806,6 +3079,7 @@ var HttpServer = class {
|
|
|
2806
3079
|
return;
|
|
2807
3080
|
}
|
|
2808
3081
|
const payload = parsed;
|
|
3082
|
+
const projectId = typeof payload.projectId === "string" ? payload.projectId : payload.appName && this.projectManager ? getOrCreateProjectId(this.projectManager, payload.appName) : void 0;
|
|
2809
3083
|
if (!payload.sessionId || !Array.isArray(payload.events) || payload.events.length === 0) {
|
|
2810
3084
|
this.json(res, {
|
|
2811
3085
|
error: "Required: sessionId (string), events (non-empty array)",
|
|
@@ -2822,9 +3096,16 @@ var HttpServer = class {
|
|
|
2822
3096
|
timestamp: Date.now(),
|
|
2823
3097
|
eventType: "session",
|
|
2824
3098
|
appName: payload.appName,
|
|
3099
|
+
projectId,
|
|
2825
3100
|
connectedAt: Date.now(),
|
|
2826
3101
|
sdkVersion: payload.sdkVersion ?? "http"
|
|
2827
3102
|
});
|
|
3103
|
+
if (this.pmStore) {
|
|
3104
|
+
try {
|
|
3105
|
+
this.pmStore.autoLinkApp(payload.appName, projectId);
|
|
3106
|
+
} catch {
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
2828
3109
|
}
|
|
2829
3110
|
const VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
2830
3111
|
"network",
|
|
@@ -3297,6 +3578,10 @@ var PmStore = class {
|
|
|
3297
3578
|
this.db.exec("ALTER TABLE pm_projects ADD COLUMN runtime_apps TEXT DEFAULT NULL");
|
|
3298
3579
|
} catch {
|
|
3299
3580
|
}
|
|
3581
|
+
try {
|
|
3582
|
+
this.db.exec("ALTER TABLE pm_projects ADD COLUMN runtime_project_id TEXT DEFAULT NULL");
|
|
3583
|
+
} catch {
|
|
3584
|
+
}
|
|
3300
3585
|
}
|
|
3301
3586
|
// ============================================================
|
|
3302
3587
|
// Projects
|
|
@@ -3382,6 +3667,10 @@ var PmStore = class {
|
|
|
3382
3667
|
sets.push("runtimescope_project = ?");
|
|
3383
3668
|
params.push(updates.runtimescopeProject ?? null);
|
|
3384
3669
|
}
|
|
3670
|
+
if (updates.runtimeProjectId !== void 0) {
|
|
3671
|
+
sets.push("runtime_project_id = ?");
|
|
3672
|
+
params.push(updates.runtimeProjectId ?? null);
|
|
3673
|
+
}
|
|
3385
3674
|
if (updates.metadata !== void 0) {
|
|
3386
3675
|
sets.push("metadata = ?");
|
|
3387
3676
|
params.push(JSON.stringify(updates.metadata));
|
|
@@ -3392,6 +3681,55 @@ var PmStore = class {
|
|
|
3392
3681
|
params.push(id);
|
|
3393
3682
|
this.db.prepare(`UPDATE pm_projects SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
3394
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
|
+
}
|
|
3395
3733
|
listCategories() {
|
|
3396
3734
|
const rows = this.db.prepare("SELECT DISTINCT category FROM pm_projects WHERE category IS NOT NULL ORDER BY category ASC").all();
|
|
3397
3735
|
return rows.map((r) => r.category);
|
|
@@ -3403,6 +3741,7 @@ var PmStore = class {
|
|
|
3403
3741
|
path: row.path ?? void 0,
|
|
3404
3742
|
claudeProjectKey: row.claude_project_key ?? void 0,
|
|
3405
3743
|
runtimescopeProject: row.runtimescope_project ?? void 0,
|
|
3744
|
+
runtimeProjectId: row.runtime_project_id ?? void 0,
|
|
3406
3745
|
runtimeApps: row.runtime_apps ? JSON.parse(row.runtime_apps) : void 0,
|
|
3407
3746
|
phase: row.phase,
|
|
3408
3747
|
managementAuthorized: row.management_authorized === 1,
|
|
@@ -3700,6 +4039,7 @@ var PmStore = class {
|
|
|
3700
4039
|
p.category,
|
|
3701
4040
|
p.sdk_installed,
|
|
3702
4041
|
p.runtimescope_project,
|
|
4042
|
+
p.runtime_apps,
|
|
3703
4043
|
COUNT(s.id) as session_count,
|
|
3704
4044
|
COALESCE(SUM(s.cost_microdollars), 0) as total_cost,
|
|
3705
4045
|
COALESCE(SUM(s.active_minutes), 0) as total_active_minutes,
|
|
@@ -4238,13 +4578,13 @@ async function parseSessionJsonl(jsonlPath, sessionId, projectId) {
|
|
|
4238
4578
|
|
|
4239
4579
|
// src/pm/project-discovery.ts
|
|
4240
4580
|
import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
4241
|
-
import { join as
|
|
4581
|
+
import { join as join4, basename as basename2 } from "path";
|
|
4242
4582
|
import { existsSync as existsSync5 } from "fs";
|
|
4243
4583
|
import { homedir as homedir3 } from "os";
|
|
4244
4584
|
var LOG_PREFIX = "[RuntimeScope PM]";
|
|
4245
4585
|
async function detectSdkInstalled(projectPath) {
|
|
4246
4586
|
try {
|
|
4247
|
-
const pkgPath =
|
|
4587
|
+
const pkgPath = join4(projectPath, "package.json");
|
|
4248
4588
|
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
4249
4589
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4250
4590
|
if ("@runtimescope/sdk" in allDeps || "@runtimescope/server-sdk" in allDeps) {
|
|
@@ -4254,13 +4594,13 @@ async function detectSdkInstalled(projectPath) {
|
|
|
4254
4594
|
const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages ?? [];
|
|
4255
4595
|
for (const ws of workspaces) {
|
|
4256
4596
|
const wsBase = ws.replace(/\/?\*$/, "");
|
|
4257
|
-
const wsDir =
|
|
4597
|
+
const wsDir = join4(projectPath, wsBase);
|
|
4258
4598
|
try {
|
|
4259
4599
|
const entries = await readdir2(wsDir, { withFileTypes: true });
|
|
4260
4600
|
for (const entry of entries) {
|
|
4261
4601
|
if (!entry.isDirectory()) continue;
|
|
4262
4602
|
try {
|
|
4263
|
-
const wsPkg = JSON.parse(await readFile2(
|
|
4603
|
+
const wsPkg = JSON.parse(await readFile2(join4(wsDir, entry.name, "package.json"), "utf-8"));
|
|
4264
4604
|
const wsDeps = { ...wsPkg.dependencies, ...wsPkg.devDependencies };
|
|
4265
4605
|
if ("@runtimescope/sdk" in wsDeps || "@runtimescope/server-sdk" in wsDeps) {
|
|
4266
4606
|
return true;
|
|
@@ -4275,7 +4615,7 @@ async function detectSdkInstalled(projectPath) {
|
|
|
4275
4615
|
} catch {
|
|
4276
4616
|
}
|
|
4277
4617
|
try {
|
|
4278
|
-
await stat2(
|
|
4618
|
+
await stat2(join4(projectPath, "node_modules", "@runtimescope"));
|
|
4279
4619
|
return true;
|
|
4280
4620
|
} catch {
|
|
4281
4621
|
return false;
|
|
@@ -4318,7 +4658,7 @@ function resolvePathSegments(parts) {
|
|
|
4318
4658
|
}
|
|
4319
4659
|
for (let count = remaining.length; count >= 1; count--) {
|
|
4320
4660
|
const segment = remaining.slice(0, count).join("-");
|
|
4321
|
-
const candidate =
|
|
4661
|
+
const candidate = join4(prefix, segment);
|
|
4322
4662
|
if (count === remaining.length) {
|
|
4323
4663
|
if (existsSync5(candidate)) return candidate;
|
|
4324
4664
|
} else {
|
|
@@ -4345,7 +4685,7 @@ var ProjectDiscovery = class {
|
|
|
4345
4685
|
constructor(pmStore, projectManager, claudeBaseDir) {
|
|
4346
4686
|
this.pmStore = pmStore;
|
|
4347
4687
|
this.projectManager = projectManager;
|
|
4348
|
-
this.claudeBaseDir = claudeBaseDir ??
|
|
4688
|
+
this.claudeBaseDir = claudeBaseDir ?? join4(homedir3(), ".claude");
|
|
4349
4689
|
}
|
|
4350
4690
|
claudeBaseDir;
|
|
4351
4691
|
/**
|
|
@@ -4378,7 +4718,7 @@ var ProjectDiscovery = class {
|
|
|
4378
4718
|
sessionsUpdated: 0,
|
|
4379
4719
|
errors: []
|
|
4380
4720
|
};
|
|
4381
|
-
const projectsDir =
|
|
4721
|
+
const projectsDir = join4(this.claudeBaseDir, "projects");
|
|
4382
4722
|
try {
|
|
4383
4723
|
await stat2(projectsDir);
|
|
4384
4724
|
} catch {
|
|
@@ -4431,9 +4771,14 @@ var ProjectDiscovery = class {
|
|
|
4431
4771
|
const sourcePath = existing?.path ?? projectDir;
|
|
4432
4772
|
const sdkInstalled = await detectSdkInstalled(sourcePath);
|
|
4433
4773
|
if (existing) {
|
|
4774
|
+
const apps = existing.runtimeApps ?? [];
|
|
4775
|
+
if (!apps.some((a) => a.toLowerCase() === projectName.toLowerCase())) {
|
|
4776
|
+
apps.push(projectName);
|
|
4777
|
+
}
|
|
4434
4778
|
const updated = {
|
|
4435
4779
|
...existing,
|
|
4436
4780
|
runtimescopeProject: projectName,
|
|
4781
|
+
runtimeApps: apps,
|
|
4437
4782
|
sdkInstalled: sdkInstalled || existing.sdkInstalled,
|
|
4438
4783
|
updatedAt: now
|
|
4439
4784
|
};
|
|
@@ -4446,6 +4791,7 @@ var ProjectDiscovery = class {
|
|
|
4446
4791
|
name: projectName,
|
|
4447
4792
|
path: fsPath,
|
|
4448
4793
|
runtimescopeProject: projectName,
|
|
4794
|
+
runtimeApps: [projectName],
|
|
4449
4795
|
phase: "application_development",
|
|
4450
4796
|
managementAuthorized: false,
|
|
4451
4797
|
probableToComplete: true,
|
|
@@ -4484,10 +4830,10 @@ var ProjectDiscovery = class {
|
|
|
4484
4830
|
if (!project.claudeProjectKey) {
|
|
4485
4831
|
return 0;
|
|
4486
4832
|
}
|
|
4487
|
-
const projectDir =
|
|
4833
|
+
const projectDir = join4(this.claudeBaseDir, "projects", project.claudeProjectKey);
|
|
4488
4834
|
let sessionsIndexed = 0;
|
|
4489
4835
|
try {
|
|
4490
|
-
const indexPath =
|
|
4836
|
+
const indexPath = join4(projectDir, "sessions-index.json");
|
|
4491
4837
|
let indexEntries = null;
|
|
4492
4838
|
try {
|
|
4493
4839
|
const indexContent = await readFile2(indexPath, "utf-8");
|
|
@@ -4500,7 +4846,7 @@ var ProjectDiscovery = class {
|
|
|
4500
4846
|
for (const jsonlFile of jsonlFiles) {
|
|
4501
4847
|
try {
|
|
4502
4848
|
const sessionId = jsonlFile.replace(".jsonl", "");
|
|
4503
|
-
const jsonlPath =
|
|
4849
|
+
const jsonlPath = join4(projectDir, jsonlFile);
|
|
4504
4850
|
const fileStat = await stat2(jsonlPath);
|
|
4505
4851
|
const fileSize = fileStat.size;
|
|
4506
4852
|
const existingSession = await this.pmStore.getSession(sessionId);
|
|
@@ -4535,7 +4881,7 @@ var ProjectDiscovery = class {
|
|
|
4535
4881
|
* Process a single Claude project directory key.
|
|
4536
4882
|
*/
|
|
4537
4883
|
async processClaudeProject(key, result) {
|
|
4538
|
-
const projectDir =
|
|
4884
|
+
const projectDir = join4(this.claudeBaseDir, "projects", key);
|
|
4539
4885
|
let fsPath = decodeClaudeKey(key);
|
|
4540
4886
|
if (!fsPath) {
|
|
4541
4887
|
fsPath = await this.resolvePathFromIndex(projectDir);
|
|
@@ -4587,7 +4933,7 @@ var ProjectDiscovery = class {
|
|
|
4587
4933
|
*/
|
|
4588
4934
|
async resolvePathFromIndex(projectDir) {
|
|
4589
4935
|
try {
|
|
4590
|
-
const indexPath =
|
|
4936
|
+
const indexPath = join4(projectDir, "sessions-index.json");
|
|
4591
4937
|
const content = await readFile2(indexPath, "utf-8");
|
|
4592
4938
|
const index = JSON.parse(content);
|
|
4593
4939
|
const entry = index.entries?.find((e) => e.projectPath);
|
|
@@ -4602,11 +4948,11 @@ var ProjectDiscovery = class {
|
|
|
4602
4948
|
*/
|
|
4603
4949
|
async indexSessionsForClaudeProject(projectId, claudeKey) {
|
|
4604
4950
|
const counts = { discovered: 0, updated: 0 };
|
|
4605
|
-
const projectDir =
|
|
4951
|
+
const projectDir = join4(this.claudeBaseDir, "projects", claudeKey);
|
|
4606
4952
|
try {
|
|
4607
4953
|
let indexEntries = null;
|
|
4608
4954
|
try {
|
|
4609
|
-
const indexPath =
|
|
4955
|
+
const indexPath = join4(projectDir, "sessions-index.json");
|
|
4610
4956
|
const indexContent = await readFile2(indexPath, "utf-8");
|
|
4611
4957
|
const index = JSON.parse(indexContent);
|
|
4612
4958
|
indexEntries = index.entries ?? [];
|
|
@@ -4617,7 +4963,7 @@ var ProjectDiscovery = class {
|
|
|
4617
4963
|
for (const jsonlFile of jsonlFiles) {
|
|
4618
4964
|
try {
|
|
4619
4965
|
const sessionId = jsonlFile.replace(".jsonl", "");
|
|
4620
|
-
const jsonlPath =
|
|
4966
|
+
const jsonlPath = join4(projectDir, jsonlFile);
|
|
4621
4967
|
const fileStat = await stat2(jsonlPath);
|
|
4622
4968
|
const fileSize = fileStat.size;
|
|
4623
4969
|
const existingSession = await this.pmStore.getSession(sessionId);
|
|
@@ -4798,6 +5144,9 @@ export {
|
|
|
4798
5144
|
__require,
|
|
4799
5145
|
RingBuffer,
|
|
4800
5146
|
EventStore,
|
|
5147
|
+
generateProjectId,
|
|
5148
|
+
isValidProjectId,
|
|
5149
|
+
getOrCreateProjectId,
|
|
4801
5150
|
SqliteStore,
|
|
4802
5151
|
isSqliteAvailable,
|
|
4803
5152
|
SessionRateLimiter,
|
|
@@ -4809,6 +5158,12 @@ export {
|
|
|
4809
5158
|
generateApiKey,
|
|
4810
5159
|
BUILT_IN_RULES,
|
|
4811
5160
|
Redactor,
|
|
5161
|
+
getPidsOnPort,
|
|
5162
|
+
getListenPorts,
|
|
5163
|
+
getProcessCwd,
|
|
5164
|
+
findPidsInDirectory,
|
|
5165
|
+
parseProcessList,
|
|
5166
|
+
getProcessMemoryMB,
|
|
4812
5167
|
SessionManager,
|
|
4813
5168
|
HttpServer,
|
|
4814
5169
|
PmStore,
|
|
@@ -4817,4 +5172,4 @@ export {
|
|
|
4817
5172
|
parseSessionJsonl,
|
|
4818
5173
|
ProjectDiscovery
|
|
4819
5174
|
};
|
|
4820
|
-
//# sourceMappingURL=chunk-
|
|
5175
|
+
//# sourceMappingURL=chunk-HZWALDZM.js.map
|