monomind 2.0.3 → 2.1.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.
Files changed (89) hide show
  1. package/package.json +9 -5
  2. package/packages/@monomind/cli/.claude/helpers/control-start.cjs +13 -20
  3. package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
  4. package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
  5. package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
  6. package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +75 -18
  7. package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
  8. package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +16 -7
  9. package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
  10. package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +4 -4
  11. package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +40 -22
  12. package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
  13. package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
  14. package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +54 -4
  15. package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
  16. package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
  17. package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
  18. package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
  19. package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
  20. package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
  21. package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
  22. package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
  23. package/packages/@monomind/cli/bin/cli.js +10 -1
  24. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
  25. package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
  26. package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
  27. package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
  28. package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
  29. package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
  30. package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
  31. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
  32. package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
  33. package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
  34. package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
  35. package/packages/@monomind/cli/dist/src/commands/org.js +306 -129
  36. package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
  37. package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
  38. package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
  39. package/packages/@monomind/cli/dist/src/index.js +64 -28
  40. package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
  41. package/packages/@monomind/cli/dist/src/init/executor.js +63 -49
  42. package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
  43. package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
  44. package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
  45. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
  46. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
  47. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
  48. package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
  49. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +51 -34
  50. package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
  51. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
  52. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +65 -43
  53. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
  54. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
  55. package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
  56. package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
  57. package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
  58. package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
  59. package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
  60. package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
  61. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
  62. package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +24 -0
  63. package/packages/@monomind/cli/dist/src/orgrt/broker.js +68 -0
  64. package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +30 -0
  65. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +96 -7
  66. package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
  67. package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +105 -8
  68. package/packages/@monomind/cli/dist/src/orgrt/live.html +56 -0
  69. package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
  70. package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
  71. package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
  72. package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +8 -0
  73. package/packages/@monomind/cli/dist/src/orgrt/server.js +72 -0
  74. package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
  75. package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
  76. package/packages/@monomind/cli/dist/src/output.js +12 -5
  77. package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
  78. package/packages/@monomind/cli/dist/src/parser.js +130 -5
  79. package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
  80. package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
  81. package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
  82. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
  83. package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
  84. package/packages/@monomind/cli/dist/src/ui/dashboard.html +40 -40
  85. package/packages/@monomind/cli/dist/src/ui/orgs.html +110 -11
  86. package/packages/@monomind/cli/dist/src/ui/server.mjs +304 -133
  87. package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
  88. package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
  89. package/packages/@monomind/cli/package.json +6 -2
@@ -2,6 +2,7 @@ import http from 'http';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import os from 'os';
5
+ import crypto from 'crypto';
5
6
  import { fileURLToPath } from 'url';
6
7
  import { createRequire } from 'module';
7
8
  import { collectAll, getWatchPaths, collectProject, collectSessions, collectSwarm, collectSwarmHistory, appendSwarmHistory, collectSwarmEvents, getSwarmDataSize, cleanSwarmData, collectAgents, collectTokens, collectHooks, collectKnowledge, collectMetrics, collectMemory, collectMemoryFiles, collectSystem } from './collector.mjs';
@@ -242,12 +243,77 @@ async function _initRunDb(monoHome) {
242
243
  }
243
244
  }
244
245
 
246
+ // Single-writer guard for the run-events.db snapshot persist: bindServer() can start a
247
+ // SECOND dashboard instance on port+1 when 4242 is already taken (two projects' dashboards
248
+ // running simultaneously, or a stale-but-still-bound old instance). Each instance holds an
249
+ // independent in-memory sql.js copy; without a lock, each instance's periodic full-snapshot
250
+ // overwrite silently clobbers the other's accumulated events (last writer wins, no error).
251
+ // Claiming this lock before every snapshot write ensures only ONE live instance actually
252
+ // persists at a time — the other still runs, just skips its write until it can claim the
253
+ // lock (e.g. after the current holder exits and its lock goes stale). This doesn't merge
254
+ // both instances' events into one file, but it stops them from destructively overwriting
255
+ // each other. Pattern mirrors .claude/helpers/utils/fs-helpers.cjs's claimLock/releaseLock
256
+ // (wx-create, rename-to-reclaim-stale) — replicated inline here since this file ships
257
+ // standalone in the published package and can't depend on repo-local dev tooling.
258
+ function _runDbLockPath() {
259
+ return _runDbPath ? `${_runDbPath}.lock` : null;
260
+ }
261
+
262
+ function _claimRunDbLock(staleMs) {
263
+ const lockPath = _runDbLockPath();
264
+ if (!lockPath) return false;
265
+ staleMs = staleMs || 5000;
266
+ const tryCreate = () => {
267
+ try {
268
+ fs.writeFileSync(lockPath, String(process.pid), { flag: 'wx' });
269
+ return true;
270
+ } catch (_) {
271
+ return false;
272
+ }
273
+ };
274
+ if (tryCreate()) return true;
275
+ try {
276
+ const stat = fs.statSync(lockPath);
277
+ if (Date.now() - stat.mtimeMs < staleMs) return false; // held by a live/fresh owner
278
+ // Stale lock — reclaim via atomic rename so only one racing process wins it.
279
+ const claimed = `${lockPath}.${process.pid}.${Date.now()}.stale`;
280
+ try {
281
+ fs.renameSync(lockPath, claimed);
282
+ } catch (_) {
283
+ return false; // another process already reclaimed it
284
+ }
285
+ try { fs.unlinkSync(claimed); } catch (_) {}
286
+ return tryCreate();
287
+ } catch (_) {
288
+ // Lock vanished between our failed create and this stat — retry once.
289
+ return tryCreate();
290
+ }
291
+ }
292
+
293
+ function _releaseRunDbLock() {
294
+ const lockPath = _runDbLockPath();
295
+ if (!lockPath) return;
296
+ try {
297
+ if (Number(fs.readFileSync(lockPath, 'utf8')) === process.pid) fs.unlinkSync(lockPath);
298
+ } catch (_) {}
299
+ }
300
+
301
+ function _writeRunDbSnapshot() {
302
+ if (!_runDb || !_runDbPath) return;
303
+ if (!_claimRunDbLock()) return; // another live instance owns the write right now
304
+ try {
305
+ fs.writeFileSync(_runDbPath, Buffer.from(_runDb.export()));
306
+ } catch (_) {
307
+ // best-effort — matches prior swallow-on-error behavior
308
+ } finally {
309
+ _releaseRunDbLock();
310
+ }
311
+ }
312
+
245
313
  function _persistRunDb() {
246
314
  if (!_runDb || !_runDbPath) return;
247
315
  clearTimeout(_runDbPersistTimer);
248
- _runDbPersistTimer = setTimeout(() => {
249
- try { fs.writeFileSync(_runDbPath, Buffer.from(_runDb.export())); } catch (_) {}
250
- }, 1000);
316
+ _runDbPersistTimer = setTimeout(_writeRunDbSnapshot, 1000);
251
317
  }
252
318
 
