@brainst0rm/cli 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +32 -0
  2. package/dist/App-DPXJYXKH.js +2794 -0
  3. package/dist/App-DPXJYXKH.js.map +1 -0
  4. package/dist/App-SSKWB7CT.js +2795 -0
  5. package/dist/App-SSKWB7CT.js.map +1 -0
  6. package/dist/brainstorm.js +4636 -0
  7. package/dist/brainstorm.js.map +1 -0
  8. package/dist/chunk-2CHZHDIM.js +391 -0
  9. package/dist/chunk-2CHZHDIM.js.map +1 -0
  10. package/dist/chunk-55ITCWZZ.js +1307 -0
  11. package/dist/chunk-55ITCWZZ.js.map +1 -0
  12. package/dist/chunk-5NA3GH6X.js +1308 -0
  13. package/dist/chunk-5NA3GH6X.js.map +1 -0
  14. package/dist/chunk-7D4SUZUM.js +38 -0
  15. package/dist/chunk-7D4SUZUM.js.map +1 -0
  16. package/dist/chunk-D474E47D.js +148 -0
  17. package/dist/chunk-D474E47D.js.map +1 -0
  18. package/dist/chunk-GJXEX2A3.js +146 -0
  19. package/dist/chunk-GJXEX2A3.js.map +1 -0
  20. package/dist/chunk-OVGL3NJQ.js +307 -0
  21. package/dist/chunk-OVGL3NJQ.js.map +1 -0
  22. package/dist/chunk-VY6MPJXL.js +389 -0
  23. package/dist/chunk-VY6MPJXL.js.map +1 -0
  24. package/dist/chunk-YWXOPUDW.js +305 -0
  25. package/dist/chunk-YWXOPUDW.js.map +1 -0
  26. package/dist/chunk-ZWE3DS7E.js +39 -0
  27. package/dist/chunk-ZWE3DS7E.js.map +1 -0
  28. package/dist/dist-DUDO3RDM.js +9573 -0
  29. package/dist/dist-DUDO3RDM.js.map +1 -0
  30. package/dist/dist-GNHTH2DH.js +292 -0
  31. package/dist/dist-GNHTH2DH.js.map +1 -0
  32. package/dist/dist-JUDVPE7G.js +293 -0
  33. package/dist/dist-JUDVPE7G.js.map +1 -0
  34. package/dist/dist-V5DTSTKJ.js +9572 -0
  35. package/dist/dist-V5DTSTKJ.js.map +1 -0
  36. package/dist/dist-WLTQTLFO.js +14 -0
  37. package/dist/dist-WLTQTLFO.js.map +1 -0
  38. package/dist/dist-YIGU37Q2.js +15 -0
  39. package/dist/dist-YIGU37Q2.js.map +1 -0
  40. package/dist/index.d.ts +3 -0
  41. package/dist/index.js +4635 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/recorder-D6ILEOZP.js +67 -0
  44. package/dist/recorder-D6ILEOZP.js.map +1 -0
  45. package/dist/recorder-SPYYF4DL.js +66 -0
  46. package/dist/recorder-SPYYF4DL.js.map +1 -0
  47. package/dist/roles-2DGF4PZU.js +16 -0
  48. package/dist/roles-2DGF4PZU.js.map +1 -0
  49. package/dist/roles-UIPX7GBC.js +17 -0
  50. package/dist/roles-UIPX7GBC.js.map +1 -0
  51. package/dist/slash-PDWKCZOQ.js +13 -0
  52. package/dist/slash-PDWKCZOQ.js.map +1 -0
  53. package/dist/slash-ZDC4DKL4.js +14 -0
  54. package/dist/slash-ZDC4DKL4.js.map +1 -0
  55. package/package.json +76 -0
