@brainst0rm/cli 0.13.0 → 0.14.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 (57) hide show
  1. package/dist/{App-SSKWB7CT.js → App-HGJSYIVS.js} +133 -62
  2. package/dist/App-HGJSYIVS.js.map +1 -0
  3. package/dist/{App-DPXJYXKH.js → App-XCJW3A4I.js} +133 -62
  4. package/dist/App-XCJW3A4I.js.map +1 -0
  5. package/dist/brainstorm.js +1428 -150
  6. package/dist/brainstorm.js.map +1 -1
  7. package/dist/{chunk-7D4SUZUM.js → chunk-PR4QN5HX.js} +6 -1
  8. package/dist/{chunk-ZWE3DS7E.js → chunk-SEGVTWSK.js} +6 -1
  9. package/dist/{chunk-5NA3GH6X.js → chunk-WYQU3HAB.js} +175 -10
  10. package/dist/chunk-WYQU3HAB.js.map +1 -0
  11. package/dist/{chunk-55ITCWZZ.js → chunk-Z2QIGVYT.js} +175 -10
  12. package/dist/chunk-Z2QIGVYT.js.map +1 -0
  13. package/dist/{dist-GNHTH2DH.js → dist-3BC75XHW.js} +2 -2
  14. package/dist/dist-42TQFHMB.js +1640 -0
  15. package/dist/dist-42TQFHMB.js.map +1 -0
  16. package/dist/dist-BULARAL3.js +7308 -0
  17. package/dist/dist-BULARAL3.js.map +1 -0
  18. package/dist/{dist-JUDVPE7G.js → dist-ET6CSS7O.js} +2 -2
  19. package/dist/{dist-DUDO3RDM.js → dist-GVK5Q4YK.js} +62 -16
  20. package/dist/{dist-V5DTSTKJ.js.map → dist-GVK5Q4YK.js.map} +1 -1
  21. package/dist/{dist-V5DTSTKJ.js → dist-K7BDAMTO.js} +62 -16
  22. package/dist/{dist-DUDO3RDM.js.map → dist-K7BDAMTO.js.map} +1 -1
  23. package/dist/{dist-WLTQTLFO.js → dist-OYQTULIU.js} +2 -2
  24. package/dist/dist-S5JYXFUW.js +7307 -0
  25. package/dist/dist-S5JYXFUW.js.map +1 -0
  26. package/dist/{dist-YIGU37Q2.js → dist-Y25MC2VO.js} +2 -2
  27. package/dist/dist-Z4SBSK4Q.js +1639 -0
  28. package/dist/dist-Z4SBSK4Q.js.map +1 -0
  29. package/dist/index.js +1428 -150
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp-server-CROPNYHI.js +4252 -0
  32. package/dist/mcp-server-CROPNYHI.js.map +1 -0
  33. package/dist/mcp-server-IJVEG6CS.js +4251 -0
  34. package/dist/mcp-server-IJVEG6CS.js.map +1 -0
  35. package/dist/{recorder-D6ILEOZP.js → recorder-33N4U6TO.js} +2 -2
  36. package/dist/{recorder-SPYYF4DL.js → recorder-APOOXJYA.js} +2 -2
  37. package/dist/{roles-2DGF4PZU.js → roles-GKDCLP5G.js} +2 -2
  38. package/dist/{roles-UIPX7GBC.js → roles-UUIISXEW.js} +2 -2
  39. package/dist/{slash-PDWKCZOQ.js → slash-4XSR3SJD.js} +3 -3
  40. package/dist/{slash-ZDC4DKL4.js → slash-CVWH3LTR.js} +3 -3
  41. package/package.json +4 -2
  42. package/dist/App-DPXJYXKH.js.map +0 -1
  43. package/dist/App-SSKWB7CT.js.map +0 -1
  44. package/dist/chunk-55ITCWZZ.js.map +0 -1
  45. package/dist/chunk-5NA3GH6X.js.map +0 -1
  46. /package/dist/{chunk-7D4SUZUM.js.map → chunk-PR4QN5HX.js.map} +0 -0
  47. /package/dist/{chunk-ZWE3DS7E.js.map → chunk-SEGVTWSK.js.map} +0 -0
  48. /package/dist/{dist-GNHTH2DH.js.map → dist-3BC75XHW.js.map} +0 -0
  49. /package/dist/{dist-JUDVPE7G.js.map → dist-ET6CSS7O.js.map} +0 -0
  50. /package/dist/{dist-WLTQTLFO.js.map → dist-OYQTULIU.js.map} +0 -0
  51. /package/dist/{dist-YIGU37Q2.js.map → dist-Y25MC2VO.js.map} +0 -0
  52. /package/dist/{recorder-D6ILEOZP.js.map → recorder-33N4U6TO.js.map} +0 -0
  53. /package/dist/{recorder-SPYYF4DL.js.map → recorder-APOOXJYA.js.map} +0 -0
  54. /package/dist/{roles-2DGF4PZU.js.map → roles-GKDCLP5G.js.map} +0 -0
  55. /package/dist/{roles-UIPX7GBC.js.map → roles-UUIISXEW.js.map} +0 -0
  56. /package/dist/{slash-PDWKCZOQ.js.map → slash-4XSR3SJD.js.map} +0 -0
  57. /package/dist/{slash-ZDC4DKL4.js.map → slash-CVWH3LTR.js.map} +0 -0