253
319
  function _insertRunEvent(ev, source) {
@@ -266,6 +332,50 @@ function _insertRunEvent(ev, source) {
266
332
  }
267
333
  // ─────────────────────────────────────────────────────────────────────────────
268
334
 
335
+ // ── Bounded file reads for session JSONL (P3-6b) ────────────────────────────
336
+ // /api/session already caps memory correctly by tailing; /api/session-journal and
337
+ // /api/session-errors used to fs.readFileSync() whole session files regardless of
338
+ // size, so one request against a multi-hundred-MB session log (a real scenario for
339
+ // long-running org sessions) buffers the entire file. These helpers cap the read to
340
+ // a fixed byte window via openSync/readSync (mirrors the byte-offset tail-read the
341
+ // /api/loops handler already uses for ScheduleWakeup lookups) instead of loading the
342
+ // whole file.
343
+ function _readTailLines(filePath, maxBytes) {
344
+ maxBytes = maxBytes || 4 * 1024 * 1024; // 4MB — generous for the most recent activity
345
+ const stat = fs.statSync(filePath);
346
+ const start = Math.max(0, stat.size - maxBytes);
347
+ const len = stat.size - start;
348
+ const buf = Buffer.alloc(len);
349
+ const fd = fs.openSync(filePath, 'r');
350
+ try {
351
+ fs.readSync(fd, buf, 0, len, start);
352
+ } finally {
353
+ fs.closeSync(fd);
354
+ }
355
+ let text = buf.toString('utf8');
356
+ if (start > 0) {
357
+ // We started mid-file — the first line is very likely a partial line, drop it.
358
+ const nl = text.indexOf('\n');
359
+ text = nl === -1 ? '' : text.slice(nl + 1);
360
+ }
361
+ return text.split('\n').filter(Boolean);
362
+ }
363
+
364
+ function _readHeadText(filePath, maxBytes) {
365
+ maxBytes = maxBytes || 8192; // enough for the JSONL header line(s) we need to inspect
366
+ const stat = fs.statSync(filePath);
367
+ const len = Math.min(maxBytes, stat.size);
368
+ const buf = Buffer.alloc(len);
369
+ const fd = fs.openSync(filePath, 'r');
370
+ try {
371
+ fs.readSync(fd, buf, 0, len, 0);
372
+ } finally {
373
+ fs.closeSync(fd);
374
+ }
375
+ return buf.toString('utf8');
376
+ }
377
+ // ─────────────────────────────────────────────────────────────────────────────
378
+
269
379
  function appendToFile(filePath, line) {
270
380
  const prev = _writeQueue.get(filePath) || Promise.resolve();
271
381
  const next = prev.then(() => {
@@ -466,7 +576,7 @@ function bindServer(server, port) {
466
576
  let attempt = 0;
467
577
 
468
578
  function tryPort(p) {
469
- server.listen(p, () => resolve(p));
579
+ server.listen(p, '127.0.0.1', () => resolve(p));
470
580
  server.once('error', (err) => {
471
581
  if (err.code === 'EADDRINUSE' && attempt < maxTries) {
472
582
  attempt += 1;
@@ -534,6 +644,18 @@ function resolveSlugToPath(slug, projDir) {
534
644
  }
535
645
 
536
646
  export async function startServer({ port = 4242, projectDir, openBrowser = true } = {}) {
647
+ // ── Security: per-process auth credential for mutating (non-GET) requests ─
648
+ // Generated once per server start and written to a well-known location so
649
+ // trusted local callers (CLI, control-start.cjs) can read it and pass it
650
+ // back via an auth header or query param on non-GET requests.
651
+ const authBytes = crypto.randomBytes(24);
652
+ const dashboardAuthValue = authBytes.toString('hex');
653
+ try {
654
+ const authFileDir = path.join(projectDir || process.cwd(), '.monomind');
655
+ fs.mkdirSync(authFileDir, { recursive: true });
656
+ fs.writeFileSync(path.join(authFileDir, 'dashboard-token'), dashboardAuthValue, { mode: 0o600 });
657
+ } catch (_) {}
658
+
537
659
  // Parse a .claude/agents/*.md definition into { name, description, capability{}, document }.
538
660
  // Tolerant line-based parse of the YAML frontmatter (expertise / task_types as lists).
539
661
  function parseAgentDef(raw) {
@@ -845,13 +967,46 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
845
967
  }
846
968
  }
847
969
  }
848
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
970
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
849
971
  res.end('{"ok":true}');
850
972
  }
851
973
 
852
974
  const server = http.createServer(async (req, res) => {
853
975
  const url = req.url.split('?')[0];
854
976
 
977
+ // ── Security: strict CORS allow-list ────────────────────────────────────
978
+ // Only reflect Origin when it is this dashboard's own loopback origin.
979
+ // Otherwise the header is omitted entirely, so cross-origin reads fail
980
+ // closed rather than defaulting to '*'.
981
+ const _reqOrigin = req.headers.origin || '';
982
+ const _boundPortForCors = currentPort || boundPort || port;
983
+ const _allowedOrigins = new Set([
984
+ `http://localhost:${_boundPortForCors}`,
985
+ `http://127.0.0.1:${_boundPortForCors}`,
986
+ ]);
987
+ const corsOrigin = _allowedOrigins.has(_reqOrigin) ? _reqOrigin : undefined;
988
+
989
+ // ── Security: require a matching auth credential on all non-GET routes ──
990
+ // GET requests remain open (read-only loopback dashboard viewing).
991
+ if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') {
992
+ let _suppliedAuth = req.headers['x-monomind-token'] || '';
993
+ if (!_suppliedAuth) {
994
+ try { _suppliedAuth = new URL(req.url, 'http://localhost').searchParams.get('token') || ''; } catch (_) {}
995
+ }
996
+ const _suppliedAuthBuf = Buffer.from(String(_suppliedAuth));
997
+ const _expectedAuthBuf = Buffer.from(dashboardAuthValue);
998
+ const _authMatches = _suppliedAuthBuf.length === _expectedAuthBuf.length
999
+ && crypto.timingSafeEqual(_suppliedAuthBuf, _expectedAuthBuf);
1000
+ if (!_suppliedAuth || !_authMatches) {
1001
+ res.writeHead(401, {
1002
+ 'Content-Type': 'application/json',
1003
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
1004
+ });
1005
+ res.end(JSON.stringify({ error: 'Unauthorized: missing or invalid auth token' }));
1006
+ return;
1007
+ }
1008
+ }
1009
+
855
1010
  // ------------------------------------------------------------------ GET /
856
1011
  if (req.method === 'GET' && url === '/') {
857
1012
  const htmlPath = path.join(__dirname, 'dashboard.html');
@@ -905,11 +1060,11 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
905
1060
  }
906
1061
  let branch = '';
907
1062
  try { branch = gitExec('git rev-parse --abbrev-ref HEAD', { cwd, encoding: 'utf8' }).trim(); } catch {}
908
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1063
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
909
1064
  res.end(JSON.stringify({ name, email, cwd, remoteUrl, branch }));
910
1065
  } catch (_) {
911
1066
  const cwd2 = projectDir || process.cwd();
912
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1067
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
913
1068
  res.end(JSON.stringify({ name: '', email: '', cwd: cwd2, remoteUrl: '', branch: '' }));
914
1069
  }
915
1070
  return;
@@ -923,7 +1078,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
923
1078
  const snapshot = await collectAll(dir);
924
1079
  res.writeHead(200, {
925
1080
  'Content-Type': 'application/json',
926
- 'Access-Control-Allow-Origin': '*',
1081
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
927
1082
  'Cache-Control': 'no-cache',
928
1083
  });
929
1084
  res.end(JSON.stringify(snapshot));
@@ -950,7 +1105,11 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
950
1105
  // system files to any process that can reach localhost:4242.
951
1106
  const _resolvedFile = path.resolve(file);
952
1107
  const _homeDir = os.homedir();
953
- if (!_resolvedFile.startsWith(_homeDir + path.sep) && !_resolvedFile.startsWith(_homeDir)) {
1108
+ // Bare-prefix check (no path.sep) is bypassable: with home "/Users/bob", a path
1109
+ // like "/Users/bobsecrets/x.jsonl" starts with the literal string "/Users/bob"
1110
+ // even though it's a sibling directory, not a subdirectory of home. Require the
1111
+ // separator (or exact equality to the home dir itself) so only true descendants pass.
1112
+ if (_resolvedFile !== _homeDir && !_resolvedFile.startsWith(_homeDir + path.sep)) {
954
1113
  res.writeHead(403, { 'Content-Type': 'application/json' });
955
1114
  res.end(JSON.stringify({ error: 'Access denied: file must be within the home directory' }));
956
1115
  return;
@@ -967,7 +1126,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
967
1126
  const events = parseSessionLines(lines);
968
1127
  res.writeHead(200, {
969
1128
  'Content-Type': 'application/json',
970
- 'Access-Control-Allow-Origin': '*',
1129
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
971
1130
  'Cache-Control': 'no-cache',
972
1131
  });
973
1132
  res.end(JSON.stringify({ events, total: allLines.length, shown: lines.length }));
@@ -1005,7 +1164,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1005
1164
  const modelBreakdown = {};
1006
1165
  const filesTouchedSet = new Set();
1007
1166
  try {
1008
- const lines = fs.readFileSync(fp, 'utf8').split('\n').filter(Boolean);
1167
+ const lines = _readTailLines(fp);
1009
1168
  let pendingCompact = false;
1010
1169
  for (const line of lines) {
1011
1170
  let e; try { e = JSON.parse(line); } catch { continue; }
@@ -1062,7 +1221,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1062
1221
  const summary = summaries.length ? summaries[summaries.length - 1].text : null;
1063
1222
  sessions.push({ id, mtime, firstTs, lastTs, lastPrompt, summaries, summary, compactCount, errorCount, totalDurationMs, totalMessages, totalCost, toolCalls, userMessages, cacheReadTokens, totalInputTokens, modelBreakdown, filesTouched, file: fp });
1064
1223
  }
1065
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1224
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1066
1225
  res.end(JSON.stringify({ sessions }));
1067
1226
  } catch (err) {
1068
1227
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1077,7 +1236,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1077
1236
  const dir = qs.get('dir') || '';
1078
1237
  const q = (qs.get('q') || '').toLowerCase().trim();
1079
1238
  if (!q || q.length < 2) {
1080
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1239
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1081
1240
  res.end(JSON.stringify({ results: [] }));
1082
1241
  return;
1083
1242
  }
@@ -1118,7 +1277,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1118
1277
  } catch {}
1119
1278
  if (matches.length) results.push({ id, file: fp, lastPrompt, mtime, matches });
1120
1279
  }
1121
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1280
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1122
1281
  res.end(JSON.stringify({ results, q }));
1123
1282
  } catch (err) {
1124
1283
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1181,7 +1340,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1181
1340
  return tb - ta;
1182
1341
  });
1183
1342
 
1184
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1343
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1185
1344
  res.end(JSON.stringify({ events: events.slice(0, limit) }));
1186
1345
  } catch (err) {
1187
1346
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1233,7 +1392,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1233
1392
  const errors = Object.entries(errorCounts)
1234
1393
  .map(([tool, count]) => ({ tool, count, total: totalCounts[tool] || count }))
1235
1394
  .sort((a,b) => b.count - a.count);
1236
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1395
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1237
1396
  res.end(JSON.stringify({ errors }));
1238
1397
  } catch (err) {
1239
1398
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1284,7 +1443,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1284
1443
  const tools = Object.entries(toolCounts)
1285
1444
  .map(([tool, count]) => ({ tool, count, errors: errorCounts[tool] || 0 }))
1286
1445
  .sort((a,b) => b.count - a.count);
1287
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1446
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1288
1447
  res.end(JSON.stringify({ tools }));
1289
1448
  } catch (err) {
1290
1449
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1319,7 +1478,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1319
1478
  if (totalCost > 0) projectCosts.push({ path: projPath, cost: totalCost, sessions: sessionFiles.length });
1320
1479
  }
1321
1480
  projectCosts.sort((a, b) => b.cost - a.cost);
1322
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1481
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1323
1482
  res.end(JSON.stringify({ projects: projectCosts }));
1324
1483
  } catch (err) {
1325
1484
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1352,7 +1511,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1352
1511
  } catch {}
1353
1512
  return { slug, path: projPath, name, sessionCount, memoryCount, lastActivity: lastActivity || null };
1354
1513
  }).filter(p => p.sessionCount > 0 || fs.existsSync(path.join(p.path, '.monomind'))).sort((a, b) => (b.lastActivity || 0) - (a.lastActivity || 0));
1355
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1514
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1356
1515
  res.end(JSON.stringify({ projects }));
1357
1516
  } catch (err) {
1358
1517
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1381,7 +1540,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1381
1540
  let kg = [];
1382
1541
  try { const raw = fs.readFileSync(path.join(palaceDir, 'kg.json'), 'utf8'); kg = JSON.parse(raw); } catch {}
1383
1542
 
1384
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1543
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1385
1544
  res.end(JSON.stringify({ drawers, identity, kg }));
1386
1545
  } catch (err) {
1387
1546
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1432,7 +1591,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1432
1591
  }
1433
1592
  }
