@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.
- package/dist/{App-SSKWB7CT.js → App-HGJSYIVS.js} +133 -62
- package/dist/App-HGJSYIVS.js.map +1 -0
- package/dist/{App-DPXJYXKH.js → App-XCJW3A4I.js} +133 -62
- package/dist/App-XCJW3A4I.js.map +1 -0
- package/dist/brainstorm.js +1428 -150
- package/dist/brainstorm.js.map +1 -1
- package/dist/{chunk-7D4SUZUM.js → chunk-PR4QN5HX.js} +6 -1
- package/dist/{chunk-ZWE3DS7E.js → chunk-SEGVTWSK.js} +6 -1
- package/dist/{chunk-5NA3GH6X.js → chunk-WYQU3HAB.js} +175 -10
- package/dist/chunk-WYQU3HAB.js.map +1 -0
- package/dist/{chunk-55ITCWZZ.js → chunk-Z2QIGVYT.js} +175 -10
- package/dist/chunk-Z2QIGVYT.js.map +1 -0
- package/dist/{dist-GNHTH2DH.js → dist-3BC75XHW.js} +2 -2
- package/dist/dist-42TQFHMB.js +1640 -0
- package/dist/dist-42TQFHMB.js.map +1 -0
- package/dist/dist-BULARAL3.js +7308 -0
- package/dist/dist-BULARAL3.js.map +1 -0
- package/dist/{dist-JUDVPE7G.js → dist-ET6CSS7O.js} +2 -2
- package/dist/{dist-DUDO3RDM.js → dist-GVK5Q4YK.js} +62 -16
- package/dist/{dist-V5DTSTKJ.js.map → dist-GVK5Q4YK.js.map} +1 -1
- package/dist/{dist-V5DTSTKJ.js → dist-K7BDAMTO.js} +62 -16
- package/dist/{dist-DUDO3RDM.js.map → dist-K7BDAMTO.js.map} +1 -1
- package/dist/{dist-WLTQTLFO.js → dist-OYQTULIU.js} +2 -2
- package/dist/dist-S5JYXFUW.js +7307 -0
- package/dist/dist-S5JYXFUW.js.map +1 -0
- package/dist/{dist-YIGU37Q2.js → dist-Y25MC2VO.js} +2 -2
- package/dist/dist-Z4SBSK4Q.js +1639 -0
- package/dist/dist-Z4SBSK4Q.js.map +1 -0
- package/dist/index.js +1428 -150
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-CROPNYHI.js +4252 -0
- package/dist/mcp-server-CROPNYHI.js.map +1 -0
- package/dist/mcp-server-IJVEG6CS.js +4251 -0
- package/dist/mcp-server-IJVEG6CS.js.map +1 -0
- package/dist/{recorder-D6ILEOZP.js → recorder-33N4U6TO.js} +2 -2
- package/dist/{recorder-SPYYF4DL.js → recorder-APOOXJYA.js} +2 -2
- package/dist/{roles-2DGF4PZU.js → roles-GKDCLP5G.js} +2 -2
- package/dist/{roles-UIPX7GBC.js → roles-UUIISXEW.js} +2 -2
- package/dist/{slash-PDWKCZOQ.js → slash-4XSR3SJD.js} +3 -3
- package/dist/{slash-ZDC4DKL4.js → slash-CVWH3LTR.js} +3 -3
- package/package.json +4 -2
- package/dist/App-DPXJYXKH.js.map +0 -1
- package/dist/App-SSKWB7CT.js.map +0 -1
- package/dist/chunk-55ITCWZZ.js.map +0 -1
- package/dist/chunk-5NA3GH6X.js.map +0 -1
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-PR4QN5HX.js.map} +0 -0
- /package/dist/{chunk-ZWE3DS7E.js.map → chunk-SEGVTWSK.js.map} +0 -0
- /package/dist/{dist-GNHTH2DH.js.map → dist-3BC75XHW.js.map} +0 -0
- /package/dist/{dist-JUDVPE7G.js.map → dist-ET6CSS7O.js.map} +0 -0
- /package/dist/{dist-WLTQTLFO.js.map → dist-OYQTULIU.js.map} +0 -0
- /package/dist/{dist-YIGU37Q2.js.map → dist-Y25MC2VO.js.map} +0 -0
- /package/dist/{recorder-D6ILEOZP.js.map → recorder-33N4U6TO.js.map} +0 -0
- /package/dist/{recorder-SPYYF4DL.js.map → recorder-APOOXJYA.js.map} +0 -0
- /package/dist/{roles-2DGF4PZU.js.map → roles-GKDCLP5G.js.map} +0 -0
- /package/dist/{roles-UIPX7GBC.js.map → roles-UUIISXEW.js.map} +0 -0
- /package/dist/{slash-PDWKCZOQ.js.map → slash-4XSR3SJD.js.map} +0 -0
- /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-
|
|
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 {
|
|
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)
|
|
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 {
|
|
1707
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
2227
|
-
const { ProjectManager } = await import("./dist-
|
|
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-
|
|
2260
|
-
const { ProjectManager } = await import("./dist-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
2698
|
-
const { ProjectManager } = await import("./dist-
|
|
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-
|
|
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-
|
|
2784
|
-
const { ProjectManager } = await import("./dist-
|
|
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("
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
)
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
const
|
|
4085
|
-
const
|
|
4086
|
-
const
|
|
4087
|
-
|
|
4088
|
-
|
|
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
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
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
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
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
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
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
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
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
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
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
|
-
|
|
4447
|
+
console.log(` \u25CB ${p.id.padEnd(8)} unreachable (${res.status})`);
|
|
4169
4448
|
}
|
|
4170
|
-
}
|
|
4171
|
-
|
|
4449
|
+
} catch {
|
|
4450
|
+
console.log(` \u25CB ${p.id.padEnd(8)} offline`);
|
|
4172
4451
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
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
|
-
`
|
|
4466
|
+
`
|
|
4467
|
+
\u2713 Added brainstorm MCP server to ~/.claude/mcp.json`
|
|
4190
4468
|
);
|
|
4191
|
-
|
|
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-
|
|
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
|
-
|
|
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)
|
|
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-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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);
|