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