1434
1593
 
1435
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1594
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1436
1595
  res.end(JSON.stringify({ adrs }));
1437
1596
  } catch (err) {
1438
1597
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1501,7 +1660,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1501
1660
  }
1502
1661
  } catch {}
1503
1662
 
1504
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1663
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1505
1664
  res.end(JSON.stringify({ memories: memories.concat(backend), memDir }));
1506
1665
  } catch (err) {
1507
1666
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1534,7 +1693,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1534
1693
  }
1535
1694
  fs.mkdirSync(memDir, { recursive: true });
1536
1695
  fs.writeFileSync(fp, content || '', 'utf8');
1537
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1696
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1538
1697
  res.end(JSON.stringify({ ok: true }));
1539
1698
  } catch (err) {
1540
1699
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1567,7 +1726,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1567
1726
  return;
1568
1727
  }
1569
1728
  fs.unlinkSync(fp);
1570
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1729
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1571
1730
  res.end(JSON.stringify({ ok: true }));
1572
1731
  } catch (err) {
1573
1732
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1588,7 +1747,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1588
1747
  const raw = fs.readFileSync(feedbackPath, 'utf-8');
1589
1748
  rows = raw.split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
1590
1749
  }
1591
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1750
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1592
1751
  res.end(JSON.stringify(rows));
1593
1752
  } catch (err) {
1594
1753
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1663,7 +1822,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1663
1822
  lastWrite,
1664
1823
  memDir,
1665
1824
  };
1666
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1825
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1667
1826
  res.end(JSON.stringify({ stats }));
1668
1827
  } catch (err) {
1669
1828
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1792,7 +1951,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1792
1951
  if (hasRepeatLoops) loops = loops.filter(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
1793
1952
 
1794
1953
  loops.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
1795
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1954
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
1796
1955
  res.end(JSON.stringify({ loops }));
1797
1956
  } catch (err) { res.writeHead(500); res.end(JSON.stringify({ error: err.message })); }
1798
1957
  return;
@@ -1811,7 +1970,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1811
1970
  const loopsDir = path.join(_stopDir, '.monomind', 'loops');
1812
1971
  fs.mkdirSync(loopsDir, { recursive: true });
1813
1972
  fs.writeFileSync(path.join(loopsDir, `${id}.stop`), `stop-requested-${Date.now()}`);
1814
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1973
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1815
1974
  res.end(JSON.stringify({ ok: true }));
1816
1975
  } catch (err) { res.writeHead(500); res.end(JSON.stringify({ error: err.message })); }
1817
1976
  });
@@ -1843,7 +2002,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1843
2002
  const nowMs = Date.now();
1844
2003
  const loop = { id, type: 'repeat', name: name || prompt.slice(0, 40), prompt, interval: interval || '1h', maxReps, status: 'active', currentRep: 0, startedAt: nowMs, lastRunAt: null };
1845
2004
  fs.writeFileSync(path.join(loopsDir, `${id}.json`), JSON.stringify(loop, null, 2));
1846
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2005
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1847
2006
  res.end(JSON.stringify({ ok: true, id }));
1848
2007
  } catch (err) { res.writeHead(500); res.end(JSON.stringify({ error: err.message })); }
1849
2008
  });
@@ -1868,18 +2027,20 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1868
2027
  if (f.includes(sessionId) || sessionId === f.replace('.jsonl', '')) { fp = path.join(projectClaudeDir, f); break; }
1869
2028
  }
1870
2029
  if (!fp) {
1871
- // fallback: find by scanning
2030
+ // fallback: find by scanning — the sessionId lives in the first JSONL line, so a
2031
+ // small head-read is enough; no need to load the whole (potentially huge) file.
1872
2032
  for (const f of files) {
1873
- const raw = fs.readFileSync(path.join(projectClaudeDir, f), 'utf8');
1874
- const lines = raw.trim().split('\n').filter(Boolean);
1875
- if (lines.length > 0) {
1876
- try { const first = JSON.parse(lines[0]); if (first.sessionId === sessionId) { fp = path.join(projectClaudeDir, f); break; } } catch {}
2033
+ const head = _readHeadText(path.join(projectClaudeDir, f));
2034
+ const firstLine = head.split('\n', 1)[0];
2035
+ if (firstLine) {
2036
+ try { const first = JSON.parse(firstLine); if (first.sessionId === sessionId) { fp = path.join(projectClaudeDir, f); break; } } catch {}
1877
2037
  }
1878
2038
  }
1879
2039
  }
1880
2040
  if (!fp) { res.writeHead(404); res.end(JSON.stringify({ errors: [] })); return; }
1881
- const raw = fs.readFileSync(fp, 'utf8');
1882
- const lines = raw.trim().split('\n').filter(Boolean);
2041
+ // Only the most recent portion of the file matters for a dashboard error feed —
2042
+ // tail-cap the read so a multi-hundred-MB session log doesn't get buffered whole.
2043
+ const lines = _readTailLines(fp);
1883
2044
  const errors = [];
1884
2045
  for (const line of lines) {
1885
2046
  try {
@@ -1894,7 +2055,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1894
2055
  }
1895
2056
  } catch {}
1896
2057
  }
1897
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2058
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1898
2059
  res.end(JSON.stringify({ errors: errors.slice(0, 50) }));
1899
2060
  } catch (err) { res.writeHead(500); res.end(JSON.stringify({ errors: [], error: err.message })); }
1900
2061
  return;
@@ -1910,7 +2071,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1910
2071
  'Content-Type': 'text/event-stream',
1911
2072
  'Cache-Control': 'no-cache',
1912
2073
  'Connection': 'keep-alive',
1913
- 'Access-Control-Allow-Origin': '*',
2074
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
1914
2075
  });