@@ -5,12 +5,18 @@ import {
5
5
  import {
6
6
  ROLES
7
7
  } from "./chunk-OVGL3NJQ.js";
8
- import "./chunk-ZWE3DS7E.js";
8
+ import "./chunk-SEGVTWSK.js";
9
9
 
10
10
  // src/bin/brainstorm.ts
11
11
  import { Command } from "commander";
12
+ import { initSentry, captureError, flushSentry } from "@brainst0rm/shared";
12
13
  import { loadConfig } from "@brainst0rm/config";
13
- import { getDb, closeDb, CostRepository } from "@brainst0rm/db";
14
+ import {
15
+ getDb,
16
+ closeDb,
17
+ CostRepository,
18
+ RoutingOutcomeRepository
19
+ } from "@brainst0rm/db";
14
20
  import {
15
21
  createProviderRegistry,
16
22
  getBrainstormApiKey,
@@ -824,7 +830,11 @@ var PROVIDER_KEY_NAMES = [
824
830
  "GOOGLE_GENERATIVE_AI_API_KEY",
825
831
  "DEEPSEEK_API_KEY",
826
832
  "MOONSHOT_API_KEY",
827
- "BRAINSTORM_ADMIN_KEY"
833
+ "BRAINSTORM_ADMIN_KEY",
834
+ // God Mode connector keys — resolved so connectors can authenticate
835
+ "BRAINSTORM_MSP_API_KEY",
836
+ "BRAINSTORM_EMAIL_API_KEY",
837
+ "BRAINSTORM_VM_API_KEY"
828
838
  ];
829
839
  async function resolveProviderKeys() {
830
840
  const vault = new BrainstormVault(VAULT_PATH);
@@ -835,7 +845,10 @@ async function resolveProviderKeys() {
835
845
  const resolved = /* @__PURE__ */ new Map();
836
846
  for (const name of PROVIDER_KEY_NAMES) {
837
847
  const value = await resolver.get(name);
838
- if (value) resolved.set(name, value);
848
+ if (value) {
849
+ resolved.set(name, value);
850
+ process.env[name] = value;
851
+ }
839
852
  }
840
853
  return { get: (name) => resolved.get(name) ?? null };
841
854
  }
@@ -1704,13 +1717,24 @@ ${stdinText}`;
1704
1717
  config.shell.containerImage,
1705
1718
  config.shell.containerTimeout
1706
1719
  );
1707
- const { prompt: rawPrompt, frontmatter } = buildSystemPrompt(projectPath);
1708
- const systemPrompt = rawPrompt + buildToolAwarenessSection(tools.listTools());
1720
+ const {
1721
+ prompt: rawPrompt,
1722
+ segments: rawSegments,
1723
+ frontmatter
1724
+ } = buildSystemPrompt(projectPath);
1725
+ const toolSection = buildToolAwarenessSection(tools.listTools());
1726
+ const systemPrompt = rawPrompt + toolSection;
1727
+ const systemSegments = rawSegments.length > 0 ? [
1728
+ { text: rawSegments[0].text + toolSection, cacheable: true },
1729
+ ...rawSegments.slice(1)
1730
+ ] : [{ text: systemPrompt, cacheable: true }];
1731
+ const routingOutcomeRepo = new RoutingOutcomeRepository(db);
1709
1732
  const router = new BrainstormRouter(
1710
1733
  config,
1711
1734
  registry,
1712
1735
  costTracker,
1713
- frontmatter
1736
+ frontmatter,
1737
+ routingOutcomeRepo.loadAggregated()
1714
1738
  );
1715
1739
  const permissionManager = new PermissionManager(
1716
1740
  config.general.defaultPermissionMode,
@@ -1722,6 +1746,63 @@ ${stdinText}`;
1722
1746
  } else if (!isCommunityTier || hasDirectKeys) {
1723
1747
  router.setStrategy("quality-first");
1724
1748
  }
1749
+ const runHasConnectorKey = !!(process.env.BRAINSTORM_MSP_API_KEY || process.env.BRAINSTORM_EMAIL_API_KEY || process.env.BRAINSTORM_VM_API_KEY || process.env._GM_MSP_KEY || process.env._GM_EMAIL_KEY || process.env._GM_VM_KEY);
1750
+ if (runHasConnectorKey || config.godmode.enabled) {
1751
+ try {
1752
+ const {
1753
+ connectGodMode: connectGM,
1754
+ createProductConnectors: createPC,
1755
+ setAuditPersister: setAP
1756
+ } = await import("@brainst0rm/godmode");
1757
+ const { ChangeSetLogRepository: CSLogRun } = await import("@brainst0rm/db");
1758
+ const csLogRun = new CSLogRun(db);
1759
+ setAP((entry) => {
1760
+ csLogRun.log({
1761
+ changesetId: entry.changesetId,
1762
+ connector: entry.connector,
1763
+ action: entry.action,
1764
+ description: entry.description,
1765
+ riskScore: entry.riskScore,
1766
+ status: entry.status,
1767
+ changesJson: entry.changesJson,
1768
+ simulationJson: entry.simulationJson,
1769
+ rollbackJson: entry.rollbackJson,
1770
+ createdAt: entry.createdAt,
1771
+ executedAt: entry.executedAt,
1772
+ sessionId: null
1773
+ });
1774
+ });
1775
+ const defaultConns = {
1776
+ msp: {
1777
+ enabled: true,
1778
+ baseUrl: process.env.BRAINSTORM_MSP_URL ?? "https://brainstormmsp.ai",
1779
+ apiKeyName: "BRAINSTORM_MSP_API_KEY"
1780
+ }
1781
+ };
1782
+ const mergedConfig = {
1783
+ ...config.godmode,
1784
+ connectors: { ...defaultConns, ...config.godmode.connectors }
1785
+ };
1786
+ const activeConns = await createPC(mergedConfig);
1787
+ const gmResult = await connectGM(tools, mergedConfig, activeConns);
1788
+ if (gmResult.connectedSystems.length > 0) {
1789
+ const gmToolSection = buildToolAwarenessSection(tools.listTools());
1790
+ systemSegments[0] = {
1791
+ text: rawSegments[0]?.text + gmToolSection + "\n" + (gmResult.promptSegment?.text ?? ""),
1792
+ cacheable: true
1793
+ };
1794
+ process.stderr.write(
1795
+ `[godmode] Connected: ${gmResult.connectedSystems.map((s) => s.displayName).join(", ")} (${gmResult.totalTools} tools)
1796
+ `
1797
+ );
1798
+ }
1799
+ } catch (err) {
1800
+ process.stderr.write(
1801
+ `[godmode] ${err instanceof Error ? err.message : String(err)}
1802
+ `
1803
+ );
1804
+ }
1805
+ }
1725
1806
  const session = sessionManager.start(projectPath);
1726
1807
  sessionManager.addUserMessage(finalPrompt);
1727
1808
  let fullResponse = "";
@@ -1740,12 +1821,14 @@ ${stdinText}`;
1740
1821
  sessionId: session.id,
1741
1822
  projectPath,
1742
1823
  systemPrompt,
1824
+ systemSegments,
1743
1825
  disableTools: !opts.tools,
1744
1826
  preferredModelId: opts.model ?? (resolvedKeys.get("MOONSHOT_API_KEY") ? "moonshot/kimi-k2.5" : isCommunityTier && !resolvedKeys.get("DEEPSEEK_API_KEY") && !resolvedKeys.get("ANTHROPIC_API_KEY") && !resolvedKeys.get("OPENAI_API_KEY") && !resolvedKeys.get("GOOGLE_GENERATIVE_AI_API_KEY") ? "brainstormrouter/auto" : void 0),
1745
1827
  maxSteps: parseInt(opts.maxSteps ?? "1"),
1746
1828
  compaction: buildCompactionCallbacks(sessionManager),
1747
1829
  permissionCheck: (tool, args) => permissionManager.check(tool, args),
1748
- middleware
1830
+ middleware,
1831
+ routingOutcomeRepo
1749
1832
  })) {
1750
1833
  switch (event.type) {
1751
1834
  case "thinking":
@@ -1849,6 +1932,7 @@ Error: ${event.error.message}
1849
1932
  }
1850
1933
  if (fullResponse) {
1851
1934
  sessionManager.addAssistantMessage(fullResponse);
1935
+ sessionManager.flush();
1852
1936
  }
1853
1937
  }
1854
1938
  );
@@ -2105,7 +2189,7 @@ vaultCmd.command("status").description("Show vault and backend status").action(a
2105
2189
  });
2106
2190
  var projectsCmd = program.command("projects").description("Manage registered projects");
2107
2191
  projectsCmd.command("list").description("List all registered projects").option("--all", "Include inactive projects").action(async (opts) => {
2108
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2192
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2109
2193
  const db = getDb();
2110
2194
  const pm = new ProjectManager(db);
2111
2195
  const projects = pm.projects.list(opts.all);
@@ -2130,7 +2214,7 @@ projectsCmd.command("list").description("List all registered projects").option("
2130
2214
  });
2131
2215
  projectsCmd.command("register").argument("<path>", "Path to project directory").option("-n, --name <name>", "Project name (default: directory name)").option("--budget-daily <amount>", "Daily budget limit in dollars").option("--budget-monthly <amount>", "Monthly budget limit in dollars").description("Register a project").action(
2132
2216
  async (path, opts) => {
2133
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2217
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2134
2218
  const db = getDb();
2135
2219
  const pm = new ProjectManager(db);
2136
2220
  try {
@@ -2151,7 +2235,7 @@ projectsCmd.command("register").argument("<path>", "Path to project directory").
2151
2235
  }
2152
2236
  );
2153
2237
  projectsCmd.command("switch").argument("<name>", "Project name to switch to").description("Set the active project for this session").action(async (name) => {
2154
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2238
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2155
2239
  const db = getDb();
2156
2240
  const pm = new ProjectManager(db);
2157
2241
  try {
@@ -2168,7 +2252,7 @@ projectsCmd.command("switch").argument("<name>", "Project name to switch to").de
2168
2252
  }
2169
2253
  });
2170
2254
  projectsCmd.command("show").argument("<name>", "Project name").description("Show project dashboard").action(async (name) => {
2171
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2255
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2172
2256
  const db = getDb();
2173
2257
  const pm = new ProjectManager(db);
2174
2258
  const project = pm.projects.getByName(name);
@@ -2205,7 +2289,7 @@ projectsCmd.command("show").argument("<name>", "Project name").description("Show
2205
2289
  console.log();
2206
2290
  });
2207
2291
  projectsCmd.command("import").argument("[dir]", "Parent directory to scan", join3(homedir(), "Projects")).description("Scan a directory and register all project subdirectories").action(async (dir) => {
2208
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2292
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2209
2293
  const db = getDb();
2210
2294
  const pm = new ProjectManager(db);
2211
2295
  const registered = pm.import(dir);
@@ -2224,8 +2308,8 @@ projectsCmd.command("import").argument("[dir]", "Parent directory to scan", join
2224
2308
  });
2225
2309
  var scheduleCmd = program.command("schedule").description("Manage scheduled tasks");
2226
2310
  scheduleCmd.command("list").option("-p, --project <name>", "Filter by project").description("List scheduled tasks").action(async (opts) => {
2227
- const { ScheduledTaskRepository } = await import("./dist-DUDO3RDM.js");
2228
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2311
+ const { ScheduledTaskRepository } = await import("./dist-GVK5Q4YK.js");
2312
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2229
2313
  const db = getDb();
2230
2314
  const taskRepo = new ScheduledTaskRepository(db);
2231
2315
  let projectId;
@@ -2257,8 +2341,8 @@ scheduleCmd.command("list").option("-p, --project <name>", "Filter by project").
2257
2341
  console.log();
2258
2342
  });
2259
2343
  scheduleCmd.command("add").argument("<prompt>", "Task instruction").requiredOption("-p, --project <name>", "Project name").option("-n, --name <name>", "Task name (default: first 30 chars of prompt)").option("--cron <expression>", "Cron schedule (e.g. '0 9 * * *')").option("--budget <amount>", "Budget limit per run in dollars", "0.50").option("--max-turns <n>", "Maximum turns per run", "20").option("--allow-mutations", "Allow file writes and shell commands").option("--model <id>", "Model override for this task").description("Add a scheduled task").action(async (prompt, opts) => {
2260
- const { ScheduledTaskRepository, validateCron, validateTaskSafety } = await import("./dist-DUDO3RDM.js");
2261
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2344
+ const { ScheduledTaskRepository, validateCron, validateTaskSafety } = await import("./dist-GVK5Q4YK.js");
2345
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2262
2346
  const db = getDb();
2263
2347
  const pm = new ProjectManager(db);
2264
2348
  const project = pm.projects.getByName(opts.project);
@@ -2288,7 +2372,7 @@ scheduleCmd.command("add").argument("<prompt>", "Task instruction").requiredOpti
2288
2372
  console.log(`
2289
2373
  \u2713 Created task "${task.name}" (${task.id.slice(0, 8)})`);
2290
2374
  if (task.cronExpression) {
2291
- const { describeCron } = await import("./dist-DUDO3RDM.js");
2375
+ const { describeCron } = await import("./dist-GVK5Q4YK.js");
2292
2376
  console.log(` Schedule: ${describeCron(task.cronExpression)}`);
2293
2377
  }
2294
2378
  if (warnings.length > 0) {
@@ -2298,7 +2382,7 @@ scheduleCmd.command("add").argument("<prompt>", "Task instruction").requiredOpti
2298
2382
  console.log();
2299
2383
  });
2300
2384
  scheduleCmd.command("run").option("--task-id <id>", "Run a specific task").option("--dry-run", "Show what would run without executing").description("Trigger due tasks").action(async (opts) => {
2301
- const { TriggerRunner } = await import("./dist-DUDO3RDM.js");
2385
+ const { TriggerRunner } = await import("./dist-GVK5Q4YK.js");
2302
2386
  const db = getDb();
2303
2387
  const runner = new TriggerRunner(db);
2304
2388
  const result = await runner.runDueTasks(opts);
@@ -2317,7 +2401,7 @@ scheduleCmd.command("run").option("--task-id <id>", "Run a specific task").optio
2317
2401
  console.log();
2318
2402
  });
2319
2403
  scheduleCmd.command("history").option("--task-id <id>", "Filter by task").option("-n, --limit <count>", "Number of runs to show", "10").description("Show task run history").action(async (opts) => {
2320
- const { TaskRunRepository, ScheduledTaskRepository } = await import("./dist-DUDO3RDM.js");
2404
+ const { TaskRunRepository, ScheduledTaskRepository } = await import("./dist-GVK5Q4YK.js");
2321
2405
  const db = getDb();
2322
2406
  const runRepo = new TaskRunRepository(db);
2323
2407
  const taskRepo = new ScheduledTaskRepository(db);
@@ -2338,7 +2422,7 @@ scheduleCmd.command("history").option("--task-id <id>", "Filter by task").option
2338
2422
  console.log();
2339
2423
  });
2340
2424
  scheduleCmd.command("pause").argument("<task-id>", "Task ID to pause").description("Pause a scheduled task").action(async (taskId) => {
2341
- const { ScheduledTaskRepository } = await import("./dist-DUDO3RDM.js");
2425
+ const { ScheduledTaskRepository } = await import("./dist-GVK5Q4YK.js");
2342
2426
  const db = getDb();
2343
2427
  const repo = new ScheduledTaskRepository(db);
2344
2428
  repo.updateStatus(taskId, "paused");
@@ -2346,7 +2430,7 @@ scheduleCmd.command("pause").argument("<task-id>", "Task ID to pause").descripti
2346
2430
  `);
2347
2431
  });
