@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.
@@ -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 Database(options.dbPath);
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 Database(options.dbPath);
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 = createRequire(import.meta.url);
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: randomBytes(32).toString("hex"),
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 join2 } from "path";
1910
+ import { join as join3 } from "path";
1626
1911
  import { homedir as homedir2 } from "os";
1627
- import { spawn, execSync, execFileSync } from "child_process";
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 = join2(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
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(join2(memoryDir, filename), "utf-8");
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 = join2(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
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 = join2(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
2268
+ const memoryDir = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
1984
2269
  await mkdir(memoryDir, { recursive: true });
1985
- await writeFile(join2(memoryDir, filename), content, "utf-8");
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 = join2(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
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 = join2(filePath, "..");
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 = join2(project.path, "package.json");
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
- try {
2250
- const output = execSync(
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: join2(home, ".claude", "CLAUDE.md"),
2513
- project: claudeProjectKey ? join2(home, ".claude", "projects", claudeProjectKey, "CLAUDE.md") : join2(projectPath ?? "", ".claude", "CLAUDE.md"),
2514
- local: projectPath ? join2(projectPath, "CLAUDE.md") : join2(home, "CLAUDE.md")
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 execFileSync("git", args, { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 10 * 1024 * 1024 });
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
- execFileSync("git", ["rev-parse", "--git-dir"], { cwd: path, encoding: "utf-8", timeout: 3e3 });
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 Database2 from "better-sqlite3";
3440
+ import Database from "better-sqlite3";
3128
3441
  var PmStore = class {
3129
3442
  db;
3130
3443
  constructor(options) {
3131
- this.db = new Database2(options.dbPath);
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 join3, basename as basename2 } from "path";
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 = join3(projectPath, "package.json");
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 = join3(projectPath, wsBase);
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(join3(wsDir, entry.name, "package.json"), "utf-8"));
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(join3(projectPath, "node_modules", "@runtimescope"));
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 = join3(prefix, segment);
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 ?? join3(homedir3(), ".claude");
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 = join3(this.claudeBaseDir, "projects");
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 = join3(this.claudeBaseDir, "projects", project.claudeProjectKey);
4833
+ const projectDir = join4(this.claudeBaseDir, "projects", project.claudeProjectKey);
4456
4834
  let sessionsIndexed = 0;
4457
4835
  try {
4458
- const indexPath = join3(projectDir, "sessions-index.json");
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 = join3(projectDir, jsonlFile);
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 = join3(this.claudeBaseDir, "projects", key);
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 = join3(projectDir, "sessions-index.json");
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 = join3(this.claudeBaseDir, "projects", claudeKey);
4951
+ const projectDir = join4(this.claudeBaseDir, "projects", claudeKey);
4574
4952
  try {
4575
4953
  let indexEntries = null;
4576
4954
  try {
4577
- const indexPath = join3(projectDir, "sessions-index.json");
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 = join3(projectDir, jsonlFile);
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-TUFSIGGJ.js.map
5175
+ //# sourceMappingURL=chunk-HZWALDZM.js.map