1915
2076
  const send = (ev, data) => { try { res.write(`event: ${ev}\ndata: ${JSON.stringify(data)}\n\n`); } catch {} };
1916
2077
  send('connected', { ts: Date.now() });
@@ -1954,7 +2115,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1954
2115
  return;
1955
2116
  }
1956
2117
  fs.writeFileSync(chunksFile, filtered.map(e => JSON.stringify(e)).join('\n') + (filtered.length ? '\n' : ''), 'utf8');
1957
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2118
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1958
2119
  res.end(JSON.stringify({ ok: true, removed: before - filtered.length }));
1959
2120
  } catch (err) {
1960
2121
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1993,7 +2154,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1993
2154
  }
1994
2155
  entries[idx] = { ...entries[idx], text };
1995
2156
  fs.writeFileSync(chunksFile, entries.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf8');
1996
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2157
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1997
2158
  res.end(JSON.stringify({ ok: true }));
1998
2159
  } catch (err) {
1999
2160
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -2037,9 +2198,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2037
2198
  } finally { closeDb(db); }
2038
2199
  } catch {
2039
2200
  // Fallback: sqlite3 CLI + inline Sigma.js graph
2040
- const { execSync } = await import('child_process');
2201
+ const { execFileSync } = await import('child_process');
2041
2202
  const runSql = (sql) => {
2042
- try { return JSON.parse(execSync(`sqlite3 -json "${dbPath}"`, { encoding: 'utf-8', timeout: 15000, maxBuffer: 50*1024*1024, input: sql + ';' }) || '[]'); } catch { return []; }
2203
+ try { return JSON.parse(execFileSync('sqlite3', ['-json', dbPath], { encoding: 'utf-8', timeout: 15000, maxBuffer: 50*1024*1024, input: sql + ';' }) || '[]'); } catch { return []; }
2043
2204
  };
2044
2205
  const rawNodes = runSql('SELECT id, name, label, file_path, community_id FROM nodes LIMIT 2000');
2045
2206
  const rawEdges = runSql('SELECT source_id, target_id, relation FROM edges');
@@ -2070,7 +2231,7 @@ ${edgesJson}.forEach(e=>{try{g.addEdge(e.source,e.target,{size:0.5,color:'#333'}
2070
2231
  new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{color:'#ccc'},labelSize:10});
2071
2232
  <\/script></body></html>`;
2072
2233
  }
2073
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
2234
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
2074
2235
  res.end(html);
2075
2236
  return;
2076
2237
  }
@@ -2078,7 +2239,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2078
2239
  // Fallback: try legacy graph.html on disk
2079
2240
  const htmlPath = path.join(d, '.monomind', 'graph', 'graph.html');
2080
2241
  const html = fs.readFileSync(htmlPath, 'utf-8');
2081
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
2242
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
2082
2243
  res.end(html);
2083
2244
  } catch (err) {
2084
2245
  res.writeHead(404, { 'Content-Type': 'text/html' });
@@ -2122,7 +2283,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2122
2283
  ].join('\n');
2123
2284
  } finally { closeDb(db); }
2124
2285
  }
2125
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
2286
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
2126
2287
  res.end(JSON.stringify({ exists, report, stats }));
2127
2288
  } catch (err) {
2128
2289
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -2140,10 +2301,10 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2140
2301
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2141
2302
  let nodes = [], edges = [];
2142
2303
  if (fs.existsSync(dbPath)) {
2143
- const { execSync } = await import('child_process');
2304
+ const { execFileSync } = await import('child_process');
2144
2305
  const runSql = (sql, timeout = 10000) => {
2145
2306
  try {
2146
- return JSON.parse(execSync(`sqlite3 -json "${dbPath}"`,
2307
+ return JSON.parse(execFileSync('sqlite3', ['-json', dbPath],
2147
2308
  { encoding: 'utf-8', timeout, maxBuffer: 50 * 1024 * 1024, input: sql + ';' }) || '[]');
2148
2309
  } catch { return []; }
2149
2310
  };
@@ -2166,7 +2327,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2166
2327
  nodes = topNodes.map(n => ({ id: n.id, label: n.name || n.id, type: n.label || 'unknown', degree: degree.get(n.id) || 0 }));
2167
2328
  edges = rawEdges.filter(e => topIds.has(e.source_id) && topIds.has(e.target_id)).slice(0, 2000).map(e => ({ source: e.source_id, target: e.target_id, relation: e.relation || 'REF' }));
2168
2329
  }
2169
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
2330
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
2170
2331
  res.end(JSON.stringify({ nodes, edges }));
2171
2332
  } catch (err) {
2172
2333
  res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
@@ -2195,7 +2356,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2195
2356
  const importScript = path.join(process.cwd(), 'scripts', 'ua-import.mjs');
2196
2357
  const enrichScript = path.join(process.cwd(), 'scripts', 'ua-enrich.mjs');
2197
2358
 
2198
- res.writeHead(202, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2359
+ res.writeHead(202, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2199
2360
 
2200
2361
  if (uaGraph && fs.existsSync(importScript)) {
2201
2362
  res.end(JSON.stringify({ status: 'importing', source: uaGraph }));
@@ -2224,7 +2385,19 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2224
2385
  const dir = qs.get('dir') || projectDir || process.cwd();
2225
2386
  const d = path.resolve(dir || process.cwd());
2226
2387
 
2227
- res.writeHead(202, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2388
+ // Security: this route spawns `node --eval` with `cwd: d`, which lets
2389
+ // Node resolve '@monoes/monograph' against d's node_modules. Only ever
2390
+ // allow this for the server's own project root — never an
2391
+ // attacker-controlled `?dir=` — since a planted node_modules there
2392
+ // would achieve RCE. (See P0-6.)
2393
+ const _serverRoot = path.resolve(projectDir || process.cwd());
2394
+ if (d !== _serverRoot) {
2395
+ res.writeHead(400, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2396
+ res.end(JSON.stringify({ error: 'monograph-build only supports the server project root' }));
2397
+ return;
2398
+ }
2399
+
2400
+ res.writeHead(202, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2228
2401
  res.end(JSON.stringify({ status: 'building', dir: d }));
2229
2402
 
2230
2403
  // Build via monograph in background
@@ -2245,7 +2418,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2245
2418
  const qs2 = new URL(req.url, 'http://localhost').searchParams;
2246
2419
  const d2 = path.resolve(qs2.get('dir') || projectDir || process.cwd());
2247
2420
  const state = buildDocsState.get(d2) || { status: 'idle' };
2248
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2421
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2249
2422
  res.end(JSON.stringify(state));
2250
2423
  return;
2251
2424
  }
@@ -2258,7 +2431,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2258
2431
  const d = path.resolve(dir || process.cwd());
2259
2432
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2260
2433
  if (!fs.existsSync(dbPath)) {
2261
- res.writeHead(400, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2434
+ res.writeHead(400, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2262
2435
  res.end(JSON.stringify({ error: 'monograph.db not found — run BUILD GRAPH first' }));
2263
2436
  return;
2264
2437
  }
@@ -2266,14 +2439,14 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2266
2439
  // Reject if already running
2267
2440
  const existing = buildDocsState.get(d);
2268
2441
  if (existing && existing.status === 'pending') {
2269
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2442
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2270
2443
  res.end(JSON.stringify({ status: 'pending', message: 'Build already in progress' }));
2271
2444
  return;
2272
2445
  }
2273
2446
 
2274
2447
  const startedAt = Date.now();
2275
2448
  buildDocsState.set(d, { status: 'pending', sections: 0, files: 0, error: null, startedAt });
2276
- res.writeHead(202, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2449
+ res.writeHead(202, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2277
2450
  res.end(JSON.stringify({ status: 'pending', dir: d }));
2278
2451
 
2279
2452
  // Run doc parsing in background
@@ -2472,7 +2645,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2472
2645
  } catch {}
2473
2646
  }
2474
2647
  } finally { closeDb(db); }
2475
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2648
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2476
2649
  res.end(JSON.stringify({ content, filePath, startLine, endLine, language, name, type }));
2477
2650
  } catch (err) {
2478
2651
  res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
@@ -2492,7 +2665,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2492
2665
  const d = path.resolve(dir || process.cwd());
2493
2666
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2494
2667
  if (!q) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?q=' })); return; }
2495
- if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ nodes: [] })); return; }
2668
+ if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); res.end(JSON.stringify({ nodes: [] })); return; }
2496
2669
  const { openDb, closeDb } = await import(new URL('../../../../monograph/dist/src/storage/db.js', import.meta.url).href);
2497
2670
  const { ftsSearch } = await import(new URL('../../../../monograph/dist/src/storage/fts-store.js', import.meta.url).href);
2498
2671
  const db = openDb(dbPath);
@@ -2505,7 +2678,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2505
2678
  return { id: h.id, label: h.name, type: h.label, degree: 0, filePath: h.filePath || h.file_path, startLine: h.startLine || h.start_line, snippet };
2506
2679
  });
2507
2680
  } finally { closeDb(db); }
2508
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2681
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2509
2682
  res.end(JSON.stringify({ nodes }));
2510
2683
  } catch (err) {
2511
2684
  res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
@@ -2524,7 +2697,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2524
2697
  const maxDepth = Math.min(4, parseInt(qs.get('depth') || '3', 10));
2525
2698
  const d = path.resolve(dir || process.cwd());
2526
2699
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2527
- if (!id || !fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ related: [] })); return; }
2700
+ if (!id || !fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); res.end(JSON.stringify({ related: [] })); return; }
2528
2701
  const { openDb, closeDb } = await import(new URL('../../../../monograph/dist/src/storage/db.js', import.meta.url).href);
2529
2702
  const db = openDb(dbPath);
2530
2703
  const related = [];
@@ -2548,7 +2721,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2548
2721
  frontier = next;
2549
2722
  }
2550
2723
  } finally { closeDb(db); }
2551
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2724
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2552
2725
  res.end(JSON.stringify({ related }));
2553
2726
  } catch (err) {
2554
2727
  res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
@@ -2608,7 +2781,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2608
2781
  }
2609
2782
  result.markdown = lines2.join('\n');
2610
2783
  } finally { closeDb(db); }
2611
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2784
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2612
2785
  res.end(JSON.stringify(result));
2613
2786
  } catch (err) {
2614
2787
  res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
@@ -2625,7 +2798,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2625
2798
  const d = path.resolve(dir || process.cwd());
2626
2799
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2627
2800
  if (!q) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?q= parameter' })); return; }
2628
- if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ success: false, result: 'Graph not built yet. Run: monomind monograph build' })); return; }
2801
+ if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); res.end(JSON.stringify({ success: false, result: 'Graph not built yet. Run: monomind monograph build' })); return; }
2629
2802
  const { openDb, closeDb } = await import(new URL('../../../../monograph/dist/src/storage/db.js', import.meta.url).href);
2630
2803
  const { ftsSearch } = await import(new URL('../../../../monograph/dist/src/storage/fts-store.js', import.meta.url).href);
2631
2804
  const db = openDb(dbPath);
@@ -2644,7 +2817,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2644
2817
  }
2645
2818
  }
2646
2819
  } finally { closeDb(db); }
2647
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2820
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2648
2821
  res.end(JSON.stringify({ success: true, query: q, result }));
2649
2822
  } catch (err) {
2650
2823
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -2662,7 +2835,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2662
2835
  const d = path.resolve(dir || process.cwd());
2663
2836
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2664
2837
  if (!nodeQ) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?node= parameter' })); return; }
2665
- if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ success: false, explanation: 'Graph not built yet. Run: monomind monograph build' })); return; }
2838
+ if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); res.end(JSON.stringify({ success: false, explanation: 'Graph not built yet. Run: monomind monograph build' })); return; }
2666
2839
  const { openDb, closeDb } = await import(new URL('../../../../monograph/dist/src/storage/db.js', import.meta.url).href);
2667
2840
  const { ftsSearch } = await import(new URL('../../../../monograph/dist/src/storage/fts-store.js', import.meta.url).href);
2668
2841
  const db = openDb(dbPath);
@@ -2686,7 +2859,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2686
2859
  ].filter(Boolean).join('\n');
2687
2860
  }
2688
2861
  } finally { closeDb(db); }
2689
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2862
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2690
2863
  res.end(JSON.stringify({ success: true, node: nodeQ, explanation }));
2691
2864
  } catch (err) {
2692
2865
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -2705,7 +2878,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2705
2878
  const d = path.resolve(dir || process.cwd());
2706
2879
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2707
2880
  if (!from || !to) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?from= and ?to= parameters' })); return; }
2708
- if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ success: false, path: 'Graph not built yet.' })); return; }
2881
+ if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); res.end(JSON.stringify({ success: false, path: 'Graph not built yet.' })); return; }
2709
2882
  // Import only graphology-free storage modules to avoid broken graphology dep
2710
2883
  const { openDb, closeDb } = await import(new URL('../../../../monograph/dist/src/storage/db.js', import.meta.url).href);
2711
2884
  const { ftsSearch } = await import(new URL('../../../../monograph/dist/src/storage/fts-store.js', import.meta.url).href);
@@ -2752,7 +2925,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2752
2925
  pathResult = names.join(' → ') + ` (${p.length - 1} hop${p.length !== 2 ? 's' : ''})`;
2753
2926
  }
2754
2927
  } finally { closeDb(db); }
2755
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2928
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2756
2929
  res.end(JSON.stringify({ success: true, from, to, path: pathResult }));
2757
2930
  } catch (err) {
2758
2931
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -2774,7 +2947,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2774
2947
  process.kill(pid, 0);
2775
2948
  running = true;
2776
2949
  } catch {}
2777
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2950
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2778
2951
  res.end(JSON.stringify({ running, pid }));
2779
2952
  } catch (err) {
2780
2953
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -2800,7 +2973,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2800
2973
  } catch {}
2801
2974
 
2802
2975
  if (wasRunning) {
2803
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2976
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2804
2977
  res.end(JSON.stringify({ running: false, action: 'stopped' }));
2805
2978
  } else {
2806
2979
  const { spawn: sp } = await import('child_process');
@@ -2808,7 +2981,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2808
2981
  child.unref();
2809
2982
  try { fs.mkdirSync(path.join(d, '.monomind'), { recursive: true }); } catch {}
2810
2983
  try { fs.writeFileSync(pidPath, String(child.pid)); } catch {}
2811
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2984
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
2812
2985
  res.end(JSON.stringify({ running: true, pid: child.pid, action: 'started' }));
2813
2986
  }
2814
2987
  } catch (err) {
@@ -2823,7 +2996,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
2823
2996
  let body = '';
2824
2997
  req.on('data', c => { body += c; if (body.length > 2097152) { req.destroy(); return; } });
2825
2998
  req.on('end', async () => {
2826
- const json = res => { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); };
2999
+ const json = res => { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); };
2827
3000
  const ok = (data) => { json(res); res.end(JSON.stringify({ content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }] })); };
2828
3001
  const err = (msg) => { json(res); res.end(JSON.stringify({ error: msg })); };
2829
3002
  try {
@@ -3272,14 +3445,14 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3272
3445
  const gp = fs.existsSync(graphPath) ? graphPath : (fs.existsSync(legacyPath) ? legacyPath : null);
3273
3446
 
3274
3447
  if (!gp) {
3275
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3448
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3276
3449
  res.end(JSON.stringify({ available: false }));
3277
3450
  return;
3278
3451
  }
3279
3452
 
3280
3453
  const { execSync: ex } = await import('child_process');
3281
3454
  const out = ex(`graphify benchmark ${gp}`, { encoding: 'utf8', cwd: d, timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] });
3282
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3455
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3283
3456
  res.end(JSON.stringify({ available: true, result: out }));
3284
3457
  } catch (err) {
3285
3458
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -3417,7 +3590,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3417
3590
  }
3418
3591
  }
3419
3592
 
3420
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
3593
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
3421
3594
  res.end(JSON.stringify({ nodes, edges }));
3422
3595
  } catch (err) {
3423
3596
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -3434,7 +3607,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3434
3607
  const entries = collectSwarmHistory(path.resolve(dir));
3435
3608
  res.writeHead(200, {
3436
3609
  'Content-Type': 'application/json',
3437
- 'Access-Control-Allow-Origin': '*',
3610
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
3438
3611
  'Cache-Control': 'no-cache',
3439
3612
  });
3440
3613
  res.end(JSON.stringify({ entries }));
@@ -3459,7 +3632,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3459
3632
  const agentId = typeof _rawAgentId === 'string' ? _rawAgentId.slice(0, 256) : undefined;
3460
3633
  const last = qs.get('last') ? parseInt(qs.get('last')) : undefined;
3461
3634
  const events = collectSwarmEvents(path.resolve(dir), { swarmId, agentId, last });
3462
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
3635
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
3463
3636
  res.end(JSON.stringify({ events, count: events.length }));
3464
3637
  } catch (err) {
3465
3638
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -3473,7 +3646,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3473
3646
  try {
3474
3647
  const dir = new URL(req.url, 'http://localhost').searchParams.get('dir') || projectDir || process.cwd();
3475
3648
  const size = getSwarmDataSize(path.resolve(dir));
3476
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3649
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3477
3650
  res.end(JSON.stringify(size));
3478
3651
  } catch (err) {
3479
3652
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -3487,7 +3660,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3487
3660
  try {
3488
3661
  const dir = new URL(req.url, 'http://localhost').searchParams.get('dir') || projectDir || process.cwd();
3489
3662
  const result = cleanSwarmData(path.resolve(dir));
3490
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3663
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3491
3664
  res.end(JSON.stringify({ success: true, ...result }));
3492
3665
  } catch (err) {
3493
3666
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -3507,7 +3680,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3507
3680
  const trackerPath = path.join(dir, '.claude', 'helpers', 'token-tracker.cjs');
3508
3681
  const fallback = () => {
3509
3682
  const summary = (() => { try { return JSON.parse(fs.readFileSync(path.join(dir, '.monomind', 'metrics', 'token-summary.json'), 'utf8')); } catch { return {}; } })();
3510
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
3683
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
3511
3684
  const fbSum = { todayCost: summary.todayCost || 0, cost: summary.todayCost || 0, todayCalls: summary.todayCalls || 0, calls: summary.todayCalls || 0, totalTokens: 0, totalTokensIn: 0, totalTokensOut: 0, cacheTokens: 0, modelCount: 0 };
3512
3685
  res.end(JSON.stringify({ summary: fbSum, totalCost: summary.todayCost || 0, totalCalls: summary.todayCalls || 0, totalIn: 0, totalOut: 0, totalCR: 0, totalCW: 0, rows: [], models: [], categories: [], tools: [], mcpServers: [], projects: [], modelBreakdown: {}, categoryBreakdown: {}, toolBreakdown: {}, mcpBreakdown: {}, periodLabel: period }));
3513
3686
  };
@@ -3564,7 +3737,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3564
3737
  rows.sort((a, b) => b.cost - a.cost);
3565
3738
  // Summary object matching client expectations
3566
3739
  const summary = { todayCost: totalCost, cost: totalCost, todayCalls: totalCalls, calls: totalCalls, totalTokens: totalIn + totalOut, totalTokensIn: totalIn, totalTokensOut: totalOut, cacheTokens: totalCR, modelCount: models.length };
3567
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
3740
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
3568
3741
  res.end(JSON.stringify({ summary, totalCost, totalCalls, totalIn, totalOut, totalCR, totalCW, rows, models, categories, tools, mcpServers, projects: projectRows, modelBreakdown, categoryBreakdown, toolBreakdown, mcpBreakdown, periodLabel: period }));
3569
3742
  } catch (e) { fallback(); }
3570
3743
  } catch (err) {
@@ -3594,7 +3767,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3594
3767
  }
3595
3768
  res.writeHead(200, {
3596
3769
  'Content-Type': 'application/json',
3597
- 'Access-Control-Allow-Origin': '*',
3770
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
3598
3771
  'Cache-Control': 'no-cache',
3599
3772
  });
3600
3773
  res.end(JSON.stringify(partial));
@@ -3611,7 +3784,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3611
3784
  'Content-Type': 'text/event-stream',
3612
3785
  'Cache-Control': 'no-cache',
3613
3786
  Connection: 'keep-alive',
3614
- 'Access-Control-Allow-Origin': '*',
3787
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
3615
3788
  'X-Accel-Buffering': 'no',
3616
3789
  });
3617
3790
 
@@ -3660,11 +3833,11 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3660
3833
  res.end(JSON.stringify({ exists: false }));
3661
3834
  return;
3662
3835
  }
3663
- const { execSync } = await import('child_process');
3836
+ const { execFileSync } = await import('child_process');
3664
3837
  // Pipe SQL via stdin to avoid shell quoting issues with single-quoted SQL strings.
3665
3838
  const runSql = (sql, timeout = 5000) => {
3666
3839
  try {
3667
- return execSync(`sqlite3 -json "${dbPath}"`,
3840
+ return execFileSync('sqlite3', ['-json', dbPath],
3668
3841
  { encoding: 'utf-8', timeout: timeout, input: sql + ';' });
3669
3842
  } catch (e) { return '[]'; }
3670
3843
  };
@@ -3750,7 +3923,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3750
3923
  } catch(_) {}
3751
3924
  }
3752
3925
  }
3753
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3926
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3754
3927
  res.end(JSON.stringify(orgs));
3755
3928
  } catch(_) { res.writeHead(500); res.end('[]'); }
3756
3929
  return;
@@ -3772,7 +3945,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3772
3945
  fs.mkdirSync(orgsDir, { recursive: true });
3773
3946
  const destFile = path.join(orgsDir, `${orgName}.json`);
3774
3947
  fs.writeFileSync(destFile, JSON.stringify({ ...cfg, name: orgName }, null, 2), 'utf8');
3775
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3948
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3776
3949
  res.end(JSON.stringify({ ok: true, name: orgName, file: destFile }));
3777
3950
  } catch (e) {
3778
3951
  res.writeHead(400, { 'Content-Type': 'application/json' });
@@ -3798,7 +3971,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3798
3971
  const destFile = path.join(orgsDir, `${name}.json`);
3799
3972
  const cleanCfg = Object.fromEntries(Object.entries({ ...cfg, name }).filter(([k]) => !k.startsWith('_')));
3800
3973
  fs.writeFileSync(destFile, JSON.stringify(cleanCfg, null, 2), 'utf8');
3801
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3974
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3802
3975
  res.end(JSON.stringify({ ok: true, name, file: destFile }));
3803
3976
  } catch (e) {
3804
3977
  res.writeHead(400, { 'Content-Type': 'application/json' });
@@ -3818,7 +3991,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3818
3991
  const _orgsOneProjDir = _resolveOrgProjectDir(orgName, _orgsOneRoot) || _orgsOneRoot;
3819
3992
  const f = path.join(_orgsOneProjDir, '.monomind', 'orgs', `${orgName}.json`);
3820
3993
  if (!fs.existsSync(f)) { res.writeHead(404); res.end('{"error":"not found"}'); return; }
3821
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3994
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3822
3995
  res.end(fs.readFileSync(f, 'utf8'));
3823
3996
  } catch(_) { res.writeHead(500); res.end('{}'); }
3824
3997
  return;
@@ -3904,7 +4077,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3904
4077
  lastEventAt: _runstateData?.lastEventAt || null,
3905
4078
  agentStates: _runstateData?.agentStates || {} };
3906
4079
 
3907
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4080
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3908
4081
  res.end(JSON.stringify(result));
3909
4082
  } catch(_) { res.writeHead(500); res.end('{}'); }
3910
4083
  return;
@@ -3955,7 +4128,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3955
4128
  }
3956
4129
 
3957
4130
  const out = events.filter(e => e && e.ts).sort((a, b) => b.ts - a.ts).slice(0, 100);
3958
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4131
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3959
4132
  res.end(JSON.stringify(out));
3960
4133
  } catch(_) { res.writeHead(500); res.end('[]'); }
3961
4134
  return;
@@ -3970,9 +4143,9 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3970
4143
  const _projsQs = new URL(req.url, 'http://localhost').searchParams;
3971
4144
  const d = path.resolve(_projsQs.get('dir') || projectDir || process.cwd());
3972
4145
  const projFile = path.join(d, '.monomind', 'orgs', `${orgName}-projects.json`);
3973
- if (!fs.existsSync(projFile)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('[]'); return; }
4146
+ if (!fs.existsSync(projFile)) { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); res.end('[]'); return; }
3974
4147
  const data = JSON.parse(fs.readFileSync(projFile, 'utf8'));
3975
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4148
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3976
4149
  res.end(JSON.stringify(data.projects || []));
3977
4150
  } catch(_) { res.writeHead(500); res.end('[]'); }
3978
4151
  return;
@@ -3988,12 +4161,12 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
3988
4161
  const d = path.resolve(_membersQs.get('dir') || projectDir || process.cwd());
3989
4162
  const membersFile = path.join(d, '.monomind', 'orgs', `${orgName}-members.json`);
3990
4163
  if (!fs.existsSync(membersFile)) {
3991
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4164
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3992
4165
  res.end('{"members":[],"join_requests":[]}');
3993
4166
  return;
3994
4167
  }
3995
4168
  const data = JSON.parse(fs.readFileSync(membersFile, 'utf8'));
3996
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4169
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
3997
4170
  res.end(JSON.stringify(data));
3998
4171
  } catch(_) { res.writeHead(500); res.end('{}'); }
3999
4172
  return;
@@ -4014,7 +4187,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4014
4187
  const orgFile = path.join(d, '.monomind', 'orgs', `${orgName}.json`);
4015
4188
  let defaultAdapter = 'claude-sonnet-4-6';
4016
4189
  try { defaultAdapter = JSON.parse(fs.readFileSync(orgFile, 'utf8'))?.run_config?.ceo_adapter || defaultAdapter; } catch(_) {}
4017
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4190
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4018
4191
  res.end(JSON.stringify({ default_adapter: defaultAdapter, adapters: [
4019
4192
  { type: 'claude-local', label: 'Claude (local CLI)', source: 'built-in', disabled: false, modelsCount: 3 },
4020
4193
  { type: 'gemini-local', label: 'Gemini (local)', source: 'built-in', disabled: false, modelsCount: 1 },
@@ -4023,7 +4196,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4023
4196
  return;
4024
4197
  }
4025
4198
  const data = JSON.parse(fs.readFileSync(adaptersFile, 'utf8'));
4026
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4199
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4027
4200
  res.end(JSON.stringify(data));
4028
4201
  } catch(_) { res.writeHead(500); res.end('{}'); }
4029
4202
  return;
@@ -4078,7 +4251,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4078
4251
  } catch(_) {}
4079
4252
  }
4080
4253
 
4081
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4254
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4082
4255
  res.end(JSON.stringify({ skills, role_skill_map: roleSkillMap }));
4083
4256
  } catch(_) { res.writeHead(500); res.end('{}'); }
4084
4257
  return;
@@ -4134,7 +4307,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4134
4307
  }
4135
4308
  }
4136
4309
 
4137
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
4310
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
4138
4311
  res.end(JSON.stringify({ role, definition }));
4139
4312
  } catch (_) { res.writeHead(500); res.end('{}'); }
4140
4313
  return;
@@ -4147,7 +4320,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4147
4320
  const orgName = decodeURIComponent(urlObj.pathname.split('/')[3]);
4148
4321
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
4149
4322
  const q = (urlObj.searchParams.get('q') || '').toLowerCase().trim();
4150
- if (!q || q.length < 2) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('{"hits":[]}'); return; }
4323
+ if (!q || q.length < 2) { res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) }); res.end('{"hits":[]}'); return; }
4151
4324
 
4152
4325
  const d = path.resolve(urlObj.searchParams.get('dir') || projectDir || process.cwd());
4153
4326
  const orgsDir = path.join(d, '.monomind', 'orgs');
@@ -4219,7 +4392,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4219
4392
  }
4220
4393
  }
4221
4394
 
4222
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4395
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4223
4396
  res.end(JSON.stringify({ q, hits: hits.slice(0, 50) }));
4224
4397
  } catch(_) { res.writeHead(500); res.end('{}'); }
4225
4398
  return;
@@ -4505,7 +4678,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4505
4678
  skills: r.skills || [],
4506
4679
  };
4507
4680
  });
4508
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4681
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4509
4682
  res.end(JSON.stringify({ agents }));
4510
4683
  } catch(_) { res.writeHead(500); res.end('{"agents":[]}'); }
4511
4684
  return;
@@ -4542,7 +4715,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4542
4715
  ts: a.ts || null,
4543
4716
  }));
4544
4717
  const pending = approvals.filter(a => a.status === 'pending' || a.status === 'revision_requested').length;
4545
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4718
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4546
4719
  res.end(JSON.stringify({ approvals, pending }));
4547
4720
  } catch(_) { res.writeHead(500); res.end('{"approvals":[],"pending":0}'); }
4548
4721
  return;
@@ -4585,7 +4758,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4585
4758
  const event = { type: 'org:approval:resolved', org: orgName, approval_id: approvalId, status, ts: Date.now() };
4586
4759
  appendToFile(path.join(path.resolve(_postApprovalsQs.get('dir') || projectDir || process.cwd()), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n').catch(() => {});
4587
4760
  broadcastMm(event);
4588
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4761
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4589
4762
  res.end(JSON.stringify({ ok: true, status }));
4590
4763
  } catch(_) { res.writeHead(500); res.end('{}'); }
4591
4764
  return;
@@ -4613,7 +4786,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4613
4786
  lastUsedAt: s.lastUsedAt || null,
4614
4787
  usageCount: s.usageCount || 0,
4615
4788
  }));
4616
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4789
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4617
4790
  res.end(JSON.stringify({ secrets }));
4618
4791
  } catch(_) { res.writeHead(500); res.end('{"secrets":[]}'); }
4619
4792
  return;
@@ -4770,7 +4943,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4770
4943
  const goalsFile = path.join(_goalsProjDir, '.monomind', 'orgs', `${orgName}-goals.json`);
4771
4944
  let data = { goals: [] };
4772
4945
  try { data = JSON.parse(fs.readFileSync(goalsFile, 'utf8')); } catch(_) {}
4773
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4946
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4774
4947
  res.end(JSON.stringify({ goals: data.goals || [] }));
4775
4948
  } catch(_) { res.writeHead(500); res.end('{"goals":[]}'); }
4776
4949
  return;
@@ -4819,7 +4992,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4819
4992
  }
4820
4993
  } catch(_) {}
4821
4994
  }
4822
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4995
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4823
4996
  res.end(JSON.stringify({ routines: data.routines || [] }));
4824
4997
  } catch(_) { res.writeHead(500); res.end('{"routines":[]}'); }
4825
4998
  return;
@@ -4841,7 +5014,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4841
5014
  const payload = { org: orgName, updated_at: new Date().toISOString(), goals: parsed.goals };
4842
5015
  fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
4843
5016
  fs.renameSync(tmp, goalsFile);
4844
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5017
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4845
5018
  res.end(JSON.stringify({ ok: true, count: parsed.goals.length }));
4846
5019
  } catch(_) { res.writeHead(500); res.end('{"error":"' + String(_).replace(/"/g, '\\"') + '"}'); }
4847
5020
  return;
@@ -4863,7 +5036,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4863
5036
  const payload = { org: orgName, updated_at: new Date().toISOString(), routines: parsed.routines };
4864
5037
  fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
4865
5038
  fs.renameSync(tmp, routinesFile);
4866
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5039
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4867
5040
  res.end(JSON.stringify({ ok: true, count: parsed.routines.length }));
4868
5041
  } catch(_) { res.writeHead(500); res.end('{"error":"' + String(_).replace(/"/g, '\\"') + '"}'); }
4869
5042
  return;
@@ -4917,7 +5090,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4917
5090
  if (fs.existsSync(agentsDir)) walkAgents(agentsDir);
4918
5091
  }
4919
5092
  files.sort((a, b) => new Date(b.mtime) - new Date(a.mtime));
4920
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
5093
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
4921
5094
  res.end(JSON.stringify(files));
4922
5095
  } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
4923
5096
  return;
@@ -4941,7 +5114,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4941
5114
  if (!stat.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
4942
5115
  if (stat.size > 524288) { res.writeHead(413); res.end('File too large'); return; }
4943
5116
  const content = fs.readFileSync(resolved, 'utf8');
4944
- res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
5117
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4945
5118
  res.end(content);
4946
5119
  } catch(_) { res.writeHead(500); res.end('Internal error'); }
4947
5120
  return;
@@ -4984,7 +5157,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4984
5157
  const deleteEvent = { type: 'org:delete', org: orgName, ts: Date.now() };
4985
5158
  appendToFile(path.join(projectDir || process.cwd(), 'data', 'mastermind-events.jsonl'), JSON.stringify(deleteEvent) + '\n').catch(() => {});
4986
5159
  broadcastMm(deleteEvent);
4987
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5160
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4988
5161
  res.end('{"ok":true}');
4989
5162
  } catch(_) { res.writeHead(500); res.end('{}'); }
4990
5163
  return;
@@ -5008,7 +5181,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5008
5181
  fs.writeFileSync(path.join(stopDir, `${orgName}.stop`), String(Date.now()));
5009
5182
  } catch(_) {}
5010
5183
  broadcastMm(stopEvent);
5011
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5184
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5012
5185
  res.end('{"ok":true}');
5013
5186
  } catch(_) { res.writeHead(500); res.end('{}'); }
5014
5187
  return;
@@ -5034,7 +5207,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5034
5207
  try { fs.mkdirSync(destOrgsDir, { recursive: true }); } catch(_) {}
5035
5208
  const destFile = path.join(destOrgsDir, `${orgName}.json`);
5036
5209
  fs.copyFileSync(srcFile, destFile);
5037
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5210
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5038
5211
  res.end(JSON.stringify({ ok: true, destFile }));
5039
5212
  } catch(e) { res.writeHead(500); res.end(JSON.stringify({ error: String(e.message || e) })); }
5040
5213
  return;
@@ -5121,7 +5294,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5121
5294
  break;
5122
5295
  }
5123
5296
  }
5124
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
5297
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
5125
5298
  res.end(JSON.stringify(runs));
5126
5299
  } catch (_) { res.writeHead(500); res.end('[]'); }
5127
5300
  return;
@@ -5147,7 +5320,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5147
5320
  if (!fs.existsSync(_tf)) continue;
5148
5321
  const _tLines = fs.readFileSync(_tf, 'utf8').split('\n').filter(Boolean);
5149
5322
  const _tEvs = _tLines.map(l => { try { const e = JSON.parse(l); return { type: 'org:comms', from: e.role || e.from || 'agent', to: e.to || 'all', msg: e.message || e.msg || '', ts: e.ts ? (typeof e.ts === 'number' ? e.ts : new Date(e.ts).getTime()) : Date.now(), org: _rvOrgName, runId: _rvRunId }; } catch { return null; } }).filter(Boolean);
5150
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5323
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5151
5324
  res.end(JSON.stringify(_tEvs));
5152
5325
  return;
5153
5326
  }
@@ -5187,7 +5360,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5187
5360
  // For in-progress runs (no .warm.jsonl), org:comms also go to .convs.jsonl (stripped form).
5188
5361
  // They're already in .jsonl as full events, so .convs.jsonl would duplicate — skip it.
5189
5362
  events.sort((a, b) => (a.ts || 0) - (b.ts || 0));
5190
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
5363
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
5191
5364
  res.end(JSON.stringify(events));
5192
5365
  } catch (_) { res.writeHead(500); res.end('[]'); }
5193
5366
  return;
@@ -5210,12 +5383,12 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5210
5383
  // Reject files >2MB to avoid blocking the event loop
5211
5384
  if (_size > 2 * 1024 * 1024) { res.writeHead(413); res.end(JSON.stringify({ error: 'file too large', size: _size })); return; }
5212
5385
  if (!_mime.startsWith('text/') && _mime !== 'application/json') {
5213
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5386
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5214
5387
  res.end(JSON.stringify({ binary: true, mimeType: _mime, size: _size }));
5215
5388
  return;
5216
5389
  }
5217
5390
  const _content = fs.readFileSync(_filePath, 'utf8');
5218
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5391
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5219
5392
  res.end(JSON.stringify({ content: _content, mimeType: _mime, size: _size }));
5220
5393
  } catch (_e) { res.writeHead(500); res.end(JSON.stringify({ error: 'read failed' })); }
5221
5394
  return;
@@ -5233,7 +5406,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5233
5406
  'Content-Type': 'text/event-stream',
5234
5407
  'Cache-Control': 'no-cache',
5235
5408
  'Connection': 'keep-alive',
5236
- 'Access-Control-Allow-Origin': '*',
5409
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
5237
5410
  });
5238
5411
  res.write(': connected\n\n');
5239
5412
  addMmClient(res);
@@ -5300,7 +5473,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5300
5473
  }
5301
5474
  }
5302
5475
  allSessions.sort((a,b) => (b.ts||b.startedAt||0)-(a.ts||a.startedAt||0));
5303
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5476
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5304
5477
  res.end(JSON.stringify(allSessions.slice(0, limitParam)));
5305
5478
  } catch (_) { res.writeHead(200); res.end('[]'); }
5306
5479
  return;
@@ -5332,7 +5505,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5332
5505
  else if (ev.type === 'session:complete') lines.push(`\`${t}\` **SESSION COMPLETE** — status: ${ev.status}, domains: ${(ev.domains || []).join(', ')}`);
5333
5506
  else lines.push(`\`${t}\` ${ev.type} ${JSON.stringify(ev)}`);
5334
5507
  }
5335
- res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
5508
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5336
5509
  res.end(lines.join('\n'));
5337
5510
  } catch (_) { res.writeHead(500); res.end('Error'); }