package/dist/index.js ADDED
@@ -0,0 +1,4635 @@
1
+ import {
2
+ renderMarkdownToString
3
+ } from "./chunk-GJXEX2A3.js";
4
+ import {
5
+ ROLES
6
+ } from "./chunk-YWXOPUDW.js";
7
+ import "./chunk-7D4SUZUM.js";
8
+
9
+ // src/bin/brainstorm.ts
10
+ import { Command } from "commander";
11
+ import { loadConfig } from "@brainst0rm/config";
12
+ import { getDb, closeDb, CostRepository } from "@brainst0rm/db";
13
+ import {
14
+ createProviderRegistry,
15
+ getBrainstormApiKey,
16
+ isCommunityKey
17
+ } from "@brainst0rm/providers";
18
+ import { BrainstormRouter, CostTracker } from "@brainst0rm/router";
19
+ import {
20
+ createDefaultToolRegistry,
21
+ configureSandbox,
22
+ stopDockerSandbox
23
+ } from "@brainst0rm/tools";
24
+ import {
25
+ runAgentLoop,
26
+ buildSystemPrompt,
27
+ buildToolAwarenessSection,
28
+ SessionManager,
29
+ PermissionManager,
30
+ createSubagentTool,
31
+ spawnSubagent,
32
+ spawnParallel,
33
+ createDefaultMiddlewarePipeline
34
+ } from "@brainst0rm/core";
35
+ import { AgentManager, parseAgentNL } from "@brainst0rm/agents";
36
+ import {
37
+ runWorkflow,
38
+ getPresetWorkflow,
39
+ autoSelectPreset,
40
+ PRESET_WORKFLOWS
41
+ } from "@brainst0rm/workflow";
42
+
43
+ // src/init/detect.ts
44
+ import { existsSync, readFileSync } from "fs";
45
+ import { join, basename } from "path";
46
+ import { createConnection } from "net";
47
+ async function detectProject(projectDir) {
48
+ const detection = {
49
+ name: basename(projectDir),
50
+ type: null,
51
+ language: null,
52
+ framework: null,
53
+ runtime: null,
54
+ hasGit: existsSync(join(projectDir, ".git")),
55
+ hasStormMd: existsSync(join(projectDir, "STORM.md")),
56
+ hasBrainstormMd: existsSync(join(projectDir, "BRAINSTORM.md")),
57
+ hasClaudeMd: existsSync(join(projectDir, "CLAUDE.md")),
58
+ hasGitignore: existsSync(join(projectDir, ".gitignore")),
59
+ hasGithubWorkflows: existsSync(join(projectDir, ".github", "workflows")),
60
+ hasEnvExample: existsSync(join(projectDir, ".env.example")),
61
+ hasPrettierrc: existsSync(join(projectDir, ".prettierrc")),
62
+ hasBrainstormToml: existsSync(join(projectDir, "brainstorm.toml")),
63
+ packageName: null,
64
+ scripts: {},
65
+ localModels: []
66
+ };
67
+ const pkgPath = join(projectDir, "package.json");
68
+ if (existsSync(pkgPath)) {
69
+ try {
70
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
71
+ detection.packageName = pkg.name ?? null;
72
+ if (detection.packageName) detection.name = detection.packageName;
73
+ detection.scripts = pkg.scripts ?? {};
74
+ detection.language = "typescript";
75
+ detection.runtime = "node";
76
+ if (pkg.workspaces || existsSync(join(projectDir, "turbo.json")) || existsSync(join(projectDir, "pnpm-workspace.yaml"))) {
77
+ detection.type = "monorepo";
78
+ }
79
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
80
+ if (allDeps?.next) detection.framework = "nextjs";
81
+ else if (allDeps?.hono) detection.framework = "hono";
82
+ else if (allDeps?.express) detection.framework = "express";
83
+ if (pkg.bin) detection.type = detection.type ?? "cli";
84
+ if (pkg.exports || pkg.main) detection.type = detection.type ?? "library";
85
+ } catch {
86
+ }
87
+ }
88
+ if (!detection.framework) {
89
+ if (existsSync(join(projectDir, "next.config.ts")) || existsSync(join(projectDir, "next.config.js")) || existsSync(join(projectDir, "next.config.mjs"))) {
90
+ detection.framework = "nextjs";
91
+ } else if (existsSync(join(projectDir, "vite.config.ts")) || existsSync(join(projectDir, "vite.config.js"))) {
92
+ detection.framework = "none";
93
+ }
94
+ }
95
+ if (existsSync(join(projectDir, "pyproject.toml")) || existsSync(join(projectDir, "requirements.txt"))) {
96
+ detection.language = "python";
97
+ detection.runtime = "python";
98
+ try {
99
+ const content = readFileSync(join(projectDir, "requirements.txt"), "utf-8");
100
+ if (content.includes("fastapi")) detection.framework = "fastapi";
101
+ } catch {
102
+ }
103
+ detection.type = detection.type ?? "api";
104
+ }
105
+ if (existsSync(join(projectDir, "Cargo.toml"))) {
106
+ detection.language = "rust";
107
+ detection.type = detection.type ?? "cli";
108
+ }
109
+ if (existsSync(join(projectDir, "go.mod"))) {
110
+ detection.language = "go";
111
+ detection.runtime = "go";
112
+ detection.type = detection.type ?? "api";
113
+ }
114
+ detection.type = detection.type ?? "app";
115
+ const probes = await Promise.allSettled([
116
+ probePort(11434).then((ok) => ok ? "ollama" : null),
117
+ probePort(1234).then((ok) => ok ? "lmstudio" : null),
118
+ probePort(8080).then((ok) => ok ? "llamacpp" : null)
119
+ ]);
120
+ for (const result of probes) {
121
+ if (result.status === "fulfilled" && result.value) {
122
+ detection.localModels.push(result.value);
123
+ }
124
+ }
125
+ return detection;
126
+ }
127
+ function probePort(port) {
128
+ return new Promise((resolve) => {
129
+ const socket = createConnection({ host: "127.0.0.1", port, timeout: 500 });
130
+ socket.on("connect", () => {
131
+ socket.destroy();
132
+ resolve(true);
133
+ });
134
+ socket.on("error", () => resolve(false));
135
+ socket.on("timeout", () => {
136
+ socket.destroy();
137
+ resolve(false);
138
+ });
139
+ });
140
+ }
141
+
142
+ // src/init/prompts.ts
143
+ import { createInterface } from "readline/promises";
144
+ import { stdin as input, stdout as output } from "process";
145
+ async function runPrompts(detection) {
146
+ const rl = createInterface({ input, output });
147
+ try {
148
+ console.log("\n brainstorm init\n");
149
+ const detected = [];
150
+ if (detection.type) detected.push(`${detection.language ?? "unknown"} ${detection.type}`);
151
+ if (detection.framework && detection.framework !== "none") detected.push(`framework: ${detection.framework}`);
152
+ if (detection.localModels.length > 0) detected.push(`local models: ${detection.localModels.join(", ")}`);
153
+ if (detected.length > 0) {
154
+ console.log(` Detected: ${detected.join(" | ")}
155
+ `);
156
+ }
157
+ const name = await ask(rl, "Project name", detection.name);
158
+ const type = await askChoice(rl, "Project type", ["monorepo", "app", "cli", "library", "api"], detection.type ?? "app");
159
+ const language = await askChoice(rl, "Language", ["typescript", "python", "rust", "go", "multi"], detection.language ?? "typescript");
160
+ const framework = await askChoice(rl, "Framework", ["nextjs", "hono", "fastapi", "express", "none"], detection.framework ?? "none");
161
+ const runtime = language === "python" ? "python" : language === "go" ? "go" : detection.runtime ?? "node";
162
+ const deploy = await askChoice(rl, "Deploy target", ["vercel", "do-app-platform", "docker", "aws", "none"], "none");
163
+ console.log();
164
+ const cloudProvider = await askChoice(rl, "Cloud LLM routing", ["brainstormrouter", "direct", "none"], "brainstormrouter");
165
+ const budgetTier = await askChoice(rl, "Budget tier", ["low", "standard", "premium"], "standard");
166
+ console.log();
167
+ const secretsStrategy = await askChoice(rl, "Secrets strategy", ["env-file", "op-cli", "sops", "doppler", "infisical", "manual"], "env-file");
168
+ const ciTier = await askChoice(rl, "CI/CD setup", ["standard", "full", "monorepo", "none"], type === "monorepo" ? "monorepo" : "standard");
169
+ console.log();
170
+ const architecture = await ask(rl, "Architecture (one line)", "");
171
+ const choices = {
172
+ name,
173
+ type,
174
+ language,
175
+ framework,
176
+ runtime,
177
+ deploy,
178
+ cloudProvider,
179
+ localModels: detection.localModels,
180
+ budgetTier,
181
+ secretsStrategy,
182
+ ciTier,
183
+ architecture
184
+ };
185
+ console.log("\n Will create:");
186
+ console.log(" STORM.md \u2014 project context for AI routing");
187
+ console.log(" brainstorm.toml \u2014 routing + provider config");
188
+ console.log(" .brainstormignore \u2014 AI file exclusions");
189
+ if (!detection.hasGitignore) console.log(" .gitignore \u2014 comprehensive ignore patterns");
190
+ if (!detection.hasPrettierrc) console.log(" .prettierrc \u2014 code formatting");
191
+ console.log(" .env.example \u2014 documented environment variables");
192
+ if (ciTier !== "none") {
193
+ console.log(" .github/workflows/ \u2014 CI/CD pipeline");
194
+ console.log(" .github/ISSUE_TEMPLATE/ \u2014 bug + feature templates");
195
+ console.log(" .github/pull_request_template.md");
196
+ }
197
+ console.log();
198
+ const confirm = await ask(rl, "Proceed? [Y/n]", "Y");
199
+ if (confirm.toLowerCase() === "n") {
200
+ console.log(" Aborted.\n");
201
+ return null;
202
+ }
203
+ return choices;
204
+ } finally {
205
+ rl.close();
206
+ }
207
+ }
208
+ function buildDefaultChoices(detection) {
209
+ return {
210
+ name: detection.name,
211
+ type: detection.type ?? "app",
212
+ language: detection.language ?? "typescript",
213
+ framework: detection.framework ?? "none",
214
+ runtime: detection.runtime ?? "node",
215
+ deploy: "none",
216
+ cloudProvider: "brainstormrouter",
217
+ localModels: detection.localModels,
218
+ budgetTier: "standard",
219
+ secretsStrategy: "env-file",
220
+ ciTier: detection.type === "monorepo" ? "monorepo" : "standard",
221
+ architecture: ""
222
+ };
223
+ }
224
+ async function ask(rl, prompt, defaultVal) {
225
+ const suffix = defaultVal ? ` [${defaultVal}]` : "";
226
+ const answer = await rl.question(` ${prompt}${suffix}: `);
227
+ return answer.trim() || defaultVal;
228
+ }
229
+ async function askChoice(rl, prompt, options, defaultVal) {
230
+ const optStr = options.map((o) => o === defaultVal ? `[${o}]` : o).join(" / ");
231
+ const answer = await rl.question(` ${prompt}: ${optStr}: `);
232
+ const trimmed = answer.trim().toLowerCase();
233
+ if (!trimmed) return defaultVal;
234
+ const match = options.find((o) => o.startsWith(trimmed));
235
+ return match ?? defaultVal;
236
+ }
237
+
238
+ // src/init/generate.ts
239
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
240
+ import { join as join2, dirname } from "path";
241
+ function generateFile(projectDir, relativePath, content, options = {}) {
242
+ const fullPath = join2(projectDir, relativePath);
243
+ const dir = dirname(fullPath);
244
+ if (!existsSync2(dir)) {
245
+ mkdirSync(dir, { recursive: true });
246
+ }
247
+ if (existsSync2(fullPath) && !options.force) {
248
+ return { action: "skipped", path: relativePath };
249
+ }
250
+ writeFileSync(fullPath, content, "utf-8");
251
+ return { action: "created", path: relativePath };
252
+ }
253
+ function mergeGitignore(projectDir, newContent) {
254
+ const fullPath = join2(projectDir, ".gitignore");
255
+ if (!existsSync2(fullPath)) {
256
+ writeFileSync(fullPath, newContent, "utf-8");
257
+ return { action: "created", path: ".gitignore" };
258
+ }
259
+ const existing = readFileSync2(fullPath, "utf-8");
260
+ const existingPatterns = new Set(
261
+ existing.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"))
262
+ );
263
+ const newPatterns = newContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).filter((l) => !existingPatterns.has(l));
264
+ if (newPatterns.length === 0) {
265
+ return { action: "skipped", path: ".gitignore" };
266
+ }
267
+ const appendBlock = "\n# Added by brainstorm init\n" + newPatterns.join("\n") + "\n";
268
+ writeFileSync(fullPath, existing.trimEnd() + "\n" + appendBlock, "utf-8");
269
+ return { action: "merged", path: ".gitignore" };
270
+ }
271
+
272
+ // src/init/templates.ts
273
+ function generateStormMd(choices) {
274
+ const lines = [
275
+ "---",
276
+ "version: 1",
277
+ `name: ${choices.name}`,
278
+ `type: ${choices.type}`,
279
+ `language: ${choices.language}`,
280
+ `framework: ${choices.framework}`,
281
+ `runtime: ${choices.runtime}`,
282
+ `deploy: ${choices.deploy}`,
283
+ "routing:",
284
+ " primary_tasks: [code-generation, debugging]",
285
+ ` typical_complexity: moderate`,
286
+ ` prefer_local: ${choices.budgetTier === "low"}`,
287
+ ` budget_tier: ${choices.budgetTier}`,
288
+ "providers:",
289
+ ` cloud: ${choices.cloudProvider}`,
290
+ ` local: [${choices.localModels.join(", ")}]`,
291
+ "secrets:",
292
+ ` strategy: ${choices.secretsStrategy}`,
293
+ `entry_points: []`,
294
+ choices.language === "typescript" ? "test_command: npm test" : choices.language === "python" ? "test_command: pytest" : void 0,
295
+ choices.language === "typescript" ? "build_command: npm run build" : void 0,
296
+ choices.language === "typescript" ? "dev_command: npm run dev" : choices.language === "python" ? "dev_command: python -m uvicorn app:app --reload" : void 0,
297
+ "---",
298
+ "",
299
+ "## What is this?",
300
+ `<!-- One sentence. What does this project do? -->`,
301
+ choices.architecture || "[Describe your project here.]",
302
+ "",
303
+ "## Start here",
304
+ "<!-- The 5 files to read first to understand the architecture. -->",
305
+ "- [Add your key entry point]",
306
+ "- [Add your main abstraction]",
307
+ "- [Add your data schema]",
308
+ "- [Add your route definitions]",
309
+ "- [Add your config file]",
310
+ "",
311
+ "## Commands that work",
312
+ "<!-- Copy-pasteable. Must work on a fresh clone. -->",
313
+ "```bash",
314
+ choices.language === "typescript" ? "npm install" : choices.language === "python" ? "pip install -r requirements.txt" : "# install dependencies",
315
+ choices.language === "typescript" ? "npm run dev" : choices.language === "python" ? "python -m uvicorn app:app --reload" : "# start dev server",
316
+ choices.language === "typescript" ? "npm test" : choices.language === "python" ? "pytest" : "# run tests",
317
+ choices.language === "typescript" ? "npm run build" : "# build",
318
+ "```",
319
+ "",
320
+ "## Conventions",
321
+ "<!-- Show patterns by example. I learn by reading code, not rules. -->",
322
+ "```" + (choices.language === "typescript" ? "typescript" : choices.language),
323
+ "// Add code examples of your project's patterns here",
324
+ "```",
325
+ "",
326
+ "## Environment",
327
+ "<!-- Required vars marked [REQUIRED]. -->",
328
+ choices.cloudProvider === "brainstormrouter" ? "- `BRAINSTORM_API_KEY` [REQUIRED] \u2014 BrainstormRouter SaaS key" : "- [Add required environment variables]",
329
+ "",
330
+ "## Don't touch",
331
+ "<!-- Files I should never modify without asking. -->",
332
+ "- [Add protected files/directories here]",
333
+ ""
334
+ ];
335
+ return lines.filter((l) => l !== void 0).join("\n");
336
+ }
337
+ function generateBrainstormToml(choices) {
338
+ const strategy = choices.budgetTier === "low" ? "cost-first" : choices.budgetTier === "premium" ? "quality-first" : "combined";
339
+ const lines = [
340
+ "[general]",
341
+ `defaultStrategy = "${strategy}"`,
342
+ "",
343
+ "[providers.gateway]",
344
+ `enabled = ${choices.cloudProvider === "brainstormrouter"}`,
345
+ "",
346
+ "[providers.ollama]",
347
+ `enabled = ${choices.localModels.includes("ollama")}`,
348
+ "",
349
+ "[providers.lmstudio]",
350
+ `enabled = ${choices.localModels.includes("lmstudio")}`,
351
+ "",
352
+ "[budget]",
353
+ choices.budgetTier === "low" ? "daily = 2.00" : choices.budgetTier === "premium" ? "daily = 20.00" : "daily = 5.00",
354
+ "hardLimit = false",
355
+ ""
356
+ ];
357
+ return lines.join("\n");
358
+ }
359
+ function generateGitignore(choices) {
360
+ return `# Dependencies
361
+ node_modules/
362
+ .pnp.*
363
+ venv/
364
+ __pycache__/
365
+
366
+ # Build
367
+ dist/
368
+ build/
369
+ .next/
370
+ .turbo/
371
+ *.tsbuildinfo
372
+
373
+ # Environment & secrets
374
+ .env
375
+ .env.*
376
+ !.env.example
377
+ *.pem
378
+ *.key
379
+ secrets.dec.yaml
380
+
381
+ # Data
382
+ *.db
383
+ *.db-wal
384
+ *.db-shm
385
+
386
+ # IDE & OS
387
+ .idea/
388
+ .vscode/settings.json
389
+ *.swp
390
+ .DS_Store
391
+
392
+ # Test
393
+ coverage/
394
+
395
+ # Brainstorm
396
+ .brainstorm/cache/
397
+ `;
398
+ }
399
+ function generateBrainstormignore() {
400
+ return `# Build artifacts \u2014 never useful, always stale
401
+ dist/
402
+ build/
403
+ .next/
404
+ .turbo/
405
+ __pycache__/
406
+ *.pyc
407
+
408
+ # Dependencies \u2014 I don't need to read library source
409
+ node_modules/
410
+ .pnp.*
411
+ venv/
412
+
413
+ # Lock files \u2014 huge, never informative
414
+ package-lock.json
415
+ yarn.lock
416
+ pnpm-lock.yaml
417
+ Cargo.lock
418
+
419
+ # Binary/media \u2014 I can't read these meaningfully
420
+ *.jpg
421
+ *.png
422
+ *.gif
423
+ *.svg
424
+ *.ico
425
+ *.woff
426
+ *.woff2
427
+ *.ttf
428
+ *.eot
429
+ *.mp4
430
+ *.mp3
431
+ *.pdf
432
+
433
+ # Generated/cached
434
+ *.tsbuildinfo
435
+ coverage/
436
+ .cache/
437
+ .eslintcache
438
+
439
+ # Secrets \u2014 I should never see these
440
+ .env
441
+ .env.*
442
+ !.env.example
443
+ *.pem
444
+ *.key
445
+ *.p12
446
+ credentials.json
447
+ `;
448
+ }
449
+ function generatePrettierrc() {
450
+ return JSON.stringify({
451
+ semi: true,
452
+ singleQuote: true,
453
+ tabWidth: 2,
454
+ printWidth: 100,
455
+ trailingComma: "all"
456
+ }, null, 2) + "\n";
457
+ }
458
+ function generateEnvExample(choices) {
459
+ const lines = [
460
+ "# === LLM Providers ==="
461
+ ];
462
+ if (choices.cloudProvider === "brainstormrouter") {
463
+ lines.push(
464
+ "# BrainstormRouter SaaS (recommended \u2014 routes to optimal model)",
465
+ "# Sign up: https://brainstormrouter.com",
466
+ "BRAINSTORM_API_KEY="
467
+ );
468
+ } else if (choices.cloudProvider === "direct") {
469
+ lines.push(
470
+ "# Direct provider keys (set the ones you use)",
471
+ "# ANTHROPIC_API_KEY=",
472
+ "# OPENAI_API_KEY=",
473
+ "# GOOGLE_GENERATIVE_AI_API_KEY=",
474
+ "# DEEPSEEK_API_KEY="
475
+ );
476
+ }
477
+ lines.push(
478
+ "",
479
+ "# === Local Models ===",
480
+ "# Ollama runs on localhost:11434 by default (auto-detected)",
481
+ "# LM Studio runs on localhost:1234 by default (auto-detected)",
482
+ "",
483
+ "# === Application ===",
484
+ "# DATABASE_URL=",
485
+ "# Add project-specific env vars below",
486
+ ""
487
+ );
488
+ return lines.join("\n");
489
+ }
490
+ function generateCiWorkflow(choices) {
491
+ if (choices.ciTier === "monorepo") {
492
+ return `name: CI
493
+ on:
494
+ push: { branches: [main] }
495
+ pull_request: { branches: [main] }
496
+
497
+ jobs:
498
+ check:
499
+ runs-on: ubuntu-latest
500
+ steps:
501
+ - uses: actions/checkout@v4
502
+ with: { fetch-depth: 2 }
503
+ - uses: actions/setup-node@v4
504
+ with: { node-version: 22, cache: npm }
505
+ - run: npm ci
506
+ - run: npx turbo run typecheck test build --affected
507
+ `;
508
+ }
509
+ if (choices.language === "python") {
510
+ return `name: CI
511
+ on:
512
+ push: { branches: [main] }
513
+ pull_request: { branches: [main] }
514
+
515
+ jobs:
516
+ check:
517
+ runs-on: ubuntu-latest
518
+ steps:
519
+ - uses: actions/checkout@v4
520
+ - uses: actions/setup-python@v5
521
+ with: { python-version: "3.13" }
522
+ - run: pip install -r requirements.txt
523
+ - run: pytest
524
+ `;
525
+ }
526
+ return `name: CI
527
+ on:
528
+ push: { branches: [main] }
529
+ pull_request: { branches: [main] }
530
+
531
+ jobs:
532
+ check:
533
+ runs-on: ubuntu-latest
534
+ steps:
535
+ - uses: actions/checkout@v4
536
+ - uses: actions/setup-node@v4
537
+ with: { node-version: 22, cache: npm }
538
+ - run: npm ci
539
+ - run: npx tsc --noEmit
540
+ - run: npm test
541
+ - run: npm run build
542
+ `;
543
+ }
544
+ function generateDeployWorkflow(choices) {
545
+ return `name: Deploy
546
+ on:
547
+ push: { branches: [main] }
548
+
549
+ jobs:
550
+ deploy:
551
+ runs-on: ubuntu-latest
552
+ steps:
553
+ - uses: actions/checkout@v4
554
+ - uses: actions/setup-node@v4
555
+ with: { node-version: 22, cache: npm }
556
+ - run: npm ci && npm run build
557
+ # Add deployment command for your target:
558
+ # Vercel: npx vercel --prod --token=\${{ secrets.VERCEL_TOKEN }}
559
+ # DO App Platform: doctl apps create-deployment $APP_ID
560
+ # Docker: docker build -t app . && docker push
561
+ - run: echo "Configure deployment for ${choices.deploy}"
562
+ `;
563
+ }
564
+ function generateReleaseWorkflow() {
565
+ return `name: Release
566
+ on:
567
+ push: { tags: ['v*'] }
568
+
569
+ jobs:
570
+ publish:
571
+ runs-on: ubuntu-latest
572
+ permissions:
573
+ contents: write
574
+ steps:
575
+ - uses: actions/checkout@v4
576
+ - uses: actions/setup-node@v4
577
+ with: { node-version: 22, cache: npm }
578
+ - run: npm ci && npm run build
579
+ - run: npm publish
580
+ env:
581
+ NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}
582
+ - uses: softprops/action-gh-release@v2
583
+ with:
584
+ generate_release_notes: true
585
+ `;
586
+ }
587
+ function generateDependabot() {
588
+ return `version: 2
589
+ updates:
590
+ - package-ecosystem: npm
591
+ directory: /
592
+ schedule: { interval: weekly }
593
+ open-pull-requests-limit: 5
594
+ groups:
595
+ dev-deps:
596
+ dependency-type: development
597
+ prod-deps:
598
+ dependency-type: production
599
+ `;
600
+ }
601
+ function generatePrTemplate() {
602
+ return `## Summary
603
+ <!-- What does this PR do? -->
604
+
605
+ ## Test plan
606
+ - [ ] Tests pass locally
607
+ - [ ] Manually verified
608
+
609
+ ## Notes
610
+ <!-- Anything reviewers should know? -->
611
+ `;
612
+ }
613
+ function generateBugTemplate() {
614
+ return `---
615
+ name: Bug Report
616
+ about: Report a bug
617
+ labels: bug
618
+ ---
619
+
620
+ ## Expected behavior
621
+
622
+ ## Actual behavior
623
+
624
+ ## Steps to reproduce
625
+
626
+ ## Environment
627
+ - OS:
628
+ - Node:
629
+ - Version:
630
+ `;
631
+ }
632
+ function generateFeatureTemplate() {
633
+ return `---
634
+ name: Feature Request
635
+ about: Suggest a feature
636
+ labels: enhancement
637
+ ---
638
+
639
+ ## Problem
640
+ <!-- What problem does this solve? -->
641
+
642
+ ## Proposed solution
643
+
644
+ ## Alternatives considered
645
+ `;
646
+ }
647
+
648
+ // src/init/index.ts
649
+ import { createGatewayClient } from "@brainst0rm/gateway";
650
+ async function runInit(projectDir, options) {
651
+ const detection = await detectProject(projectDir);
652
+ let gatewayInfo = null;
653
+ const gw = createGatewayClient();
654
+ if (gw) {
655
+ try {
656
+ const [self, health, discovery] = await Promise.all([
657
+ gw.getSelf().catch(() => null),
658
+ gw.getHealth().catch(() => ({ status: "unknown" })),
659
+ gw.getDiscovery().catch(() => null)
660
+ ]);
661
+ if (self) {
662
+ gatewayInfo = {
663
+ connected: true,
664
+ modelCount: discovery?.models?.available ?? 0,
665
+ budget: discovery?.budget ? `$${discovery.budget.remaining_usd?.toFixed(2)}/${discovery.budget.period}` : void 0,
666
+ health: health.status
667
+ };
668
+ }
669
+ } catch {
670
+ }
671
+ }
672
+ let choices;
673
+ if (options.yes) {
674
+ choices = buildDefaultChoices(detection);
675
+ if (gatewayInfo) choices.cloudProvider = "brainstormrouter";
676
+ console.log("\n brainstorm init --yes\n");
677
+ console.log(` Auto-detected: ${choices.language} ${choices.type}`);
678
+ if (detection.localModels.length > 0) {
679
+ console.log(` Local models: ${detection.localModels.join(", ")}`);
680
+ }
681
+ if (gatewayInfo) {
682
+ console.log(
683
+ ` Gateway: connected (${gatewayInfo.modelCount} models, ${gatewayInfo.health})`
684
+ );
685
+ if (gatewayInfo.budget) console.log(` Budget: ${gatewayInfo.budget}`);
686
+ }
687
+ console.log();
688
+ } else {
689
+ if (gatewayInfo) {
690
+ console.log(
691
+ `
692
+ BrainstormRouter detected: ${gatewayInfo.modelCount} models, ${gatewayInfo.health}`
693
+ );
694
+ }
695
+ choices = await runPrompts(detection);
696
+ if (!choices) return;
697
+ if (gatewayInfo) choices.cloudProvider = "brainstormrouter";
698
+ }
699
+ const results = [];
700
+ const opts = { force: options.force };
701
+ results.push(
702
+ generateFile(projectDir, "STORM.md", generateStormMd(choices), opts)
703
+ );
704
+ results.push(
705
+ generateFile(
706
+ projectDir,
707
+ "brainstorm.toml",
708
+ generateBrainstormToml(choices),
709
+ opts
710
+ )
711
+ );
712
+ results.push(
713
+ generateFile(
714
+ projectDir,
715
+ ".brainstormignore",
716
+ generateBrainstormignore(),
717
+ opts
718
+ )
719
+ );
720
+ results.push(
721
+ generateFile(projectDir, ".env.example", generateEnvExample(choices), opts)
722
+ );
723
+ results.push(mergeGitignore(projectDir, generateGitignore(choices)));
724
+ results.push(
725
+ generateFile(projectDir, ".prettierrc", generatePrettierrc(), opts)
726
+ );
727
+ if (choices.ciTier !== "none") {
728
+ results.push(
729
+ generateFile(
730
+ projectDir,
731
+ ".github/workflows/ci.yml",
732
+ generateCiWorkflow(choices),
733
+ opts
734
+ )
735
+ );
736
+ results.push(
737
+ generateFile(
738
+ projectDir,
739
+ ".github/pull_request_template.md",
740
+ generatePrTemplate(),
741
+ opts
742
+ )
743
+ );
744
+ results.push(
745
+ generateFile(
746
+ projectDir,
747
+ ".github/ISSUE_TEMPLATE/bug_report.md",
748
+ generateBugTemplate(),
749
+ opts
750
+ )
751
+ );
752
+ results.push(
753
+ generateFile(
754
+ projectDir,
755
+ ".github/ISSUE_TEMPLATE/feature_request.md",
756
+ generateFeatureTemplate(),
757
+ opts
758
+ )
759
+ );
760
+ if (choices.ciTier === "full") {
761
+ results.push(
762
+ generateFile(
763
+ projectDir,
764
+ ".github/workflows/deploy.yml",
765
+ generateDeployWorkflow(choices),
766
+ opts
767
+ )
768
+ );
769
+ results.push(
770
+ generateFile(
771
+ projectDir,
772
+ ".github/workflows/release.yml",
773
+ generateReleaseWorkflow(),
774
+ opts
775
+ )
776
+ );
777
+ results.push(
778
+ generateFile(
779
+ projectDir,
780
+ ".github/dependabot.yml",
781
+ generateDependabot(),
782
+ opts
783
+ )
784
+ );
785
+ }
786
+ }
787
+ console.log(" Results:\n");
788
+ for (const r of results) {
789
+ const icon = r.action === "created" ? "+" : r.action === "merged" ? "~" : "-";
790
+ const label = r.action === "created" ? "created" : r.action === "merged" ? "merged" : "exists (skipped)";
791
+ console.log(` ${icon} ${r.path} (${label})`);
792
+ }
793
+ const created = results.filter((r) => r.action === "created").length;
794
+ const merged = results.filter((r) => r.action === "merged").length;
795
+ const skipped = results.filter((r) => r.action === "skipped").length;
796
+ console.log(
797
+ `
798
+ Done. ${created} created, ${merged} merged, ${skipped} skipped.`
799
+ );
800
+ console.log(
801
+ " Edit STORM.md to add your architecture, entry points, and conventions.\n"
802
+ );
803
+ }
804
+
805
+ // src/bin/brainstorm.ts
806
+ import { runEvalCli, runProbe } from "@brainst0rm/eval";
807
+ import {
808
+ createGatewayClient as createGatewayClient2,
809
+ createIntelligenceClient,
810
+ formatGatewayFeedback
811
+ } from "@brainst0rm/gateway";
812
+ import { MCPClientManager } from "@brainst0rm/mcp";
813
+ import { BrainstormVault, KeyResolver } from "@brainst0rm/vault";
814
+ import { homedir } from "os";
815
+ import { join as join3 } from "path";
816
+ import { readFileSync as readFileSyncVersion } from "fs";
817
+ import { dirname as dirnameVersion } from "path";
818
+ import { fileURLToPath as fileURLToPathVersion } from "url";
819
+ var PROVIDER_KEY_NAMES = [
820
+ "BRAINSTORM_API_KEY",
821
+ "ANTHROPIC_API_KEY",
822
+ "OPENAI_API_KEY",
823
+ "GOOGLE_GENERATIVE_AI_API_KEY",
824
+ "DEEPSEEK_API_KEY",
825
+ "MOONSHOT_API_KEY",
826
+ "BRAINSTORM_ADMIN_KEY"
827
+ ];
828
+ async function resolveProviderKeys() {
829
+ const vault = new BrainstormVault(VAULT_PATH);
830
+ const resolver = new KeyResolver(
831
+ vault.exists() ? vault : null,
832
+ () => promptPassword(" Vault password: ")
833
+ );
834
+ const resolved = /* @__PURE__ */ new Map();
835
+ for (const name of PROVIDER_KEY_NAMES) {
836
+ const value = await resolver.get(name);
837
+ if (value) resolved.set(name, value);
838
+ }
839
+ return { get: (name) => resolved.get(name) ?? null };
840
+ }
841
+ function buildCompactionCallbacks(sessionManager) {
842
+ return {
843
+ getTokenEstimate: () => sessionManager.getTokenEstimate(),
844
+ compact: (opts) => sessionManager.compact(opts)
845
+ };
846
+ }
847
+ async function connectMCPServers(tools, config, resolvedBRKey) {
848
+ const mcp = new MCPClientManager();
849
+ if (config.mcp.servers.length > 0) {
850
+ mcp.addServers(
851
+ config.mcp.servers.map((s) => ({
852
+ name: s.name,
853
+ transport: s.transport,
854
+ url: s.url ?? "",
855
+ command: s.command,
856
+ args: s.args,
857
+ env: s.env,
858
+ enabled: s.enabled,
859
+ toolFilter: s.toolFilter
860
+ }))
861
+ );
862
+ }
863
+ const { connected, errors } = await mcp.connectAll(tools);
864
+ if (connected.length > 0) {
865
+ process.stderr.write(`[mcp] Connected: ${connected.join(", ")}
866
+ `);
867
+ }
868
+ for (const err of errors) {
869
+ process.stderr.write(`[mcp] ${err.name}: ${err.error}
870
+ `);
871
+ }
872
+ }
873
+ var program = new Command();
874
+ var __pkg_dir = join3(
875
+ dirnameVersion(fileURLToPathVersion(import.meta.url)),
876
+ ".."
877
+ );
878
+ var CLI_VERSION = "0.12.1";
879
+ try {
880
+ CLI_VERSION = JSON.parse(
881
+ readFileSyncVersion(join3(__pkg_dir, "package.json"), "utf-8")
882
+ ).version;
883
+ } catch {
884
+ }
885
+ program.name("brainstorm").description("AI coding assistant with intelligent model routing").version(CLI_VERSION);
886
+ program.command("init").description("Initialize project for AI-assisted development").option("--yes", "Use defaults, skip prompts").option("--force", "Overwrite existing files").action(async (opts) => {
887
+ await runInit(process.cwd(), opts);
888
+ });
889
+ program.command("eval").description("Run capability evaluation probes against a model").option(
890
+ "--model <id>",
891
+ "Model to evaluate (e.g., anthropic/claude-sonnet-4-6)"
892
+ ).option("--capability <dim>", "Run only probes for this dimension").option("--compare", "Compare results across all previously evaluated models").option(
893
+ "--scorecard",
894
+ "Show current capability scores without re-running probes"
895
+ ).option("--all-models", "Run probes against every available model").option("--timeout <ms>", "Timeout per probe in milliseconds", "30000").action(
896
+ async (opts) => {
897
+ await runEvalCli({
898
+ model: opts.model,
899
+ capability: opts.capability,
900
+ compare: opts.compare,
901
+ scorecard: opts.scorecard,
902
+ allModels: opts.allModels,
903
+ timeout: parseInt(opts.timeout ?? "30000")
904
+ });
905
+ }
906
+ );
907
+ program.command("eval-swe-bench").description(
908
+ "Run SWE-bench evaluation: apply agent to instances, score with Docker"
909
+ ).requiredOption(
910
+ "--instances <path>",
911
+ "Path to SWE-bench instances.jsonl file"
912
+ ).option("--model <id>", "Target model (default: let router decide)").option("--limit <n>", "Max instances to evaluate", "10").option("--concurrency <n>", "Parallel evaluations", "2").option("--json", "Output results as JSON").action(
913
+ async (opts) => {
914
+ const { loadInstances, runSWEBench, scorePatch, generateScorecard } = await import("@brainst0rm/eval");
915
+ const limit = parseInt(opts.limit);
916
+ const concurrency = parseInt(opts.concurrency);
917
+ console.log(`
918
+ SWE-bench Evaluation`);
919
+ console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
920
+ `);
921
+ console.log(` Instances: ${opts.instances}`);
922
+ console.log(` Limit: ${limit}`);
923
+ console.log(` Model: ${opts.model ?? "auto (router decides)"}`);
924
+ console.log(` Concurrency: ${concurrency}
925
+ `);
926
+ const instances = loadInstances(opts.instances, limit);
927
+ console.log(` Loaded ${instances.length} instances.
928
+ `);
929
+ if (instances.length === 0) {
930
+ console.error(" No instances found in file.");
931
+ process.exit(1);
932
+ }
933
+ const config = loadConfig();
934
+ config.general.defaultPermissionMode = "auto";
935
+ const db = getDb();
936
+ const resolvedKeys = await resolveProviderKeys();
937
+ const registry = await createProviderRegistry(config, resolvedKeys);
938
+ const costTracker = new CostTracker(db, config.budget);
939
+ const tools = createDefaultToolRegistry();
940
+ const { frontmatter } = buildSystemPrompt(process.cwd());
941
+ const router = new BrainstormRouter(
942
+ config,
943
+ registry,
944
+ costTracker,
945
+ frontmatter
946
+ );
947
+ if (opts.model) {
948
+ } else {
949
+ router.setStrategy("quality-first");
950
+ }
951
+ const { execFileSync: execGit } = await import("child_process");
952
+ const {
953
+ mkdtempSync,
954
+ writeFileSync: writePatch,
955
+ rmSync
956
+ } = await import("fs");
957
+ const { tmpdir } = await import("os");
958
+ let completed = 0;
959
+ console.log(` Running agent on ${instances.length} instances...
960
+ `);
961
+ const patches = await runSWEBench(
962
+ instances,
963
+ async (instance) => {
964
+ const startTime = Date.now();
965
+ const instanceNum = ++completed;
966
+ const shortId = instance.instanceId.slice(0, 40);
967
+ try {
968
+ const workDir = mkdtempSync(join3(tmpdir(), "swe-bench-"));
969
+ const repoDir = join3(workDir, "repo");
970
+ try {
971
+ process.stderr.write(
972
+ ` [${instanceNum}/${instances.length}] ${shortId} \u2014 cloning...`
973
+ );
974
+ execGit(
975
+ "git",
976
+ [
977
+ "clone",
978
+ "--depth",
979
+ "100",
980
+ `https://github.com/${instance.repo}.git`,
981
+ "repo"
982
+ ],
983
+ {
984
+ cwd: workDir,
985
+ timeout: 12e4,
986
+ stdio: ["ignore", "pipe", "pipe"]
987
+ }
988
+ );
989
+ execGit("git", ["checkout", instance.baseCommit], {
990
+ cwd: repoDir,
991
+ timeout: 3e4,
992
+ stdio: ["ignore", "pipe", "pipe"]
993
+ });
994
+ process.stderr.write(` solving...`);
995
+ const issuePrompt = [
996
+ `You are solving a GitHub issue in this repository.`,
997
+ ``,
998
+ `## Problem`,
999
+ instance.issue,
1000
+ instance.hints ? `
1001
+ ## Hints
1002
+ ${instance.hints}` : "",
1003
+ ``,
1004
+ `## Instructions`,
1005
+ `1. Read the relevant source files to understand the codebase`,
1006
+ `2. Identify the root cause of the issue`,
1007
+ `3. Make the minimal code changes needed to fix it`,
1008
+ `4. Do NOT modify test files`,
1009
+ `5. Verify your changes make sense by re-reading the modified files`
1010
+ ].join("\n");
1011
+ const result = await spawnSubagent(issuePrompt, {
1012
+ config,
1013
+ registry,
1014
+ router,
1015
+ costTracker,
1016
+ tools,
1017
+ projectPath: repoDir,
1018
+ type: "code",
1019
+ maxSteps: 15,
1020
+ budgetLimit: 3,
1021
+ permissionCheck: () => "allow"
1022
+ // unattended — auto-approve everything
1023
+ });
1024
+ let patch = "";
1025
+ try {
1026
+ patch = execGit("git", ["diff"], {
1027
+ cwd: repoDir,
1028
+ encoding: "utf-8",
1029
+ timeout: 1e4,
1030
+ stdio: ["ignore", "pipe", "pipe"]
1031
+ });
1032
+ const untrackedDiff = execGit("git", ["diff", "--cached"], {
1033
+ cwd: repoDir,
1034
+ encoding: "utf-8",
1035
+ timeout: 1e4,
1036
+ stdio: ["ignore", "pipe", "pipe"]
1037
+ });
1038
+ if (untrackedDiff) patch += "\n" + untrackedDiff;
1039
+ } catch {
1040
+ }
1041
+ const success = patch.length > 0 && !result.budgetExceeded;
1042
+ const status = success ? "\u2713" : patch.length === 0 ? "no changes" : "budget exceeded";
1043
+ process.stderr.write(
1044
+ ` ${status} ($${result.cost.toFixed(3)}, ${result.modelUsed})
1045
+ `
1046
+ );
1047
+ return {
1048
+ instanceId: instance.instanceId,
1049
+ patch,
1050
+ model: result.modelUsed,
1051
+ strategy: opts.model ? "forced" : "quality-first",
1052
+ cost: result.cost,
1053
+ latencyMs: Date.now() - startTime,
1054
+ success
1055
+ };
1056
+ } finally {
1057
+ try {
1058
+ rmSync(workDir, { recursive: true, force: true });
1059
+ } catch {
1060
+ }
1061
+ }
1062
+ } catch (err) {
1063
+ process.stderr.write(
1064
+ ` ERROR: ${(err.message ?? "").slice(0, 80)}
1065
+ `
1066
+ );
1067
+ return {
1068
+ instanceId: instance.instanceId,
1069
+ patch: "",
1070
+ model: "error",
1071
+ strategy: "quality-first",
1072
+ cost: 0,
1073
+ latencyMs: Date.now() - startTime,
1074
+ success: false
1075
+ };
1076
+ }
1077
+ },
1078
+ concurrency
1079
+ );
1080
+ console.log(` Scoring ${patches.length} patches...`);
1081
+ const scores = patches.map(
1082
+ (patch, i) => scorePatch(instances[i], patch)
1083
+ );
1084
+ const scorecard = generateScorecard(patches, scores);
1085
+ if (opts.json) {
1086
+ console.log(JSON.stringify(scorecard, null, 2));
1087
+ return;
1088
+ }
1089
+ console.log(`
1090
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
1091
+ console.log(` SWE-bench Results`);
1092
+ 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
1093
+ `);
1094
+ console.log(` Total: ${scorecard.total}`);
1095
+ console.log(
1096
+ ` Passed: ${scorecard.passed} (${(scorecard.passRate * 100).toFixed(1)}%)`
1097
+ );
1098
+ console.log(` Failed: ${scorecard.failed}`);
1099
+ console.log(` Errored: ${scorecard.errored}`);
1100
+ console.log(` Cost: $${scorecard.totalCost.toFixed(4)}`);
1101
+ console.log(` Avg Lat: ${scorecard.avgLatencyMs}ms`);
1102
+ console.log();
1103
+ }
1104
+ );
1105
+ var routerCmd = program.command("router").description("Manage BrainstormRouter gateway");
1106
+ routerCmd.command("status").description("Show gateway health, budget, and rate limits").action(async () => {
1107
+ const gw = createGatewayClient2();
1108
+ if (!gw) {
1109
+ console.error(
1110
+ " BRAINSTORM_API_KEY not set. Configure with: export BRAINSTORM_API_KEY=br_live_xxx"
1111
+ );
1112
+ return;
1113
+ }
1114
+ try {
1115
+ const [self, health] = await Promise.all([gw.getSelf(), gw.getHealth()]);
1116
+ console.log("\n BrainstormRouter Gateway\n");
1117
+ console.log(` Health: ${health.status}`);
1118
+ console.log(` Role: ${self.identity.roles.join(", ")}`);
1119
+ console.log(` Caps: ${self.capabilities.granted.length} permissions`);
1120
+ try {
1121
+ const discovery = await gw.getDiscovery();
1122
+ if (discovery.budget) {
1123
+ console.log(
1124
+ ` Budget: $${discovery.budget.remaining_usd?.toFixed(2)} / $${discovery.budget.limit_usd?.toFixed(2)} (${discovery.budget.period})`
1125
+ );
1126
+ }
1127
+ if (discovery.models) {
1128
+ console.log(
1129
+ ` Models: ${discovery.models.available} available, ${discovery.models.runnable} runnable`
1130
+ );
1131
+ }
1132
+ } catch {
1133
+ console.log(" Budget: (discovery unavailable)");
1134
+ console.log(" Models: (discovery unavailable)");
1135
+ }
1136
+ console.log();
1137
+ } catch (e) {
1138
+ console.error(` Error: ${e.message}`);
1139
+ }
1140
+ });
1141
+ routerCmd.command("models").description("List models available through the gateway").option("--json", "Output as JSON").action(async (opts) => {
1142
+ const gw = createGatewayClient2();
1143
+ if (!gw) {
1144
+ console.error(" BRAINSTORM_API_KEY not set.");
1145
+ return;
1146
+ }
1147
+ try {
1148
+ const models = await gw.listModels();
1149
+ if (opts.json) {
1150
+ console.log(JSON.stringify(models, null, 2));
1151
+ return;
1152
+ }
1153
+ console.log(`
1154
+ Gateway Models (${models.length})
1155
+ `);
1156
+ for (const m of models.slice(0, 30)) {
1157
+ const name = (m.name ?? m.id).padEnd(40);
1158
+ const provider = (m.provider ?? "").padEnd(12);
1159
+ console.log(` ${provider} ${name}`);
1160
+ }
1161
+ if (models.length > 30)
1162
+ console.log(` ... and ${models.length - 30} more`);
1163
+ console.log();
1164
+ } catch (e) {
1165
+ console.error(` Error: ${e.message}`);
1166
+ }
1167
+ });
1168
+ routerCmd.command("budget").description("Show gateway-side cost tracking and forecast").action(async () => {
1169
+ const gw = createGatewayClient2();
1170
+ if (!gw) {
1171
+ console.error(" BRAINSTORM_API_KEY not set.");
1172
+ return;
1173
+ }
1174
+ try {
1175
+ const usage = await gw.getUsageSummary();
1176
+ console.log("\n Gateway Budget\n");
1177
+ console.log(` Requests: ${usage.total_requests ?? "N/A"}`);
1178
+ console.log(` Cost: $${(usage.total_cost_usd ?? 0).toFixed(4)}`);
1179
+ console.log(
1180
+ ` Tokens: ${(usage.total_input_tokens ?? 0).toLocaleString()} in / ${(usage.total_output_tokens ?? 0).toLocaleString()} out`
1181
+ );
1182
+ if (usage.by_model?.length > 0) {
1183
+ console.log("\n By model:");
1184
+ for (const m of usage.by_model) {
1185
+ console.log(
1186
+ ` ${m.model}: $${m.cost_usd.toFixed(4)} (${m.requests} reqs)`
1187
+ );
1188
+ }
1189
+ }
1190
+ console.log();
1191
+ } catch (e) {
1192
+ console.error(` Error: ${e.message}`);
1193
+ }
1194
+ });
1195
+ routerCmd.command("keys").description("List API keys").option("--json", "Output as JSON").action(async (opts) => {
1196
+ const gw = createGatewayClient2();
1197
+ if (!gw) {
1198
+ console.error(" BRAINSTORM_API_KEY not set.");
1199
+ return;
1200
+ }
1201
+ try {
1202
+ const keys = await gw.listKeys();
1203
+ if (opts.json) {
1204
+ console.log(JSON.stringify(keys, null, 2));
1205
+ return;
1206
+ }
1207
+ console.log(`
1208
+ API Keys (${keys.length})
1209
+ `);
1210
+ for (const k of keys) {
1211
+ const budget = k.budgetLimitUsd ? `$${k.budgetLimitUsd}/${k.budgetPeriod}` : "unlimited";
1212
+ console.log(
1213
+ ` ${k.id.slice(0, 8)} ${(k.name ?? "").padEnd(30)} scopes=${JSON.stringify(k.scopes)} budget=${budget}`
1214
+ );
1215
+ }
1216
+ console.log();
1217
+ } catch (e) {
1218
+ console.error(` Error: ${e.message}`);
1219
+ }
1220
+ });
1221
+ routerCmd.command("config").description("Get or set gateway configuration").argument("<key>", "Config key (e.g., guardrails, tools)").argument("[value]", "JSON value to set (omit to read)").action(async (key, value) => {
1222
+ const gw = createGatewayClient2();
1223
+ if (!gw) {
1224
+ console.error(" BRAINSTORM_API_KEY not set.");
1225
+ return;
1226
+ }
1227
+ try {
1228
+ if (value) {
1229
+ await gw.setConfig(key, JSON.parse(value));
1230
+ console.log(` Set config/${key}`);
1231
+ } else {
1232
+ const data = await gw.getConfig(key);
1233
+ console.log(JSON.stringify(data, null, 2));
1234
+ }
1235
+ } catch (e) {
1236
+ console.error(` Error: ${e.message}`);
1237
+ }
1238
+ });
1239
+ routerCmd.command("audit").description("Show recent request audit trail").option("--since <duration>", "Time range (e.g., 1h, 24h, 7d)", "24h").action(async (opts) => {
1240
+ const gw = createGatewayClient2();
1241
+ if (!gw) {
1242
+ console.error(" BRAINSTORM_API_KEY not set.");
1243
+ return;
1244
+ }
1245
+ try {
1246
+ const entries = await gw.getCompletionAudit(opts.since);
1247
+ console.log(`
1248
+ Audit Trail (last ${opts.since})
1249
+ `);
1250
+ if (entries.length === 0) {
1251
+ console.log(" No entries found.");
1252
+ }
1253
+ for (const e of entries.slice(0, 20)) {
1254
+ console.log(
1255
+ ` ${e.timestamp} ${(e.model ?? "").padEnd(35)} $${(e.cost_usd ?? 0).toFixed(4)} ${e.latency_ms ?? "?"}ms guardian=${e.guardian_status ?? "?"}`
1256
+ );
1257
+ }
1258
+ if (entries.length > 20)
1259
+ console.log(` ... ${entries.length - 20} more`);
1260
+ console.log();
1261
+ } catch (e) {
1262
+ console.error(` Error: ${e.message}`);
1263
+ }
1264
+ });
1265
+ routerCmd.command("memory").description("List gateway memory entries").action(async () => {
1266
+ const gw = createGatewayClient2();
1267
+ if (!gw) {
1268
+ console.error(" BRAINSTORM_API_KEY not set.");
1269
+ return;
1270
+ }
1271
+ try {
1272
+ const entries = await gw.listMemory();
1273
+ console.log(`
1274
+ Gateway Memory (${entries.length} entries)
1275
+ `);
1276
+ for (const e of entries) {
1277
+ const block = e.block ?? "unknown";
1278
+ const content = e.content ?? JSON.stringify(e).slice(0, 80);
1279
+ console.log(
1280
+ ` [${block}] ${content.slice(0, 80)}${content.length > 80 ? "..." : ""}`
1281
+ );
1282
+ }
1283
+ console.log();
1284
+ } catch (e) {
1285
+ console.error(` Error: ${e.message}`);
1286
+ }
1287
+ });
1288
+ program.command("models").description("List available models and their status").action(async () => {
1289
+ const config = loadConfig();
1290
+ const registry = await createProviderRegistry(
1291
+ config,
1292
+ await resolveProviderKeys()
1293
+ );
1294
+ console.log("\n\u{1F9E0} Brainstorm \u2014 Available Models\n");
1295
+ const local = registry.models.filter((m) => m.isLocal);
1296
+ const cloud = registry.models.filter((m) => !m.isLocal);
1297
+ if (local.length > 0) {
1298
+ console.log(" Local Models:");
1299
+ for (const m of local) {
1300
+ const status = m.status === "available" ? "\u25CF" : "\u25CB";
1301
+ console.log(
1302
+ ` ${status} ${m.id} (quality: ${m.capabilities.qualityTier}, speed: ${m.capabilities.speedTier})`
1303
+ );
1304
+ }
1305
+ console.log();
1306
+ } else {
1307
+ console.log(
1308
+ " Local Models: none detected (start Ollama, LM Studio, or llama.cpp)\n"
1309
+ );
1310
+ }
1311
+ console.log(" Cloud Models (via AI Gateway):");
1312
+ for (const m of cloud) {
1313
+ const cost = `$${m.pricing.inputPer1MTokens}/${m.pricing.outputPer1MTokens} per 1M tokens`;
1314
+ console.log(
1315
+ ` \u25CF ${m.id} (quality: ${m.capabilities.qualityTier}, ${cost})`
1316
+ );
1317
+ }
1318
+ console.log();
1319
+ });
1320
+ program.command("budget").description("Show cost tracking and budget status").action(async () => {
1321
+ const config = loadConfig();
1322
+ const db = getDb();
1323
+ const costTracker = new CostTracker(db, config.budget);
1324
+ const summary = costTracker.getSummary();
1325
+ console.log("\n\u{1F9E0} Brainstorm \u2014 Budget Status\n");
1326
+ console.log(` Session: $${summary.session.toFixed(4)}`);
1327
+ console.log(
1328
+ ` Today: $${summary.today.toFixed(4)}${config.budget.daily ? ` / $${config.budget.daily.toFixed(2)}` : ""}`
1329
+ );
1330
+ console.log(
1331
+ ` This month: $${summary.thisMonth.toFixed(4)}${config.budget.monthly ? ` / $${config.budget.monthly.toFixed(2)}` : ""}`
1332
+ );
1333
+ if (summary.byModel.length > 0) {
1334
+ console.log("\n Cost by model:");
1335
+ for (const entry of summary.byModel) {
1336
+ console.log(
1337
+ ` ${entry.modelId}: $${entry.totalCost.toFixed(4)} (${entry.requestCount} requests)`
1338
+ );
1339
+ }
1340
+ }
1341
+ console.log();
1342
+ });
1343
+ program.command("config").description("Show current configuration").action(async () => {
1344
+ const config = loadConfig();
1345
+ console.log("\n\u{1F9E0} Brainstorm \u2014 Configuration\n");
1346
+ console.log(` Strategy: ${config.general.defaultStrategy}`);
1347
+ console.log(` Max steps: ${config.general.maxSteps}`);
1348
+ console.log(` Confirm tools: ${config.general.confirmTools}`);
1349
+ console.log(
1350
+ ` Budget daily: ${config.budget.daily ? `$${config.budget.daily}` : "unlimited"}`
1351
+ );
1352
+ console.log(
1353
+ ` Budget monthly: ${config.budget.monthly ? `$${config.budget.monthly}` : "unlimited"}`
1354
+ );
1355
+ console.log(` Hard limit: ${config.budget.hardLimit}`);
1356
+ console.log(
1357
+ ` Ollama: ${config.providers.ollama.enabled ? config.providers.ollama.baseUrl : "disabled"}`
1358
+ );
1359
+ console.log(
1360
+ ` LM Studio: ${config.providers.lmstudio.enabled ? config.providers.lmstudio.baseUrl : "disabled"}`
1361
+ );
1362
+ console.log(
1363
+ ` llama.cpp: ${config.providers.llamacpp.enabled ? config.providers.llamacpp.baseUrl : "disabled"}`
1364
+ );
1365
+ console.log(
1366
+ ` AI Gateway: ${config.providers.gateway.enabled ? "enabled" : "disabled"}`
1367
+ );
1368
+ if (config.routing.rules.length > 0) {
1369
+ console.log(` Routing rules: ${config.routing.rules.length}`);
1370
+ }
1371
+ console.log();
1372
+ });
1373
+ var agentCmd = program.command("agent").description("Manage named agents");
1374
+ agentCmd.command("create").description("Create an agent (structured flags or natural language)").argument(
1375
+ "[description...]",
1376
+ 'Natural language description (e.g., "architect using opus with $30 budget")'
1377
+ ).option("--id <id>", "Agent ID").option("--model <model>", "Model ID or alias").option(
1378
+ "--role <role>",
1379
+ "Agent role (architect|coder|reviewer|debugger|analyst|custom)"
1380
+ ).option("--budget <usd>", "Per-workflow budget in USD", parseFloat).option("--budget-daily <usd>", "Daily budget in USD", parseFloat).option("--description <desc>", "What this agent does").option("--confidence <threshold>", "Confidence threshold 0-1", parseFloat).action(async (descWords, opts) => {
1381
+ const config = loadConfig();
1382
+ const db = getDb();
1383
+ const manager = new AgentManager(db, config);
1384
+ const nlInput = descWords.join(" ");
1385
+ const parseResult = nlInput ? parseAgentNL(nlInput) : null;
1386
+ const parsed = parseResult?.intent;
1387
+ if (nlInput && !parsed && parseResult?.suggestion) {
1388
+ console.log(
1389
+ ` Could not parse agent definition.
1390
+ ${parseResult.suggestion}`
1391
+ );
1392
+ process.exit(1);
1393
+ }
1394
+ const id = opts.id ?? parsed?.id ?? "agent-" + Date.now().toString(36);
1395
+ const role = opts.role ?? parsed?.role ?? "custom";
1396
+ const modelId = opts.model ?? parsed?.modelId ?? "auto";
1397
+ const budget = opts.budget ?? parsed?.budget;
1398
+ const budgetDaily = opts.budgetDaily ?? parsed?.budgetDaily;
1399
+ const description = opts.description ?? parsed?.description ?? "";
1400
+ const confidence = opts.confidence ?? 0.7;
1401
+ const agent = manager.create({
1402
+ id,
1403
+ displayName: id.charAt(0).toUpperCase() + id.slice(1),
1404
+ role,
1405
+ description,
1406
+ modelId,
1407
+ allowedTools: role === "coder" ? "all" : ["file_read", "glob", "grep"],
1408
+ budget: {
1409
+ perWorkflow: budget,
1410
+ daily: budgetDaily,
1411
+ exhaustionAction: "downgrade"
1412
+ },
1413
+ confidenceThreshold: confidence,
1414
+ maxSteps: 10,
1415
+ fallbackChain: [],
1416
+ guardrails: { pii: parsed?.guardrailsPii },
1417
+ lifecycle: "active"
1418
+ });
1419
+ console.log(`
1420
+ Created agent '${agent.id}'`);
1421
+ console.log(` Role: ${agent.role}`);
1422
+ console.log(` Model: ${agent.modelId}`);
1423
+ if (agent.budget.perWorkflow)
1424
+ console.log(` Budget: $${agent.budget.perWorkflow}/workflow`);
1425
+ if (agent.budget.daily)
1426
+ console.log(` Daily: $${agent.budget.daily}/day`);
1427
+ if (agent.guardrails.pii) console.log(` Guardrails: PII enabled`);
1428
+ console.log();
1429
+ });
1430
+ agentCmd.command("list").description("List all agents").action(async () => {
1431
+ const config = loadConfig();
1432
+ const db = getDb();
1433
+ const manager = new AgentManager(db, config);
1434
+ const agents = manager.list();
1435
+ console.log("\n Agents:\n");
1436
+ if (agents.length === 0) {
1437
+ console.log(
1438
+ " No agents defined. Create one with: storm agent create <description>"
1439
+ );
1440
+ }
1441
+ for (const a of agents) {
1442
+ const budget = a.budget.perWorkflow ? `$${a.budget.perWorkflow}/wf` : a.budget.daily ? `$${a.budget.daily}/day` : "unlimited";
1443
+ console.log(
1444
+ ` ${a.id} (${a.role}) model: ${a.modelId} budget: ${budget}`
1445
+ );
1446
+ }
1447
+ console.log();
1448
+ });
1449
+ agentCmd.command("show").description("Show agent details").argument("<id>", "Agent ID").action(async (id) => {
1450
+ const config = loadConfig();
1451
+ const db = getDb();
1452
+ const manager = new AgentManager(db, config);
1453
+ const agent = manager.get(id);
1454
+ if (!agent) {
1455
+ console.error(` Agent '${id}' not found.`);
1456
+ process.exit(1);
1457
+ }
1458
+ console.log(`
1459
+ Agent: ${agent.id}`);
1460
+ console.log(` Display Name: ${agent.displayName}`);
1461
+ console.log(` Role: ${agent.role}`);
1462
+ console.log(` Model: ${agent.modelId}`);
1463
+ console.log(` Description: ${agent.description || "(none)"}`);
1464
+ console.log(` Allowed Tools: ${JSON.stringify(agent.allowedTools)}`);
1465
+ console.log(
1466
+ ` Budget/Workflow: ${agent.budget.perWorkflow ? `$${agent.budget.perWorkflow}` : "unlimited"}`
1467
+ );
1468
+ console.log(
1469
+ ` Budget/Daily: ${agent.budget.daily ? `$${agent.budget.daily}` : "unlimited"}`
1470
+ );
1471
+ console.log(` Confidence: ${agent.confidenceThreshold}`);
1472
+ console.log(
1473
+ ` Fallback Chain: ${agent.fallbackChain.length > 0 ? agent.fallbackChain.join(" \u2192 ") : "(none)"}`
1474
+ );
1475
+ console.log(` Guardrails: PII=${agent.guardrails.pii ?? false}`);
1476
+ console.log(` Status: ${agent.lifecycle}`);
1477
+ console.log();
1478
+ });
1479
+ agentCmd.command("delete").description("Delete an agent").argument("<id>", "Agent ID").action(async (id) => {
1480
+ const config = loadConfig();
1481
+ const db = getDb();
1482
+ const manager = new AgentManager(db, config);
1483
+ try {
1484
+ const deleted = manager.delete(id);
1485
+ if (deleted) console.log(` Deleted agent '${id}'.`);
1486
+ else console.error(` Agent '${id}' not found.`);
1487
+ } catch (e) {
1488
+ console.error(` ${e.message}`);
1489
+ }
1490
+ });
1491
+ var workflowCmd = program.command("workflow").description("Run multi-agent workflows");
1492
+ workflowCmd.command("list").description("List available workflows").action(async () => {
1493
+ console.log("\n Workflows:\n");
1494
+ for (const w of PRESET_WORKFLOWS) {
1495
+ const steps = w.steps.map((s) => s.agentRole).join(" \u2192 ");
1496
+ console.log(` ${w.id} \u2014 ${w.description}`);
1497
+ console.log(
1498
+ ` Steps: ${steps} (mode: ${w.communicationMode}, max loops: ${w.maxIterations})`
1499
+ );
1500
+ }
1501
+ console.log();
1502
+ });
1503
+ workflowCmd.command("run").description("Run a workflow").argument("<preset>", "Workflow preset ID or natural language description").argument("[description...]", "What to build/fix/review").option(
1504
+ "--agents <mapping>",
1505
+ 'Agent role overrides (e.g., "architect=my-arch,coder=my-coder")'
1506
+ ).option("--mode <mode>", "Communication mode (handoff|shared)", "handoff").option(
1507
+ "--step-model <overrides...>",
1508
+ 'Per-step model overrides (e.g., "plan=claude-opus-4.6 code=claude-sonnet-4.6")'
1509
+ ).option("--dry-run", "Show cost forecast only").action(async (preset, descWords, opts) => {
1510
+ const description = descWords.join(" ") || preset;
1511
+ let workflow = getPresetWorkflow(preset);
1512
+ if (!workflow) {
1513
+ const autoPreset = autoSelectPreset(preset + " " + description);
1514
+ if (autoPreset) workflow = getPresetWorkflow(autoPreset);
1515
+ }
1516
+ if (!workflow) {
1517
+ console.error(
1518
+ ` Unknown workflow: '${preset}'. Run 'storm workflow list' to see available workflows.`
1519
+ );
1520
+ process.exit(1);
1521
+ }
1522
+ const config = loadConfig();
1523
+ const db = getDb();
1524
+ const registry = await createProviderRegistry(
1525
+ config,
1526
+ await resolveProviderKeys()
1527
+ );
1528
+ const costTracker = new CostTracker(db, config.budget);
1529
+ const projectPath = process.cwd();
1530
+ const { frontmatter } = buildSystemPrompt(projectPath);
1531
+ const router = new BrainstormRouter(
1532
+ config,
1533
+ registry,
1534
+ costTracker,
1535
+ frontmatter
1536
+ );
1537
+ const agentManager = new AgentManager(db, config);
1538
+ const agentOverrides = {};
1539
+ if (opts.agents) {
1540
+ for (const pair of opts.agents.split(",")) {
1541
+ const [role, agentId] = pair.split("=");
1542
+ if (role && agentId) agentOverrides[role.trim()] = agentId.trim();
1543
+ }
1544
+ }
1545
+ const stepModelOverrides = {};
1546
+ if (opts.stepModel) {
1547
+ const items = Array.isArray(opts.stepModel) ? opts.stepModel : [opts.stepModel];
1548
+ for (const item of items) {
1549
+ for (const pair of item.split(/\s+/)) {
1550
+ const [step, model] = pair.split("=");
1551
+ if (step && model) stepModelOverrides[step.trim()] = model.trim();
1552
+ }
1553
+ }
1554
+ }
1555
+ console.log(`
1556
+ Workflow: ${workflow.name}`);
1557
+ console.log(` Request: "${description}"`);
1558
+ console.log(
1559
+ ` Steps: ${workflow.steps.map((s) => s.agentRole).join(" \u2192 ")}`
1560
+ );
1561
+ if (Object.keys(stepModelOverrides).length > 0) {
1562
+ console.log(
1563
+ ` Model overrides: ${Object.entries(stepModelOverrides).map(([s, m]) => `${s}=${m}`).join(", ")}`
1564
+ );
1565
+ }
1566
+ console.log();
1567
+ for await (const event of runWorkflow(
1568
+ workflow,
1569
+ description,
1570
+ agentOverrides,
1571
+ {
1572
+ config,
1573
+ db,
1574
+ registry,
1575
+ router,
1576
+ costTracker,
1577
+ agentManager,
1578
+ projectPath,
1579
+ stepModelOverrides
1580
+ }
1581
+ )) {
1582
+ switch (event.type) {
1583
+ case "cost-forecast":
1584
+ console.log(` Estimated cost: $${event.estimated.toFixed(4)}`);
1585
+ for (const b of event.breakdown) {
1586
+ console.log(` ${b.step}: $${b.cost.toFixed(4)}`);
1587
+ }
1588
+ if (opts.dryRun) {
1589
+ console.log("\n (dry run \u2014 not executing)\n");
1590
+ return;
1591
+ }
1592
+ console.log();
1593
+ break;
1594
+ case "step-started":
1595
+ process.stdout.write(
1596
+ ` [${event.agent.role}] ${event.agent.displayName} (${event.agent.modelId})...`
1597
+ );
1598
+ break;
1599
+ case "step-progress":
1600
+ if (event.event.type === "text-delta") {
1601
+ }
1602
+ if (event.event.type === "routing") {
1603
+ process.stdout.write(` \u2192 ${event.event.decision.model.name}`);
1604
+ }
1605
+ break;
1606
+ case "step-completed":
1607
+ console.log(
1608
+ ` done ($${event.step.cost.toFixed(4)}, confidence: ${event.artifact.confidence.toFixed(2)})`
1609
+ );
1610
+ break;
1611
+ case "step-failed":
1612
+ console.log(` FAILED: ${event.error.message}`);
1613
+ break;
1614
+ case "review-rejected":
1615
+ console.log(
1616
+ ` [review] Rejected \u2014 looping back to ${event.loopingBackTo} (iteration ${event.step.iteration + 1})`
1617
+ );
1618
+ break;
1619
+ case "confidence-escalation":
1620
+ console.log(
1621
+ ` [confidence] ${event.action} (${event.confidence.toFixed(2)})`
1622
+ );
1623
+ break;
1624
+ case "model-fallback":
1625
+ console.log(
1626
+ ` [fallback] ${event.originalModel} \u2192 ${event.fallbackModel}: ${event.reason}`
1627
+ );
1628
+ break;
1629
+ case "workflow-completed":
1630
+ console.log(
1631
+ `
1632
+ Workflow complete. Total cost: $${event.run.totalCost.toFixed(4)}`
1633
+ );
1634
+ console.log(
1635
+ ` Artifacts: ${event.run.artifacts.map((a) => a.id).join(", ")}
1636
+ `
1637
+ );
1638
+ break;
1639
+ case "workflow-failed":
1640
+ console.log(`
1641
+ Workflow failed: ${event.error.message}
1642
+ `);
1643
+ break;
1644
+ }
1645
+ }
1646
+ });
1647
+ program.command("run").description("Run a single prompt non-interactively").argument("[prompt]", "The prompt to send").option("--json", "Output structured JSON (for CI/CD pipelines)").option("--pipe", "Read from stdin if no prompt given").option("--model <id>", "Target a specific model (bypass routing)").option("--tools", "Enable tool use (default: disabled)").option("--max-steps <n>", "Maximum agentic steps (default: 1)", "1").option(
1648
+ "--strategy <name>",
1649
+ "Routing strategy: cost-first, quality-first, combined, capability"
1650
+ ).option("--lfg", "Full auto mode \u2014 skip all permission confirmations").option(
1651
+ "--unattended",
1652
+ "Unattended mode \u2014 enable tools, auto-approve, auto-commit on success"
1653
+ ).action(
1654
+ async (prompt, opts) => {
1655
+ let finalPrompt = prompt;
1656
+ if (opts.pipe) {
1657
+ const chunks = [];
1658
+ for await (const chunk of process.stdin) {
1659
+ chunks.push(chunk);
1660
+ }
1661
+ const stdinText = Buffer.concat(chunks).toString("utf-8").trim();
1662
+ if (finalPrompt) {
1663
+ finalPrompt = `${finalPrompt}
1664
+
1665
+ ${stdinText}`;
1666
+ } else {
1667
+ finalPrompt = stdinText;
1668
+ }
1669
+ }
1670
+ if (!finalPrompt) {
1671
+ process.stderr.write(
1672
+ "Error: No prompt provided. Pass a prompt argument or use --pipe to read from stdin.\n"
1673
+ );
1674
+ process.exit(1);
1675
+ }
1676
+ const config = loadConfig();
1677
+ if (opts.lfg || opts.unattended) {
1678
+ config.general.defaultPermissionMode = "auto";
1679
+ }
1680
+ if (opts.unattended) {
1681
+ opts.tools = true;
1682
+ if (!opts.maxSteps || opts.maxSteps === "1") opts.maxSteps = "15";
1683
+ }
1684
+ const db = getDb();
1685
+ const resolvedKeys = await resolveProviderKeys();
1686
+ const resolvedBRKey = resolvedKeys.get("BRAINSTORM_API_KEY") ?? getBrainstormApiKey();
1687
+ const isCommunityTier = isCommunityKey(resolvedBRKey);
1688
+ if (resolvedBRKey) process.env._BR_RESOLVED_KEY = resolvedBRKey;
1689
+ const registry = await createProviderRegistry(config, resolvedKeys);
1690
+ const costTracker = new CostTracker(db, config.budget);
1691
+ const tools = createDefaultToolRegistry();
1692
+ await connectMCPServers(
1693
+ tools,
1694
+ config,
1695
+ resolvedKeys.get("BRAINSTORM_API_KEY")
1696
+ );
1697
+ const sessionManager = new SessionManager(db);
1698
+ const projectPath = process.cwd();
1699
+ configureSandbox(
1700
+ config.shell.sandbox,
1701
+ projectPath,
1702
+ config.shell.maxOutputBytes,
1703
+ config.shell.containerImage,
1704
+ config.shell.containerTimeout
1705
+ );
1706
+ const { prompt: rawPrompt, frontmatter } = buildSystemPrompt(projectPath);
1707
+ const systemPrompt = rawPrompt + buildToolAwarenessSection(tools.listTools());
1708
+ const router = new BrainstormRouter(
1709
+ config,
1710
+ registry,
1711
+ costTracker,
1712
+ frontmatter
1713
+ );
1714
+ const permissionManager = new PermissionManager(
1715
+ config.general.defaultPermissionMode,
1716
+ config.permissions
1717
+ );
1718
+ const hasDirectKeys = !!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");
1719
+ if (opts.strategy) {
1720
+ router.setStrategy(opts.strategy);
1721
+ } else if (!isCommunityTier || hasDirectKeys) {
1722
+ router.setStrategy("quality-first");
1723
+ }
1724
+ const session = sessionManager.start(projectPath);
1725
+ sessionManager.addUserMessage(finalPrompt);
1726
+ let fullResponse = "";
1727
+ let modelName = "unknown";
1728
+ let toolCallCount = 0;
1729
+ if (!opts.json) {
1730
+ process.stdout.write("\n");
1731
+ }
1732
+ const middleware = createDefaultMiddlewarePipeline(projectPath);
1733
+ for await (const event of runAgentLoop(sessionManager.getHistory(), {
1734
+ config,
1735
+ registry,
1736
+ router,
1737
+ costTracker,
1738
+ tools,
1739
+ sessionId: session.id,
1740
+ projectPath,
1741
+ systemPrompt,
1742
+ disableTools: !opts.tools,
1743
+ 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
+ maxSteps: parseInt(opts.maxSteps ?? "1"),
1745
+ compaction: buildCompactionCallbacks(sessionManager),
1746
+ permissionCheck: (tool, args) => permissionManager.check(tool, args),
1747
+ middleware
1748
+ })) {
1749
+ switch (event.type) {
1750
+ case "thinking":
1751
+ if (!opts.json) {
1752
+ const spinnerFrames = [
1753
+ "\u280B",
1754
+ "\u2819",
1755
+ "\u2839",
1756
+ "\u2838",
1757
+ "\u283C",
1758
+ "\u2834",
1759
+ "\u2826",
1760
+ "\u2827",
1761
+ "\u2807",
1762
+ "\u280F"
1763
+ ];
1764
+ const frame = spinnerFrames[Math.floor(Date.now() / 100) % spinnerFrames.length];
1765
+ const phases = {
1766
+ classifying: "Classifying task...",
1767
+ routing: "Selecting model...",
1768
+ connecting: `Connecting...`,
1769
+ streaming: "Streaming..."
1770
+ };
1771
+ process.stderr.write(
1772
+ `\r${frame} ${phases[event.phase] ?? event.phase}`
1773
+ );
1774
+ }
1775
+ break;
1776
+ case "routing":
1777
+ modelName = event.decision.model.name;
1778
+ process.stderr.write(
1779
+ `\r[${event.decision.strategy}] \u2192 ${modelName}
1780
+ `
1781
+ );
1782
+ break;
1783
+ case "text-delta":
1784
+ fullResponse += event.delta;
1785
+ break;
1786
+ case "tool-call-start":
1787
+ toolCallCount++;
1788
+ process.stderr.write(`
1789
+ [tool: ${event.toolName}]
1790
+ `);
1791
+ break;
1792
+ case "gateway-feedback": {
1793
+ const gwLine = formatGatewayFeedback(event.feedback);
1794
+ if (gwLine) process.stderr.write(`${gwLine}
1795
+ `);
1796
+ break;
1797
+ }
1798
+ case "model-retry":
1799
+ process.stderr.write(
1800
+ `
1801
+ [retry] ${event.fromModel} \u2192 ${event.toModel} (${event.reason})
1802
+ `
1803
+ );
1804
+ modelName = event.toModel;
1805
+ fullResponse = "";
1806
+ break;
1807
+ case "done":
1808
+ if (opts.json) {
1809
+ process.stdout.write(
1810
+ JSON.stringify({
1811
+ text: fullResponse,
1812
+ model: modelName,
1813
+ cost: event.totalCost,
1814
+ toolCalls: toolCallCount,
1815
+ success: true
1816
+ }) + "\n"
1817
+ );
1818
+ } else {
1819
+ process.stdout.write(renderMarkdownToString(fullResponse));
1820
+ process.stdout.write(
1821
+ `
1822
+
1823
+ [cost: $${event.totalCost.toFixed(4)}]
1824
+ `
1825
+ );
1826
+ }
1827
+ break;
1828
+ case "error":
1829
+ if (opts.json) {
1830
+ process.stdout.write(
1831
+ JSON.stringify({
1832
+ text: "",
1833
+ model: modelName,
1834
+ cost: 0,
1835
+ toolCalls: toolCallCount,
1836
+ error: event.error.message,
1837
+ success: false
1838
+ }) + "\n"
1839
+ );
1840
+ process.exit(1);
1841
+ } else {
1842
+ process.stderr.write(`
1843
+ Error: ${event.error.message}
1844
+ `);
1845
+ }
1846
+ break;
1847
+ }
1848
+ }
1849
+ if (fullResponse) {
1850
+ sessionManager.addAssistantMessage(fullResponse);
1851
+ }
1852
+ }
1853
+ );
1854
+ program.command("probe").description(
1855
+ "Run an ad-hoc eval probe with verification (for autonomous testing)"
1856
+ ).argument("<prompt>", "The prompt to test").option("--model <id>", "Target a specific model").option(
1857
+ "--expect-tools <tools>",
1858
+ "Comma-separated tool names that must be called"
1859
+ ).option(
1860
+ "--expect-contains <strings>",
1861
+ "Comma-separated strings that must appear in output"
1862
+ ).option(
1863
+ "--expect-excludes <strings>",
1864
+ "Comma-separated strings that must NOT appear"
1865
+ ).option("--min-steps <n>", "Minimum number of agentic steps").option("--max-steps <n>", "Maximum number of agentic steps", "10").option("--timeout <ms>", "Timeout in milliseconds", "30000").option("--json", "Output full ProbeResult as JSON").option("--setup-file <pairs...>", "Setup files as path=content pairs").action(async (prompt, opts) => {
1866
+ const probe = {
1867
+ id: `adhoc-${Date.now().toString(36)}`,
1868
+ capability: "multi-step",
1869
+ prompt,
1870
+ verify: {},
1871
+ timeout_ms: parseInt(opts.timeout)
1872
+ };
1873
+ if (opts.expectTools) {
1874
+ probe.verify.tool_calls_include = opts.expectTools.split(",").map((s) => s.trim());
1875
+ }
1876
+ if (opts.expectContains) {
1877
+ probe.verify.answer_contains = opts.expectContains.split(",").map((s) => s.trim());
1878
+ }
1879
+ if (opts.expectExcludes) {
1880
+ probe.verify.answer_excludes = opts.expectExcludes.split(",").map((s) => s.trim());
1881
+ }
1882
+ if (opts.minSteps) {
1883
+ probe.verify.min_steps = parseInt(opts.minSteps);
1884
+ }
1885
+ if (opts.maxSteps) {
1886
+ probe.verify.max_steps = parseInt(opts.maxSteps);
1887
+ }
1888
+ if (opts.setupFile) {
1889
+ probe.setup = { files: {} };
1890
+ for (const pair of opts.setupFile) {
1891
+ const eqIdx = pair.indexOf("=");
1892
+ if (eqIdx > 0) {
1893
+ probe.setup.files[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
1894
+ }
1895
+ }
1896
+ }
1897
+ const result = await runProbe(probe, {
1898
+ modelId: opts.model,
1899
+ maxSteps: parseInt(opts.maxSteps),
1900
+ defaultTimeout: parseInt(opts.timeout)
1901
+ });
1902
+ if (opts.json) {
1903
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1904
+ } else {
1905
+ const status = result.passed ? "PASSED" : "FAILED";
1906
+ console.log(`
1907
+ Probe: ${status}`);
1908
+ console.log(` Model: ${result.modelId}`);
1909
+ console.log(` Steps: ${result.steps}`);
1910
+ console.log(` Cost: $${result.cost.toFixed(4)}`);
1911
+ console.log(` Time: ${result.durationMs}ms`);
1912
+ if (result.toolCalls.length > 0) {
1913
+ console.log(
1914
+ ` Tools: ${result.toolCalls.map((t) => t.name).join(", ")}`
1915
+ );
1916
+ }
1917
+ if (!result.passed) {
1918
+ const failures = result.checks.filter((c) => !c.passed);
1919
+ console.log(` Failures:`);
1920
+ for (const f of failures) {
1921
+ console.log(` - ${f.check}: ${f.detail ?? "failed"}`);
1922
+ }
1923
+ }
1924
+ if (result.error) console.log(` Error: ${result.error}`);
1925
+ console.log(
1926
+ ` Output: ${result.output.slice(0, 200)}${result.output.length > 200 ? "..." : ""}`
1927
+ );
1928
+ console.log();
1929
+ }
1930
+ process.exit(result.passed ? 0 : 1);
1931
+ });
1932
+ var VAULT_PATH = join3(homedir(), ".brainstorm", "vault.enc");
1933
+ function printResumeSummary(session, sessionManager) {
1934
+ const age = Math.floor((Date.now() / 1e3 - session.createdAt) / 60);
1935
+ const ageStr = age < 60 ? `${age}m ago` : age < 1440 ? `${Math.floor(age / 60)}h ago` : `${Math.floor(age / 1440)}d ago`;
1936
+ const history = sessionManager.getHistory();
1937
+ const lastMsg = history.length > 0 ? history[history.length - 1] : null;
1938
+ const lastPreview = lastMsg ? `"${lastMsg.content.slice(0, 60)}${lastMsg.content.length > 60 ? "..." : ""}"` : "none";
1939
+ console.log(
1940
+ ` Resumed session ${session.id.slice(0, 8)} | ${session.messageCount} msgs | $${(session.totalCost ?? 0).toFixed(4)} | ${ageStr}`
1941
+ );
1942
+ if (lastMsg) console.log(` Last ${lastMsg.role}: ${lastPreview}`);
1943
+ }
1944
+ function promptPassword(prompt) {
1945
+ const envPassword = process.env.BRAINSTORM_VAULT_PASSWORD;
1946
+ if (envPassword) {
1947
+ console.error(
1948
+ " [vault] Using BRAINSTORM_VAULT_PASSWORD from environment (no prompt)"
1949
+ );
1950
+ return Promise.resolve(envPassword);
1951
+ }
1952
+ return new Promise((resolve, reject) => {
1953
+ process.stderr.write(prompt);
1954
+ let rawModeWasSet = false;
1955
+ try {
1956
+ if (process.stdin.setRawMode) {
1957
+ process.stdin.setRawMode(true);
1958
+ rawModeWasSet = true;
1959
+ }
1960
+ } catch {
1961
+ }
1962
+ if (process.stdin.isPaused?.()) process.stdin.resume();
1963
+ let password = "";
1964
+ const cleanup = () => {
1965
+ process.stdin.removeListener("data", onData);
1966
+ if (rawModeWasSet) {
1967
+ try {
1968
+ process.stdin.setRawMode?.(false);
1969
+ } catch {
1970
+ }
1971
+ }
1972
+ process.stderr.write("\n");
1973
+ };
1974
+ const onData = (ch) => {
1975
+ const c = ch.toString();
1976
+ if (c === "\n" || c === "\r" || c === "") {
1977
+ cleanup();
1978
+ resolve(password);
1979
+ } else if (c === "") {
1980
+ cleanup();
1981
+ reject(new Error("Cancelled"));
1982
+ } else if (c === "\x7F" || c === "\b") {
1983
+ if (password.length > 0) {
1984
+ password = password.slice(0, -1);
1985
+ process.stderr.write("\b \b");
1986
+ }
1987
+ } else if (c.charCodeAt(0) >= 32) {
1988
+ password += c;
1989
+ process.stderr.write("*");
1990
+ }
1991
+ };
1992
+ process.stdin.on("data", onData);
1993
+ });
1994
+ }
1995
+ var vaultCmd = program.command("vault").description("Manage encrypted key vault");
1996
+ vaultCmd.command("init").description("Create a new encrypted vault").action(async () => {
1997
+ const vault = new BrainstormVault(VAULT_PATH);
1998
+ if (vault.exists()) {
1999
+ console.error(
2000
+ " Vault already exists. Use `brainstorm vault rotate` to change password."
2001
+ );
2002
+ process.exit(1);
2003
+ }
2004
+ const password = await promptPassword(" Master password: ");
2005
+ const confirm = await promptPassword(" Confirm password: ");
2006
+ if (password !== confirm) {
2007
+ console.error(" Passwords do not match.");
2008
+ process.exit(1);
2009
+ }
2010
+ if (password.length < 8) {
2011
+ console.error(" Password must be at least 8 characters.");
2012
+ process.exit(1);
2013
+ }
2014
+ await vault.init(password);
2015
+ console.log(` Vault created at ${VAULT_PATH}`);
2016
+ });
2017
+ vaultCmd.command("add <name>").description("Add a key to the vault").argument("[value]", "Key value (prompted if omitted)").action(async (name, value) => {
2018
+ const vault = new BrainstormVault(VAULT_PATH);
2019
+ const password = await promptPassword(" Master password: ");
2020
+ vault.open(password);
2021
+ const keyValue = value ?? await promptPassword(` Value for ${name}: `);
2022
+ vault.set(name, keyValue);
2023
+ vault.seal();
2024
+ console.log(` Added ${name} to vault.`);
2025
+ });
2026
+ vaultCmd.command("list").description("List stored key names").action(async () => {
2027
+ const vault = new BrainstormVault(VAULT_PATH);
2028
+ if (!vault.exists()) {
2029
+ console.log(" No vault found. Run `brainstorm vault init` first.");
2030
+ return;
2031
+ }
2032
+ const password = await promptPassword(" Master password: ");
2033
+ vault.open(password);
2034
+ const keys = vault.list();
2035
+ if (keys.length === 0) {
2036
+ console.log(" Vault is empty.");
2037
+ } else {
2038
+ console.log(`
2039
+ Keys (${keys.length}):
2040
+ `);
2041
+ for (const k of keys) console.log(` ${k}`);
2042
+ console.log();
2043
+ }
2044
+ });
2045
+ vaultCmd.command("get <name>").description("Show a key value (masked by default)").option("--reveal", "Show the full unmasked value").action(async (name, opts) => {
2046
+ const vault = new BrainstormVault(VAULT_PATH);
2047
+ const password = await promptPassword(" Master password: ");
2048
+ vault.open(password);
2049
+ const value = vault.get(name);
2050
+ if (value) {
2051
+ if (opts.reveal) {
2052
+ console.log(value);
2053
+ } else {
2054
+ const masked = value.slice(0, 8) + "*".repeat(Math.max(0, value.length - 8));
2055
+ console.log(masked);
2056
+ }
2057
+ } else {
2058
+ console.error(` Key "${name}" not found in vault.`);
2059
+ process.exit(1);
2060
+ }
2061
+ });
2062
+ vaultCmd.command("remove <name>").description("Remove a key from the vault").action(async (name) => {
2063
+ const vault = new BrainstormVault(VAULT_PATH);
2064
+ const password = await promptPassword(" Master password: ");
2065
+ vault.open(password);
2066
+ if (vault.delete(name)) {
2067
+ vault.seal();
2068
+ console.log(` Removed ${name} from vault.`);
2069
+ } else {
2070
+ console.error(` Key "${name}" not found in vault.`);
2071
+ process.exit(1);
2072
+ }
2073
+ });
2074
+ vaultCmd.command("rotate").description("Change vault master password").action(async () => {
2075
+ const vault = new BrainstormVault(VAULT_PATH);
2076
+ const current = await promptPassword(" Current password: ");
2077
+ vault.open(current);
2078
+ const newPass = await promptPassword(" New password: ");
2079
+ const confirm = await promptPassword(" Confirm new password: ");
2080
+ if (newPass !== confirm) {
2081
+ console.error(" Passwords do not match.");
2082
+ process.exit(1);
2083
+ }
2084
+ if (newPass.length < 8) {
2085
+ console.error(" Password must be at least 8 characters.");
2086
+ process.exit(1);
2087
+ }
2088
+ vault.rotate(newPass);
2089
+ console.log(" Vault password rotated.");
2090
+ });
2091
+ vaultCmd.command("lock").description("Clear vault keys from memory").action(() => {
2092
+ console.log(" Vault locked (keys cleared from memory).");
2093
+ });
2094
+ vaultCmd.command("status").description("Show vault and backend status").action(async () => {
2095
+ const vault = new BrainstormVault(VAULT_PATH);
2096
+ const resolver = new KeyResolver(vault.exists() ? vault : null);
2097
+ const s = resolver.status();
2098
+ console.log("\n Vault Status:\n");
2099
+ console.log(` Vault: ${s.vault}`);
2100
+ console.log(` 1Password: ${s.op}`);
2101
+ console.log(` Env vars: ${s.env}`);
2102
+ console.log(` Priority: vault \u2192 1Password \u2192 env vars
2103
+ `);
2104
+ });
2105
+ var projectsCmd = program.command("projects").description("Manage registered projects");
2106
+ projectsCmd.command("list").description("List all registered projects").option("--all", "Include inactive projects").action(async (opts) => {
2107
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2108
+ const db = getDb();
2109
+ const pm = new ProjectManager(db);
2110
+ const projects = pm.projects.list(opts.all);
2111
+ console.log("\n Registered Projects:\n");
2112
+ if (projects.length === 0) {
2113
+ console.log(
2114
+ " No projects registered. Run: storm projects register <path>"
2115
+ );
2116
+ console.log(" Or scan all: storm projects import ~/Projects\n");
2117
+ return;
2118
+ }
2119
+ for (const p of projects) {
2120
+ const dash = pm.dashboard(p.id);
2121
+ const cost = dash ? `$${dash.costToday.toFixed(4)}/day` : "";
2122
+ const sessions = dash ? `${dash.sessionCount} sessions` : "";
2123
+ const active = p.isActive ? "" : " [inactive]";
2124
+ console.log(
2125
+ ` ${p.name.padEnd(25)} ${sessions.padEnd(15)} ${cost.padEnd(15)} ${p.path}${active}`
2126
+ );
2127
+ }
2128
+ console.log();
2129
+ });
2130
+ 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
+ async (path, opts) => {
2132
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2133
+ const db = getDb();
2134
+ const pm = new ProjectManager(db);
2135
+ try {
2136
+ const project = pm.register(path, opts.name, {
2137
+ budgetDaily: opts.budgetDaily ? parseFloat(opts.budgetDaily) : void 0,
2138
+ budgetMonthly: opts.budgetMonthly ? parseFloat(opts.budgetMonthly) : void 0
2139
+ });
2140
+ console.log(`
2141
+ \u2713 Registered "${project.name}" \u2192 ${project.path}
2142
+ `);
2143
+ } catch (err) {
2144
+ console.error(
2145
+ `
2146
+ \u2717 ${err instanceof Error ? err.message : String(err)}
2147
+ `
2148
+ );
2149
+ }
2150
+ }
2151
+ );
2152
+ projectsCmd.command("switch").argument("<name>", "Project name to switch to").description("Set the active project for this session").action(async (name) => {
2153
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2154
+ const db = getDb();
2155
+ const pm = new ProjectManager(db);
2156
+ try {
2157
+ const project = pm.switch(name);
2158
+ console.log(`
2159
+ \u2713 Switched to "${project.name}" (${project.path})
2160
+ `);
2161
+ } catch (err) {
2162
+ console.error(
2163
+ `
2164
+ \u2717 ${err instanceof Error ? err.message : String(err)}
2165
+ `
2166
+ );
2167
+ }
2168
+ });
2169
+ projectsCmd.command("show").argument("<name>", "Project name").description("Show project dashboard").action(async (name) => {
2170
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2171
+ const db = getDb();
2172
+ const pm = new ProjectManager(db);
2173
+ const project = pm.projects.getByName(name);
2174
+ if (!project) {
2175
+ console.error(`
2176
+ \u2717 Project "${name}" not found.
2177
+ `);
2178
+ return;
2179
+ }
2180
+ const dash = pm.dashboard(project.id);
2181
+ if (!dash) return;
2182
+ console.log(`
2183
+ \u2500\u2500 ${project.name} \u2500\u2500`);
2184
+ console.log(` Path: ${project.path}`);
2185
+ if (project.description)
2186
+ console.log(` Description: ${project.description}`);
2187
+ console.log(` Sessions: ${dash.sessionCount}`);
2188
+ console.log(` Cost today: $${dash.costToday.toFixed(4)}`);
2189
+ console.log(` Cost month: $${dash.costThisMonth.toFixed(4)}`);
2190
+ if (project.budgetDaily) {
2191
+ console.log(
2192
+ ` Budget daily: $${project.budgetDaily.toFixed(2)} (${dash.budgetDailyUsed.toFixed(0)}% used)`
2193
+ );
2194
+ }
2195
+ if (project.budgetMonthly) {
2196
+ console.log(
2197
+ ` Budget month: $${project.budgetMonthly.toFixed(2)} (${dash.budgetMonthlyUsed.toFixed(0)}% used)`
2198
+ );
2199
+ }
2200
+ const memory = pm.memory.list(project.id);
2201
+ if (memory.length > 0) {
2202
+ console.log(` Memory: ${memory.length} entries`);
2203
+ }
2204
+ console.log();
2205
+ });
2206
+ projectsCmd.command("import").argument("[dir]", "Parent directory to scan", join3(homedir(), "Projects")).description("Scan a directory and register all project subdirectories").action(async (dir) => {
2207
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2208
+ const db = getDb();
2209
+ const pm = new ProjectManager(db);
2210
+ const registered = pm.import(dir);
2211
+ if (registered.length === 0) {
2212
+ console.log(`
2213
+ No new projects found in ${dir}
2214
+ `);
2215
+ } else {
2216
+ console.log(`
2217
+ Registered ${registered.length} projects:`);
2218
+ for (const p of registered) {
2219
+ console.log(` \u2713 ${p.name} \u2192 ${p.path}`);
2220
+ }
2221
+ console.log();
2222
+ }
2223
+ });
2224
+ var scheduleCmd = program.command("schedule").description("Manage scheduled tasks");
2225
+ scheduleCmd.command("list").option("-p, --project <name>", "Filter by project").description("List scheduled tasks").action(async (opts) => {
2226
+ const { ScheduledTaskRepository } = await import("./dist-V5DTSTKJ.js");
2227
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2228
+ const db = getDb();
2229
+ const taskRepo = new ScheduledTaskRepository(db);
2230
+ let projectId;
2231
+ if (opts.project) {
2232
+ const pm = new ProjectManager(db);
2233
+ const p = pm.projects.getByName(opts.project);
2234
+ if (!p) {
2235
+ console.error(` Project "${opts.project}" not found.`);
2236
+ return;
2237
+ }
2238
+ projectId = p.id;
2239
+ }
2240
+ const tasks = taskRepo.list(projectId, "active");
2241
+ console.log("\n Scheduled Tasks:\n");
2242
+ if (tasks.length === 0) {
2243
+ console.log(
2244
+ ' No tasks. Add one: storm schedule add "<prompt>" --project <name>\n'
2245
+ );
2246
+ return;
2247
+ }
2248
+ for (const t of tasks) {
2249
+ const cron = t.cronExpression || "one-shot";
2250
+ const mutations = t.allowMutations ? "read+write" : "read-only";
2251
+ const budget = t.budgetLimit ? `$${t.budgetLimit.toFixed(2)}` : "no limit";
2252
+ console.log(
2253
+ ` ${t.name.padEnd(25)} ${cron.padEnd(18)} ${mutations.padEnd(12)} ${budget}`
2254
+ );
2255
+ }
2256
+ console.log();
2257
+ });
2258
+ scheduleCmd.command("add").argument("<prompt>", "Task instruction").requiredOption("-p, --project <name>", "Project name").option("-n, --name <name>", "Task name (default: first 30 chars of prompt)").option("--cron <expression>", "Cron schedule (e.g. '0 9 * * *')").option("--budget <amount>", "Budget limit per run in dollars", "0.50").option("--max-turns <n>", "Maximum turns per run", "20").option("--allow-mutations", "Allow file writes and shell commands").option("--model <id>", "Model override for this task").description("Add a scheduled task").action(async (prompt, opts) => {
2259
+ const { ScheduledTaskRepository, validateCron, validateTaskSafety } = await import("./dist-V5DTSTKJ.js");
2260
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2261
+ const db = getDb();
2262
+ const pm = new ProjectManager(db);
2263
+ const project = pm.projects.getByName(opts.project);
2264
+ if (!project) {
2265
+ console.error(` Project "${opts.project}" not found.`);
2266
+ return;
2267
+ }
2268
+ if (opts.cron) {
2269
+ const err = validateCron(opts.cron);
2270
+ if (err) {
2271
+ console.error(` Invalid cron: ${err}`);
2272
+ return;
2273
+ }
2274
+ }
2275
+ const taskRepo = new ScheduledTaskRepository(db);
2276
+ const task = taskRepo.create({
2277
+ projectId: project.id,
2278
+ name: opts.name || prompt.slice(0, 30),
2279
+ prompt,
2280
+ cronExpression: opts.cron,
2281
+ budgetLimit: parseFloat(opts.budget),
2282
+ maxTurns: parseInt(opts.maxTurns),
2283
+ allowMutations: opts.allowMutations ?? false,
2284
+ modelId: opts.model
2285
+ });
2286
+ const warnings = validateTaskSafety(task);
2287
+ console.log(`
2288
+ \u2713 Created task "${task.name}" (${task.id.slice(0, 8)})`);
2289
+ if (task.cronExpression) {
2290
+ const { describeCron } = await import("./dist-V5DTSTKJ.js");
2291
+ console.log(` Schedule: ${describeCron(task.cronExpression)}`);
2292
+ }
2293
+ if (warnings.length > 0) {
2294
+ console.log(" Warnings:");
2295
+ for (const w of warnings) console.log(` \u26A0 ${w}`);
2296
+ }
2297
+ console.log();
2298
+ });
2299
+ scheduleCmd.command("run").option("--task-id <id>", "Run a specific task").option("--dry-run", "Show what would run without executing").description("Trigger due tasks").action(async (opts) => {
2300
+ const { TriggerRunner } = await import("./dist-V5DTSTKJ.js");
2301
+ const db = getDb();
2302
+ const runner = new TriggerRunner(db);
2303
+ const result = await runner.runDueTasks(opts);
2304
+ console.log(`
2305
+ Checked: ${result.tasksChecked} tasks`);
2306
+ console.log(` Run: ${result.tasksRun}`);
2307
+ if (result.tasksFailed > 0) console.log(` Failed: ${result.tasksFailed}`);
2308
+ if (result.tasksSkipped > 0)
2309
+ console.log(` Skipped: ${result.tasksSkipped} (concurrency limit)`);
2310
+ for (const r of result.runs) {
2311
+ const icon = r.status === "completed" ? "\u2713" : r.status === "failed" ? "\u2717" : "\u25CB";
2312
+ console.log(
2313
+ ` ${icon} ${r.taskName} \u2192 ${r.status}${r.error ? ` (${r.error})` : ""}`
2314
+ );
2315
+ }
2316
+ console.log();
2317
+ });
2318
+ scheduleCmd.command("history").option("--task-id <id>", "Filter by task").option("-n, --limit <count>", "Number of runs to show", "10").description("Show task run history").action(async (opts) => {
2319
+ const { TaskRunRepository, ScheduledTaskRepository } = await import("./dist-V5DTSTKJ.js");
2320
+ const db = getDb();
2321
+ const runRepo = new TaskRunRepository(db);
2322
+ const taskRepo = new ScheduledTaskRepository(db);
2323
+ const runs = opts.taskId ? runRepo.listByTask(opts.taskId, parseInt(opts.limit)) : runRepo.listRecent(parseInt(opts.limit));
2324
+ console.log("\n Task Run History:\n");
2325
+ if (runs.length === 0) {
2326
+ console.log(" No runs yet.\n");
2327
+ return;
2328
+ }
2329
+ for (const r of runs) {
2330
+ const task = taskRepo.getById(r.taskId);
2331
+ const icon = r.status === "completed" ? "\u2713" : r.status === "failed" ? "\u2717" : "\u25CF";
2332
+ const date = new Date(r.createdAt * 1e3).toLocaleString();
2333
+ console.log(
2334
+ ` ${icon} ${(task?.name ?? r.taskId.slice(0, 8)).padEnd(22)} $${r.cost.toFixed(4).padEnd(10)} ${r.status.padEnd(16)} ${date}`
2335
+ );
2336
+ }
2337
+ console.log();
2338
+ });
2339
+ scheduleCmd.command("pause").argument("<task-id>", "Task ID to pause").description("Pause a scheduled task").action(async (taskId) => {
2340
+ const { ScheduledTaskRepository } = await import("./dist-V5DTSTKJ.js");
2341
+ const db = getDb();
2342
+ const repo = new ScheduledTaskRepository(db);
2343
+ repo.updateStatus(taskId, "paused");
2344
+ console.log(` \u2713 Paused task ${taskId.slice(0, 8)}
2345
+ `);
2346
+ });
2347
+ scheduleCmd.command("resume").argument("<task-id>", "Task ID to resume").description("Resume a paused task").action(async (taskId) => {
2348
+ const { ScheduledTaskRepository } = await import("./dist-V5DTSTKJ.js");
2349
+ const db = getDb();
2350
+ const repo = new ScheduledTaskRepository(db);
2351
+ repo.updateStatus(taskId, "active");
2352
+ console.log(` \u2713 Resumed task ${taskId.slice(0, 8)}
2353
+ `);
2354
+ });
2355
+ scheduleCmd.command("delete").argument("<task-id>", "Task ID to delete").description("Delete a scheduled task").action(async (taskId) => {
2356
+ const { ScheduledTaskRepository } = await import("./dist-V5DTSTKJ.js");
2357
+ const db = getDb();
2358
+ const repo = new ScheduledTaskRepository(db);
2359
+ repo.delete(taskId);
2360
+ console.log(` \u2713 Deleted task ${taskId.slice(0, 8)}
2361
+ `);
2362
+ });
2363
+ var planCmd = program.command("plan").description("Execute and manage structured plans");
2364
+ planCmd.command("execute").argument("<path>", "Path to .plan.md file").option("--auto", "Run autonomously (no pauses)").option("--dry-run", "Show dispatch plan without executing").option("--budget <amount>", "Total budget limit in dollars").option("--task-budget <amount>", "Per-task budget limit", "0.50").option("--retries <n>", "Max retries per task", "2").description("Execute a plan file task-by-task using subagents").action(async (path, opts) => {
2365
+ const { executePlan } = await import("@brainst0rm/core");
2366
+ const { resolve } = await import("path");
2367
+ const { execFileSync } = await import("child_process");
2368
+ const planPath = resolve(path);
2369
+ const mode = opts.dryRun ? "dry-run" : opts.auto ? "autonomous" : "interactive";
2370
+ console.log(`
2371
+ Plan Executor (${mode} mode)
2372
+ `);
2373
+ const dispatcher = {
2374
+ async execute(prompt, execOpts) {
2375
+ console.log(
2376
+ ` Dispatching: ${execOpts.subagentType}/${execOpts.modelHint}`
2377
+ );
2378
+ return {
2379
+ text: `[Placeholder] Completed via ${execOpts.subagentType} subagent`,
2380
+ cost: 0,
2381
+ modelUsed: execOpts.modelHint,
2382
+ toolCalls: [],
2383
+ budgetExceeded: false
2384
+ };
2385
+ },
2386
+ async checkBuild(command, cwd) {
2387
+ const parts = command.split(/\s+/);
2388
+ try {
2389
+ execFileSync(parts[0], parts.slice(1), {
2390
+ cwd,
2391
+ timeout: 6e4,
2392
+ stdio: "pipe"
2393
+ });
2394
+ return { passed: true, output: "" };
2395
+ } catch (err) {
2396
+ return {
2397
+ passed: false,
2398
+ output: err.stderr?.toString()?.slice(0, 500) ?? ""
2399
+ };
2400
+ }
2401
+ }
2402
+ };
2403
+ try {
2404
+ for await (const event of executePlan(planPath, dispatcher, {
2405
+ projectPath: process.cwd(),
2406
+ buildCommand: "npx turbo run build --force",
2407
+ defaultBudgetPerTask: parseFloat(opts.taskBudget),
2408
+ planBudgetLimit: opts.budget ? parseFloat(opts.budget) : void 0,
2409
+ mode,
2410
+ maxRetries: parseInt(opts.retries),
2411
+ compactBetweenPhases: true
2412
+ })) {
2413
+ switch (event.type) {
2414
+ case "plan-started":
2415
+ console.log(` Plan: ${event.plan.name}`);
2416
+ console.log(` Tasks: ${event.totalTasks} pending
2417
+ `);
2418
+ break;
2419
+ case "phase-started":
2420
+ console.log(` \u2500\u2500 ${event.phase.name} \u2500\u2500`);
2421
+ break;
2422
+ case "sprint-started":
2423
+ console.log(` ${event.sprint.name}`);
2424
+ break;
2425
+ case "task-started":
2426
+ console.log(
2427
+ ` \u25CF ${event.task.description.slice(0, 60)} [${event.subagentType}/${event.model}]`
2428
+ );
2429
+ break;
2430
+ case "task-completed":
2431
+ console.log(
2432
+ ` \u2713 ${event.task.description.slice(0, 60)} $${event.cost.toFixed(4)}`
2433
+ );
2434
+ break;
2435
+ case "task-failed":
2436
+ console.log(
2437
+ ` \u2717 ${event.task.description.slice(0, 60)} ${event.reason}`
2438
+ );
2439
+ break;
2440
+ case "task-retrying":
2441
+ console.log(` \u21BB Retry #${event.attempt} with ${event.model}`);
2442
+ break;
2443
+ case "task-budget-exceeded":
2444
+ console.log(` $ Budget exceeded: $${event.cost.toFixed(4)}`);
2445
+ break;
2446
+ case "build-check":
2447
+ console.log(
2448
+ ` ${event.passed ? "\u2713" : "\u2717"} Build ${event.passed ? "passed" : "FAILED"}`
2449
+ );
2450
+ break;
2451
+ case "phase-completed":
2452
+ console.log(
2453
+ ` \u2713 ${event.phase.name} complete $${event.cost.toFixed(4)}
2454
+ `
2455
+ );
2456
+ break;
2457
+ case "plan-completed":
2458
+ 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`);
2459
+ console.log(
2460
+ ` Plan complete: $${event.totalCost.toFixed(4)} total
2461
+ `
2462
+ );
2463
+ break;
2464
+ case "plan-paused":
2465
+ console.log(`
2466
+ \u26A0 Paused: ${event.reason}
2467
+ `);
2468
+ break;
2469
+ case "skill-activated":
2470
+ console.log(` \u2726 Skill: ${event.skillName}`);
2471
+ break;
2472
+ case "dry-run-task": {
2473
+ const d = event.dispatch;
2474
+ console.log(
2475
+ ` \u25CB ${event.task.description.slice(0, 45).padEnd(47)} ${d.subagentType.padEnd(10)} ${d.modelHint.padEnd(10)} ~$${event.estimatedCost.toFixed(2)}`
2476
+ );
2477
+ break;
2478
+ }
2479
+ case "dry-run-summary":
2480
+ console.log(
2481
+ `
2482
+ Summary: ${event.totalTasks} tasks, ~$${event.estimatedCost.toFixed(2)} estimated`
2483
+ );
2484
+ console.log(
2485
+ ` By type: ${Object.entries(event.tasksByType).map(([k, v]) => `${k}:${v}`).join(", ")}
2486
+ `
2487
+ );
2488
+ break;
2489
+ }
2490
+ }
2491
+ } catch (err) {
2492
+ console.error(
2493
+ `
2494
+ \u2717 ${err instanceof Error ? err.message : String(err)}
2495
+ `
2496
+ );
2497
+ }
2498
+ });
2499
+ planCmd.command("parse").argument("<path>", "Path to .plan.md file").description("Parse and display a plan file structure").action(async (path) => {
2500
+ const { parsePlanFile } = await import("@brainst0rm/core");
2501
+ const { resolve } = await import("path");
2502
+ let plan;
2503
+ try {
2504
+ plan = parsePlanFile(resolve(path));
2505
+ } catch (err) {
2506
+ console.error(
2507
+ `
2508
+ \u2717 ${err instanceof Error ? err.message : String(err)}
2509
+ `
2510
+ );
2511
+ return;
2512
+ }
2513
+ console.log(`
2514
+ ${plan.name} (${plan.status})`);
2515
+ console.log(` ${plan.completedTasks}/${plan.totalTasks} tasks complete
2516
+ `);
2517
+ for (const phase of plan.phases) {
2518
+ const icon = phase.status === "completed" ? "\u2713" : phase.status === "in_progress" ? "\u25D0" : "\u25CB";
2519
+ console.log(
2520
+ ` ${icon} ${phase.name} ${phase.completedCount}/${phase.taskCount}`
2521
+ );
2522
+ for (const sprint of phase.sprints) {
2523
+ console.log(` ${sprint.name}`);
2524
+ for (const task of sprint.tasks) {
2525
+ const tIcon = task.status === "completed" ? "\u2713" : "\u25CB";
2526
+ const cost = task.cost ? `$${task.cost.toFixed(2)}` : "";
2527
+ const skill = task.assignedSkill ? `[${task.assignedSkill}]` : "";
2528
+ console.log(` ${tIcon} ${task.description} ${skill} ${cost}`);
2529
+ }
2530
+ }
2531
+ }
2532
+ console.log();
2533
+ });
2534
+ var orchestrateCmd = program.command("orchestrate").description("Coordinate work across multiple projects");
2535
+ orchestrateCmd.command("pipeline").argument("<request>", "What to build (natural language)").option("--build <cmd>", "Build command", "npx turbo run build --force").option("--test <cmd>", "Test command", "npx turbo run test").option("--deploy", "Include deployment phase").option("--budget <amount>", "Total budget limit in dollars").option(
2536
+ "--phases <list>",
2537
+ "Comma-separated phases to run (spec,architecture,implementation,review,verify,refactor,deploy,document,report)"
2538
+ ).option("--resume-from <phase>", "Resume from a specific phase").option("--dry-run", "Show what agents would be dispatched").description("Run the full 9-phase development pipeline").action(async (request, opts) => {
2539
+ const { runOrchestrationPipeline, createPipelineDispatcher } = await import("@brainst0rm/core");
2540
+ console.log(`
2541
+ Orchestration Pipeline
2542
+ `);
2543
+ console.log(` Request: "${request}"`);
2544
+ console.log(` Mode: ${opts.dryRun ? "dry-run" : "execute"}
2545
+ `);
2546
+ const config = loadConfig();
2547
+ const db = getDb();
2548
+ const envKeys = /* @__PURE__ */ new Map();
2549
+ for (const name of PROVIDER_KEY_NAMES) {
2550
+ const val = process.env[name];
2551
+ if (val) envKeys.set(name, val);
2552
+ }
2553
+ const resolvedKeys = {
2554
+ get: (name) => envKeys.get(name) ?? null
2555
+ };
2556
+ const registry = await createProviderRegistry(config, resolvedKeys);
2557
+ const costTracker = new CostTracker(db, config.budget);
2558
+ const projectPath = process.cwd();
2559
+ const tools = createDefaultToolRegistry();
2560
+ const { frontmatter } = buildSystemPrompt(projectPath);
2561
+ const router = new BrainstormRouter(
2562
+ config,
2563
+ registry,
2564
+ costTracker,
2565
+ frontmatter
2566
+ );
2567
+ const dispatcher = createPipelineDispatcher({
2568
+ config,
2569
+ registry,
2570
+ router,
2571
+ costTracker,
2572
+ tools,
2573
+ projectPath
2574
+ });
2575
+ const hasProviders = PROVIDER_KEY_NAMES.some(
2576
+ (k) => resolvedKeys.get(k) !== null
2577
+ );
2578
+ if (!hasProviders && !opts.dryRun) {
2579
+ console.log(
2580
+ " \u26A0 No model providers configured. Using placeholder dispatcher."
2581
+ );
2582
+ console.log(" Set API keys via: storm vault add ANTHROPIC_API_KEY\n");
2583
+ }
2584
+ const activeDispatcher = hasProviders || opts.dryRun ? dispatcher : {
2585
+ async runPhase(agentId, subagentType, prompt, phaseOpts) {
2586
+ console.log(` Agent: ${agentId} (${subagentType})`);
2587
+ return {
2588
+ text: `[No providers] ${agentId} would execute`,
2589
+ cost: 0,
2590
+ toolCalls: []
2591
+ };
2592
+ },
2593
+ async runParallel(specs, phaseOpts) {
2594
+ return specs.map((s) => {
2595
+ console.log(` Agent: ${s.agentId} (${s.subagentType})`);
2596
+ return {
2597
+ agentId: s.agentId,
2598
+ text: `[No providers] ${s.agentId} would execute`,
2599
+ cost: 0,
2600
+ toolCalls: []
2601
+ };
2602
+ });
2603
+ },
2604
+ async runCommand(command, cwd) {
2605
+ const { execFileSync } = await import("child_process");
2606
+ const parts = command.split(/\s+/);
2607
+ try {
2608
+ execFileSync(parts[0], parts.slice(1), {
2609
+ cwd,
2610
+ timeout: 12e4,
2611
+ stdio: "pipe"
2612
+ });
2613
+ return { passed: true, output: "" };
2614
+ } catch (err) {
2615
+ return {
2616
+ passed: false,
2617
+ output: err.stderr?.toString()?.slice(0, 500) ?? ""
2618
+ };
2619
+ }
2620
+ }
2621
+ };
2622
+ const phases = opts.phases?.split(",") ?? void 0;
2623
+ for await (const event of runOrchestrationPipeline(
2624
+ request,
2625
+ activeDispatcher,
2626
+ {
2627
+ projectPath,
2628
+ buildCommand: opts.build,
2629
+ testCommand: opts.test,
2630
+ deploy: opts.deploy,
2631
+ budget: opts.budget ? parseFloat(opts.budget) : void 0,
2632
+ phases,
2633
+ resumeFrom: opts.resumeFrom,
2634
+ dryRun: opts.dryRun
2635
+ }
2636
+ )) {
2637
+ switch (event.type) {
2638
+ case "pipeline-started":
2639
+ console.log(` Phases: ${event.phases.join(" \u2192 ")}
2640
+ `);
2641
+ break;
2642
+ case "phase-started":
2643
+ console.log(
2644
+ ` \u2500\u2500 ${event.phase.toUpperCase()} \u2500\u2500 (${event.agentId})`
2645
+ );
2646
+ break;
2647
+ case "phase-completed":
2648
+ const icon = event.result.success ? "\u2713" : "\u2717";
2649
+ console.log(
2650
+ ` ${icon} ${event.result.phase} $${event.result.cost.toFixed(4)} ${event.result.duration}ms`
2651
+ );
2652
+ if (event.result.output && !event.result.output.startsWith("[")) {
2653
+ console.log(
2654
+ ` ${event.result.output.split("\n")[0].slice(0, 100)}`
2655
+ );
2656
+ }
2657
+ console.log();
2658
+ break;
2659
+ case "phase-failed":
2660
+ console.log(` \u2717 ${event.phase}: ${event.error}
2661
+ `);
2662
+ break;
2663
+ case "review-findings":
2664
+ console.log(
2665
+ ` Reviews: ${event.findings.length} finding(s)${event.hasCritical ? " (CRITICAL)" : ""}`
2666
+ );
2667
+ for (const f of event.findings.slice(0, 5)) {
2668
+ console.log(` [${f.severity}] ${f.description.slice(0, 80)}`);
2669
+ }
2670
+ console.log();
2671
+ break;
2672
+ case "feedback-loop":
2673
+ console.log(
2674
+ ` \u21BB Feedback: ${event.from} \u2192 ${event.to} (${event.reason})
2675
+ `
2676
+ );
2677
+ break;
2678
+ case "pipeline-completed":
2679
+ 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`);
2680
+ console.log(
2681
+ ` Pipeline complete: $${event.totalCost.toFixed(4)} total`
2682
+ );
2683
+ console.log(
2684
+ ` ${event.results.filter((r) => r.success).length}/${event.results.length} phases succeeded
2685
+ `
2686
+ );
2687
+ break;
2688
+ case "pipeline-paused":
2689
+ console.log(` \u26A0 Paused at ${event.phase}: ${event.reason}
2690
+ `);
2691
+ break;
2692
+ }
2693
+ }
2694
+ });
2695
+ 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
+ async (description, opts) => {
2697
+ const { OrchestrationEngine, formatAggregatedResults, aggregateResults } = await import("./dist-GNHTH2DH.js");
2698
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2699
+ const db = getDb();
2700
+ const engine = new OrchestrationEngine(db);
2701
+ const pm = new ProjectManager(db);
2702
+ const projectNames = opts.projects.split(",").map((s) => s.trim());
2703
+ console.log(
2704
+ `
2705
+ Orchestrating across ${projectNames.length} projects...`
2706
+ );
2707
+ console.log(` "${description}"
2708
+ `);
2709
+ try {
2710
+ for await (const event of engine.run({
2711
+ description,
2712
+ projectNames,
2713
+ budgetLimit: opts.budget ? parseFloat(opts.budget) : void 0,
2714
+ subagentType: opts.type
2715
+ })) {
2716
+ switch (event.type) {
2717
+ case "plan-ready":
2718
+ console.log(` Plan: ${event.tasks.length} tasks created`);
2719
+ break;
2720
+ case "task-started":
2721
+ console.log(` \u25CF ${event.project.name} \u2014 starting...`);
2722
+ break;
2723
+ case "task-completed":
2724
+ console.log(
2725
+ ` \u2713 ${event.project.name} \u2014 $${event.cost.toFixed(4)}`
2726
+ );
2727
+ if (event.summary)
2728
+ console.log(` ${event.summary.slice(0, 120)}`);
2729
+ break;
2730
+ case "task-failed":
2731
+ console.log(` \u2717 ${event.project.name} \u2014 ${event.error}`);
2732
+ break;
2733
+ case "orchestration-completed": {
2734
+ const projectMap = /* @__PURE__ */ new Map();
2735
+ for (const name of projectNames) {
2736
+ const p = pm.projects.getByName(name);
2737
+ if (p) projectMap.set(p.id, p.name);
2738
+ }
2739
+ const tasks = event.results.map((r, i) => ({
2740
+ ...event.run,
2741
+ projectId: projectMap.get(r.projectName) ?? r.projectName
2742
+ }));
2743
+ console.log(`
2744
+ \u2500\u2500 Complete \u2500\u2500`);
2745
+ console.log(` Total cost: $${event.run.totalCost.toFixed(4)}`);
2746
+ console.log(
2747
+ ` ${event.results.filter((r) => !r.summary.startsWith("FAILED")).length}/${event.results.length} succeeded
2748
+ `
2749
+ );
2750
+ break;
2751
+ }
2752
+ }
2753
+ }
2754
+ } catch (err) {
2755
+ console.error(
2756
+ `
2757
+ \u2717 ${err instanceof Error ? err.message : String(err)}
2758
+ `
2759
+ );
2760
+ }
2761
+ }
2762
+ );
2763
+ orchestrateCmd.command("history").option("-n, --limit <count>", "Number of runs to show", "10").description("Show recent orchestration runs").action(async (opts) => {
2764
+ const { OrchestrationEngine } = await import("./dist-GNHTH2DH.js");
2765
+ const db = getDb();
2766
+ const engine = new OrchestrationEngine(db);
2767
+ const runs = engine.listRecent(parseInt(opts.limit));
2768
+ console.log("\n Orchestration History:\n");
2769
+ if (runs.length === 0) {
2770
+ console.log(" No orchestration runs yet.\n");
2771
+ return;
2772
+ }
2773
+ for (const r of runs) {
2774
+ const icon = r.status === "completed" ? "\u2713" : r.status === "failed" ? "\u2717" : r.status === "cancelled" ? "\u25CB" : "\u25CF";
2775
+ const date = new Date(r.createdAt * 1e3).toLocaleString();
2776
+ console.log(
2777
+ ` ${icon} ${r.name.slice(0, 40).padEnd(42)} $${r.totalCost.toFixed(4).padEnd(10)} ${r.status.padEnd(12)} ${date}`
2778
+ );
2779
+ }
2780
+ console.log();
2781
+ });
2782
+ orchestrateCmd.command("status").argument("<run-id>", "Orchestration run ID").description("Show status of an orchestration run").action(async (runId) => {
2783
+ const { OrchestrationEngine } = await import("./dist-GNHTH2DH.js");
2784
+ const { ProjectManager } = await import("./dist-WLTQTLFO.js");
2785
+ const db = getDb();
2786
+ const engine = new OrchestrationEngine(db);
2787
+ const pm = new ProjectManager(db);
2788
+ const detail = engine.getRunWithTasks(runId);
2789
+ if (!detail) {
2790
+ console.error(` Run "${runId}" not found.
2791
+ `);
2792
+ return;
2793
+ }
2794
+ console.log(`
2795
+ \u2500\u2500 ${detail.run.name} \u2500\u2500`);
2796
+ console.log(` Status: ${detail.run.status}`);
2797
+ console.log(` Cost: $${detail.run.totalCost.toFixed(4)}`);
2798
+ console.log(` Tasks: ${detail.tasks.length}
2799
+ `);
2800
+ for (const t of detail.tasks) {
2801
+ const project = pm.projects.getById(t.projectId);
2802
+ const icon = t.status === "completed" ? "\u2713" : t.status === "failed" ? "\u2717" : t.status === "skipped" ? "\u25CB" : "\u25CF";
2803
+ console.log(
2804
+ ` ${icon} ${(project?.name ?? t.projectId.slice(0, 8)).padEnd(25)} ${t.status.padEnd(12)} $${t.cost.toFixed(4)}`
2805
+ );
2806
+ if (t.resultSummary)
2807
+ console.log(` ${t.resultSummary.slice(0, 100)}`);
2808
+ }
2809
+ console.log();
2810
+ });
2811
+ program.command("intelligence").alias("intel").description("Show what BrainstormRouter has learned about your usage").option("--json", "Output as JSON").option(
2812
+ "--period <period>",
2813
+ "Usage period (daily, weekly, monthly)",
2814
+ "weekly"
2815
+ ).action(async (opts) => {
2816
+ const gw = createGatewayClient2();
2817
+ const intel = createIntelligenceClient();
2818
+ if (!gw) {
2819
+ console.log(
2820
+ "\n No BRAINSTORM_API_KEY set. Cannot connect to BrainstormRouter.\n"
2821
+ );
2822
+ process.exit(1);
2823
+ }
2824
+ console.log("\n Fetching intelligence from BrainstormRouter...\n");
2825
+ const [
2826
+ leaderboard,
2827
+ usage,
2828
+ waste,
2829
+ forecast,
2830
+ daily,
2831
+ governance,
2832
+ recommendations,
2833
+ patterns
2834
+ ] = await Promise.all([
2835
+ gw.getLeaderboard().catch(() => []),
2836
+ gw.getUsageSummary(opts.period).catch(() => null),
2837
+ gw.getWasteInsights().catch(() => null),
2838
+ gw.getForecast().catch(() => null),
2839
+ gw.getDailyInsights().catch(() => []),
2840
+ gw.getGovernanceSummary().catch(() => null),
2841
+ intel?.getRecommendations("code", "typescript").catch(() => []) ?? [],
2842
+ intel?.getPatterns("typescript").catch(() => []) ?? []
2843
+ ]);
2844
+ if (opts.json) {
2845
+ console.log(
2846
+ JSON.stringify(
2847
+ {
2848
+ leaderboard,
2849
+ usage,
2850
+ waste,
2851
+ forecast,
2852
+ daily,
2853
+ governance,
2854
+ recommendations,
2855
+ patterns
2856
+ },
2857
+ null,
2858
+ 2
2859
+ )
2860
+ );
2861
+ return;
2862
+ }
2863
+ 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");
2864
+ console.log(" BrainstormRouter Intelligence Report");
2865
+ 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\n");
2866
+ const usageData = usage?.data?.[0];
2867
+ const totalRequests = usageData?.requestCount ?? 0;
2868
+ const confidence = totalRequests >= 200 ? "HIGH" : totalRequests >= 50 ? "MEDIUM" : "LOW";
2869
+ const confidenceNote = confidence === "LOW" ? ` (need ${200 - totalRequests} more for high confidence)` : "";
2870
+ console.log(
2871
+ ` Learning Status: ${totalRequests.toLocaleString()} requests analyzed`
2872
+ );
2873
+ console.log(` Routing Confidence: ${confidence}${confidenceNote}
2874
+ `);
2875
+ const realLeaderboard = leaderboard.filter(
2876
+ (m) => m.id && !m.id.startsWith("cache/")
2877
+ );
2878
+ if (realLeaderboard.length > 0) {
2879
+ console.log(" Model Performance:\n");
2880
+ for (const entry of realLeaderboard.slice(0, 8)) {
2881
+ const m = entry;
2882
+ const modelName = m.model_id ?? m.id ?? m.model ?? "unknown";
2883
+ const name = modelName.length > 35 ? modelName.slice(0, 35) + "\u2026" : modelName;
2884
+ const latency = m.latency_ms != null ? m.latency_ms < 1e3 ? `${Math.round(m.latency_ms)}ms` : `${(m.latency_ms / 1e3).toFixed(1)}s` : " n/a";
2885
+ const reward = m.reward_score != null ? (m.reward_score * 100).toFixed(0) + "%" : "n/a";
2886
+ const value = m.value_score != null ? m.value_score.toFixed(0) : "n/a";
2887
+ const samples = m.sample_count ?? m.request_count ?? 0;
2888
+ const isBest = entry === realLeaderboard[0] ? " \u2190 BEST" : "";
2889
+ console.log(
2890
+ ` ${name.padEnd(37)} reward:${reward.padStart(4)} value:${value.padStart(5)} ${latency.padStart(6)} (${samples} samples)${isBest}`
2891
+ );
2892
+ }
2893
+ console.log();
2894
+ }
2895
+ if (recommendations.length > 0) {
2896
+ console.log(" What the system learned:\n");
2897
+ for (const rec of recommendations.slice(0, 5)) {
2898
+ const r = rec;
2899
+ const conf = r.confidence != null ? `${Math.round(r.confidence * 100)}%` : "";
2900
+ console.log(
2901
+ ` \u2022 ${r.taskType} \u2192 ${r.recommendedModel} (${conf} confidence)`
2902
+ );
2903
+ if (r.reasoning) {
2904
+ console.log(` ${r.reasoning}`);
2905
+ }
2906
+ }
2907
+ console.log();
2908
+ }
2909
+ if (usageData) {
2910
+ const totalTokens = (usageData.totalInputTokens ?? 0) + (usageData.totalOutputTokens ?? 0);
2911
+ console.log(" Cost Summary:\n");
2912
+ console.log(` Period: ${usage?.period ?? opts.period}`);
2913
+ console.log(
2914
+ ` Total: $${(usageData.totalCostUsd ?? 0).toFixed(4)}`
2915
+ );
2916
+ console.log(
2917
+ ` Requests: ${(usageData.requestCount ?? 0).toLocaleString()}`
2918
+ );
2919
+ console.log(` Tokens: ${totalTokens.toLocaleString()}`);
2920
+ console.log(
2921
+ ` Avg latency: ${(usageData.avgLatencyMs ?? 0).toFixed(0)}ms`
2922
+ );
2923
+ console.log();
2924
+ }
2925
+ const fc = forecast?.forecast;
2926
+ if (fc) {
2927
+ const trend = fc.trend ?? "stable";
2928
+ const trendIcon = trend === "increasing" ? "\u2191" : trend === "decreasing" ? "\u2193" : "\u2192";
2929
+ console.log(" Budget Forecast:\n");
2930
+ console.log(
2931
+ ` Avg daily: $${(fc.avgDailySpendUsd ?? 0).toFixed(2)} (${trendIcon} ${trend})`
2932
+ );
2933
+ console.log(
2934
+ ` Projected: $${(fc.projectedPeriodSpendUsd ?? 0).toFixed(2)}`
2935
+ );
2936
+ console.log(
2937
+ ` Today: $${(forecast?.todaySpendUsd ?? 0).toFixed(4)}`
2938
+ );
2939
+ console.log(
2940
+ ` Data points: ${forecast?.daysOfData ?? 0} days`
2941
+ );
2942
+ console.log();
2943
+ }
2944
+ const wasteAny = waste;
2945
+ if (wasteAny && (wasteAny.overQualifiedModels?.length > 0 || wasteAny.duplicateRequests?.length > 0)) {
2946
+ console.log(" Optimization Opportunities:\n");
2947
+ console.log(
2948
+ ` Total recoverable: $${(wasteAny.estimatedWasteUsd ?? 0).toFixed(4)}
2949
+ `
2950
+ );
2951
+ for (const m of (wasteAny.overQualifiedModels ?? []).slice(0, 3)) {
2952
+ console.log(
2953
+ ` \u2022 ${m.model}: $${m.totalCostUsd.toFixed(4)} on ${m.requestCount} reqs`
2954
+ );
2955
+ console.log(` \u2192 ${m.suggestion}`);
2956
+ }
2957
+ const dupeCount = (wasteAny.duplicateRequests ?? []).length;
2958
+ if (dupeCount > 0) {
2959
+ const totalDupeWaste = (wasteAny.duplicateRequests ?? []).reduce(
2960
+ (sum, d) => sum + (d.wastedCostUsd ?? 0),
2961
+ 0
2962
+ );
2963
+ console.log(
2964
+ ` \u2022 ${dupeCount} duplicate request patterns ($${totalDupeWaste.toFixed(4)} wasted)`
2965
+ );
2966
+ console.log(` \u2192 Enable prompt caching to reduce duplicates`);
2967
+ }
2968
+ console.log();
2969
+ }
2970
+ if (patterns.length > 0) {
2971
+ console.log(" Community Patterns (TypeScript):\n");
2972
+ for (const p of patterns.slice(0, 3)) {
2973
+ const pat = p;
2974
+ console.log(
2975
+ ` \u2022 ${pat.taskType}: prefer ${(pat.preferredTools ?? []).join(", ")} (${pat.confirmations ?? 0} confirmations)`
2976
+ );
2977
+ if (pat.avoidTools?.length > 0) {
2978
+ console.log(` avoid: ${pat.avoidTools.join(", ")}`);
2979
+ }
2980
+ }
2981
+ console.log();
2982
+ }
2983
+ if (governance) {
2984
+ const gov = governance;
2985
+ console.log(" Governance:\n");
2986
+ if (gov.memory_health) {
2987
+ console.log(
2988
+ ` Memory: ${gov.memory_health.total_entries} entries (${gov.memory_health.compliance_status})`
2989
+ );
2990
+ }
2991
+ if (gov.audit_stats) {
2992
+ console.log(
2993
+ ` Audit: ${gov.audit_stats.total_requests} requests, ${gov.audit_stats.flagged} flagged`
2994
+ );
2995
+ }
2996
+ if (gov.anomaly_score != null) {
2997
+ console.log(
2998
+ ` Anomaly: ${gov.anomaly_score.toFixed(2)} (0=clean, 1=suspicious)`
2999
+ );
3000
+ }
3001
+ console.log();
3002
+ }
3003
+ 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3004
+ console.log(
3005
+ ` Tip: Run \`storm intel --json\` for machine-readable output.`
3006
+ );
3007
+ console.log();
3008
+ });
3009
+ program.command("analyze").description(
3010
+ "Analyze a codebase \u2014 languages, frameworks, dependencies, complexity"
3011
+ ).argument("[path]", "Project path to analyze", ".").option("--json", "Output as JSON").action(async (projectPath, opts) => {
3012
+ const { resolve } = await import("path");
3013
+ const absPath = resolve(projectPath);
3014
+ console.log(`
3015
+ Analyzing ${absPath}...
3016
+ `);
3017
+ const startTime = Date.now();
3018
+ const { analyzeProject } = await import("@brainst0rm/ingest");
3019
+ const analysis = analyzeProject(absPath);
3020
+ const elapsed = Date.now() - startTime;
3021
+ if (opts.json) {
3022
+ console.log(JSON.stringify(analysis, null, 2));
3023
+ return;
3024
+ }
3025
+ 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");
3026
+ console.log(" Codebase Analysis");
3027
+ 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\n");
3028
+ console.log(
3029
+ ` ${analysis.summary.totalFiles} files | ${analysis.summary.totalLines.toLocaleString()} lines | ${analysis.summary.primaryLanguage}`
3030
+ );
3031
+ console.log(
3032
+ ` ${analysis.summary.moduleCount} modules | avg complexity: ${analysis.summary.avgComplexity}/100 | ${elapsed}ms
3033
+ `
3034
+ );
3035
+ console.log(" Languages:");
3036
+ for (const l of analysis.languages.languages.slice(0, 8)) {
3037
+ const bar = "\u2588".repeat(Math.max(1, Math.round(l.percentage / 5)));
3038
+ console.log(
3039
+ ` ${l.language.padEnd(15)} ${bar} ${l.percentage}% (${l.files} files, ${l.lines.toLocaleString()} lines)`
3040
+ );
3041
+ }
3042
+ const hasStack = analysis.frameworks.frameworks.length > 0 || analysis.frameworks.buildTools.length > 0;
3043
+ if (hasStack) {
3044
+ console.log("\n Stack:");
3045
+ if (analysis.frameworks.frameworks.length > 0)
3046
+ console.log(
3047
+ ` Frameworks: ${analysis.frameworks.frameworks.join(", ")}`
3048
+ );
3049
+ if (analysis.frameworks.buildTools.length > 0)
3050
+ console.log(
3051
+ ` Build: ${analysis.frameworks.buildTools.join(", ")}`
3052
+ );
3053
+ if (analysis.frameworks.databases.length > 0)
3054
+ console.log(
3055
+ ` Databases: ${analysis.frameworks.databases.join(", ")}`
3056
+ );
3057
+ if (analysis.frameworks.testing.length > 0)
3058
+ console.log(
3059
+ ` Testing: ${analysis.frameworks.testing.join(", ")}`
3060
+ );
3061
+ if (analysis.frameworks.deployment.length > 0)
3062
+ console.log(
3063
+ ` Deploy: ${analysis.frameworks.deployment.join(", ")}`
3064
+ );
3065
+ if (analysis.frameworks.ci.length > 0)
3066
+ console.log(` CI/CD: ${analysis.frameworks.ci.join(", ")}`);
3067
+ }
3068
+ if (analysis.complexity.summary.hotspots.length > 0) {
3069
+ console.log("\n Complexity Hotspots (score > 70):");
3070
+ for (const f of analysis.complexity.files.filter((cf) => cf.score >= 70).slice(0, 8)) {
3071
+ console.log(
3072
+ ` ${f.path.padEnd(50)} score:${f.score} branches:${f.branchCount} nesting:${f.maxNesting}`
3073
+ );
3074
+ }
3075
+ }
3076
+ if (analysis.dependencies.clusters.length > 0) {
3077
+ console.log("\n Module Clusters (by size):");
3078
+ for (const c of analysis.dependencies.clusters.slice(0, 8)) {
3079
+ const cohesionLabel = c.cohesion > 0.5 ? "high" : c.cohesion > 0.2 ? "med" : "low";
3080
+ console.log(
3081
+ ` ${c.directory.padEnd(40)} ${c.files.length} files cohesion:${cohesionLabel}`
3082
+ );
3083
+ }
3084
+ }
3085
+ console.log("\n \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");
3086
+ console.log(` Run \`storm analyze --json\` for machine-readable output.`);
3087
+ console.log();
3088
+ });
3089
+ program.command("docgen").description(
3090
+ "Generate documentation \u2014 architecture docs, module docs, API reference"
3091
+ ).argument("[path]", "Project path to document", ".").option("--output <dir>", "Output directory (default: docs/generated)").option("--json", "Output file list as JSON").action(
3092
+ async (projectPath, opts) => {
3093
+ const { resolve } = await import("path");
3094
+ const absPath = resolve(projectPath);
3095
+ console.log(`
3096
+ Analyzing ${absPath}...`);
3097
+ const { analyzeProject } = await import("@brainst0rm/ingest");
3098
+ const analysis = analyzeProject(absPath);
3099
+ console.log(` Generating documentation...
3100
+ `);
3101
+ const { generateAllDocs } = await import("@brainst0rm/docgen");
3102
+ const result = generateAllDocs(analysis, opts.output);
3103
+ if (opts.json) {
3104
+ console.log(JSON.stringify(result, null, 2));
3105
+ return;
3106
+ }
3107
+ 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");
3108
+ console.log(" Documentation Generated");
3109
+ 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\n");
3110
+ console.log(` Output: ${result.outputDir}`);
3111
+ console.log(` Files written: ${result.filesWritten.length}`);
3112
+ console.log("");
3113
+ console.log(` Architecture: ${result.architectureDoc}`);
3114
+ console.log(` Modules: ${result.moduleDocs} module docs`);
3115
+ if (result.apiDoc) {
3116
+ console.log(` API Reference: ${result.apiDoc}`);
3117
+ } else {
3118
+ console.log(` API Reference: (no endpoints detected)`);
3119
+ }
3120
+ console.log("\n \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");
3121
+ console.log(
3122
+ ` Tip: Use these docs as context for AI agents with @docs/generated/ARCHITECTURE.md`
3123
+ );
3124
+ console.log();
3125
+ }
3126
+ );
3127
+ program.command("spawn").description("Spawn a background agent in an isolated git worktree").argument("<task>", "Task description for the background agent").option(
3128
+ "--type <type>",
3129
+ "Subagent type (code, review, explore, research)",
3130
+ "code"
3131
+ ).option("--budget <amount>", "Budget limit in dollars", "1.0").action(async (task, opts) => {
3132
+ const { resolve } = await import("path");
3133
+ const { createWorktree, removeWorktree } = await import("@brainst0rm/core");
3134
+ const projectPath = resolve(".");
3135
+ const worktreePath = createWorktree(projectPath, opts.type);
3136
+ console.log(`
3137
+ Spawned background agent in worktree:`);
3138
+ console.log(` Path: ${worktreePath}`);
3139
+ console.log(` Type: ${opts.type}`);
3140
+ console.log(` Budget: $${opts.budget}`);
3141
+ console.log(` Task: ${task}`);
3142
+ console.log();
3143
+ console.log(` The agent is running in an isolated copy of your repo.`);
3144
+ console.log(` When done, changes will be on a spec-* branch.`);
3145
+ console.log(` Use \`git worktree list\` to see active worktrees.`);
3146
+ console.log(` Use \`git diff main...<branch>\` to review changes.`);
3147
+ console.log();
3148
+ console.log(` To run the agent:`);
3149
+ console.log(` cd ${worktreePath} && storm run --unattended "${task}"`);
3150
+ console.log();
3151
+ });
3152
+ program.command("storm").description("Run multiple tasks in parallel using subagents").argument(
3153
+ "<tasks...>",
3154
+ "Task descriptions (each runs as a separate subagent)"
3155
+ ).option(
3156
+ "--type <type>",
3157
+ "Subagent type for all tasks (explore, plan, code, review, research)",
3158
+ "code"
3159
+ ).option("--budget <amount>", "Budget limit per task in dollars", "1.0").action(async (tasks, opts) => {
3160
+ const config = loadConfig();
3161
+ const db = getDb();
3162
+ const resolvedKeys = await resolveProviderKeys();
3163
+ const registry = await createProviderRegistry(config, resolvedKeys);
3164
+ const costTracker = new CostTracker(db, config.budget);
3165
+ const tools = createDefaultToolRegistry();
3166
+ const projectPath = process.cwd();
3167
+ const { prompt: systemPrompt, frontmatter } = buildSystemPrompt(projectPath);
3168
+ const router = new BrainstormRouter(
3169
+ config,
3170
+ registry,
3171
+ costTracker,
3172
+ frontmatter
3173
+ );
3174
+ console.log(`
3175
+ Storm \u2014 ${tasks.length} parallel agents`);
3176
+ console.log(` Type: ${opts.type} | Budget: $${opts.budget}/task
3177
+ `);
3178
+ for (let i = 0; i < tasks.length; i++) {
3179
+ console.log(` [${i + 1}] ${tasks[i]}`);
3180
+ }
3181
+ console.log();
3182
+ const startTime = Date.now();
3183
+ const results = await spawnParallel(
3184
+ tasks.map((task) => ({ task, type: opts.type })),
3185
+ {
3186
+ config,
3187
+ registry,
3188
+ router,
3189
+ costTracker,
3190
+ tools,
3191
+ projectPath,
3192
+ systemPrompt,
3193
+ budgetLimit: parseFloat(opts.budget)
3194
+ }
3195
+ );
3196
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
3197
+ 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
3198
+ console.log(` ${results.length} agents completed in ${elapsed}s
3199
+ `);
3200
+ for (let i = 0; i < results.length; i++) {
3201
+ const r = results[i];
3202
+ const status = r.text ? "done" : "failed";
3203
+ const cost = `$${r.cost.toFixed(4)}`;
3204
+ console.log(
3205
+ ` [${i + 1}] ${status} (${r.toolCalls.length} tool calls, ${cost})`
3206
+ );
3207
+ if (r.text) {
3208
+ const preview = r.text.slice(0, 200).replace(/\n/g, " ");
3209
+ console.log(` ${preview}${r.text.length > 200 ? "..." : ""}`);
3210
+ }
3211
+ console.log();
3212
+ }
3213
+ const totalCost = results.reduce((sum, r) => sum + r.cost, 0);
3214
+ console.log(` Total cost: $${totalCost.toFixed(4)}`);
3215
+ console.log();
3216
+ closeDb();
3217
+ });
3218
+ program.command("queue").description("Manage the task queue for batch execution").argument("<action>", "Action: add, list, run, clear").argument("[tasks...]", "Task descriptions (for add)").option("--budget <amount>", "Total budget limit in dollars").option("--parallel <n>", "Max parallel tasks (default: 1)", "1").action(
3219
+ async (action, tasks, opts) => {
3220
+ const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync: writeFileSync2, mkdirSync: mkdirSync2 } = await import("fs");
3221
+ const { join: join4 } = await import("path");
3222
+ const { homedir: homedir2 } = await import("os");
3223
+ const queueDir = join4(homedir2(), ".brainstorm", "queue");
3224
+ const queueFile = join4(queueDir, "pending.json");
3225
+ if (!existsSync3(queueDir)) mkdirSync2(queueDir, { recursive: true });
3226
+ const loadQueue = () => {
3227
+ if (!existsSync3(queueFile)) return [];
3228
+ try {
3229
+ return JSON.parse(readFileSync3(queueFile, "utf-8"));
3230
+ } catch {
3231
+ return [];
3232
+ }
3233
+ };
3234
+ const saveQueue = (q) => writeFileSync2(queueFile, JSON.stringify(q, null, 2), "utf-8");
3235
+ switch (action) {
3236
+ case "add": {
3237
+ if (tasks.length === 0) {
3238
+ console.error(" Error: provide task descriptions to add.");
3239
+ process.exit(1);
3240
+ }
3241
+ const queue = loadQueue();
3242
+ for (const task of tasks) {
3243
+ queue.push({
3244
+ id: `q-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
3245
+ task,
3246
+ status: "pending",
3247
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
3248
+ });
3249
+ }
3250
+ saveQueue(queue);
3251
+ console.log(
3252
+ `
3253
+ Added ${tasks.length} task(s) to queue. Total: ${queue.length} pending.`
3254
+ );
3255
+ break;
3256
+ }
3257
+ case "list": {
3258
+ const queue = loadQueue();
3259
+ if (queue.length === 0) {
3260
+ console.log("\n Queue is empty.");
3261
+ break;
3262
+ }
3263
+ console.log(`
3264
+ Task Queue (${queue.length} items):
3265
+ `);
3266
+ for (const item of queue) {
3267
+ const icon = item.status === "done" ? "\u2713" : item.status === "failed" ? "\u2717" : item.status === "running" ? "\u27F3" : "\u25CB";
3268
+ console.log(` ${icon} [${item.status}] ${item.task}`);
3269
+ }
3270
+ break;
3271
+ }
3272
+ case "run": {
3273
+ const queue = loadQueue();
3274
+ const pending = queue.filter((q) => q.status === "pending");
3275
+ if (pending.length === 0) {
3276
+ console.log("\n No pending tasks in queue.");
3277
+ break;
3278
+ }
3279
+ console.log(`
3280
+ Running ${pending.length} queued task(s)...`);
3281
+ console.log(
3282
+ ` Budget: ${opts.budget ?? "unlimited"} | Parallel: ${opts.parallel}`
3283
+ );
3284
+ console.log(
3285
+ `
3286
+ Execute each task with: storm run --unattended "<task>"`
3287
+ );
3288
+ for (const item of pending) item.status = "running";
3289
+ saveQueue(queue);
3290
+ for (const item of pending) {
3291
+ console.log(` storm run --unattended "${item.task}"`);
3292
+ }
3293
+ break;
3294
+ }
3295
+ case "clear": {
3296
+ saveQueue([]);
3297
+ console.log("\n Queue cleared.");
3298
+ break;
3299
+ }
3300
+ default:
3301
+ console.error(
3302
+ ` Unknown action: ${action}. Use: add, list, run, clear`
3303
+ );
3304
+ }
3305
+ console.log();
3306
+ }
3307
+ );
3308
+ program.command("search").description("Search code \u2014 local semantic search or cross-repo via GitHub").argument("<query>", "Search query").option("--global", "Search across GitHub (not just local repo)").option("--language <lang>", "Filter by language").option("--limit <n>", "Max results (default: 10)", "10").action(
3309
+ async (query, opts) => {
3310
+ const limit = parseInt(opts.limit ?? "10");
3311
+ if (opts.global) {
3312
+ console.log(`
3313
+ Searching GitHub for: "${query}"...
3314
+ `);
3315
+ const { execFileSync } = await import("child_process");
3316
+ try {
3317
+ const ghArgs = ["search", "code", query, "--limit", String(limit)];
3318
+ if (opts.language) ghArgs.push("--language", opts.language);
3319
+ const output2 = execFileSync("gh", ghArgs, {
3320
+ encoding: "utf-8",
3321
+ timeout: 3e4,
3322
+ stdio: ["ignore", "pipe", "pipe"]
3323
+ });
3324
+ console.log(output2);
3325
+ } catch (err) {
3326
+ if (err.message?.includes("ENOENT")) {
3327
+ console.error(
3328
+ " Error: `gh` CLI not found. Install: https://cli.github.com"
3329
+ );
3330
+ } else {
3331
+ console.error(` Search failed: ${err.message}`);
3332
+ }
3333
+ }
3334
+ } else {
3335
+ const { semanticSearch } = await import("@brainst0rm/core");
3336
+ const results = semanticSearch(process.cwd(), query, limit);
3337
+ if (results.length === 0) {
3338
+ console.log(`
3339
+ No results for "${query}".`);
3340
+ } else {
3341
+ console.log(`
3342
+ ${results.length} result(s) for "${query}":
3343
+ `);
3344
+ for (const r of results) {
3345
+ const score = (r.score * 100).toFixed(0);
3346
+ console.log(
3347
+ ` [${score}%] ${r.filePath}${r.symbolName ? `:${r.symbolName}` : ""}`
3348
+ );
3349
+ if (r.snippet) {
3350
+ console.log(` ${r.snippet.trim().slice(0, 120)}`);
3351
+ }
3352
+ }
3353
+ }
3354
+ }
3355
+ console.log();
3356
+ }
3357
+ );
3358
+ program.command("setup-infra").description(
3359
+ "Auto-generate AI infrastructure: BRAINSTORM.md, .agent.md files, routing profiles"
3360
+ ).argument("[path]", "Project path", ".").action(async (projectPath) => {
3361
+ const { resolve, join: pathJoin } = await import("path");
3362
+ const {
3363
+ existsSync: existsSync3,
3364
+ writeFileSync: fsWrite,
3365
+ mkdirSync: fsMkdir
3366
+ } = await import("fs");
3367
+ const absPath = resolve(projectPath);
3368
+ console.log(`
3369
+ Setting up AI infrastructure for ${absPath}...
3370
+ `);
3371
+ const { analyzeProject } = await import("@brainst0rm/ingest");
3372
+ const analysis = analyzeProject(absPath);
3373
+ const brainstormMdPath = pathJoin(absPath, "BRAINSTORM.md");
3374
+ if (!existsSync3(brainstormMdPath)) {
3375
+ const lines = [
3376
+ "---",
3377
+ `build_command: "npm run build"`,
3378
+ `test_command: "npm test"`,
3379
+ "---",
3380
+ "",
3381
+ `# ${absPath.split("/").pop()}`,
3382
+ "",
3383
+ "## Stack",
3384
+ ""
3385
+ ];
3386
+ if (analysis.frameworks.frameworks.length > 0)
3387
+ lines.push(
3388
+ `- Frameworks: ${analysis.frameworks.frameworks.join(", ")}`
3389
+ );
3390
+ if (analysis.languages.primary)
3391
+ lines.push(`- Primary language: ${analysis.languages.primary}`);
3392
+ if (analysis.frameworks.databases.length > 0)
3393
+ lines.push(`- Databases: ${analysis.frameworks.databases.join(", ")}`);
3394
+ if (analysis.frameworks.testing.length > 0)
3395
+ lines.push(`- Testing: ${analysis.frameworks.testing.join(", ")}`);
3396
+ lines.push("", "## Architecture", "");
3397
+ lines.push(
3398
+ `${analysis.summary.totalFiles} files, ${analysis.summary.totalLines.toLocaleString()} lines across ${analysis.summary.moduleCount} modules.`
3399
+ );
3400
+ if (analysis.dependencies.entryPoints.length > 0) {
3401
+ lines.push("", "Entry points:");
3402
+ for (const ep of analysis.dependencies.entryPoints.slice(0, 10)) {
3403
+ lines.push(`- \`${ep}\``);
3404
+ }
3405
+ }
3406
+ lines.push(
3407
+ "",
3408
+ "## Conventions",
3409
+ "",
3410
+ "<!-- Add project conventions here -->"
3411
+ );
3412
+ fsWrite(brainstormMdPath, lines.join("\n"), "utf-8");
3413
+ console.log(` \u2713 Generated BRAINSTORM.md`);
3414
+ } else {
3415
+ console.log(` \xB7 BRAINSTORM.md already exists (skipped)`);
3416
+ }
3417
+ const agentsDir = pathJoin(absPath, ".brainstorm", "agents");
3418
+ if (!existsSync3(agentsDir)) fsMkdir(agentsDir, { recursive: true });
3419
+ let agentsCreated = 0;
3420
+ for (const cluster of analysis.dependencies.clusters.slice(0, 10)) {
3421
+ const safeName = cluster.directory.replace(/[/\\]/g, "-").replace(/^-/, "");
3422
+ const agentPath = pathJoin(agentsDir, `${safeName}.agent.md`);
3423
+ if (existsSync3(agentPath)) continue;
3424
+ const node = analysis.dependencies.nodes.find(
3425
+ (n) => cluster.files.includes(n.path)
3426
+ );
3427
+ const lang = node?.language ?? analysis.languages.primary;
3428
+ const exports = cluster.files.flatMap(
3429
+ (f) => analysis.dependencies.nodes.find((n) => n.path === f)?.exports ?? []
3430
+ ).slice(0, 20);
3431
+ const agentLines = [
3432
+ "---",
3433
+ `name: ${safeName}-expert`,
3434
+ `role: coder`,
3435
+ `model: auto`,
3436
+ "---",
3437
+ "",
3438
+ `# ${safeName} Module Expert`,
3439
+ "",
3440
+ `You are an expert in the ${safeName} module of this project.`,
3441
+ "",
3442
+ `## Context`,
3443
+ "",
3444
+ `- Language: ${lang}`,
3445
+ `- Files: ${cluster.files.length}`,
3446
+ `- Cohesion: ${cluster.cohesion > 0.5 ? "high" : cluster.cohesion > 0.2 ? "medium" : "low"}`
3447
+ ];
3448
+ if (exports.length > 0) {
3449
+ agentLines.push(`- Key exports: ${exports.join(", ")}`);
3450
+ }
3451
+ agentLines.push(
3452
+ "",
3453
+ "## Files",
3454
+ "",
3455
+ ...cluster.files.slice(0, 15).map((f) => `- \`${f}\``)
3456
+ );
3457
+ if (cluster.files.length > 15)
3458
+ agentLines.push(`- ... and ${cluster.files.length - 15} more`);
3459
+ fsWrite(agentPath, agentLines.join("\n"), "utf-8");
3460
+ agentsCreated++;
3461
+ }
3462
+ console.log(
3463
+ ` \u2713 Generated ${agentsCreated} .agent.md files in .brainstorm/agents/`
3464
+ );
3465
+ const { generateAllDocs } = await import("@brainst0rm/docgen");
3466
+ const docResult = generateAllDocs(analysis);
3467
+ console.log(
3468
+ ` \u2713 Generated ${docResult.filesWritten.length} documentation files`
3469
+ );
3470
+ const { initRecipeDir } = await import("@brainst0rm/workflow");
3471
+ initRecipeDir(absPath);
3472
+ console.log(` \u2713 Initialized .brainstorm/recipes/`);
3473
+ console.log("\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
3474
+ console.log(" AI Infrastructure Setup Complete");
3475
+ 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\n");
3476
+ console.log(` BRAINSTORM.md \u2192 project context for agents`);
3477
+ console.log(
3478
+ ` .brainstorm/agents/ \u2192 ${agentsCreated} domain expert agents`
3479
+ );
3480
+ console.log(` .brainstorm/recipes/ \u2192 shareable workflow templates`);
3481
+ console.log(` docs/generated/ \u2192 architecture + module + API docs`);
3482
+ console.log(
3483
+ `
3484
+ Next: Run \`storm chat\` to start working with AI agents that know your codebase.`
3485
+ );
3486
+ console.log();
3487
+ });
3488
+ 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
+ const { classifyTask } = await import("@brainst0rm/router");
3490
+ const taskText = task ?? "write a function that validates email addresses";
3491
+ const profile = classifyTask(taskText);
3492
+ if (opts.json) {
3493
+ console.log(JSON.stringify({ task: taskText, profile }, null, 2));
3494
+ return;
3495
+ }
3496
+ console.log("\n Route Explain");
3497
+ 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\n");
3498
+ console.log(` Task: "${taskText.slice(0, 80)}"`);
3499
+ console.log();
3500
+ console.log(` Classification:`);
3501
+ console.log(` Type: ${profile.type}`);
3502
+ console.log(` Complexity: ${profile.complexity}`);
3503
+ console.log(` Tools: ${profile.requiresToolUse ? "yes" : "no"}`);
3504
+ console.log(` Reasoning: ${profile.requiresReasoning ? "yes" : "no"}`);
3505
+ if (profile.language) console.log(` Language: ${profile.language}`);
3506
+ if (profile.domain) console.log(` Domain: ${profile.domain}`);
3507
+ console.log(
3508
+ ` Est tokens: ${profile.estimatedTokens.input}in / ${profile.estimatedTokens.output}out`
3509
+ );
3510
+ console.log();
3511
+ console.log(` Routing Logic:`);
3512
+ if (profile.type === "ingest")
3513
+ console.log(` \u2192 Ingest pipeline: analysis + docgen + infra setup`);
3514
+ else if (profile.type === "audit")
3515
+ console.log(` \u2192 Full review pipeline: security + quality + tech debt`);
3516
+ else if (profile.type === "migration")
3517
+ console.log(` \u2192 Migration pipeline: parallel agents per module`);
3518
+ else if (profile.type === "documentation")
3519
+ console.log(` \u2192 Documentation pipeline: architecture + module docs`);
3520
+ else if (profile.requiresReasoning)
3521
+ console.log(
3522
+ ` \u2192 Routes to frontier model (Opus/GPT-5.4) for reasoning`
3523
+ );
3524
+ else if (profile.complexity === "trivial" || profile.complexity === "simple")
3525
+ console.log(
3526
+ ` \u2192 Routes to fast/cheap model (Haiku/Flash) for simple tasks`
3527
+ );
3528
+ else
3529
+ console.log(
3530
+ ` \u2192 Routes based on active strategy (quality/cost/combined)`
3531
+ );
3532
+ console.log();
3533
+ });
3534
+ program.command("loop").description("Run a prompt or slash command on a recurring interval").argument("<prompt>", "Prompt or /command to run repeatedly").option("-i, --interval <minutes>", "Interval between runs in minutes", "10").option(
3535
+ "-n, --max-runs <count>",
3536
+ "Maximum number of runs (0 = unlimited)",
3537
+ "0"
3538
+ ).action(
3539
+ async (prompt, opts) => {
3540
+ const intervalMs = Math.max(1, parseInt(opts.interval) || 10) * 60 * 1e3;
3541
+ const maxRuns = parseInt(opts.maxRuns) || 0;
3542
+ let runCount = 0;
3543
+ console.log(
3544
+ `
3545
+ Loop: "${prompt}" every ${opts.interval}m${maxRuns > 0 ? ` (max ${maxRuns} runs)` : ""}`
3546
+ );
3547
+ console.log(` Press Ctrl+C to stop.
3548
+ `);
3549
+ const runOnce = async () => {
3550
+ runCount++;
3551
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
3552
+ console.log(` [${ts}] Run #${runCount}...`);
3553
+ try {
3554
+ const { execFile } = await import("child_process");
3555
+ const { promisify } = await import("util");
3556
+ const execFileAsync = promisify(execFile);
3557
+ const stormBin = process.argv[1];
3558
+ const { stdout, stderr } = await execFileAsync(
3559
+ process.execPath,
3560
+ [stormBin, "run", prompt],
3561
+ {
3562
+ cwd: process.cwd(),
3563
+ timeout: 5 * 60 * 1e3,
3564
+ // 5 min max per run
3565
+ env: { ...process.env }
3566
+ }
3567
+ );
3568
+ if (stdout.trim()) console.log(stdout.trim());
3569
+ if (stderr.trim()) console.error(stderr.trim());
3570
+ } catch (err) {
3571
+ console.error(
3572
+ ` Error: ${(err.stderr ?? err.message).slice(0, 200)}`
3573
+ );
3574
+ }
3575
+ if (maxRuns > 0 && runCount >= maxRuns) {
3576
+ console.log(`
3577
+ Loop complete (${runCount} runs).`);
3578
+ process.exit(0);
3579
+ }
3580
+ };
3581
+ await runOnce();
3582
+ setInterval(runOnce, intervalMs);
3583
+ }
3584
+ );
3585
+ program.command("memory").description("View and manage agent memory entries").argument("[action]", "Action: list, search, forget", "list").argument("[query]", "Search query or memory key to forget").action(async (action, query) => {
3586
+ const { MemoryManager } = await import("@brainst0rm/core");
3587
+ const homePath = join3(homedir(), ".brainstorm", "memory");
3588
+ const memory = new MemoryManager(homePath);
3589
+ switch (action) {
3590
+ case "list": {
3591
+ const entries = memory.list();
3592
+ if (entries.length === 0) {
3593
+ console.log("\n No memory entries.\n");
3594
+ return;
3595
+ }
3596
+ console.log(`
3597
+ Memory (${entries.length} entries):
3598
+ `);
3599
+ for (const entry of entries) {
3600
+ const typeIcon = entry.type === "user" ? "\u{1F464}" : entry.type === "feedback" ? "\u{1F4AC}" : entry.type === "project" ? "\u{1F4C1}" : "\u{1F517}";
3601
+ console.log(` ${typeIcon} ${entry.name}`);
3602
+ console.log(` ${entry.description.slice(0, 80)}`);
3603
+ }
3604
+ console.log();
3605
+ break;
3606
+ }
3607
+ case "search": {
3608
+ if (!query) {
3609
+ console.error(" Usage: storm memory search <query>");
3610
+ process.exit(1);
3611
+ }
3612
+ const results = memory.search(query);
3613
+ if (results.length === 0) {
3614
+ console.log(`
3615
+ No memory entries matching "${query}".
3616
+ `);
3617
+ return;
3618
+ }
3619
+ console.log(
3620
+ `
3621
+ Found ${results.length} entries matching "${query}":
3622
+ `
3623
+ );
3624
+ for (const entry of results) {
3625
+ console.log(` ${entry.name}: ${entry.description.slice(0, 80)}`);
3626
+ }
3627
+ console.log();
3628
+ break;
3629
+ }
3630
+ case "forget": {
3631
+ if (!query) {
3632
+ console.error(" Usage: storm memory forget <key>");
3633
+ process.exit(1);
3634
+ }
3635
+ const deleted = memory.delete(query);
3636
+ if (deleted) {
3637
+ console.log(`
3638
+ Forgot: "${query}"
3639
+ `);
3640
+ } else {
3641
+ console.log(`
3642
+ Memory "${query}" not found.
3643
+ `);
3644
+ }
3645
+ break;
3646
+ }
3647
+ default:
3648
+ console.error(
3649
+ ` Unknown action: ${action}. Use list, search, or forget.`
3650
+ );
3651
+ process.exit(1);
3652
+ }
3653
+ });
3654
+ program.command("sessions").description("List recent chat sessions").option("-n, --limit <count>", "Number of sessions to show", "10").action(async (opts) => {
3655
+ const db = getDb();
3656
+ const sessionManager = new SessionManager(db);
3657
+ const sessions = sessionManager.listRecent(parseInt(opts.limit));
3658
+ console.log("\n Recent Sessions:\n");
3659
+ if (sessions.length === 0) {
3660
+ console.log(" No sessions found.");
3661
+ }
3662
+ for (const s of sessions) {
3663
+ const age = Math.floor((Date.now() / 1e3 - s.updatedAt) / 60);
3664
+ const ageStr = age < 60 ? `${age}m ago` : age < 1440 ? `${Math.floor(age / 60)}h ago` : `${Math.floor(age / 1440)}d ago`;
3665
+ console.log(
3666
+ ` ${s.id.slice(0, 8)} ${s.messageCount} msgs $${s.totalCost.toFixed(4)} ${ageStr} ${s.projectPath}`
3667
+ );
3668
+ }
3669
+ console.log();
3670
+ });
3671
+ program.command("metrics").description("Export tool stats, model latency, and cost breakdown").option("--json", "Output as JSON").action(async (opts) => {
3672
+ const db = getDb();
3673
+ const costRepo = new CostRepository(db);
3674
+ const byModel = costRepo.recentByModel(20);
3675
+ const byTaskType = costRepo.byTaskType();
3676
+ const todayCost = costRepo.totalCostToday();
3677
+ const monthCost = costRepo.totalCostThisMonth();
3678
+ if (opts.json) {
3679
+ console.log(
3680
+ JSON.stringify({ todayCost, monthCost, byModel, byTaskType }, null, 2)
3681
+ );
3682
+ return;
3683
+ }
3684
+ console.log("\n Cost Summary:");
3685
+ console.log(` Today: $${todayCost.toFixed(4)}`);
3686
+ console.log(` This month: $${monthCost.toFixed(4)}`);
3687
+ if (byModel.length > 0) {
3688
+ console.log("\n Cost by Model:");
3689
+ for (const m of byModel) {
3690
+ console.log(
3691
+ ` ${m.modelId.padEnd(40)} $${m.totalCost.toFixed(4)} (${m.requestCount} reqs)`
3692
+ );
3693
+ }
3694
+ }
3695
+ if (byTaskType.length > 0) {
3696
+ console.log("\n Cost by Task Type:");
3697
+ for (const t of byTaskType) {
3698
+ console.log(
3699
+ ` ${t.taskType.padEnd(20)} $${t.totalCost.toFixed(4)} (${t.requestCount} reqs, avg $${t.avgCost.toFixed(4)})`
3700
+ );
3701
+ }
3702
+ }
3703
+ console.log();
3704
+ });
3705
+ program.command("ingest").description(
3706
+ "Full ingest pipeline: analyze \u2192 generate docs \u2192 set up AI infrastructure"
3707
+ ).argument("[path]", "Project path to ingest", ".").option("--depth <level>", "Analysis depth: quick or full", "full").option("--output <dir>", "Output directory for analysis artifacts").action(
3708
+ async (projectPath, opts) => {
3709
+ const { resolve } = await import("path");
3710
+ const absPath = resolve(projectPath);
3711
+ const startTime = Date.now();
3712
+ console.log(`
3713
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
3714
+ console.log(` Brainstorm Ingest \u2014 ${absPath}`);
3715
+ 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
3716
+ `);
3717
+ console.log(` Phase 1: Analyzing codebase...`);
3718
+ const { analyzeProject } = await import("@brainst0rm/ingest");
3719
+ const analysis = analyzeProject(absPath);
3720
+ console.log(
3721
+ ` \u2713 ${analysis.summary.totalFiles} files, ${analysis.summary.totalLines.toLocaleString()} lines, ${analysis.summary.moduleCount} modules`
3722
+ );
3723
+ console.log(` Phase 2: Generating documentation...`);
3724
+ const { generateAllDocs } = await import("@brainst0rm/docgen");
3725
+ const docResult = generateAllDocs(analysis, opts.output);
3726
+ console.log(` \u2713 ${docResult.filesWritten.length} doc files written`);
3727
+ console.log(` Phase 3: Setting up AI infrastructure...`);
3728
+ const {
3729
+ existsSync: existsSync3,
3730
+ writeFileSync: fsWrite,
3731
+ mkdirSync: fsMkdir
3732
+ } = await import("fs");
3733
+ const { join: pathJoin } = await import("path");
3734
+ const bmPath = pathJoin(absPath, "BRAINSTORM.md");
3735
+ if (!existsSync3(bmPath)) {
3736
+ const lines = [
3737
+ "---",
3738
+ `build_command: "npm run build"`,
3739
+ `test_command: "npm test"`,
3740
+ "---",
3741
+ "",
3742
+ `# ${absPath.split("/").pop()}`,
3743
+ "",
3744
+ `${analysis.languages.primary} project with ${analysis.summary.frameworkList.join(", ") || "no detected frameworks"}.`,
3745
+ `${analysis.summary.totalFiles} files, ${analysis.summary.totalLines.toLocaleString()} lines across ${analysis.summary.moduleCount} modules.`
3746
+ ];
3747
+ fsWrite(bmPath, lines.join("\n"), "utf-8");
3748
+ console.log(` \u2713 Generated BRAINSTORM.md`);
3749
+ }
3750
+ const agentsDir = pathJoin(absPath, ".brainstorm", "agents");
3751
+ if (!existsSync3(agentsDir)) fsMkdir(agentsDir, { recursive: true });
3752
+ let agentCount = 0;
3753
+ for (const cluster of analysis.dependencies.clusters.slice(0, 10)) {
3754
+ const safeName = cluster.directory.replace(/[/\\]/g, "-").replace(/^-/, "");
3755
+ const agentPath = pathJoin(agentsDir, `${safeName}.agent.md`);
3756
+ if (!existsSync3(agentPath)) {
3757
+ fsWrite(
3758
+ agentPath,
3759
+ `---
3760
+ name: ${safeName}-expert
3761
+ role: coder
3762
+ ---
3763
+
3764
+ # ${safeName} Expert
3765
+
3766
+ Domain expert for the ${safeName} module.
3767
+ `,
3768
+ "utf-8"
3769
+ );
3770
+ agentCount++;
3771
+ }
3772
+ }
3773
+ console.log(` \u2713 ${agentCount} agent profiles created`);
3774
+ const { initRecipeDir } = await import("@brainst0rm/workflow");
3775
+ initRecipeDir(absPath);
3776
+ console.log(` \u2713 Recipe directory initialized`);
3777
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
3778
+ console.log(`
3779
+ \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`);
3780
+ console.log(` Ingest complete in ${elapsed}s.`);
3781
+ console.log(
3782
+ ` Your codebase is now AI-ready. Run \`storm chat\` to start.`
3783
+ );
3784
+ console.log();
3785
+ }
3786
+ );
3787
+ program.command("audit").description(
3788
+ "Full code audit: security, quality, tech debt, dependency review"
3789
+ ).argument("[path]", "Project path to audit", ".").option("--json", "Output as JSON").option(
3790
+ "--focus <area>",
3791
+ "Focus area: security, quality, dependencies, all",
3792
+ "all"
3793
+ ).action(
3794
+ async (projectPath, opts) => {
3795
+ const { resolve } = await import("path");
3796
+ const absPath = resolve(projectPath);
3797
+ console.log(`
3798
+ Auditing ${absPath}...
3799
+ `);
3800
+ const { analyzeProject } = await import("@brainst0rm/ingest");
3801
+ const analysis = analyzeProject(absPath);
3802
+ const findings = [];
3803
+ if (opts.focus === "all" || opts.focus === "quality") {
3804
+ for (const f of analysis.complexity.files.filter(
3805
+ (cf) => cf.score >= 70
3806
+ )) {
3807
+ findings.push({
3808
+ severity: "warning",
3809
+ category: "complexity",
3810
+ message: `High complexity score (${f.score}/100) \u2014 consider refactoring`,
3811
+ file: f.path
3812
+ });
3813
+ }
3814
+ }
3815
+ if (opts.focus === "all" || opts.focus === "quality") {
3816
+ for (const f of analysis.complexity.files.filter(
3817
+ (cf) => cf.lines > 500
3818
+ )) {
3819
+ findings.push({
3820
+ severity: "info",
3821
+ category: "file-size",
3822
+ message: `Large file (${f.lines} lines) \u2014 consider splitting`,
3823
+ file: f.path
3824
+ });
3825
+ }
3826
+ }
3827
+ if (opts.focus === "all" || opts.focus === "quality") {
3828
+ for (const c of analysis.dependencies.clusters.filter(
3829
+ (cl) => cl.cohesion < 0.1
3830
+ )) {
3831
+ findings.push({
3832
+ severity: "info",
3833
+ category: "cohesion",
3834
+ message: `Low cohesion module (${c.cohesion.toFixed(2)}) \u2014 files may be unrelated`,
3835
+ file: c.directory
3836
+ });
3837
+ }
3838
+ }
3839
+ if (opts.json) {
3840
+ console.log(
3841
+ JSON.stringify({ findings, summary: analysis.summary }, null, 2)
3842
+ );
3843
+ return;
3844
+ }
3845
+ console.log(` Audit Results: ${findings.length} finding(s)
3846
+ `);
3847
+ const bySeverity = { warning: 0, info: 0, error: 0 };
3848
+ for (const f of findings) {
3849
+ const icon = f.severity === "warning" ? "\u26A0" : f.severity === "error" ? "\u2717" : "\u2139";
3850
+ console.log(
3851
+ ` ${icon} [${f.category}] ${f.message}${f.file ? ` (${f.file})` : ""}`
3852
+ );
3853
+ bySeverity[f.severity]++;
3854
+ }
3855
+ console.log(
3856
+ `
3857
+ Summary: ${bySeverity.error} errors, ${bySeverity.warning} warnings, ${bySeverity.info} info`
3858
+ );
3859
+ console.log();
3860
+ }
3861
+ );
3862
+ program.command("share").description("Export or import session context for team sharing").argument("<action>", "Action: export or import").argument("[file]", "File path for export/import").action(async (action, file) => {
3863
+ const { writeFileSync: writeFileSync2, readFileSync: readFileSync3 } = await import("fs");
3864
+ const db = getDb();
3865
+ const sessionManager = new SessionManager(db);
3866
+ if (action === "export") {
3867
+ const sessions = db.prepare("SELECT * FROM sessions ORDER BY created_at DESC LIMIT 1").all();
3868
+ if (sessions.length === 0) {
3869
+ console.log("\n No sessions to export.");
3870
+ return;
3871
+ }
3872
+ const session = sessions[0];
3873
+ const messages = db.prepare("SELECT * FROM messages WHERE session_id = ?").all(session.id);
3874
+ const exportData = {
3875
+ version: 1,
3876
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
3877
+ session: { id: session.id, projectPath: session.project_path },
3878
+ messages
3879
+ };
3880
+ const outPath = file ?? `brainstorm-session-${session.id.slice(0, 8)}.json`;
3881
+ writeFileSync2(outPath, JSON.stringify(exportData, null, 2), "utf-8");
3882
+ console.log(
3883
+ `
3884
+ Exported session to ${outPath} (${messages.length} messages)`
3885
+ );
3886
+ } else if (action === "import") {
3887
+ if (!file) {
3888
+ console.error("\n Usage: storm share import <file.json>");
3889
+ process.exit(1);
3890
+ }
3891
+ const data = JSON.parse(readFileSync3(file, "utf-8"));
3892
+ console.log(
3893
+ `
3894
+ Imported session context: ${data.messages?.length ?? 0} messages from ${data.exportedAt}`
3895
+ );
3896
+ console.log(` Use this context in your next chat session.`);
3897
+ } else {
3898
+ console.error(`
3899
+ Unknown action: ${action}. Use: export or import`);
3900
+ }
3901
+ console.log();
3902
+ closeDb();
3903
+ });
3904
+ program.command("cloud").description("Run agents remotely via BrainstormRouter cloud").argument("<action>", "Action: run, status, list").argument("[task]", "Task description (for run)").option("--budget <amount>", "Budget limit in dollars", "5.0").action(async (action, task, opts) => {
3905
+ console.log(`
3906
+ BrainstormRouter Cloud Agents`);
3907
+ 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
3908
+ `);
3909
+ switch (action) {
3910
+ case "run":
3911
+ if (!task) {
3912
+ console.error(" Usage: storm cloud run <task>");
3913
+ break;
3914
+ }
3915
+ console.log(` Task: ${task}`);
3916
+ console.log(` Budget: $${opts?.budget ?? "5.0"}`);
3917
+ console.log(` Status: Queued`);
3918
+ console.log(
3919
+ `
3920
+ Cloud execution requires a BrainstormRouter Pro subscription.`
3921
+ );
3922
+ console.log(
3923
+ ` Sign up at https://brainstorm.co/cloud to enable remote agents.`
3924
+ );
3925
+ break;
3926
+ case "status":
3927
+ console.log(` No active cloud agents.`);
3928
+ break;
3929
+ case "list":
3930
+ console.log(` No completed cloud runs.`);
3931
+ break;
3932
+ default:
3933
+ console.error(` Unknown action: ${action}. Use: run, status, list`);
3934
+ }
3935
+ console.log();
3936
+ });
3937
+ program.command("ci-gen").description("Generate CI/CD workflow files (GitHub Actions, GitLab CI)").argument("[platform]", "CI platform: github, gitlab", "github").option("--output <path>", "Output path").action(async (platform, opts) => {
3938
+ const { existsSync: existsSync3, writeFileSync: writeFileSync2, mkdirSync: mkdirSync2 } = await import("fs");
3939
+ const { join: join4 } = await import("path");
3940
+ const { analyzeProject } = await import("@brainst0rm/ingest");
3941
+ const projectPath = process.cwd();
3942
+ const analysis = analyzeProject(projectPath);
3943
+ if (platform === "github") {
3944
+ const workflowDir = opts.output ?? join4(projectPath, ".github", "workflows");
3945
+ if (!existsSync3(workflowDir)) mkdirSync2(workflowDir, { recursive: true });
3946
+ const buildCmd = analysis.frameworks.packageManagers.includes("pnpm") ? "pnpm" : analysis.frameworks.packageManagers.includes("yarn") ? "yarn" : "npm";
3947
+ const hasTurbo = analysis.frameworks.buildTools.includes("Turborepo");
3948
+ const workflow = [
3949
+ "name: Brainstorm AI Review",
3950
+ "",
3951
+ "on:",
3952
+ " pull_request:",
3953
+ " branches: [main, master]",
3954
+ "",
3955
+ "jobs:",
3956
+ " ai-review:",
3957
+ " runs-on: ubuntu-latest",
3958
+ " steps:",
3959
+ " - uses: actions/checkout@v4",
3960
+ ` - uses: actions/setup-node@v4`,
3961
+ " with:",
3962
+ ' node-version: "22"',
3963
+ ` - run: ${buildCmd} install`,
3964
+ hasTurbo ? ` - run: npx turbo run build test` : ` - run: ${buildCmd} run build && ${buildCmd} test`,
3965
+ "",
3966
+ " # AI-assisted code review via Brainstorm",
3967
+ ` - name: Brainstorm Review`,
3968
+ ` run: npx @brainst0rm/cli run --unattended "Review the PR changes for bugs and security issues"`,
3969
+ " env:",
3970
+ " BRAINSTORM_API_KEY: ${{ secrets.BRAINSTORM_API_KEY }}"
3971
+ ];
3972
+ const outPath = join4(workflowDir, "brainstorm-review.yml");
3973
+ writeFileSync2(outPath, workflow.join("\n"), "utf-8");
3974
+ console.log(`
3975
+ Generated GitHub Actions workflow: ${outPath}`);
3976
+ } else if (platform === "gitlab") {
3977
+ const outPath = opts.output ?? join4(projectPath, ".gitlab-ci-brainstorm.yml");
3978
+ const workflow = [
3979
+ "brainstorm-review:",
3980
+ " stage: review",
3981
+ " image: node:22",
3982
+ " script:",
3983
+ " - npm install",
3984
+ ' - npx @brainst0rm/cli run --unattended "Review changes for bugs and security"',
3985
+ " only:",
3986
+ " - merge_requests",
3987
+ " variables:",
3988
+ " BRAINSTORM_API_KEY: $BRAINSTORM_API_KEY"
3989
+ ];
3990
+ writeFileSync2(outPath, workflow.join("\n"), "utf-8");
3991
+ console.log(`
3992
+ Generated GitLab CI config: ${outPath}`);
3993
+ } else {
3994
+ console.error(`
3995
+ Unknown platform: ${platform}. Use: github, gitlab`);
3996
+ }
3997
+ console.log();
3998
+ });
3999
+ program.command("start").description(
4000
+ "One-command setup: detect project, connect to community tier, start chatting"
4001
+ ).action(async () => {
4002
+ const { resolve } = await import("path");
4003
+ const { existsSync: existsSync3 } = await import("fs");
4004
+ const projectPath = resolve(".");
4005
+ console.log(`
4006
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
4007
+ console.log(` brainstorm start`);
4008
+ 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
4009
+ `);
4010
+ const hasBrainstormMd = existsSync3(resolve("BRAINSTORM.md")) || existsSync3(resolve("STORM.md"));
4011
+ const hasConfig = existsSync3(resolve("brainstorm.toml"));
4012
+ if (!hasBrainstormMd && !hasConfig) {
4013
+ console.log(` Step 1: Initializing project...`);
4014
+ const { execFileSync } = await import("child_process");
4015
+ try {
4016
+ execFileSync(process.execPath, [process.argv[1], "init", "--yes"], {
4017
+ cwd: projectPath,
4018
+ stdio: "inherit"
4019
+ });
4020
+ } catch {
4021
+ const { writeFileSync: writeFileSync2 } = await import("fs");
4022
+ writeFileSync2(
4023
+ resolve("BRAINSTORM.md"),
4024
+ `# ${projectPath.split("/").pop()}
4025
+
4026
+ Project initialized by \`storm start\`.
4027
+ `,
4028
+ "utf-8"
4029
+ );
4030
+ console.log(` \u2713 Generated BRAINSTORM.md`);
4031
+ }
4032
+ } else {
4033
+ console.log(` Step 1: Project already initialized \u2713`);
4034
+ }
4035
+ const brKey = process.env.BRAINSTORM_API_KEY;
4036
+ if (brKey) {
4037
+ console.log(` Step 2: BrainstormRouter API key detected \u2713`);
4038
+ } else {
4039
+ console.log(` Step 2: Using free community tier`);
4040
+ console.log(` \u2192 10 requests/min \xB7 $5/month cap \xB7 362 models`);
4041
+ console.log(` \u2192 Upgrade: https://brainstormrouter.com/dashboard`);
4042
+ }
4043
+ console.log(` Step 3: Checking connectivity...`);
4044
+ try {
4045
+ const resp = await fetch("https://api.brainstormrouter.com/health", {
4046
+ signal: AbortSignal.timeout(5e3)
4047
+ });
4048
+ if (resp.ok) {
4049
+ console.log(` \u2713 BrainstormRouter reachable`);
4050
+ } else {
4051
+ console.log(
4052
+ ` \u26A0 BrainstormRouter returned ${resp.status} (will work offline with local models)`
4053
+ );
4054
+ }
4055
+ } catch {
4056
+ console.log(
4057
+ ` \u26A0 BrainstormRouter unreachable (will work offline with local models)`
4058
+ );
4059
+ }
4060
+ console.log(`
4061
+ \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`);
4062
+ console.log(` Ready! Run one of:`);
4063
+ console.log(` storm chat Interactive session`);
4064
+ console.log(` storm ingest Analyze this codebase`);
4065
+ console.log(` storm run "prompt" Single-shot execution`);
4066
+ console.log();
4067
+ });
4068
+ program.command("chat", { isDefault: true }).description("Start an interactive chat session").option("--simple", "Use simple readline interface instead of TUI").option("--continue", "Resume the most recent session").option("--resume <id>", "Resume a specific session by ID").option("--fork <id>", "Fork a session (copy history, new session)").option("--lfg", "Full auto mode \u2014 skip all permission confirmations").option(
4069
+ "--strategy <name>",
4070
+ "Routing strategy: cost-first, quality-first, combined, capability"
4071
+ ).option("--verbose-routing", "Print routing decisions to stderr").option(
4072
+ "--fast",
4073
+ "Fast startup \u2014 skip provider discovery, MCP connections, and eval probes"
4074
+ ).action(
4075
+ async (opts) => {
4076
+ const config = loadConfig();
4077
+ if (opts.lfg) {
4078
+ config.general.defaultPermissionMode = "auto";
4079
+ }
4080
+ if (opts.fast) {
4081
+ config.general.skipProviderDiscovery = true;
4082
+ config.general.skipEvalProbes = true;
4083
+ }
4084
+ const db = getDb();
4085
+ const resolvedKeys = await resolveProviderKeys();
4086
+ const resolvedBRKey = resolvedKeys.get("BRAINSTORM_API_KEY") ?? getBrainstormApiKey();
4087
+ const isCommunityTier = isCommunityKey(resolvedBRKey);
4088
+ if (resolvedBRKey) process.env._BR_RESOLVED_KEY = resolvedBRKey;
4089
+ const registry = await createProviderRegistry(config, resolvedKeys);
4090
+ const costTracker = new CostTracker(db, config.budget);
4091
+ const tools = createDefaultToolRegistry();
4092
+ if (!opts.fast)
4093
+ await connectMCPServers(
4094
+ tools,
4095
+ config,
4096
+ resolvedKeys.get("BRAINSTORM_API_KEY")
4097
+ );
4098
+ const projectPath = process.cwd();
4099
+ configureSandbox(
4100
+ config.shell.sandbox,
4101
+ projectPath,
4102
+ config.shell.maxOutputBytes,
4103
+ config.shell.containerImage,
4104
+ config.shell.containerTimeout
4105
+ );
4106
+ const permissionManager = new PermissionManager(
4107
+ config.general.defaultPermissionMode,
4108
+ config.permissions
4109
+ );
4110
+ let currentOutputStyle = config.general.outputStyle ?? "concise";
4111
+ let currentRole;
4112
+ const sessionManager = new SessionManager(db);
4113
+ const middleware = createDefaultMiddlewarePipeline(projectPath);
4114
+ let { prompt: systemPrompt, frontmatter } = buildSystemPrompt(
4115
+ projectPath,
4116
+ currentOutputStyle
4117
+ );
4118
+ systemPrompt += buildToolAwarenessSection(tools.listTools());
4119
+ const router = new BrainstormRouter(
4120
+ config,
4121
+ registry,
4122
+ costTracker,
4123
+ frontmatter
4124
+ );
4125
+ const hasOwnKeys = !!resolvedKeys.get("DEEPSEEK_API_KEY") || !!resolvedKeys.get("ANTHROPIC_API_KEY") || !!resolvedKeys.get("OPENAI_API_KEY") || !!resolvedKeys.get("MOONSHOT_API_KEY") || !!resolvedKeys.get("GOOGLE_GENERATIVE_AI_API_KEY");
4126
+ if (opts.strategy) {
4127
+ router.setStrategy(opts.strategy);
4128
+ } else if ((!isCommunityTier || hasOwnKeys) && router.getActiveStrategy() !== "capability") {
4129
+ router.setStrategy("quality-first");
4130
+ }
4131
+ const subagentTool = createSubagentTool({
4132
+ config,
4133
+ registry,
4134
+ router,
4135
+ costTracker,
4136
+ tools,
4137
+ projectPath,
4138
+ permissionCheck: (name, perm) => permissionManager.check(name, perm),
4139
+ containerIsolation: config.shell.sandbox === "container"
4140
+ });
4141
+ tools.register(subagentTool);
4142
+ const hasDirectProviderKeys = !!resolvedKeys.get("DEEPSEEK_API_KEY") || !!resolvedKeys.get("ANTHROPIC_API_KEY") || !!resolvedKeys.get("OPENAI_API_KEY") || !!resolvedKeys.get("GOOGLE_GENERATIVE_AI_API_KEY") || !!resolvedKeys.get("MOONSHOT_API_KEY");
4143
+ let preferredModelId = resolvedKeys.get(
4144
+ "MOONSHOT_API_KEY"
4145
+ ) ? "moonshot/kimi-k2.5" : isCommunityTier && !hasDirectProviderKeys ? "brainstormrouter/auto" : void 0;
4146
+ let session;
4147
+ if (opts.fork) {
4148
+ session = sessionManager.fork(opts.fork);
4149
+ if (!session) {
4150
+ console.error(` Session '${opts.fork}' not found.`);
4151
+ process.exit(1);
4152
+ }
4153
+ console.log(
4154
+ ` Forked session ${opts.fork.slice(0, 8)} -> ${session.id.slice(0, 8)}`
4155
+ );
4156
+ } else if (opts.resume) {
4157
+ session = sessionManager.resume(opts.resume);
4158
+ if (!session) {
4159
+ console.error(` Session '${opts.resume}' not found.`);
4160
+ process.exit(1);
4161
+ }
4162
+ printResumeSummary(session, sessionManager);
4163
+ } else if (opts.continue) {
4164
+ session = sessionManager.resumeLatest(projectPath);
4165
+ if (!session) {
4166
+ session = sessionManager.start(projectPath);
4167
+ } else {
4168
+ printResumeSummary(session, sessionManager);
4169
+ }
4170
+ } else {
4171
+ session = sessionManager.start(projectPath);
4172
+ }
4173
+ const localCount = registry.models.filter((m) => m.isLocal).length;
4174
+ const cloudCount = registry.models.filter((m) => !m.isLocal).length;
4175
+ if (opts.simple) {
4176
+ const readline = await import("readline/promises");
4177
+ const rl = readline.createInterface({
4178
+ input: process.stdin,
4179
+ output: process.stdout
4180
+ });
4181
+ console.log(`
4182
+ \u{1F9E0} brainstorm v0.1.0`);
4183
+ console.log(
4184
+ ` Strategy: ${router.getActiveStrategy()} | Models: ${localCount} local, ${cloudCount} cloud`
4185
+ );
4186
+ console.log(` Project: ${projectPath}`);
4187
+ if (isCommunityTier)
4188
+ console.log(
4189
+ ` Community tier (5 req/min, cheap models). Set BRAINSTORM_API_KEY for full access.`
4190
+ );
4191
+ console.log(
4192
+ ` Commands: /quit, /model <id>, /strategy <name>, /compact`
4193
+ );
4194
+ console.log(` Ctrl+C to interrupt, Ctrl+D to exit.
4195
+ `);
4196
+ let simpleAbortController = null;
4197
+ process.on("SIGINT", () => {
4198
+ if (simpleAbortController) {
4199
+ simpleAbortController.abort();
4200
+ simpleAbortController = null;
4201
+ process.stdout.write("\n [interrupted]\n\n");
4202
+ } else {
4203
+ rl.close();
4204
+ process.exit(0);
4205
+ }
4206
+ });
4207
+ while (true) {
4208
+ const input2 = await rl.question("you > ");
4209
+ if (!input2.trim()) continue;
4210
+ if (input2.trim() === "/quit" || input2.trim() === "/exit") break;
4211
+ if (input2.startsWith("/")) {
4212
+ const { isSlashCommand, executeSlashCommand } = await import("./slash-PDWKCZOQ.js");
4213
+ if (isSlashCommand(input2)) {
4214
+ const result = await executeSlashCommand(input2, {
4215
+ getModel: () => preferredModelId,
4216
+ getSessionCost: () => costTracker.getSessionCost(),
4217
+ getTokenCount: () => ({
4218
+ input: 0,
4219
+ output: 0
4220
+ }),
4221
+ exit: () => {
4222
+ rl.close();
4223
+ process.exit(0);
4224
+ },
4225
+ clearHistory: () => {
4226
+ session = sessionManager.start(projectPath);
4227
+ },
4228
+ setModel: (m) => {
4229
+ preferredModelId = m;
4230
+ },
4231
+ setStrategy: (s) => {
4232
+ router.setStrategy(s);
4233
+ },
4234
+ getStrategy: () => router.getActiveStrategy(),
4235
+ setMode: (m) => {
4236
+ permissionManager.setMode(m);
4237
+ },
4238
+ getMode: () => permissionManager.getMode(),
4239
+ setOutputStyle: (s) => {
4240
+ currentOutputStyle = s;
4241
+ const rebuilt = buildSystemPrompt(
4242
+ projectPath,
4243
+ currentOutputStyle
4244
+ );
4245
+ systemPrompt = rebuilt.prompt + buildToolAwarenessSection(tools.listTools());
4246
+ },
4247
+ getOutputStyle: () => currentOutputStyle,
4248
+ getBudget: () => {
4249
+ const remaining = costTracker.getRemainingBudget();
4250
+ if (remaining === null) return null;
4251
+ return {
4252
+ remaining,
4253
+ limit: config.budget.perSession ?? 0
4254
+ };
4255
+ },
4256
+ compact: async () => {
4257
+ const result2 = await sessionManager.compact({
4258
+ contextWindow: 2e5,
4259
+ keepRecent: 5
4260
+ });
4261
+ console.log(
4262
+ ` Compacted: ${result2.removed} messages removed (${result2.tokensBefore} \u2192 ${result2.tokensAfter} tokens)`
4263
+ );
4264
+ }
4265
+ });
4266
+ console.log(` ${result}`);
4267
+ continue;
4268
+ }
4269
+ }
4270
+ sessionManager.addUserMessage(input2);
4271
+ let fullResponse = "";
4272
+ const sessionTotalBefore = costTracker.getSessionCost();
4273
+ process.stdout.write("\nbrainstorm > ");
4274
+ simpleAbortController = new AbortController();
4275
+ const roleToolFilter = currentRole && ROLES[currentRole] ? {
4276
+ allowedTools: ROLES[currentRole].allowedTools,
4277
+ blockedTools: ROLES[currentRole].blockedTools
4278
+ } : void 0;
4279
+ for await (const event of runAgentLoop(sessionManager.getHistory(), {
4280
+ config,
4281
+ registry,
4282
+ router,
4283
+ costTracker,
4284
+ tools,
4285
+ sessionId: session.id,
4286
+ projectPath,
4287
+ systemPrompt,
4288
+ compaction: buildCompactionCallbacks(sessionManager),
4289
+ signal: simpleAbortController.signal,
4290
+ permissionCheck: (name, perm) => permissionManager.check(name, perm),
4291
+ preferredModelId,
4292
+ middleware,
4293
+ roleToolFilter,
4294
+ onTurnComplete: (ctx) => {
4295
+ ctx.turn = sessionManager.incrementTurn();
4296
+ ctx.sessionMinutes = sessionManager.getSessionMinutes();
4297
+ sessionManager.addTurnContext(ctx);
4298
+ }
4299
+ })) {
4300
+ switch (event.type) {
4301
+ case "thinking": {
4302
+ const spinFrames = [
4303
+ "\u280B",
4304
+ "\u2819",
4305
+ "\u2839",
4306
+ "\u2838",
4307
+ "\u283C",
4308
+ "\u2834",
4309
+ "\u2826",
4310
+ "\u2827",
4311
+ "\u2807",
4312
+ "\u280F"
4313
+ ];
4314
+ const f = spinFrames[Math.floor(Date.now() / 100) % spinFrames.length];
4315
+ const chatPhases = {
4316
+ classifying: "Analyzing...",
4317
+ routing: "Selecting model...",
4318
+ connecting: "Connecting...",
4319
+ streaming: "Streaming..."
4320
+ };
4321
+ process.stderr.write(
4322
+ `\r ${f} ${chatPhases[event.phase] ?? event.phase}`
4323
+ );
4324
+ break;
4325
+ }
4326
+ case "routing":
4327
+ process.stderr.write(`\r [${event.decision.model.name}]
4328
+ `);
4329
+ if (opts.verboseRouting) {
4330
+ const d = event.decision;
4331
+ process.stderr.write(
4332
+ ` routing: strategy=${d.strategy} model=${d.model.id} provider=${d.model.provider} cost=$${d.estimatedCost.toFixed(4)} reason="${d.reason}"
4333
+ `
4334
+ );
4335
+ }
4336
+ break;
4337
+ case "text-delta":
4338
+ fullResponse += event.delta;
4339
+ process.stdout.write(event.delta);
4340
+ break;
4341
+ case "tool-call-start":
4342
+ process.stdout.write(`
4343
+ [tool: ${event.toolName}]
4344
+ `);
4345
+ break;
4346
+ case "tool-call-result":
4347
+ break;
4348
+ // Tool results are shown by the model's text response
4349
+ case "model-retry":
4350
+ process.stderr.write(
4351
+ `
4352
+ [retry: ${event.fromModel} \u2192 ${event.toModel}]
4353
+ `
4354
+ );
4355
+ break;
4356
+ case "gateway-feedback": {
4357
+ const gw = formatGatewayFeedback(event.feedback);
4358
+ if (gw) process.stderr.write(` ${gw}
4359
+ `);
4360
+ break;
4361
+ }
4362
+ case "context-budget":
4363
+ process.stderr.write(
4364
+ ` [${Math.round(event.used / 1e3)}k/${Math.round(event.limit / 1e3)}k tokens (${event.percent}%)]
4365
+ `
4366
+ );
4367
+ break;
4368
+ case "interrupted":
4369
+ process.stdout.write("\n [interrupted]\n\n");
4370
+ break;
4371
+ case "done": {
4372
+ const turn = sessionManager.getTurnCount();
4373
+ const turnCost = event.totalCost - (sessionTotalBefore ?? 0);
4374
+ sessionManager.syncSessionCost(turnCost);
4375
+ process.stdout.write(
4376
+ `
4377
+ [Turn ${turn}: $${turnCost.toFixed(4)} | Session: $${event.totalCost.toFixed(4)}]
4378
+
4379
+ `
4380
+ );
4381
+ break;
4382
+ }
4383
+ case "error":
4384
+ process.stderr.write(`
4385
+ Error: ${event.error.message}
4386
+
4387
+ `);
4388
+ break;
4389
+ }
4390
+ }
4391
+ simpleAbortController = null;
4392
+ if (fullResponse) sessionManager.addAssistantMessage(fullResponse);
4393
+ }
4394
+ rl.close();
4395
+ return;
4396
+ }
4397
+ const { render } = await import("ink");
4398
+ const React = await import("react");
4399
+ const { App } = await import("./App-DPXJYXKH.js");
4400
+ let currentAbortController = null;
4401
+ function handleSendMessage(text) {
4402
+ sessionManager.addUserMessage(text);
4403
+ currentAbortController = new AbortController();
4404
+ const roleFilter = currentRole && ROLES[currentRole] ? {
4405
+ allowedTools: ROLES[currentRole].allowedTools,
4406
+ blockedTools: ROLES[currentRole].blockedTools
4407
+ } : void 0;
4408
+ const gen = runAgentLoop(sessionManager.getHistory(), {
4409
+ config,
4410
+ registry,
4411
+ router,
4412
+ costTracker,
4413
+ tools,
4414
+ sessionId: session.id,
4415
+ projectPath,
4416
+ systemPrompt,
4417
+ compaction: buildCompactionCallbacks(sessionManager),
4418
+ signal: currentAbortController.signal,
4419
+ permissionCheck: (name, perm) => permissionManager.check(name, perm),
4420
+ middleware,
4421
+ preferredModelId,
4422
+ roleToolFilter: roleFilter
4423
+ });
4424
+ return (async function* () {
4425
+ let fullResponse = "";
4426
+ for await (const event of gen) {
4427
+ if (event.type === "text-delta") fullResponse += event.delta;
4428
+ yield event;
4429
+ }
4430
+ if (fullResponse) sessionManager.addAssistantMessage(fullResponse);
4431
+ currentAbortController = null;
4432
+ })();
4433
+ }
4434
+ function handleAbort() {
4435
+ if (currentAbortController) {
4436
+ currentAbortController.abort();
4437
+ currentAbortController = null;
4438
+ }
4439
+ }
4440
+ const modelData = registry.models.map((m) => ({
4441
+ id: m.id,
4442
+ name: m.name,
4443
+ provider: m.provider,
4444
+ qualityTier: m.capabilities?.qualityTier ?? 3,
4445
+ speedTier: m.capabilities?.speedTier ?? 2,
4446
+ pricing: {
4447
+ input: m.pricing?.inputPer1MTokens ?? 0,
4448
+ output: m.pricing?.outputPer1MTokens ?? 0
4449
+ },
4450
+ status: m.status ?? "available"
4451
+ }));
4452
+ const brGateway = createGatewayClient2();
4453
+ render(
4454
+ React.createElement(App, {
4455
+ strategy: config.general.defaultStrategy,
4456
+ modelCount: { local: localCount, cloud: cloudCount },
4457
+ onSendMessage: handleSendMessage,
4458
+ onAbort: handleAbort,
4459
+ models: modelData,
4460
+ gateway: brGateway,
4461
+ configInfo: {
4462
+ strategy: config.general.defaultStrategy,
4463
+ permissionMode: config.general.defaultPermissionMode ?? "confirm",
4464
+ outputStyle: config.general.outputStyle ?? "concise",
4465
+ sandbox: config.shell?.sandbox ?? "none"
4466
+ },
4467
+ vaultInfo: {
4468
+ exists: new BrainstormVault(VAULT_PATH).exists(),
4469
+ isOpen: false,
4470
+ keyCount: 0,
4471
+ keys: [],
4472
+ createdAt: null,
4473
+ opAvailable: !!process.env.OP_SERVICE_ACCOUNT_TOKEN,
4474
+ resolvedKeys: PROVIDER_KEY_NAMES.filter((k) => resolvedKeys.get(k))
4475
+ },
4476
+ memoryInfo: await (async () => {
4477
+ try {
4478
+ const { MemoryManager } = await import("@brainst0rm/core");
4479
+ const mem = new MemoryManager(projectPath);
4480
+ const entries = mem.list();
4481
+ const types = {};
4482
+ for (const e of entries) {
4483
+ types[e.type] = (types[e.type] ?? 0) + 1;
4484
+ }
4485
+ return { localCount: entries.length, types };
4486
+ } catch {
4487
+ return { localCount: 0, types: {} };
4488
+ }
4489
+ })(),
4490
+ slashCallbacks: {
4491
+ setModel: (model) => {
4492
+ preferredModelId = model;
4493
+ },
4494
+ setStrategy: (s) => {
4495
+ router.setStrategy(s);
4496
+ },
4497
+ getStrategy: () => router.getActiveStrategy(),
4498
+ setMode: (mode) => {
4499
+ permissionManager.setMode(mode);
4500
+ },
4501
+ getMode: () => permissionManager.getMode(),
4502
+ setOutputStyle: (style) => {
4503
+ currentOutputStyle = style;
4504
+ const rebuilt = buildSystemPrompt(
4505
+ projectPath,
4506
+ currentOutputStyle
4507
+ );
4508
+ systemPrompt = rebuilt.prompt + buildToolAwarenessSection(tools.listTools());
4509
+ },
4510
+ getOutputStyle: () => currentOutputStyle,
4511
+ rebuildSystemPrompt: (basePromptOverride) => {
4512
+ const rebuilt = buildSystemPrompt(
4513
+ projectPath,
4514
+ currentOutputStyle,
4515
+ basePromptOverride
4516
+ );
4517
+ systemPrompt = rebuilt.prompt + buildToolAwarenessSection(tools.listTools());
4518
+ },
4519
+ getActiveRole: () => currentRole,
4520
+ setActiveRole: (role) => {
4521
+ currentRole = role;
4522
+ },
4523
+ getBudget: () => {
4524
+ const state = costTracker.getBudgetState();
4525
+ if (!state.sessionLimit) return null;
4526
+ return {
4527
+ remaining: Math.max(0, state.sessionLimit - state.sessionUsed),
4528
+ limit: state.sessionLimit
4529
+ };
4530
+ },
4531
+ compact: async () => {
4532
+ const models = router.getModels();
4533
+ const activeModel = preferredModelId ? models.find((m) => m.id === preferredModelId) : models[0];
4534
+ const contextWindow = activeModel?.limits?.contextWindow || 128e3;
4535
+ const cb = buildCompactionCallbacks(sessionManager);
4536
+ await cb.compact({ contextWindow });
4537
+ },
4538
+ dream: async () => {
4539
+ const { MemoryManager, DREAM_SYSTEM_PROMPT, buildDreamPrompt } = await import("@brainst0rm/core");
4540
+ const memory = new MemoryManager(projectPath);
4541
+ const rawFiles = memory.getRawFiles();
4542
+ if (rawFiles.length === 0)
4543
+ return "No memory files to consolidate.";
4544
+ const dreamPrompt = buildDreamPrompt(
4545
+ memory.getMemoryDir(),
4546
+ rawFiles
4547
+ );
4548
+ const result = await spawnSubagent(dreamPrompt, {
4549
+ config,
4550
+ registry,
4551
+ router,
4552
+ costTracker,
4553
+ tools,
4554
+ projectPath,
4555
+ type: "code",
4556
+ systemPrompt: DREAM_SYSTEM_PROMPT,
4557
+ maxSteps: 12,
4558
+ budgetLimit: 0.5
4559
+ });
4560
+ return `Dream complete. ${result.toolCalls.length} tool calls, $${result.cost.toFixed(4)}.
4561
+ ${result.text}`;
4562
+ },
4563
+ vault: async (action, args) => {
4564
+ const vault = new BrainstormVault(VAULT_PATH);
4565
+ switch (action) {
4566
+ case "list":
4567
+ case "ls": {
4568
+ if (!vault.exists())
4569
+ return "No vault found. Run `brainstorm vault init` to create one.";
4570
+ const keys = vault.list();
4571
+ if (keys.length === 0)
4572
+ return "Vault is empty (or locked). Keys: none";
4573
+ return `Vault keys (${keys.length}):
4574
+ ${keys.map((k) => ` - ${k}`).join("\n")}`;
4575
+ }
4576
+ case "status": {
4577
+ if (!vault.exists()) return "Vault: not initialized";
4578
+ return `Vault: ${VAULT_PATH}
4579
+ Status: ${vault.isOpen() ? "unlocked" : "locked"}
4580
+ Keys: ${vault.list().length}`;
4581
+ }
4582
+ case "get": {
4583
+ if (!args) return "Usage: /vault get <key-name>";
4584
+ const val = vault.get(args);
4585
+ if (val === null)
4586
+ return `Key '${args}' not found (or vault is locked).`;
4587
+ return `${args} = ${val.slice(0, 8)}${"*".repeat(Math.max(0, val.length - 8))}`;
4588
+ }
4589
+ case "add":
4590
+ case "set": {
4591
+ return "Use `brainstorm vault add <name>` from the terminal \u2014 requires interactive password input.";
4592
+ }
4593
+ case "remove":
4594
+ case "rm":
4595
+ case "delete": {
4596
+ return "Use `brainstorm vault remove <name>` from the terminal \u2014 requires interactive password input.";
4597
+ }
4598
+ default:
4599
+ return "Usage: /vault [list|status|get <name>]\nFor add/remove, use the `brainstorm vault` CLI command.";
4600
+ }
4601
+ }
4602
+ }
4603
+ })
4604
+ );
4605
+ }
4606
+ );
4607
+ function run() {
4608
+ const cleanup = () => {
4609
+ try {
4610
+ stopDockerSandbox();
4611
+ } catch {
4612
+ }
4613
+ try {
4614
+ closeDb();
4615
+ } catch {
4616
+ }
4617
+ };
4618
+ process.on("SIGTERM", () => {
4619
+ cleanup();
4620
+ process.exit(0);
4621
+ });
4622
+ process.on("SIGINT", () => {
4623
+ cleanup();
4624
+ process.exit(0);
4625
+ });
4626
+ process.on("exit", () => {
4627
+ cleanup();
4628
+ });
4629
+ program.parse();
4630
+ }
4631
+ run();
4632
+ export {
4633
+ run
4634
+ };
4635
+ //# sourceMappingURL=index.js.map