2348
2432
  scheduleCmd.command("resume").argument("<task-id>", "Task ID to resume").description("Resume a paused task").action(async (taskId) => {
2349
- const { ScheduledTaskRepository } = await import("./dist-DUDO3RDM.js");
2433
+ const { ScheduledTaskRepository } = await import("./dist-GVK5Q4YK.js");
2350
2434
  const db = getDb();
2351
2435
  const repo = new ScheduledTaskRepository(db);
2352
2436
  repo.updateStatus(taskId, "active");
@@ -2354,7 +2438,7 @@ scheduleCmd.command("resume").argument("<task-id>", "Task ID to resume").descrip
2354
2438
  `);
2355
2439
  });
2356
2440
  scheduleCmd.command("delete").argument("<task-id>", "Task ID to delete").description("Delete a scheduled task").action(async (taskId) => {
2357
- const { ScheduledTaskRepository } = await import("./dist-DUDO3RDM.js");
2441
+ const { ScheduledTaskRepository } = await import("./dist-GVK5Q4YK.js");
2358
2442
  const db = getDb();
2359
2443
  const repo = new ScheduledTaskRepository(db);
2360
2444
  repo.delete(taskId);
@@ -2695,8 +2779,8 @@ orchestrateCmd.command("pipeline").argument("<request>", "What to build (natural
2695
2779
  });
2696
2780
  orchestrateCmd.command("run").argument("<description>", "What to do across projects").requiredOption("-p, --projects <names>", "Comma-separated project names").option("--budget <amount>", "Total budget limit in dollars").option("--type <type>", "Subagent type (explore, code, review)", "code").description("Run a cross-project orchestration").action(
2697
2781
  async (description, opts) => {
2698
- const { OrchestrationEngine, formatAggregatedResults, aggregateResults } = await import("./dist-JUDVPE7G.js");
2699
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2782
+ const { OrchestrationEngine, formatAggregatedResults, aggregateResults } = await import("./dist-ET6CSS7O.js");
2783
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2700
2784
  const db = getDb();
2701
2785
  const engine = new OrchestrationEngine(db);
2702
2786
  const pm = new ProjectManager(db);
@@ -2762,7 +2846,7 @@ orchestrateCmd.command("run").argument("<description>", "What to do across proje
2762
2846
  }
2763
2847
  );
2764
2848
  orchestrateCmd.command("history").option("-n, --limit <count>", "Number of runs to show", "10").description("Show recent orchestration runs").action(async (opts) => {
2765
- const { OrchestrationEngine } = await import("./dist-JUDVPE7G.js");
2849
+ const { OrchestrationEngine } = await import("./dist-ET6CSS7O.js");
2766
2850
  const db = getDb();
2767
2851
  const engine = new OrchestrationEngine(db);
2768
2852
  const runs = engine.listRecent(parseInt(opts.limit));
@@ -2781,8 +2865,8 @@ orchestrateCmd.command("history").option("-n, --limit <count>", "Number of runs
2781
2865
  console.log();
2782
2866
  });
2783
2867
  orchestrateCmd.command("status").argument("<run-id>", "Orchestration run ID").description("Show status of an orchestration run").action(async (runId) => {
2784
- const { OrchestrationEngine } = await import("./dist-JUDVPE7G.js");
2785
- const { ProjectManager } = await import("./dist-YIGU37Q2.js");
2868
+ const { OrchestrationEngine } = await import("./dist-ET6CSS7O.js");
2869
+ const { ProjectManager } = await import("./dist-Y25MC2VO.js");
2786
2870
  const db = getDb();
2787
2871
  const engine = new OrchestrationEngine(db);
2788
2872
  const pm = new ProjectManager(db);
@@ -3486,6 +3570,95 @@ program.command("setup-infra").description(
3486
3570
  );
3487
3571
  console.log();
3488
3572
  });
3573
+ program.command("onboard").description(
3574
+ "LLM-driven project onboarding \u2014 discover conventions, generate specialized agents, wire routing"
3575
+ ).argument("[path]", "Project path", ".").option(
3576
+ "--budget <dollars>",
3577
+ "Max spend in USD (default: auto from project size)"
3578
+ ).option("--static-only", "Skip LLM phases (equivalent to setup-infra)").option("--dry-run", "Show plan without writing files or calling LLMs").option("--phases <phases>", "Comma-separated phases to run").action(
3579
+ async (projectPath, opts) => {
3580
+ const { resolve } = await import("path");
3581
+ const { runOnboardPipeline, ALL_PHASES } = await import("./dist-42TQFHMB.js");
3582
+ const absPath = resolve(projectPath);
3583
+ const options = {
3584
+ projectPath: absPath,
3585
+ budget: opts.budget ? parseFloat(opts.budget) : void 0,
3586
+ staticOnly: opts.staticOnly ?? false,
3587
+ dryRun: opts.dryRun ?? false,
3588
+ phases: opts.phases ? opts.phases.split(",").map((p) => p.trim()) : void 0
3589
+ };
3590
+ console.log(
3591
+ `
3592
+ storm onboard ${absPath === process.cwd() ? "." : absPath}${opts.staticOnly ? " --static-only" : ""}${opts.dryRun ? " --dry-run" : ""}`
3593
+ );
3594
+ console.log();
3595
+ for await (const event of runOnboardPipeline(options)) {
3596
+ switch (event.type) {
3597
+ case "onboard-started":
3598
+ if (event.estimatedBudget > 0) {
3599
+ console.log(
3600
+ ` Budget: $${options.budget?.toFixed(2) ?? "auto"} (estimated ~$${event.estimatedBudget.toFixed(2)})`
3601
+ );
3602
+ console.log();
3603
+ }
3604
+ break;
3605
+ case "phase-started":
3606
+ process.stdout.write(` Phase: ${event.description} ...`);
3607
+ break;
3608
+ case "phase-completed": {
3609
+ const cost = event.cost > 0 ? `, $${event.cost.toFixed(2)}` : "";
3610
+ const dur = (event.durationMs / 1e3).toFixed(1);
3611
+ console.log(` done (${dur}s${cost})`);
3612
+ console.log(` ${event.summary}`);
3613
+ console.log();
3614
+ break;
3615
+ }
3616
+ case "phase-skipped": {
3617
+ const { PHASE_LABELS } = await import("./dist-42TQFHMB.js");
3618
+ const label = PHASE_LABELS[event.phase] ?? event.phase;
3619
+ console.log(` Phase: ${label} ... skipped`);
3620
+ console.log(` ${event.reason}`);
3621
+ console.log();
3622
+ break;
3623
+ }
3624
+ case "phase-failed":
3625
+ console.log(` FAILED`);
3626
+ console.log(` ${event.error}`);
3627
+ console.log();
3628
+ break;
3629
+ case "file-written":
3630
+ console.log(` \u2192 ${event.path}`);
3631
+ break;
3632
+ case "budget-warning":
3633
+ console.log(
3634
+ ` \u26A0 Budget: $${event.spent.toFixed(2)} spent, $${event.remaining.toFixed(2)} remaining`
3635
+ );
3636
+ break;
3637
+ case "onboard-completed": {
3638
+ const r = event.result;
3639
+ const dur = (r.totalDurationMs / 1e3).toFixed(1);
3640
+ console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
3641
+ console.log(
3642
+ ` Onboarding Complete \u2014 $${r.totalCost.toFixed(2)} total, ${dur}s`
3643
+ );
3644
+ console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
3645
+ if (r.filesWritten.length > 0) {
3646
+ console.log();
3647
+ for (const f of r.filesWritten) {
3648
+ console.log(` ${f}`);
3649
+ }
3650
+ }
3651
+ console.log(
3652
+ `
3653
+ Next: Run \`storm chat\` to start working with agents that know your codebase.
3654
+ `
3655
+ );
3656
+ break;
3657
+ }
3658
+ }
3659
+ }
3660
+ }
3661
+ );
3489
3662
  program.command("route").description("Explain how Brainstorm classifies and routes a task").argument("[task]", "Task description to classify").option("--json", "Output as JSON").action(async (task, opts) => {
3490
3663
  const { classifyTask } = await import("@brainst0rm/router");
3491
3664
  const taskText = task ?? "write a function that validates email addresses";
@@ -4066,134 +4239,1194 @@ Project initialized by \`storm start\`.
4066
4239
  console.log(` storm run "prompt" Single-shot execution`);
4067
4240
  console.log();
4068
4241
  });
4069
- program.command("chat", { isDefault: true }).description("Start an interactive chat session").option("--simple", "Use simple readline interface instead of TUI").option("--continue", "Resume the most recent session").option("--resume <id>", "Resume a specific session by ID").option("--fork <id>", "Fork a session (copy history, new session)").option("--lfg", "Full auto mode \u2014 skip all permission confirmations").option(
4070
- "--strategy <name>",
4071
- "Routing strategy: cost-first, quality-first, combined, capability"
4072
- ).option("--verbose-routing", "Print routing decisions to stderr").option(
4073
- "--fast",
4074
- "Fast startup \u2014 skip provider discovery, MCP connections, and eval probes"
4075
- ).action(
4076
- async (opts) => {
4077
- const config = loadConfig();
4078
- if (opts.lfg) {
4079
- config.general.defaultPermissionMode = "auto";
4080
- }
4081
- if (opts.fast) {
4082
- config.general.skipProviderDiscovery = true;
4083
- config.general.skipEvalProbes = true;
4084
- }
4085
- const db = getDb();
4086
- const resolvedKeys = await resolveProviderKeys();
4087
- const resolvedBRKey = resolvedKeys.get("BRAINSTORM_API_KEY") ?? getBrainstormApiKey();
4088
- const isCommunityTier = isCommunityKey(resolvedBRKey);
4089
- if (resolvedBRKey) process.env._BR_RESOLVED_KEY = resolvedBRKey;
4090
- const registry = await createProviderRegistry(config, resolvedKeys);
4091
- const costTracker = new CostTracker(db, config.budget);
4092
- const tools = createDefaultToolRegistry();
4093
- if (!opts.fast)
4094
- await connectMCPServers(
4095
- tools,
4096
- config,
4097
- resolvedKeys.get("BRAINSTORM_API_KEY")
4098
- );
4099
- const projectPath = process.cwd();
4100
- configureSandbox(
4101
- config.shell.sandbox,
4102
- projectPath,
4103
- config.shell.maxOutputBytes,
4104
- config.shell.containerImage,
4105
- config.shell.containerTimeout
4242
+ var platformCmd = program.command("platform").description("Platform contract tools \u2014 verify, init, manifest");
4243
+ platformCmd.command("verify").description("Verify a product implements the Brainstorm platform contract").argument("<url>", "Product API base URL (e.g., https://brainstormmsp.ai)").option("--token <jwt>", "Bearer token for authenticated endpoints").option("--timeout <ms>", "Request timeout in milliseconds", "10000").action(async (url, opts) => {
4244
+ const { verifyProductContract } = await import("@brainst0rm/godmode");
4245
+ console.log(`
4246
+ Platform Contract Verification`);
4247
+ console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
4248
+ `);
4249
+ console.log(` Target: ${url}`);
4250
+ console.log();
4251
+ const results = await verifyProductContract(url, {
4252
+ timeout: parseInt(opts.timeout ?? "10000"),
4253
+ token: opts.token
4254
+ });
4255
+ let passed = 0;
4256
+ let failed = 0;
4257
+ for (const r of results) {
4258
+ const icon = r.status === "pass" ? "\u2713" : r.status === "fail" ? "\u2717" : "\u25CB";
4259
+ const color = r.status === "pass" ? "\x1B[32m" : r.status === "fail" ? "\x1B[31m" : "\x1B[90m";
4260
+ const latency = r.latencyMs ? ` (${r.latencyMs}ms)` : "";
4261
+ console.log(
4262
+ ` ${color}${icon}\x1B[0m ${r.endpoint} \u2014 ${r.message}${latency}`
4106
4263
  );
4107
- const permissionManager = new PermissionManager(
4108
- config.general.defaultPermissionMode,
4109
- config.permissions
4264
+ if (r.status === "pass") passed++;
4265
+ else if (r.status === "fail") failed++;
4266
+ }
4267
+ console.log();
4268
+ console.log(
4269
+ ` ${passed} passed, ${failed} failed, ${results.length} total`
4270
+ );
4271
+ if (failed > 0) {
4272
+ console.log(`
4273
+ Missing endpoints need to be implemented.`);
4274
+ console.log(` See: brainstorm platform init`);
4275
+ } else {
4276
+ console.log(`
4277
+ Product implements the platform contract.`);
4278
+ }
4279
+ console.log();
4280
+ process.exit(failed > 0 ? 1 : 0);
4281
+ });
4282
+ platformCmd.command("init").description("Generate a product-manifest.yaml template").option("--id <id>", "Product ID (lowercase, hyphens)", "my-product").option("--name <name>", "Display name", "My Product").option("--url <url>", "API base URL", "http://localhost:3000").action(async (opts) => {
4283
+ const { generateManifestTemplate } = await import("@brainst0rm/godmode");
4284
+ const { writeFileSync: writeFileSync2, existsSync: existsSync3 } = await import("fs");
4285
+ const { resolve } = await import("path");
4286
+ const template = generateManifestTemplate(opts.id, opts.name, opts.url);
4287
+ const outPath = resolve("product-manifest.yaml");
4288
+ if (existsSync3(outPath)) {
4289
+ console.error(
4290
+ ` product-manifest.yaml already exists. Delete it first to regenerate.`
4110
4291
  );
4111
- let currentOutputStyle = config.general.outputStyle ?? "concise";
4112
- let currentRole;
4113
- const sessionManager = new SessionManager(db);
4114
- const middleware = createDefaultMiddlewarePipeline(projectPath);
4115
- let { prompt: systemPrompt, frontmatter } = buildSystemPrompt(
4116
- projectPath,
4117
- currentOutputStyle
4292
+ process.exit(1);
4293
+ }
4294
+ writeFileSync2(outPath, template, "utf-8");
4295
+ console.log(`
4296
+ \u2713 Generated product-manifest.yaml`);
4297
+ console.log(
4298
+ ` Edit the file, then run: brainstorm platform verify ${opts.url}
4299
+ `
4300
+ );
4301
+ });
4302
+ platformCmd.command("validate").description("Validate a product-manifest.yaml file").argument("[path]", "Path to manifest file", "product-manifest.yaml").action(async (path) => {
4303
+ const { readFileSync: readFileSync3, existsSync: existsSync3 } = await import("fs");
4304
+ const { resolve } = await import("path");
4305
+ const { validateManifestData } = await import("@brainst0rm/godmode");
4306
+ const filePath = resolve(path);
4307
+ if (!existsSync3(filePath)) {
4308
+ console.error(` File not found: ${filePath}`);
4309
+ console.error(` Run: brainstorm platform init`);
4310
+ process.exit(1);
4311
+ }
4312
+ const content = readFileSync3(filePath, "utf-8");
4313
+ let data;
4314
+ try {
4315
+ data = JSON.parse(content);
4316
+ } catch {
4317
+ console.error(
4318
+ ` Cannot parse ${path}. Install 'yaml' package or use JSON format.`
4118
4319
  );
4119
- systemPrompt += buildToolAwarenessSection(tools.listTools());
4120
- const router = new BrainstormRouter(
4121
- config,
4122
- registry,
4123
- costTracker,
4124
- frontmatter
4320
+ try {
4321
+ const yaml = await import("./dist-BULARAL3.js");
4322
+ data = yaml.parse(content);
4323
+ } catch {
4324
+ console.error(` Tip: npm install yaml`);
4325
+ process.exit(1);
4326
+ }
4327
+ }
4328
+ const result = validateManifestData(data);
4329
+ if (result.ok) {
4330
+ const m = result.manifest;
4331
+ console.log(
4332
+ `
4333
+ \u2713 Valid manifest: ${m.product.name} (${m.product.id}) v${m.product.version}`
4125
4334
  );
4126
- const hasOwnKeys = !!resolvedKeys.get("DEEPSEEK_API_KEY") || !!resolvedKeys.get("ANTHROPIC_API_KEY") || !!resolvedKeys.get("OPENAI_API_KEY") || !!resolvedKeys.get("MOONSHOT_API_KEY") || !!resolvedKeys.get("GOOGLE_GENERATIVE_AI_API_KEY");
4127
- if (opts.strategy) {
4128
- router.setStrategy(opts.strategy);
4129
- } else if ((!isCommunityTier || hasOwnKeys) && router.getActiveStrategy() !== "capability") {
4130
- router.setStrategy("quality-first");
4335
+ console.log(` API: ${m.security.api_base}`);
4336
+ console.log(
4337
+ ` Auth: human=${m.security.auth.human}, machine=${m.security.auth.machine}`
4338
+ );
4339
+ console.log(` Capabilities: ${m.capabilities.length}`);
4340
+ console.log(
4341
+ ` Events: publishes=${m.events.publishes.length}, subscribes=${m.events.subscribes.length}`
4342
+ );
4343
+ console.log();
4344
+ } else {
4345
+ console.error(`
4346
+ \u2717 Invalid manifest:`);
4347
+ for (const err of result.errors ?? []) {
4348
+ console.error(` - ${err}`);
4131
4349
  }
4132
- const subagentTool = createSubagentTool({
4133
- config,
4134
- registry,
4135
- router,
4136
- costTracker,
4137
- tools,
4138
- projectPath,
4139
- permissionCheck: (name, perm) => permissionManager.check(name, perm),
4140
- containerIsolation: config.shell.sandbox === "container"
4141
- });
4142
- tools.register(subagentTool);
4143
- const hasDirectProviderKeys = !!resolvedKeys.get("DEEPSEEK_API_KEY") || !!resolvedKeys.get("ANTHROPIC_API_KEY") || !!resolvedKeys.get("OPENAI_API_KEY") || !!resolvedKeys.get("GOOGLE_GENERATIVE_AI_API_KEY") || !!resolvedKeys.get("MOONSHOT_API_KEY");
4144
- let preferredModelId = resolvedKeys.get(
4145
- "MOONSHOT_API_KEY"
4146
- ) ? "moonshot/kimi-k2.5" : isCommunityTier && !hasDirectProviderKeys ? "brainstormrouter/auto" : void 0;
4147
- let session;
4148
- if (opts.fork) {
4149
- session = sessionManager.fork(opts.fork);
4150
- if (!session) {
4151
- console.error(` Session '${opts.fork}' not found.`);
4152
- process.exit(1);
4153
- }
4154
- console.log(
4155
- ` Forked session ${opts.fork.slice(0, 8)} -> ${session.id.slice(0, 8)}`
4156
- );
4157
- } else if (opts.resume) {
4158
- session = sessionManager.resume(opts.resume);
4159
- if (!session) {
4160
- console.error(` Session '${opts.resume}' not found.`);
4161
- process.exit(1);
4162
- }
4163
- printResumeSummary(session, sessionManager);
4164
- } else if (opts.continue) {
4165
- session = sessionManager.resumeLatest(projectPath);
4166
- if (!session) {
4167
- session = sessionManager.start(projectPath);
4350
+ console.error();
4351
+ process.exit(1);
4352
+ }
4353
+ });
4354
+ program.command("mcp").description(
4355
+ "Start MCP server (stdio) \u2014 exposes God Mode tools to Claude Code/Desktop"
4356
+ ).action(async () => {
4357
+ const { startMCPServer } = await import("./mcp-server-CROPNYHI.js");
4358
+ await startMCPServer();
4359
+ });
4360
+ program.command("setup").description(
4361
+ "Bootstrap Brainstorm on this machine \u2014 auth, config, MCP, ecosystem context"
4362
+ ).action(async () => {
4363
+ const { existsSync: existsSync3, mkdirSync: mkdirSync2, writeFileSync: writeFileSync2, readFileSync: readFileSync3 } = await import("fs");
4364
+ const { join: join4 } = await import("path");
4365
+ const { homedir: homedir2 } = await import("os");
4366
+ console.log(`
4367
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
4368
+ console.log(` brainstorm setup`);
4369
+ console.log(` \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4370
+ `);
4371
+ const brKey = process.env.BRAINSTORM_API_KEY;
4372
+ if (brKey) {
4373
+ console.log(` \u2713 BrainstormRouter API key found`);
4374
+ } else {
4375
+ console.log(` \u2717 BRAINSTORM_API_KEY not set`);
4376
+ console.log(` Get one at https://brainstormrouter.com/dashboard`);
4377
+ console.log(` Then: export BRAINSTORM_API_KEY=br_live_xxx
4378
+ `);
4379
+ }
4380
+ const opToken = process.env.OP_SERVICE_ACCOUNT_TOKEN;
4381
+ if (opToken) {
4382
+ console.log(` \u2713 1Password service account connected`);
4383
+ } else {
4384
+ console.log(` \u25CB 1Password not configured (optional)`);
4385
+ }
4386
+ console.log(`
4387
+ Testing products...
4388
+ `);
4389
+ const products = [
4390
+ {
4391
+ id: "msp",
4392
+ url: process.env.BRAINSTORM_MSP_URL ?? "https://brainstormmsp.ai",
4393
+ key: "BRAINSTORM_MSP_API_KEY"
4394
+ },
4395
+ {
4396
+ id: "br",
4397
+ url: process.env.BRAINSTORM_BR_URL ?? "https://api.brainstormrouter.com",
4398
+ key: "BRAINSTORM_API_KEY"
4399
+ },
4400
+ {
4401
+ id: "gtm",
4402
+ url: process.env.BRAINSTORM_GTM_URL ?? "https://catsfeet.com",
4403
+ key: "BRAINSTORM_GTM_API_KEY"
4404
+ },
4405
+ {
4406
+ id: "vm",
4407
+ url: process.env.BRAINSTORM_VM_URL ?? "https://vm.brainstorm.co",
4408
+ key: "BRAINSTORM_VM_API_KEY"
4409
+ },
4410
+ {
4411
+ id: "shield",
4412
+ url: process.env.BRAINSTORM_SHIELD_URL ?? "https://shield.brainstorm.co",
4413
+ key: "BRAINSTORM_SHIELD_API_KEY"
4414
+ }
4415
+ ];
4416
+ let connectedCount = 0;
4417
+ let totalTools = 0;
4418
+ const connectedSystems = [];
4419
+ for (const p of products) {
4420
+ try {
4421
+ const res = await fetch(`${p.url}/health`, {
4422
+ signal: AbortSignal.timeout(5e3)
4423
+ });
4424
+ if (res.ok) {
4425
+ const health = await res.json();
4426
+ let toolCount = 0;
4427
+ const apiKey = process.env[p.key];
4428
+ if (apiKey) {
4429
+ try {
4430
+ const toolsRes = await fetch(`${p.url}/api/v1/god-mode/tools`, {
4431
+ headers: { Authorization: `Bearer ${apiKey}` },
4432
+ signal: AbortSignal.timeout(5e3)
4433
+ });
4434
+ if (toolsRes.ok) {
4435
+ const data = await toolsRes.json();
4436
+ toolCount = data.tool_count ?? data.tools?.length ?? 0;
4437
+ }
4438
+ } catch {
4439
+ }
4440
+ }
4441
+ console.log(
4442
+ ` \u25CF ${p.id.padEnd(8)} ${String(toolCount).padStart(2)} tools ${health.version ?? ""}`
4443
+ );
4444
+ connectedCount++;
4445
+ totalTools += toolCount;
4446
+ connectedSystems.push(p.id);
4168
4447
  } else {
4169
- printResumeSummary(session, sessionManager);
4448
+ console.log(` \u25CB ${p.id.padEnd(8)} unreachable (${res.status})`);
4170
4449
  }
4171
- } else {
4172
- session = sessionManager.start(projectPath);
4450
+ } catch {
4451
+ console.log(` \u25CB ${p.id.padEnd(8)} offline`);
4173
4452
  }
4174
- const localCount = registry.models.filter((m) => m.isLocal).length;
4175
- const cloudCount = registry.models.filter((m) => !m.isLocal).length;
4176
- if (opts.simple) {
4177
- const readline = await import("readline/promises");
4178
- const rl = readline.createInterface({
4179
- input: process.stdin,
4180
- output: process.stdout
4181
- });
4182
- console.log(`
4183
- \u{1F9E0} brainstorm v0.1.0`);
4184
- console.log(
4185
- ` Strategy: ${router.getActiveStrategy()} | Models: ${localCount} local, ${cloudCount} cloud`
4186
- );
4187
- console.log(` Project: ${projectPath}`);
4188
- if (isCommunityTier)
4453
+ }
4454
+ const claudeDir = join4(homedir2(), ".claude");
4455
+ const mcpPath = join4(claudeDir, "mcp.json");
4456
+ if (existsSync3(mcpPath)) {
4457
+ try {
4458
+ const existing = JSON.parse(readFileSync3(mcpPath, "utf-8"));
4459
+ if (!existing.mcpServers?.brainstorm) {
4460
+ existing.mcpServers = existing.mcpServers ?? {};
4461
+ existing.mcpServers.brainstorm = {
4462
+ command: "brainstorm",
4463
+ args: ["mcp"]
4464
+ };
4465
+ writeFileSync2(mcpPath, JSON.stringify(existing, null, 2));
4189
4466
  console.log(
4190
- ` Community tier (5 req/min, cheap models). Set BRAINSTORM_API_KEY for full access.`
4467
+ `
4468
+ \u2713 Added brainstorm MCP server to ~/.claude/mcp.json`
4191
4469
  );
4192
- console.log(
4470
+ } else {
4471
+ console.log(
4472
+ `
4473
+ \u2713 brainstorm MCP server already in ~/.claude/mcp.json`
4474
+ );
4475
+ }
4476
+ } catch {
4477
+ console.log(`
4478
+ \u26A0 Could not update ~/.claude/mcp.json (parse error)`);
4479
+ }
4480
+ } else {
4481
+ console.log(
4482
+ `
4483
+ \u25CB ~/.claude/mcp.json not found (Claude Code not detected)`
4484
+ );
4485
+ }
4486
+ console.log(`
4487
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4488
+ console.log(
4489
+ ` ${connectedCount} products connected, ${totalTools} tools available`
4490
+ );
4491
+ console.log(` Run: brainstorm status (full diagnostic)`);
4492
+ console.log(` Run: brainstorm mcp (start MCP server for Claude)`);
4493
+ console.log();
4494
+ });
4495
+ program.command("ecosystem").alias("status").description(
4496
+ "Show full ecosystem status \u2014 all products, tools, auth, connectivity"
4497
+ ).action(async () => {
4498
+ console.log(`
4499
+ Brainstorm Ecosystem Status`);
4500
+ console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
4501
+ `);
4502
+ const brKey = process.env.BRAINSTORM_API_KEY;
4503
+ console.log(
4504
+ ` Auth: ${brKey ? "\u2713 BR key set" : "\u2717 BRAINSTORM_API_KEY not set"}`
4505
+ );
4506
+ console.log(
4507
+ ` Vault: ${process.env.OP_SERVICE_ACCOUNT_TOKEN ? "\u2713 1Password connected" : "\u25CB 1Password not configured"}`
4508
+ );
4509
+ console.log(`
4510
+ Products:`);
4511
+ const products = [
4512
+ {
4513
+ id: "msp",
4514
+ name: "BrainstormMSP",
4515
+ url: process.env.BRAINSTORM_MSP_URL ?? "https://brainstormmsp.ai",
4516
+ key: "BRAINSTORM_MSP_API_KEY"
4517
+ },
4518
+ {
4519
+ id: "br",
4520
+ name: "BrainstormRouter",
4521
+ url: process.env.BRAINSTORM_BR_URL ?? "https://api.brainstormrouter.com",
4522
+ key: "BRAINSTORM_API_KEY"
4523
+ },
4524
+ {
4525
+ id: "gtm",
4526
+ name: "BrainstormGTM",
4527
+ url: process.env.BRAINSTORM_GTM_URL ?? "https://catsfeet.com",
4528
+ key: "BRAINSTORM_GTM_API_KEY"
4529
+ },
4530
+ {
4531
+ id: "vm",
4532
+ name: "BrainstormVM",
4533
+ url: process.env.BRAINSTORM_VM_URL ?? "https://vm.brainstorm.co",
4534
+ key: "BRAINSTORM_VM_API_KEY"
4535
+ },
4536
+ {
4537
+ id: "shield",
4538
+ name: "BrainstormShield",
4539
+ url: process.env.BRAINSTORM_SHIELD_URL ?? "https://shield.brainstorm.co",
4540
+ key: "BRAINSTORM_SHIELD_API_KEY"
4541
+ }
4542
+ ];
4543
+ let totalTools = 0;
4544
+ for (const p of products) {
4545
+ try {
4546
+ const start = Date.now();
4547
+ const res = await fetch(`${p.url}/health`, {
4548
+ signal: AbortSignal.timeout(5e3)
4549
+ });
4550
+ const latency = Date.now() - start;
4551
+ if (res.ok) {
4552
+ const health = await res.json();
4553
+ let toolCount = 0;
4554
+ const apiKey = process.env[p.key];
4555
+ if (apiKey) {
4556
+ try {
4557
+ const toolsRes = await fetch(`${p.url}/api/v1/god-mode/tools`, {
4558
+ headers: { Authorization: `Bearer ${apiKey}` },
4559
+ signal: AbortSignal.timeout(5e3)
4560
+ });
4561
+ if (toolsRes.ok) {
4562
+ const data = await toolsRes.json();
4563
+ toolCount = data.tool_count ?? data.tools?.length ?? 0;
4564
+ totalTools += toolCount;
4565
+ }
4566
+ } catch {
4567
+ }
4568
+ }
4569
+ console.log(
4570
+ ` \u25CF ${p.name.padEnd(20)} ${String(toolCount).padStart(2)} tools ${p.url.padEnd(35)} ${latency}ms ${health.status ?? "ok"}`
4571
+ );
4572
+ } else {
4573
+ console.log(
4574
+ ` \u25CB ${p.name.padEnd(20)} \u2014 tools ${p.url.padEnd(35)} \u2014 ${res.status}`
4575
+ );
4576
+ }
4577
+ } catch {
4578
+ console.log(
4579
+ ` \u25CB ${p.name.padEnd(20)} \u2014 tools ${p.url.padEnd(35)} \u2014 offline`
4580
+ );
4581
+ }
4582
+ }
4583
+ const { existsSync: existsSync3 } = await import("fs");
4584
+ const { join: join4 } = await import("path");
4585
+ const { homedir: homedir2 } = await import("os");
4586
+ const mcpPath = join4(homedir2(), ".claude", "mcp.json");
4587
+ let mcpConfigured = false;
4588
+ if (existsSync3(mcpPath)) {
4589
+ try {
4590
+ const mcp = JSON.parse(
4591
+ (await import("fs")).readFileSync(mcpPath, "utf-8")
4592
+ );
4593
+ mcpConfigured = !!mcp.mcpServers?.brainstorm;
4594
+ } catch {
4595
+ }
4596
+ }
4597
+ console.log(
4598
+ `
4599
+ MCP: ${mcpConfigured ? "\u2713 brainstorm MCP server configured" : "\u25CB not configured (run brainstorm setup)"}`
4600
+ );
4601
+ console.log(`
4602
+ ${totalTools} tools available across ecosystem.`);
4603
+ console.log();
4604
+ });
4605
+ program.command("serve").description(
4606
+ "Start the Brainstorm control plane HTTP API server (God Mode over HTTP)"
4607
+ ).option("--port <port>", "Port to listen on", "8000").option("--host <host>", "Host to bind to", "127.0.0.1").option("--cors", "Enable CORS for dashboard access").action(async (opts) => {
4608
+ const { createServer } = await import("http");
4609
+ const { randomUUID } = await import("crypto");
4610
+ const {
4611
+ connectGodMode,
4612
+ createProductConnectors,
4613
+ listChangeSets,
4614
+ approveChangeSet,
4615
+ rejectChangeSet,
4616
+ verifyEvent,
4617
+ verifyJWT,
4618
+ extractBearerToken,
4619
+ setAuditPersister
4620
+ } = await import("@brainst0rm/godmode");
4621
+ const { ChangeSetLogRepository } = await import("@brainst0rm/db");
4622
+ const config = loadConfig();
4623
+ const port = parseInt(opts.port);
4624
+ const host = opts.host;
4625
+ console.log(`
4626
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
4627
+ console.log(` brainstorm serve \u2014 Control Plane API`);
4628
+ console.log(` \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4629
+ `);
4630
+ const envKeys = /* @__PURE__ */ new Map();
4631
+ for (const name of PROVIDER_KEY_NAMES) {
4632
+ const val = process.env[name];
4633
+ if (val) envKeys.set(name, val);
4634
+ }
4635
+ const resolvedKeys = {
4636
+ get: (name) => envKeys.get(name) ?? null
4637
+ };
4638
+ const registry = await createProviderRegistry(config, resolvedKeys);
4639
+ const db = getDb();
4640
+ const csLogRepo = new ChangeSetLogRepository(db);
4641
+ setAuditPersister((entry) => {
4642
+ csLogRepo.log({
4643
+ changesetId: entry.changesetId,
4644
+ connector: entry.connector,
4645
+ action: entry.action,
4646
+ description: entry.description,
4647
+ riskScore: entry.riskScore,
4648
+ status: entry.status,
4649
+ changesJson: entry.changesJson,
4650
+ simulationJson: entry.simulationJson,
4651
+ rollbackJson: entry.rollbackJson,
4652
+ createdAt: entry.createdAt,
4653
+ executedAt: entry.executedAt,
4654
+ sessionId: null
4655
+ });
4656
+ });
4657
+ const costTracker = new CostTracker(db, config.budget);
4658
+ const tools = createDefaultToolRegistry();
4659
+ const { frontmatter } = buildSystemPrompt(process.cwd());
4660
+ const router = new BrainstormRouter(
4661
+ config,
4662
+ registry,
4663
+ costTracker,
4664
+ frontmatter
4665
+ );
4666
+ const defaultConnectors = {
4667
+ msp: {
4668
+ enabled: true,
4669
+ baseUrl: process.env.BRAINSTORM_MSP_URL ?? "https://brainstormmsp.ai",
4670
+ apiKeyName: "BRAINSTORM_MSP_API_KEY"
4671
+ }
4672
+ };
4673
+ const mergedGmConfig = {
4674
+ ...config.godmode,
4675
+ connectors: { ...defaultConnectors, ...config.godmode.connectors }
4676
+ };
4677
+ const connectors = await createProductConnectors(mergedGmConfig);
4678
+ const godmode = await connectGodMode(tools, mergedGmConfig, connectors);
4679
+ console.log(
4680
+ ` God Mode: ${godmode.connectedSystems.length} systems connected, ${godmode.totalTools} tools`
4681
+ );
4682
+ for (const sys of godmode.connectedSystems) {
4683
+ console.log(
4684
+ ` \u2713 ${sys.displayName} (${sys.toolCount} tools, ${sys.latencyMs}ms)`
4685
+ );
4686
+ }
4687
+ for (const err of godmode.errors) {
4688
+ console.log(` \u2717 ${err.name}: ${err.error}`);
4689
+ }
4690
+ function json(res, status, body) {
4691
+ const payload = JSON.stringify(body);
4692
+ res.writeHead(status, {
4693
+ "Content-Type": "application/json",
4694
+ ...opts.cors ? {
4695
+ "Access-Control-Allow-Origin": "*",
4696
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
4697
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
4698
+ } : {}
4699
+ });
4700
+ res.end(payload);
4701
+ }
4702
+ function envelope(data) {
4703
+ return {
4704
+ ok: true,
4705
+ data,
4706
+ request_id: randomUUID(),
4707
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4708
+ };
4709
+ }
4710
+ function errorResponse(res, status, message) {
4711
+ json(res, status, {
4712
+ ok: false,
4713
+ error: message,
4714
+ request_id: randomUUID(),
4715
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4716
+ });
4717
+ }
4718
+ async function readBody(req) {
4719
+ const chunks = [];
4720
+ for await (const chunk of req) chunks.push(chunk);
4721
+ return Buffer.concat(chunks).toString("utf-8");
4722
+ }
4723
+ const allTools = tools.listTools();
4724
+ const server = createServer(async (req, res) => {
4725
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
4726
+ const path = url.pathname;
4727
+ const method = req.method ?? "GET";
4728
+ if (method === "OPTIONS" && opts.cors) {
4729
+ res.writeHead(204, {
4730
+ "Access-Control-Allow-Origin": "*",
4731
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
4732
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
4733
+ "Access-Control-Max-Age": "86400"
4734
+ });
4735
+ res.end();
4736
+ return;
4737
+ }
4738
+ try {
4739
+ if (path === "/health" && method === "GET") {
4740
+ json(res, 200, {
4741
+ status: "healthy",
4742
+ version: CLI_VERSION,
4743
+ uptime_seconds: Math.floor(process.uptime()),
4744
+ god_mode: {
4745
+ connected: godmode.connectedSystems.length,
4746
+ tools: godmode.totalTools
4747
+ }
4748
+ });
4749
+ return;
4750
+ }
4751
+ const jwtSecret = process.env.SUPABASE_JWT_SECRET;
4752
+ let tenantId;
4753
+ if (path.startsWith("/api/")) {
4754
+ if (!jwtSecret) {
4755
+ } else {
4756
+ const token = extractBearerToken(
4757
+ req.headers.authorization
4758
+ );
4759
+ if (!token) {
4760
+ errorResponse(res, 401, "Missing Authorization header");
4761
+ return;
4762
+ }
4763
+ const auth = verifyJWT(token, jwtSecret);
4764
+ if (!auth.authenticated) {
4765
+ errorResponse(res, 401, auth.error ?? "Authentication failed");
4766
+ return;
4767
+ }
4768
+ tenantId = auth.payload?.platform_tenant_id;
4769
+ }
4770
+ }
4771
+ if (path === "/api/v1/products" && method === "GET") {
4772
+ const products = godmode.connectedSystems.map((sys) => ({
4773
+ product: sys.name,
4774
+ display_name: sys.displayName,
4775
+ status: "healthy",
4776
+ latency_ms: sys.latencyMs,
4777
+ tool_count: sys.toolCount,
4778
+ capabilities: sys.capabilities,
4779
+ last_checked: (/* @__PURE__ */ new Date()).toISOString()
4780
+ }));
4781
+ json(res, 200, envelope(products));
4782
+ return;
4783
+ }
4784
+ if (path === "/api/v1/tools" && method === "GET") {
4785
+ json(res, 200, envelope(allTools));
4786
+ return;
4787
+ }
4788
+ if (path === "/api/v1/tools/execute" && method === "POST") {
4789
+ const body = JSON.parse(await readBody(req));
4790
+ const { tool: toolName, params } = body;
4791
+ if (!toolName) {
4792
+ errorResponse(res, 400, "Missing 'tool' field");
4793
+ return;
4794
+ }
4795
+ const tool = tools.get(toolName);
4796
+ if (!tool) {
4797
+ errorResponse(res, 404, `Tool '${toolName}' not found`);
4798
+ return;
4799
+ }
4800
+ try {
4801
+ const result = await tool.execute(params ?? {});
4802
+ json(
4803
+ res,
4804
+ 200,
4805
+ envelope({
4806
+ tool: toolName,
4807
+ result,
4808
+ executed_at: (/* @__PURE__ */ new Date()).toISOString()
4809
+ })
4810
+ );
4811
+ } catch (err) {
4812
+ const msg = err instanceof Error ? err.message : String(err);
4813
+ errorResponse(res, 500, `Tool execution failed: ${msg}`);
4814
+ }
4815
+ return;
4816
+ }
4817
+ if (path === "/api/v1/changesets" && method === "GET") {
4818
+ json(res, 200, envelope(listChangeSets()));
4819
+ return;
4820
+ }
4821
+ const approveMatch = path.match(
4822
+ /^\/api\/v1\/changesets\/([^/]+)\/approve$/
4823
+ );
4824
+ if (approveMatch && method === "POST") {
4825
+ const result = await approveChangeSet(approveMatch[1], "user");
4826
+ json(res, result.success ? 200 : 400, envelope(result));
4827
+ return;
4828
+ }
4829
+ const rejectMatch = path.match(
4830
+ /^\/api\/v1\/changesets\/([^/]+)\/reject$/
4831
+ );
4832
+ if (rejectMatch && method === "POST") {
4833
+ const result = rejectChangeSet(rejectMatch[1]);
4834
+ json(res, result.success ? 200 : 400, envelope(result));
4835
+ return;
4836
+ }
4837
+ if (path === "/api/v1/audit" && method === "GET") {
4838
+ const limit = parseInt(url.searchParams.get("limit") ?? "50");
4839
+ const offset = parseInt(url.searchParams.get("offset") ?? "0");
4840
+ try {
4841
+ const rows = db.prepare(
4842
+ `SELECT * FROM audit_log ORDER BY created_at DESC LIMIT ? OFFSET ?`
4843
+ ).all(limit, offset);
4844
+ json(res, 200, envelope({ entries: rows, limit, offset }));
4845
+ } catch {
4846
+ json(res, 200, envelope({ entries: [], limit, offset }));
4847
+ }
4848
+ return;
4849
+ }
4850
+ if (path === "/api/v1/audit/changesets" && method === "GET") {
4851
+ const { ChangeSetLogRepository: ChangeSetLogRepository2 } = await import("@brainst0rm/db");
4852
+ const csLog = new ChangeSetLogRepository2(db);
4853
+ const limit = parseInt(url.searchParams.get("limit") ?? "50");
4854
+ const offset = parseInt(url.searchParams.get("offset") ?? "0");
4855
+ const connector = url.searchParams.get("connector");
4856
+ const entries = connector ? csLog.byConnector(connector, limit) : csLog.recent(limit, offset);
4857
+ json(
4858
+ res,
4859
+ 200,
4860
+ envelope({
4861
+ entries,
4862
+ total: csLog.count(),
4863
+ limit,
4864
+ offset
4865
+ })
4866
+ );
4867
+ return;
4868
+ }
4869
+ if (path === "/api/v1/platform/events" && method === "POST") {
4870
+ const body = JSON.parse(await readBody(req));
4871
+ const masterSecret = process.env.BRAINSTORM_PLATFORM_SECRET;
4872
+ if (!masterSecret) {
4873
+ errorResponse(res, 503, "Platform secret not configured");
4874
+ return;
4875
+ }
4876
+ if (!verifyEvent(body, masterSecret)) {
4877
+ errorResponse(res, 401, "Invalid event signature");
4878
+ return;
4879
+ }
4880
+ console.log(
4881
+ ` [event] ${body.type} from ${body.product} (tenant: ${body.tenant_id})`
4882
+ );
4883
+ json(res, 200, envelope({ received: true, event_id: body.id }));
4884
+ return;
4885
+ }
4886
+ if (path === "/api/v1/chat" && method === "POST") {
4887
+ const body = JSON.parse(await readBody(req));
4888
+ const { message } = body;
4889
+ if (!message) {
4890
+ errorResponse(res, 400, "Missing 'message' field");
4891
+ return;
4892
+ }
4893
+ const sessionManager = new SessionManager(db);
4894
+ const session = sessionManager.start(process.cwd());
4895
+ const { prompt: sysPrompt, segments: sysSegments } = buildSystemPrompt(process.cwd());
4896
+ const gmPromptText = godmode.promptSegment?.text ?? "";
4897
+ const fullSystemPrompt = sysPrompt + gmPromptText;
4898
+ const messages = [{ role: "user", content: message }];
4899
+ let finalText = "";
4900
+ let totalCost = 0;
4901
+ for await (const event of runAgentLoop(messages, {
4902
+ config,
4903
+ registry,
4904
+ router,
4905
+ costTracker,
4906
+ tools,
4907
+ sessionId: session.id,
4908
+ projectPath: process.cwd(),
4909
+ systemPrompt: fullSystemPrompt,
4910
+ systemSegments: sysSegments,
4911
+ permissionCheck: () => "allow",
4912
+ middleware: createDefaultMiddlewarePipeline(process.cwd())
4913
+ })) {
4914
+ if (event.type === "text-delta") {
4915
+ finalText += event.delta;
4916
+ }
4917
+ if (event.type === "done") {
4918
+ totalCost = event.totalCost;
4919
+ }
4920
+ }
4921
+ json(
4922
+ res,
4923
+ 200,
4924
+ envelope({
4925
+ response: finalText,
4926
+ session_id: session.id,
4927
+ cost: totalCost
4928
+ })
4929
+ );
4930
+ return;
4931
+ }
4932
+ if (path === "/api/v1/chat/stream" && method === "POST") {
4933
+ const body = JSON.parse(await readBody(req));
4934
+ const { message } = body;
4935
+ if (!message) {
4936
+ errorResponse(res, 400, "Missing 'message' field");
4937
+ return;
4938
+ }
4939
+ res.writeHead(200, {
4940
+ "Content-Type": "text/event-stream",
4941
+ "Cache-Control": "no-cache",
4942
+ Connection: "keep-alive",
4943
+ ...opts.cors ? { "Access-Control-Allow-Origin": "*" } : {}
4944
+ });
4945
+ const sessionManager = new SessionManager(db);
4946
+ const session = sessionManager.start(process.cwd());
4947
+ const { prompt: sysPrompt, segments: sysSegments } = buildSystemPrompt(process.cwd());
4948
+ const gmPromptText = godmode.promptSegment?.text ?? "";
4949
+ const fullSystemPrompt = sysPrompt + gmPromptText;
4950
+ const messages = [{ role: "user", content: message }];
4951
+ for await (const event of runAgentLoop(messages, {
4952
+ config,
4953
+ registry,
4954
+ router,
4955
+ costTracker,
4956
+ tools,
4957
+ sessionId: session.id,
4958
+ projectPath: process.cwd(),
4959
+ systemPrompt: fullSystemPrompt,
4960
+ systemSegments: sysSegments,
4961
+ permissionCheck: () => "allow",
4962
+ middleware: createDefaultMiddlewarePipeline(process.cwd())
4963
+ })) {
4964
+ res.write(`data: ${JSON.stringify(event)}
4965
+
4966
+ `);
4967
+ if (event.type === "done" || event.type === "error") break;
4968
+ }
4969
+ res.write("data: [DONE]\n\n");
4970
+ res.end();
4971
+ return;
4972
+ }
4973
+ errorResponse(res, 404, `Not found: ${method} ${path}`);
4974
+ } catch (err) {
4975
+ const msg = err instanceof Error ? err.message : String(err);
4976
+ console.error(` [serve] Error: ${msg}`);
4977
+ errorResponse(res, 500, msg);
4978
+ }
4979
+ });
4980
+ server.listen(port, host, () => {
4981
+ console.log(`
4982
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4983
+ console.log(` API server listening on http://${host}:${port}`);
4984
+ console.log();
4985
+ console.log(` Endpoints:`);
4986
+ console.log(` GET /health Health check`);
4987
+ console.log(
4988
+ ` GET /api/v1/products Connected products`
4989
+ );
4990
+ console.log(
4991
+ ` GET /api/v1/tools All God Mode tools`
4992
+ );
4993
+ console.log(` POST /api/v1/tools/execute Execute a tool`);
4994
+ console.log(
4995
+ ` GET /api/v1/changesets Pending ChangeSets`
4996
+ );
4997
+ console.log(
4998
+ ` POST /api/v1/changesets/:id/approve Approve + execute`
4999
+ );
5000
+ console.log(
5001
+ ` POST /api/v1/changesets/:id/reject Reject a ChangeSet`
5002
+ );
5003
+ console.log(` GET /api/v1/audit Audit trail`);
5004
+ console.log(
5005
+ ` POST /api/v1/platform/events Receive signed events`
5006
+ );
5007
+ console.log(
5008
+ ` POST /api/v1/chat Natural language query`
5009
+ );
5010
+ console.log(
5011
+ ` POST /api/v1/chat/stream SSE streaming chat`
5012
+ );
5013
+ console.log();
5014
+ });
5015
+ await new Promise(() => {
5016
+ });
5017
+ });
5018
+ program.command("chat", { isDefault: true }).description("Start an interactive chat session").option("--simple", "Use simple readline interface instead of TUI").option(
5019
+ "--daemon",
5020
+ "Daemon mode \u2014 model-driven tick loop (requires --simple for MVP)"
5021
+ ).option("--continue", "Resume the most recent session").option("--resume <id>", "Resume a specific session by ID").option("--fork <id>", "Fork a session (copy history, new session)").option("--lfg", "Full auto mode \u2014 skip all permission confirmations").option(
5022
+ "--strategy <name>",
5023
+ "Routing strategy: cost-first, quality-first, combined, capability"
5024
+ ).option("--verbose-routing", "Print routing decisions to stderr").option(
5025
+ "--fast",
5026
+ "Fast startup \u2014 skip provider discovery, MCP connections, and eval probes"
5027
+ ).action(
5028
+ async (opts) => {
5029
+ const config = loadConfig();
5030
+ if (opts.daemon && !opts.simple) {
5031
+ console.error(
5032
+ " Daemon mode requires --simple for MVP. Run: brainstorm chat --simple --daemon"
5033
+ );
5034
+ process.exit(1);
5035
+ }
5036
+ if (opts.lfg) {
5037
+ config.general.defaultPermissionMode = "auto";
5038
+ }
5039
+ if (opts.fast) {
5040
+ config.general.skipProviderDiscovery = true;
5041
+ config.general.skipEvalProbes = true;
5042
+ }
5043
+ const db = getDb();
5044
+ const projectPath = process.cwd();
5045
+ const tools = createDefaultToolRegistry({ daemon: opts.daemon });
5046
+ configureSandbox(
5047
+ config.shell.sandbox,
5048
+ projectPath,
5049
+ config.shell.maxOutputBytes,
5050
+ config.shell.containerImage,
5051
+ config.shell.containerTimeout
5052
+ );
5053
+ const permissionManager = new PermissionManager(
5054
+ config.general.defaultPermissionMode,
5055
+ config.permissions
5056
+ );
5057
+ let currentOutputStyle = config.general.outputStyle ?? "concise";
5058
+ let currentRole;
5059
+ const sessionManager = new SessionManager(db);
5060
+ const middleware = createDefaultMiddlewarePipeline(projectPath);
5061
+ const [resolvedKeys, promptResult] = await Promise.all([
5062
+ resolveProviderKeys(),
5063
+ Promise.resolve(buildSystemPrompt(projectPath, currentOutputStyle))
5064
+ ]);
5065
+ let {
5066
+ prompt: systemPrompt,
5067
+ segments: systemSegments,
5068
+ frontmatter
5069
+ } = promptResult;
5070
+ const toolSection = buildToolAwarenessSection(tools.listTools());
5071
+ systemPrompt += toolSection;
5072
+ if (systemSegments.length > 0) {
5073
+ systemSegments[0] = {
5074
+ text: systemSegments[0].text + toolSection,
5075
+ cacheable: true
5076
+ };
5077
+ }
5078
+ const resolvedBRKey = resolvedKeys.get("BRAINSTORM_API_KEY") ?? getBrainstormApiKey();
5079
+ const isCommunityTier = isCommunityKey(resolvedBRKey);
5080
+ if (resolvedBRKey) process.env._BR_RESOLVED_KEY = resolvedBRKey;
5081
+ const [registry] = await Promise.all([
5082
+ createProviderRegistry(config, resolvedKeys),
5083
+ opts.fast ? Promise.resolve() : connectMCPServers(
5084
+ tools,
5085
+ config,
5086
+ resolvedKeys.get("BRAINSTORM_API_KEY")
5087
+ )
5088
+ ]);
5089
+ const costTracker = new CostTracker(db, config.budget);
5090
+ const routingOutcomeRepo = new RoutingOutcomeRepository(db);
5091
+ const historicalStats = routingOutcomeRepo.loadAggregated();
5092
+ const router = new BrainstormRouter(
5093
+ config,
5094
+ registry,
5095
+ costTracker,
5096
+ frontmatter,
5097
+ historicalStats
5098
+ );
5099
+ const hasOwnKeys = !!resolvedKeys.get("DEEPSEEK_API_KEY") || !!resolvedKeys.get("ANTHROPIC_API_KEY") || !!resolvedKeys.get("OPENAI_API_KEY") || !!resolvedKeys.get("MOONSHOT_API_KEY") || !!resolvedKeys.get("GOOGLE_GENERATIVE_AI_API_KEY");
5100
+ if (opts.strategy) {
5101
+ router.setStrategy(opts.strategy);
5102
+ } else if ((!isCommunityTier || hasOwnKeys) && router.getActiveStrategy() !== "capability") {
5103
+ router.setStrategy("quality-first");
5104
+ }
5105
+ const subagentTool = createSubagentTool({
5106
+ config,
5107
+ registry,
5108
+ router,
5109
+ costTracker,
5110
+ tools,
5111
+ projectPath,
5112
+ permissionCheck: (name, perm) => permissionManager.check(name, perm),
5113
+ containerIsolation: config.shell.sandbox === "container",
5114
+ parentSegments: systemSegments
5115
+ });
5116
+ tools.register(subagentTool);
5117
+ let godModeResult = null;
5118
+ const hasAnyConnectorKey = !!(process.env.BRAINSTORM_MSP_API_KEY || process.env.BRAINSTORM_EMAIL_API_KEY || process.env.BRAINSTORM_VM_API_KEY || process.env._GM_MSP_KEY || process.env._GM_EMAIL_KEY || process.env._GM_VM_KEY);
5119
+ const godmodeEnabled = config.godmode.enabled || hasAnyConnectorKey;
5120
+ if (godmodeEnabled && !opts.fast) {
5121
+ try {
5122
+ const {
5123
+ connectGodMode,
5124
+ createProductConnectors,
5125
+ setAuditPersister: setAuditPersisterChat
5126
+ } = await import("@brainst0rm/godmode");
5127
+ const { ChangeSetLogRepository: CSLogChat } = await import("@brainst0rm/db");
5128
+ const csLogChat = new CSLogChat(db);
5129
+ setAuditPersisterChat((entry) => {
5130
+ csLogChat.log({
5131
+ changesetId: entry.changesetId,
5132
+ connector: entry.connector,
5133
+ action: entry.action,
5134
+ description: entry.description,
5135
+ riskScore: entry.riskScore,
5136
+ status: entry.status,
5137
+ changesJson: entry.changesJson,
5138
+ simulationJson: entry.simulationJson,
5139
+ rollbackJson: entry.rollbackJson,
5140
+ createdAt: entry.createdAt,
5141
+ executedAt: entry.executedAt,
5142
+ sessionId: null
5143
+ });
5144
+ });
5145
+ const defaultConnectors = {
5146
+ msp: {
5147
+ enabled: true,
5148
+ baseUrl: process.env.BRAINSTORM_MSP_URL ?? "https://brainstormmsp.ai",
5149
+ apiKeyName: "BRAINSTORM_MSP_API_KEY"
5150
+ }
5151
+ };
5152
+ const mergedGmConfig = {
5153
+ ...config.godmode,
5154
+ connectors: { ...defaultConnectors, ...config.godmode.connectors }
5155
+ };
5156
+ const activeConnectors = await createProductConnectors(mergedGmConfig);
5157
+ godModeResult = await connectGodMode(
5158
+ tools,
5159
+ mergedGmConfig,
5160
+ activeConnectors
5161
+ );
5162
+ if (godModeResult.connectedSystems.length > 0) {
5163
+ process.stderr.write(
5164
+ `[godmode] Connected: ${godModeResult.connectedSystems.map((s) => s.displayName).join(", ")} (${godModeResult.totalTools} tools)
5165
+ `
5166
+ );
5167
+ if (godModeResult.promptSegment?.text) {
5168
+ systemPrompt += "\n" + godModeResult.promptSegment.text;
5169
+ if (systemSegments.length > 0) {
5170
+ systemSegments.push(godModeResult.promptSegment);
5171
+ }
5172
+ }
5173
+ }
5174
+ } catch (err) {
5175
+ process.stderr.write(
5176
+ `[godmode] Init failed: ${err instanceof Error ? err.message : String(err)}
5177
+ `
5178
+ );
5179
+ }
5180
+ }
5181
+ const hasDirectProviderKeys = !!resolvedKeys.get("DEEPSEEK_API_KEY") || !!resolvedKeys.get("ANTHROPIC_API_KEY") || !!resolvedKeys.get("OPENAI_API_KEY") || !!resolvedKeys.get("GOOGLE_GENERATIVE_AI_API_KEY") || !!resolvedKeys.get("MOONSHOT_API_KEY");
5182
+ let preferredModelId = resolvedKeys.get(
5183
+ "MOONSHOT_API_KEY"
5184
+ ) ? "moonshot/kimi-k2.5" : isCommunityTier && !hasDirectProviderKeys ? "brainstormrouter/auto" : void 0;
5185
+ let session;
5186
+ if (opts.fork) {
5187
+ session = sessionManager.fork(opts.fork);
5188
+ if (!session) {
5189
+ console.error(` Session '${opts.fork}' not found.`);
5190
+ process.exit(1);
5191
+ }
5192
+ console.log(
5193
+ ` Forked session ${opts.fork.slice(0, 8)} -> ${session.id.slice(0, 8)}`
5194
+ );
5195
+ } else if (opts.resume) {
5196
+ session = sessionManager.resume(opts.resume);
5197
+ if (!session) {
5198
+ console.error(` Session '${opts.resume}' not found.`);
5199
+ process.exit(1);
5200
+ }
5201
+ printResumeSummary(session, sessionManager);
5202
+ } else if (opts.continue) {
5203
+ if (opts.daemon) {
5204
+ const { SessionRepository: SessRepoResume } = await import("@brainst0rm/db");
5205
+ const sessRepoResume = new SessRepoResume(db);
5206
+ const lastDaemon = sessRepoResume.getLastDaemon(projectPath);
5207
+ if (lastDaemon) {
5208
+ session = sessionManager.resume(lastDaemon.id);
5209
+ if (session) {
5210
+ console.log(
5211
+ ` Resuming daemon session ${lastDaemon.id.slice(0, 8)} (${lastDaemon.tickCount ?? 0} ticks, $${(lastDaemon.totalCost ?? 0).toFixed(4)})`
5212
+ );
5213
+ } else {
5214
+ session = sessionManager.start(projectPath);
5215
+ }
5216
+ } else {
5217
+ session = sessionManager.start(projectPath);
5218
+ }
5219
+ } else {
5220
+ session = sessionManager.resumeLatest(projectPath);
5221
+ if (!session) {
5222
+ session = sessionManager.start(projectPath);
5223
+ } else {
5224
+ printResumeSummary(session, sessionManager);
5225
+ }
5226
+ }
5227
+ } else {
5228
+ session = sessionManager.start(projectPath);
5229
+ }
5230
+ const localCount = registry.models.filter((m) => m.isLocal).length;
5231
+ const cloudCount = registry.models.filter((m) => !m.isLocal).length;
5232
+ if (opts.simple) {
5233
+ const readline = await import("readline/promises");
5234
+ const rl = readline.createInterface({
5235
+ input: process.stdin,
5236
+ output: process.stdout
5237
+ });
5238
+ console.log(`
5239
+ \u{1F9E0} brainstorm v0.1.0`);
5240
+ console.log(
5241
+ ` Strategy: ${router.getActiveStrategy()} | Models: ${localCount} local, ${cloudCount} cloud`
5242
+ );
5243
+ console.log(` Project: ${projectPath}`);
5244
+ if (isCommunityTier)
5245
+ console.log(
5246
+ ` Community tier (5 req/min, cheap models). Set BRAINSTORM_API_KEY for full access.`
5247
+ );
5248
+ console.log(
4193
5249
  ` Commands: /quit, /model <id>, /strategy <name>, /compact`
4194
5250
  );
5251
+ if (opts.daemon)
5252
+ console.log(
5253
+ ` DAEMON MODE: tick every ${config.daemon.tickIntervalMs / 1e3}s, max ${config.daemon.maxTicksPerSession} ticks`
5254
+ );
4195
5255
  console.log(` Ctrl+C to interrupt, Ctrl+D to exit.
4196
5256
  `);
5257
+ if (opts.daemon) {
5258
+ const { DaemonController, DailyLog } = await import("@brainst0rm/core");
5259
+ const { DailyLogRepository, SessionRepository: SessRepo } = await import("@brainst0rm/db");
5260
+ const sessRepo = new SessRepo(db);
5261
+ sessRepo.markDaemon(session.id, config.daemon.tickIntervalMs);
5262
+ const dailyLogRepo = new DailyLogRepository(db);
5263
+ const dailyLog = new DailyLog({
5264
+ logDir: config.daemon.dailyLogDir,
5265
+ repo: dailyLogRepo,
5266
+ sessionId: session.id
5267
+ });
5268
+ dailyLog.append("Daemon session started", {
5269
+ eventType: "start"
5270
+ });
5271
+ const { TriggerRunner } = await import("./dist-GVK5Q4YK.js");
5272
+ const triggerRunner = new TriggerRunner(db);
5273
+ const daemon = new DaemonController({
5274
+ config: config.daemon,
5275
+ sessionId: session.id,
5276
+ projectPath,
5277
+ runTick: (tickMessage) => {
5278
+ sessionManager.addUserMessage(tickMessage);
5279
+ return runAgentLoop(sessionManager.getHistory(), {
5280
+ config,
5281
+ registry,
5282
+ router,
5283
+ costTracker,
5284
+ tools,
5285
+ sessionId: session.id,
5286
+ projectPath,
5287
+ systemPrompt,
5288
+ systemSegments,
5289
+ compaction: buildCompactionCallbacks(sessionManager),
5290
+ permissionCheck: (name, perm) => permissionManager.check(name, perm),
5291
+ preferredModelId,
5292
+ middleware,
5293
+ routingOutcomeRepo,
5294
+ onTurnComplete: (ctx) => {
5295
+ ctx.turn = sessionManager.incrementTurn();
5296
+ ctx.sessionMinutes = sessionManager.getSessionMinutes();
5297
+ sessionManager.addTurnContext(ctx);
5298
+ }
5299
+ });
5300
+ },
5301
+ getDueTasks: () => triggerRunner.getDueTaskSummaries(),
5302
+ getLogSummary: () => {
5303
+ const recent = dailyLog.readRecent(10);
5304
+ if (recent.length === 0) return "No recent activity.";
5305
+ return recent.map((e) => `[${e.eventType}] ${e.content.slice(0, 100)}`).join("\n");
5306
+ },
5307
+ onTickComplete: async (result) => {
5308
+ dailyLog.append(
5309
+ `${result.toolCalls.length} tools, model=${result.modelUsed}`,
5310
+ {
5311
+ tickNumber: result.tickNumber,
5312
+ eventType: "tick",
5313
+ cost: result.cost,
5314
+ modelId: result.modelUsed
5315
+ }
5316
+ );
5317
+ sessRepo.updateDaemonState(session.id, {
5318
+ tickCount: result.tickNumber,
5319
+ lastTickAt: Math.floor(Date.now() / 1e3),
5320
+ totalCost: costTracker.getSessionCost()
5321
+ });
5322
+ }
5323
+ });
5324
+ const readline2 = await import("readline/promises");
5325
+ const rl2 = readline2.createInterface({
5326
+ input: process.stdin,
5327
+ output: process.stdout
5328
+ });
5329
+ const inputLoop = (async () => {
5330
+ try {
5331
+ while (true) {
5332
+ const line = await rl2.question("");
5333
+ if (!line.trim()) continue;
5334
+ if (line.trim() === "/quit" || line.trim() === "/exit") {
5335
+ daemon.stop();
5336
+ break;
5337
+ }
5338
+ if (line.trim() === "/daemon pause") {
5339
+ daemon.pause();
5340
+ console.log(" [daemon paused]");
5341
+ continue;
5342
+ }
5343
+ if (line.trim() === "/daemon resume") {
5344
+ daemon.resume();
5345
+ console.log(" [daemon resumed]");
5346
+ continue;
5347
+ }
5348
+ if (line.trim() === "/daemon status") {
5349
+ const s = daemon.getState();
5350
+ console.log(
5351
+ ` [daemon: ${s.status} | ticks: ${s.tickCount} | cost: $${s.totalCost.toFixed(4)}]`
5352
+ );
5353
+ continue;
5354
+ }
5355
+ if (line.trim() === "/daemon log") {
5356
+ const todayLog = dailyLog.readToday();
5357
+ console.log(todayLog || " [no daemon log entries today]");
5358
+ continue;
5359
+ }
5360
+ daemon.injectUserMessage(line.trim());
5361
+ }
5362
+ } catch {
5363
+ daemon.stop();
5364
+ }
5365
+ })();
5366
+ process.on("SIGINT", () => {
5367
+ daemon.stop();
5368
+ rl2.close();
5369
+ });
5370
+ for await (const event of daemon.run()) {
5371
+ switch (event.type) {
5372
+ case "daemon-tick":
5373
+ process.stderr.write(
5374
+ ` [tick #${event.tickNumber} | $${event.cost.toFixed(4)}]
5375
+ `
5376
+ );
5377
+ break;
5378
+ case "daemon-sleep":
5379
+ process.stderr.write(
5380
+ ` [sleeping ${Math.round(event.sleepMs / 1e3)}s: ${event.reason}]
5381
+ `
5382
+ );
5383
+ break;
5384
+ case "daemon-wake":
5385
+ process.stderr.write(` [wake: ${event.trigger}]
5386
+ `);
5387
+ break;
5388
+ case "daemon-stopped":
5389
+ process.stderr.write(
5390
+ `
5391
+ [daemon stopped: ${event.tickCount} ticks, $${event.totalCost.toFixed(4)} total]
5392
+ `
5393
+ );
5394
+ break;
5395
+ case "text-delta":
5396
+ process.stdout.write(event.delta);
5397
+ break;
5398
+ case "tool-call-start":
5399
+ process.stdout.write(`
5400
+ [tool: ${event.toolName}]
5401
+ `);
5402
+ break;
5403
+ case "routing":
5404
+ process.stderr.write(`\r [${event.decision.model.name}]
5405
+ `);
5406
+ break;
5407
+ case "done": {
5408
+ const turnCost = event.totalCost - costTracker.getSessionCost();
5409
+ process.stdout.write(
5410
+ `
5411
+ [$${event.totalCost.toFixed(4)} session]
5412
+ `
5413
+ );
5414
+ break;
5415
+ }
5416
+ case "error":
5417
+ process.stderr.write(`
5418
+ Error: ${event.error.message}
5419
+ `);
5420
+ break;
5421
+ }
5422
+ }
5423
+ dailyLog.append("Daemon session ended", {
5424
+ eventType: "stop"
5425
+ });
5426
+ await inputLoop;
5427
+ rl2.close();
5428
+ return;
5429
+ }
4197
5430
  let simpleAbortController = null;
4198
5431
  process.on("SIGINT", () => {
4199
5432
  if (simpleAbortController) {
@@ -4210,7 +5443,7 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4210
5443
  if (!input2.trim()) continue;
4211
5444
  if (input2.trim() === "/quit" || input2.trim() === "/exit") break;
4212
5445
  if (input2.startsWith("/")) {
4213
- const { isSlashCommand, executeSlashCommand } = await import("./slash-ZDC4DKL4.js");
5446
+ const { isSlashCommand, executeSlashCommand } = await import("./slash-CVWH3LTR.js");
4214
5447
  if (isSlashCommand(input2)) {
4215
5448
  const result = await executeSlashCommand(input2, {
4216
5449
  getModel: () => preferredModelId,
@@ -4243,7 +5476,15 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4243
5476
  projectPath,
4244
5477
  currentOutputStyle
4245
5478
  );
4246
- systemPrompt = rebuilt.prompt + buildToolAwarenessSection(tools.listTools());
5479
+ const ts = buildToolAwarenessSection(tools.listTools());
5480
+ systemPrompt = rebuilt.prompt + ts;
5481
+ systemSegments = rebuilt.segments.length > 0 ? [
5482
+ {
5483
+ text: rebuilt.segments[0].text + ts,
5484
+ cacheable: true
5485
+ },
5486
+ ...rebuilt.segments.slice(1)
5487
+ ] : [{ text: systemPrompt, cacheable: true }];
4247
5488
  },
4248
5489
  getOutputStyle: () => currentOutputStyle,
4249
5490
  getBudget: () => {
@@ -4286,12 +5527,14 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4286
5527
  sessionId: session.id,
4287
5528
  projectPath,
4288
5529
  systemPrompt,
5530
+ systemSegments,
4289
5531
  compaction: buildCompactionCallbacks(sessionManager),
4290
5532
  signal: simpleAbortController.signal,
4291
5533
  permissionCheck: (name, perm) => permissionManager.check(name, perm),
4292
5534
  preferredModelId,
4293
5535
  middleware,
4294
5536
  roleToolFilter,
5537
+ routingOutcomeRepo,
4295
5538
  onTurnComplete: (ctx) => {
4296
5539
  ctx.turn = sessionManager.incrementTurn();
4297
5540
  ctx.sessionMinutes = sessionManager.getSessionMinutes();
@@ -4390,14 +5633,17 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4390
5633
  }
4391
5634
  }
4392
5635
  simpleAbortController = null;
4393
- if (fullResponse) sessionManager.addAssistantMessage(fullResponse);
5636
+ if (fullResponse) {
5637
+ sessionManager.addAssistantMessage(fullResponse);
5638
+ sessionManager.flush();
5639
+ }
4394
5640
  }
4395
5641
  rl.close();
4396
5642
  return;
4397
5643
  }
4398
5644
  const { render } = await import("ink");
4399
5645
  const React = await import("react");
4400
- const { App } = await import("./App-SSKWB7CT.js");
5646
+ const { App } = await import("./App-HGJSYIVS.js");
4401
5647
  let currentAbortController = null;
4402
5648
  function handleSendMessage(text) {
4403
5649
  sessionManager.addUserMessage(text);
@@ -4415,12 +5661,14 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4415
5661
  sessionId: session.id,
4416
5662
  projectPath,
4417
5663
  systemPrompt,
5664
+ systemSegments,
4418
5665
  compaction: buildCompactionCallbacks(sessionManager),
4419
5666
  signal: currentAbortController.signal,
4420
5667
  permissionCheck: (name, perm) => permissionManager.check(name, perm),
4421
5668
  middleware,
4422
5669
  preferredModelId,
4423
- roleToolFilter: roleFilter
5670
+ roleToolFilter: roleFilter,
5671
+ routingOutcomeRepo
4424
5672
  });
4425
5673
  return (async function* () {
4426
5674
  let fullResponse = "";
@@ -4428,7 +5676,10 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4428
5676
  if (event.type === "text-delta") fullResponse += event.delta;
4429
5677
  yield event;
4430
5678
  }
4431
- if (fullResponse) sessionManager.addAssistantMessage(fullResponse);
5679
+ if (fullResponse) {
5680
+ sessionManager.addAssistantMessage(fullResponse);
5681
+ sessionManager.flush();
5682
+ }
4432
5683
  currentAbortController = null;
4433
5684
  })();
4434
5685
  }
@@ -4474,6 +5725,11 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4474
5725
  opAvailable: !!process.env.OP_SERVICE_ACCOUNT_TOKEN,
4475
5726
  resolvedKeys: PROVIDER_KEY_NAMES.filter((k) => resolvedKeys.get(k))
4476
5727
  },
5728
+ godModeInfo: godModeResult ? {
5729
+ connectedSystems: godModeResult.connectedSystems,
5730
+ errors: godModeResult.errors,
5731
+ totalTools: godModeResult.totalTools
5732
+ } : void 0,
4477
5733
  memoryInfo: await (async () => {
4478
5734
  try {
4479
5735
  const { MemoryManager } = await import("@brainst0rm/core");
@@ -4506,7 +5762,12 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4506
5762
  projectPath,
4507
5763
  currentOutputStyle
4508
5764
  );
4509
- systemPrompt = rebuilt.prompt + buildToolAwarenessSection(tools.listTools());
5765
+ const ts = buildToolAwarenessSection(tools.listTools());
5766
+ systemPrompt = rebuilt.prompt + ts;
5767
+ systemSegments = rebuilt.segments.length > 0 ? [
5768
+ { text: rebuilt.segments[0].text + ts, cacheable: true },
5769
+ ...rebuilt.segments.slice(1)
5770
+ ] : [{ text: systemPrompt, cacheable: true }];
4510
5771
  },
4511
5772
  getOutputStyle: () => currentOutputStyle,
4512
5773
  rebuildSystemPrompt: (basePromptOverride) => {
@@ -4515,7 +5776,12 @@ program.command("chat", { isDefault: true }).description("Start an interactive c
4515
5776
  currentOutputStyle,
4516
5777
  basePromptOverride
4517
5778
  );
4518
- systemPrompt = rebuilt.prompt + buildToolAwarenessSection(tools.listTools());
5779
+ const ts = buildToolAwarenessSection(tools.listTools());
5780
+ systemPrompt = rebuilt.prompt + ts;
5781
+ systemSegments = rebuilt.segments.length > 0 ? [
5782
+ { text: rebuilt.segments[0].text + ts, cacheable: true },
5783
+ ...rebuilt.segments.slice(1)
5784
+ ] : [{ text: systemPrompt, cacheable: true }];
4519
5785
  },
4520
5786
  getActiveRole: () => currentRole,
4521
5787
  setActiveRole: (role) => {
@@ -4606,6 +5872,7 @@ Keys: ${vault.list().length}`;
4606
5872
  }
4607
5873
  );
4608
5874
  function run() {
5875
+ initSentry({ release: process.env.npm_package_version });
4609
5876
  const cleanup = () => {
4610
5877
  try {
4611
5878
  stopDockerSandbox();
@@ -4615,7 +5882,18 @@ function run() {
4615
5882
  closeDb();
4616
5883
  } catch {
4617
5884
  }
5885
+ flushSentry(1500).catch(() => {
5886
+ });
4618
5887
  };
5888
+ process.on("uncaughtException", (err) => {
5889
+ captureError(err, { source: "uncaughtException" });
5890
+ cleanup();
5891
+ process.exit(1);
5892
+ });
5893
+ process.on("unhandledRejection", (reason) => {
5894
+ const err = reason instanceof Error ? reason : new Error(String(reason));
5895
+ captureError(err, { source: "unhandledRejection" });
5896
+ });
4619
5897
  process.on("SIGTERM", () => {
4620
5898
  cleanup();
4621
5899
  process.exit(0);