5338
5511
  return;
@@ -5345,14 +5518,14 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5345
5518
  // Check individual session file first
5346
5519
  const sessFile = path.join(projectDir || process.cwd(), 'data', 'sessions', `${sid}.json`);
5347
5520
  if (fs.existsSync(sessFile)) {
5348
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5521
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5349
5522
  res.end(fs.readFileSync(sessFile, 'utf8'));
5350
5523
  return;
5351
5524
  }
5352
5525
  const f = path.join(projectDir || process.cwd(), 'data', 'mastermind-sessions.json');
5353
5526
  const sessions = JSON.parse(fs.readFileSync(f, 'utf8'));
5354
5527
  const s = sessions.find(x => x.id === sid);
5355
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5528
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5356
5529
  res.end(JSON.stringify(s || null));
5357
5530
  } catch (_) { res.writeHead(200); res.end('null'); }
5358
5531
  return;
@@ -5419,7 +5592,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5419
5592
  const lines = fs.readFileSync(evPath, 'utf8').split('\n').filter(l => l.trim()).slice(-10);
5420
5593
  recentEvents = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
5421
5594
  } catch(_) {}
5422
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5595
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5423
5596
  res.end(JSON.stringify({
5424
5597
  ts: Date.now(),
5425
5598
  pid: process.pid,
@@ -5469,7 +5642,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5469
5642
  const detectedRunId = path.basename(runFile, '.jsonl');
5470
5643
  const lines = fs.readFileSync(runFile, 'utf8').split('\n').filter(l => l.trim()).slice(-100);
5471
5644
  const events = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
5472
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5645
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5473
5646
  res.end(JSON.stringify({ runId: detectedRunId, events, active: activeOrgRuns.has(orgName) }));
5474
5647
  } catch(err) {
5475
5648
  res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
@@ -5672,7 +5845,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5672
5845
  } catch (_) {}
5673
5846
  }
5674
5847
  }
