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