5675
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
5848
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
5676
5849
  res.end(JSON.stringify(result));
5677
5850
  } catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message })); }
5678
5851
  return;
@@ -5746,12 +5919,12 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5746
5919
  const dir = path.resolve(qp.get('dir') || projectDir || process.cwd());
5747
5920
  const covPath = path.join(dir, '.monomind', 'audit', 'coverage.json');
5748
5921
  if (!fs.existsSync(covPath)) {
5749
- res.writeHead(404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5922
+ res.writeHead(404, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5750
5923
  res.end('{}');
5751
5924
  return;
5752
5925
  }
5753
5926
  const coverage = JSON.parse(fs.readFileSync(covPath, 'utf8'));
5754
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
5927
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}), 'Cache-Control': 'no-cache' });
5755
5928
  res.end(JSON.stringify(coverage));
5756
5929
  } catch (e) {
5757
5930
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -5819,7 +5992,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5819
5992
  const _mcLine = `data: ${JSON.stringify(_mcEvent)}\n\n`;
5820
5993
  for (const _cl of _mcFwdClients) { try { _cl.write(_mcLine); } catch (_) { _mcFwdClients.delete(_cl); } }
5821
5994
  }
5822
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
5995
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5823
5996
  res.end(JSON.stringify({ ok: true, runId: _mcRunId }));
5824
5997
  } catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message })); }
5825
5998
  return;
@@ -5837,7 +6010,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5837
6010
  'Content-Type': 'text/event-stream',
5838
6011
  'Cache-Control': 'no-cache',
5839
6012
  'Connection': 'keep-alive',
5840
- 'Access-Control-Allow-Origin': '*',
6013
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
5841
6014
  'X-Accel-Buffering': 'no',
5842
6015
  });
5843
6016
  res.write(': connected\n\n');
@@ -5934,7 +6107,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5934
6107
  const _chLine = `data: ${JSON.stringify(_chEvent)}\n\n`;
5935
6108
  for (const _cl of _chFwdClients) { try { _cl.write(_chLine); } catch(_) { _chFwdClients.delete(_cl); } }
5936
6109
  }
5937
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
6110
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5938
6111
  res.end(JSON.stringify({ ok: true }));
5939
6112
  } catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message })); }
5940
6113
  return;
@@ -6338,9 +6511,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
6338
6511
  clearInterval(_heartbeatTimer);
6339
6512
  // Flush SQLite run-event index to disk before exit (bypasses 1000ms debounce timer)
6340
6513
  clearTimeout(_runDbPersistTimer);
6341
- if (_runDb && _runDbPath) {
6342
- try { fs.writeFileSync(_runDbPath, Buffer.from(_runDb.export())); } catch (_) {}
6343
- }
6514
+ _writeRunDbSnapshot();
6344
6515
  for (const w of activeWatchers) {
6345
6516
  try {
6346
6517
  w.close();