@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.
- package/README.md +32 -0
- package/dist/App-DPXJYXKH.js +2794 -0
- package/dist/App-DPXJYXKH.js.map +1 -0
- package/dist/App-SSKWB7CT.js +2795 -0
- package/dist/App-SSKWB7CT.js.map +1 -0
- package/dist/brainstorm.js +4636 -0
- package/dist/brainstorm.js.map +1 -0
- package/dist/chunk-2CHZHDIM.js +391 -0
- package/dist/chunk-2CHZHDIM.js.map +1 -0
- package/dist/chunk-55ITCWZZ.js +1307 -0
- package/dist/chunk-55ITCWZZ.js.map +1 -0
- package/dist/chunk-5NA3GH6X.js +1308 -0
- package/dist/chunk-5NA3GH6X.js.map +1 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/chunk-D474E47D.js +148 -0
- package/dist/chunk-D474E47D.js.map +1 -0
- package/dist/chunk-GJXEX2A3.js +146 -0
- package/dist/chunk-GJXEX2A3.js.map +1 -0
- package/dist/chunk-OVGL3NJQ.js +307 -0
- package/dist/chunk-OVGL3NJQ.js.map +1 -0
- package/dist/chunk-VY6MPJXL.js +389 -0
- package/dist/chunk-VY6MPJXL.js.map +1 -0
- package/dist/chunk-YWXOPUDW.js +305 -0
- package/dist/chunk-YWXOPUDW.js.map +1 -0
- package/dist/chunk-ZWE3DS7E.js +39 -0
- package/dist/chunk-ZWE3DS7E.js.map +1 -0
- package/dist/dist-DUDO3RDM.js +9573 -0
- package/dist/dist-DUDO3RDM.js.map +1 -0
- package/dist/dist-GNHTH2DH.js +292 -0
- package/dist/dist-GNHTH2DH.js.map +1 -0
- package/dist/dist-JUDVPE7G.js +293 -0
- package/dist/dist-JUDVPE7G.js.map +1 -0
- package/dist/dist-V5DTSTKJ.js +9572 -0
- package/dist/dist-V5DTSTKJ.js.map +1 -0
- package/dist/dist-WLTQTLFO.js +14 -0
- package/dist/dist-WLTQTLFO.js.map +1 -0
- package/dist/dist-YIGU37Q2.js +15 -0
- package/dist/dist-YIGU37Q2.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4635 -0
- package/dist/index.js.map +1 -0
- package/dist/recorder-D6ILEOZP.js +67 -0
- package/dist/recorder-D6ILEOZP.js.map +1 -0
- package/dist/recorder-SPYYF4DL.js +66 -0
- package/dist/recorder-SPYYF4DL.js.map +1 -0
- package/dist/roles-2DGF4PZU.js +16 -0
- package/dist/roles-2DGF4PZU.js.map +1 -0
- package/dist/roles-UIPX7GBC.js +17 -0
- package/dist/roles-UIPX7GBC.js.map +1 -0
- package/dist/slash-PDWKCZOQ.js +13 -0
- package/dist/slash-PDWKCZOQ.js.map +1 -0
- package/dist/slash-ZDC4DKL4.js +14 -0
- package/dist/slash-ZDC4DKL4.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1307 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ROLES,
|
|
3
|
+
formatModelMenu,
|
|
4
|
+
formatRoleConfirmation,
|
|
5
|
+
getModelForRole
|
|
6
|
+
} from "./chunk-YWXOPUDW.js";
|
|
7
|
+
|
|
8
|
+
// src/commands/build-wizard.ts
|
|
9
|
+
import { autoSelectPreset, getPresetWorkflow } from "@brainst0rm/workflow";
|
|
10
|
+
var ROLE_FOR_AGENT = {
|
|
11
|
+
architect: "architect",
|
|
12
|
+
coder: "sr-developer",
|
|
13
|
+
reviewer: "qa",
|
|
14
|
+
debugger: "sr-developer",
|
|
15
|
+
analyst: "architect",
|
|
16
|
+
orchestrator: "architect",
|
|
17
|
+
"product-manager": "product-manager"
|
|
18
|
+
};
|
|
19
|
+
function createWizardState() {
|
|
20
|
+
return {
|
|
21
|
+
step: "describe",
|
|
22
|
+
description: "",
|
|
23
|
+
detectedPreset: null,
|
|
24
|
+
workflow: null,
|
|
25
|
+
assignments: [],
|
|
26
|
+
currentAssignIdx: 0,
|
|
27
|
+
totalCost: 0,
|
|
28
|
+
complexity: "moderate"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function processDescription(state, description, classify) {
|
|
32
|
+
const detectedPreset = autoSelectPreset(description) ?? "implement-feature";
|
|
33
|
+
const workflow = getPresetWorkflow(detectedPreset);
|
|
34
|
+
if (!workflow) {
|
|
35
|
+
return { ...state, step: "describe", description };
|
|
36
|
+
}
|
|
37
|
+
let complexity = "moderate";
|
|
38
|
+
if (classify) {
|
|
39
|
+
try {
|
|
40
|
+
const profile = classify(description);
|
|
41
|
+
complexity = profile.complexity ?? "moderate";
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const assignments = workflow.steps.map((step) => {
|
|
46
|
+
const roleId = ROLE_FOR_AGENT[step.agentRole] ?? "sr-developer";
|
|
47
|
+
const defaultModel = getDefaultModelForStep(roleId, complexity);
|
|
48
|
+
const pricing = getModelPricing(defaultModel.modelId);
|
|
49
|
+
const estimatedCost = estimateStepCost(complexity, pricing);
|
|
50
|
+
return {
|
|
51
|
+
stepId: step.id,
|
|
52
|
+
stepRole: step.agentRole,
|
|
53
|
+
modelId: defaultModel.modelId,
|
|
54
|
+
modelLabel: defaultModel.label,
|
|
55
|
+
estimatedCost
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
const totalCost = assignments.reduce((sum, a) => sum + a.estimatedCost, 0);
|
|
59
|
+
return {
|
|
60
|
+
...state,
|
|
61
|
+
step: "confirm",
|
|
62
|
+
description,
|
|
63
|
+
detectedPreset,
|
|
64
|
+
workflow,
|
|
65
|
+
assignments,
|
|
66
|
+
currentAssignIdx: 0,
|
|
67
|
+
totalCost,
|
|
68
|
+
complexity
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function getModelChoicesForStep(agentRole) {
|
|
72
|
+
const roleId = ROLE_FOR_AGENT[agentRole] ?? "sr-developer";
|
|
73
|
+
const role = ROLES[roleId];
|
|
74
|
+
return role?.modelChoices ?? [];
|
|
75
|
+
}
|
|
76
|
+
function getDefaultModelForStep(roleId, complexity) {
|
|
77
|
+
const role = ROLES[roleId];
|
|
78
|
+
if (!role)
|
|
79
|
+
return { modelId: "brainstormrouter/auto", label: "Auto", cost: "$0" };
|
|
80
|
+
if (complexity === "trivial" || complexity === "simple") {
|
|
81
|
+
return role.modelChoices[role.modelChoices.length - 1] ?? role.modelChoices[0];
|
|
82
|
+
}
|
|
83
|
+
return role.modelChoices.find((m) => m.default) ?? role.modelChoices[0];
|
|
84
|
+
}
|
|
85
|
+
var MODEL_PRICING = {
|
|
86
|
+
"anthropic/claude-opus-4-6": { input: 15, output: 75 },
|
|
87
|
+
"anthropic/claude-sonnet-4-6": { input: 3, output: 15 },
|
|
88
|
+
"anthropic/claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
|
|
89
|
+
"openai/gpt-5.4": { input: 2.5, output: 10 },
|
|
90
|
+
"openai/gpt-4.1-mini": { input: 0.4, output: 1.6 },
|
|
91
|
+
"google/gemini-3.1-pro": { input: 1.25, output: 5 },
|
|
92
|
+
"google/gemini-3.1-flash": { input: 0.15, output: 0.6 },
|
|
93
|
+
"deepseek/deepseek-chat": { input: 0.27, output: 1.1 },
|
|
94
|
+
"moonshot/kimi-k2.5": { input: 0.6, output: 2.4 }
|
|
95
|
+
};
|
|
96
|
+
function getModelPricing(modelId) {
|
|
97
|
+
return MODEL_PRICING[modelId] ?? { input: 1, output: 4 };
|
|
98
|
+
}
|
|
99
|
+
var COMPLEXITY_TOKENS = {
|
|
100
|
+
trivial: { input: 500, output: 200 },
|
|
101
|
+
simple: { input: 1e3, output: 500 },
|
|
102
|
+
moderate: { input: 3e3, output: 1500 },
|
|
103
|
+
complex: { input: 8e3, output: 4e3 },
|
|
104
|
+
expert: { input: 15e3, output: 8e3 }
|
|
105
|
+
};
|
|
106
|
+
function estimateStepCost(complexity, pricing) {
|
|
107
|
+
const tokens = COMPLEXITY_TOKENS[complexity] ?? COMPLEXITY_TOKENS.moderate;
|
|
108
|
+
return tokens.input / 1e6 * pricing.input + tokens.output / 1e6 * pricing.output;
|
|
109
|
+
}
|
|
110
|
+
function updateAssignment(state, stepIdx, modelChoice) {
|
|
111
|
+
const assignments = [...state.assignments];
|
|
112
|
+
const pricing = getModelPricing(modelChoice.modelId);
|
|
113
|
+
assignments[stepIdx] = {
|
|
114
|
+
...assignments[stepIdx],
|
|
115
|
+
modelId: modelChoice.modelId,
|
|
116
|
+
modelLabel: modelChoice.label,
|
|
117
|
+
estimatedCost: estimateStepCost(state.complexity, pricing)
|
|
118
|
+
};
|
|
119
|
+
const totalCost = assignments.reduce((sum, a) => sum + a.estimatedCost, 0);
|
|
120
|
+
return { ...state, assignments, totalCost };
|
|
121
|
+
}
|
|
122
|
+
var ROLE_ICONS = {
|
|
123
|
+
architect: "\u{1F3D7}",
|
|
124
|
+
coder: "\u{1F468}\u200D\u{1F4BB}",
|
|
125
|
+
reviewer: "\u{1F50D}",
|
|
126
|
+
debugger: "\u{1F527}",
|
|
127
|
+
analyst: "\u{1F4CA}"
|
|
128
|
+
};
|
|
129
|
+
function formatPipeline(state) {
|
|
130
|
+
if (!state.workflow || state.assignments.length === 0) return "";
|
|
131
|
+
const lines = [];
|
|
132
|
+
lines.push(`${state.detectedPreset}`);
|
|
133
|
+
for (let i = 0; i < state.assignments.length; i++) {
|
|
134
|
+
const a = state.assignments[i];
|
|
135
|
+
const step = state.workflow.steps[i];
|
|
136
|
+
const icon = ROLE_ICONS[a.stepRole] ?? "\u2699";
|
|
137
|
+
const shortModel = a.modelLabel.replace(/^Claude /, "").replace(/^GPT-/, "GPT-");
|
|
138
|
+
lines.push(
|
|
139
|
+
`${i + 1}. ${icon} ${a.stepRole} \u2192 ${shortModel} ~$${a.estimatedCost.toFixed(3)}`
|
|
140
|
+
);
|
|
141
|
+
if (step?.isReviewStep && step.loopBackTo) {
|
|
142
|
+
lines.push(` \u21BA loops to ${step.loopBackTo}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
lines.push(`Total: ~$${state.totalCost.toFixed(3)}`);
|
|
146
|
+
return lines.join("\n");
|
|
147
|
+
}
|
|
148
|
+
function buildWorkflowOverrides(state) {
|
|
149
|
+
const agentOverrides = {};
|
|
150
|
+
const stepModelOverrides = {};
|
|
151
|
+
for (const a of state.assignments) {
|
|
152
|
+
stepModelOverrides[a.stepId] = a.modelId;
|
|
153
|
+
}
|
|
154
|
+
return { agentOverrides, stepModelOverrides };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/commands/slash.ts
|
|
158
|
+
var commands = [
|
|
159
|
+
{
|
|
160
|
+
name: "help",
|
|
161
|
+
aliases: ["h", "?"],
|
|
162
|
+
description: "Show available slash commands",
|
|
163
|
+
usage: "/help [command]",
|
|
164
|
+
execute: (args) => {
|
|
165
|
+
if (args) {
|
|
166
|
+
const cmd = commandMap.get(args.toLowerCase());
|
|
167
|
+
if (!cmd)
|
|
168
|
+
return `Unknown command: /${args}. Type /help for all commands.`;
|
|
169
|
+
const aliases = cmd.aliases.length > 0 ? `
|
|
170
|
+
Aliases: ${cmd.aliases.map((a) => "/" + a).join(", ")}` : "";
|
|
171
|
+
return `${cmd.usage}
|
|
172
|
+
${cmd.description}${aliases}`;
|
|
173
|
+
}
|
|
174
|
+
const lines = [
|
|
175
|
+
"Commands:",
|
|
176
|
+
"",
|
|
177
|
+
"Chat",
|
|
178
|
+
" /help [cmd] Show help (detail for specific command)",
|
|
179
|
+
" /model [name] Switch model",
|
|
180
|
+
" /strategy [name] Switch routing strategy",
|
|
181
|
+
" /mode [mode] Switch permission (auto/confirm/plan)",
|
|
182
|
+
" /style [style] Switch output style",
|
|
183
|
+
" /compact [focus] Compact context with optional focus",
|
|
184
|
+
" /context Show token breakdown",
|
|
185
|
+
" /cost Show session cost",
|
|
186
|
+
" /plan Toggle plan mode (describe before execute)",
|
|
187
|
+
" /efficiency Token usage and routing savings",
|
|
188
|
+
" /clear Clear conversation",
|
|
189
|
+
"",
|
|
190
|
+
"Roles",
|
|
191
|
+
" /architect [N] Deep thinking, read-only",
|
|
192
|
+
" /sr-developer [N] Quality implementation",
|
|
193
|
+
" /jr-developer [N] Fast, cheap coding",
|
|
194
|
+
" /qa [N] Testing and review",
|
|
195
|
+
" /role Show current role",
|
|
196
|
+
" /default Reset to defaults",
|
|
197
|
+
"",
|
|
198
|
+
"Build",
|
|
199
|
+
" /build [desc] Multi-model workflow wizard",
|
|
200
|
+
" /build-go Execute pending pipeline",
|
|
201
|
+
" /build-customize See model options per step",
|
|
202
|
+
"",
|
|
203
|
+
"Intelligence",
|
|
204
|
+
" /recommend [type] Get model recommendation from BR",
|
|
205
|
+
" /stats Session analytics + BR usage",
|
|
206
|
+
"",
|
|
207
|
+
"System",
|
|
208
|
+
" /vault [action] Manage API keys",
|
|
209
|
+
" /dream Consolidate memory files",
|
|
210
|
+
"",
|
|
211
|
+
"Modes: Esc toggles Dashboard \u2502 Shift+Tab cycles permission"
|
|
212
|
+
];
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "model",
|
|
218
|
+
aliases: ["m"],
|
|
219
|
+
description: "Switch or show the active model",
|
|
220
|
+
usage: "/model [name]",
|
|
221
|
+
execute: (args, ctx) => {
|
|
222
|
+
if (!args) {
|
|
223
|
+
const current = ctx.getModel?.() ?? "auto (router-selected)";
|
|
224
|
+
return `Current model: ${current}`;
|
|
225
|
+
}
|
|
226
|
+
ctx.setModel?.(args);
|
|
227
|
+
return `Model switched to: ${args}`;
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "strategy",
|
|
232
|
+
aliases: ["fast"],
|
|
233
|
+
description: "Switch routing strategy",
|
|
234
|
+
usage: "/strategy [cost-first|quality-first|combined|capability|rule-based]",
|
|
235
|
+
execute: (args, ctx, invokedAs) => {
|
|
236
|
+
const valid = [
|
|
237
|
+
"cost-first",
|
|
238
|
+
"quality-first",
|
|
239
|
+
"combined",
|
|
240
|
+
"capability",
|
|
241
|
+
"rule-based"
|
|
242
|
+
];
|
|
243
|
+
if (!args && invokedAs === "fast") {
|
|
244
|
+
const current = ctx.getStrategy?.() ?? "combined";
|
|
245
|
+
const next = current === "cost-first" ? "quality-first" : "cost-first";
|
|
246
|
+
ctx.setStrategy?.(next);
|
|
247
|
+
return `Routing strategy: ${next}`;
|
|
248
|
+
}
|
|
249
|
+
if (!args) {
|
|
250
|
+
return `Current strategy: ${ctx.getStrategy?.() ?? "combined"}. Options: ${valid.join(", ")}`;
|
|
251
|
+
}
|
|
252
|
+
if (!valid.includes(args)) {
|
|
253
|
+
return `Unknown strategy: ${args}. Options: ${valid.join(", ")}`;
|
|
254
|
+
}
|
|
255
|
+
ctx.setStrategy?.(args);
|
|
256
|
+
return `Routing strategy: ${args}`;
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "mode",
|
|
261
|
+
aliases: [],
|
|
262
|
+
description: "Switch permission mode",
|
|
263
|
+
usage: "/mode [auto|confirm|plan]",
|
|
264
|
+
execute: (args, ctx) => {
|
|
265
|
+
const valid = ["auto", "confirm", "plan"];
|
|
266
|
+
if (!args) {
|
|
267
|
+
return `Current mode: ${ctx.getMode?.() ?? "confirm"}. Options: ${valid.join(", ")}`;
|
|
268
|
+
}
|
|
269
|
+
if (!valid.includes(args)) {
|
|
270
|
+
return `Invalid mode: ${args}. Options: ${valid.join(", ")}`;
|
|
271
|
+
}
|
|
272
|
+
ctx.setMode?.(args);
|
|
273
|
+
return `Permission mode: ${args}`;
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "cost",
|
|
278
|
+
aliases: ["$"],
|
|
279
|
+
description: "Show session cost so far",
|
|
280
|
+
usage: "/cost",
|
|
281
|
+
execute: (_args, ctx) => {
|
|
282
|
+
const cost = ctx.getSessionCost?.() ?? 0;
|
|
283
|
+
const tokens = ctx.getTokenCount?.() ?? { input: 0, output: 0 };
|
|
284
|
+
return `Session cost: $${cost.toFixed(4)}
|
|
285
|
+
Tokens: ${tokens.input.toLocaleString()} in / ${tokens.output.toLocaleString()} out`;
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "budget",
|
|
290
|
+
aliases: [],
|
|
291
|
+
description: "Show remaining budget",
|
|
292
|
+
usage: "/budget",
|
|
293
|
+
execute: (_args, ctx) => {
|
|
294
|
+
const budget = ctx.getBudget?.();
|
|
295
|
+
if (!budget) return "No budget limit set.";
|
|
296
|
+
const pct = (budget.remaining / budget.limit * 100).toFixed(1);
|
|
297
|
+
return `Budget: $${budget.remaining.toFixed(4)} remaining of $${budget.limit.toFixed(4)} (${pct}%)`;
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "clear",
|
|
302
|
+
aliases: [],
|
|
303
|
+
description: "Clear conversation history",
|
|
304
|
+
usage: "/clear",
|
|
305
|
+
execute: (_args, ctx) => {
|
|
306
|
+
ctx.clearHistory?.();
|
|
307
|
+
return "Conversation cleared.";
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "compact",
|
|
312
|
+
aliases: [],
|
|
313
|
+
description: "Compact context, optionally with focus instruction",
|
|
314
|
+
usage: "/compact [focus instruction]",
|
|
315
|
+
execute: async (args, ctx) => {
|
|
316
|
+
if (!ctx.compact) return "Compaction not available.";
|
|
317
|
+
await ctx.compact(args || void 0);
|
|
318
|
+
return args ? `Context compacted (focus: ${args})` : "Context compacted.";
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "style",
|
|
323
|
+
aliases: [],
|
|
324
|
+
description: "Switch output style",
|
|
325
|
+
usage: "/style [concise|detailed|learning]",
|
|
326
|
+
execute: (args, ctx) => {
|
|
327
|
+
const valid = ["concise", "detailed", "learning"];
|
|
328
|
+
if (!args) {
|
|
329
|
+
return `Current style: ${ctx.getOutputStyle?.() ?? "concise"}. Options: ${valid.join(", ")}`;
|
|
330
|
+
}
|
|
331
|
+
if (!valid.includes(args)) {
|
|
332
|
+
return `Invalid style: ${args}. Options: ${valid.join(", ")}`;
|
|
333
|
+
}
|
|
334
|
+
ctx.setOutputStyle?.(args);
|
|
335
|
+
return `Output style: ${args}`;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: "quit",
|
|
340
|
+
aliases: ["exit", "q"],
|
|
341
|
+
description: "Exit Brainstorm",
|
|
342
|
+
usage: "/quit",
|
|
343
|
+
execute: (_args, ctx) => {
|
|
344
|
+
ctx.exit?.();
|
|
345
|
+
return "Goodbye.";
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "vault",
|
|
350
|
+
aliases: ["keys"],
|
|
351
|
+
description: "Manage API keys in the encrypted vault",
|
|
352
|
+
usage: "/vault [list|add <name>|get <name>|remove <name>|status]",
|
|
353
|
+
execute: async (args, ctx) => {
|
|
354
|
+
if (!ctx.vault) return "Vault not available in this mode.";
|
|
355
|
+
const parts = args.split(/\s+/);
|
|
356
|
+
const action = parts[0] || "list";
|
|
357
|
+
const rest = parts.slice(1).join(" ");
|
|
358
|
+
return ctx.vault(action, rest);
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: "dream",
|
|
363
|
+
aliases: ["consolidate"],
|
|
364
|
+
description: "Consolidate memory files \u2014 merge duplicates, fix dates, prune stale refs",
|
|
365
|
+
usage: "/dream",
|
|
366
|
+
execute: async (_args, ctx) => {
|
|
367
|
+
if (!ctx.dream) return "Dream not available in this mode.";
|
|
368
|
+
return ctx.dream();
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "project",
|
|
373
|
+
aliases: ["proj"],
|
|
374
|
+
description: "Manage projects \u2014 switch, list, show dashboard",
|
|
375
|
+
usage: "/project [name|list|register|show <name>]",
|
|
376
|
+
execute: async (args, ctx) => {
|
|
377
|
+
const { ProjectManager } = await import("./dist-WLTQTLFO.js");
|
|
378
|
+
const { getDb } = await import("@brainst0rm/db");
|
|
379
|
+
const db = getDb();
|
|
380
|
+
const pm = new ProjectManager(db);
|
|
381
|
+
const parts = args.trim().split(/\s+/);
|
|
382
|
+
const action = parts[0] || "";
|
|
383
|
+
if (!action || action === "list") {
|
|
384
|
+
const projects = pm.projects.list();
|
|
385
|
+
if (projects.length === 0) {
|
|
386
|
+
return "No projects registered. Run: /project register or storm projects import ~/Projects";
|
|
387
|
+
}
|
|
388
|
+
const active = pm.getActive();
|
|
389
|
+
const lines = projects.map((p) => {
|
|
390
|
+
const marker = active && active.id === p.id ? " \u2190 active" : "";
|
|
391
|
+
return ` ${p.name.padEnd(25)} ${p.path}${marker}`;
|
|
392
|
+
});
|
|
393
|
+
return `Projects:
|
|
394
|
+
${lines.join("\n")}`;
|
|
395
|
+
}
|
|
396
|
+
if (action === "register") {
|
|
397
|
+
const path = parts[1] || process.cwd();
|
|
398
|
+
try {
|
|
399
|
+
const project = pm.register(path);
|
|
400
|
+
return `\u2713 Registered "${project.name}" \u2192 ${project.path}`;
|
|
401
|
+
} catch (err) {
|
|
402
|
+
return `\u2717 ${err instanceof Error ? err.message : String(err)}`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (action === "show") {
|
|
406
|
+
const name = parts[1];
|
|
407
|
+
if (!name) return "Usage: /project show <name>";
|
|
408
|
+
const project = pm.projects.getByName(name);
|
|
409
|
+
if (!project) return `Project "${name}" not found.`;
|
|
410
|
+
const dash = pm.dashboard(project.id);
|
|
411
|
+
if (!dash) return "Failed to load dashboard.";
|
|
412
|
+
const lines = [
|
|
413
|
+
`\u2500\u2500 ${project.name} \u2500\u2500`,
|
|
414
|
+
`Path: ${project.path}`,
|
|
415
|
+
project.description ? `Description: ${project.description}` : "",
|
|
416
|
+
`Sessions: ${dash.sessionCount}`,
|
|
417
|
+
`Cost today: $${dash.costToday.toFixed(4)}`,
|
|
418
|
+
`Cost month: $${dash.costThisMonth.toFixed(4)}`
|
|
419
|
+
].filter(Boolean);
|
|
420
|
+
if (project.budgetDaily)
|
|
421
|
+
lines.push(
|
|
422
|
+
`Budget daily: $${project.budgetDaily.toFixed(2)} (${dash.budgetDailyUsed.toFixed(0)}% used)`
|
|
423
|
+
);
|
|
424
|
+
if (project.budgetMonthly)
|
|
425
|
+
lines.push(
|
|
426
|
+
`Budget month: $${project.budgetMonthly.toFixed(2)} (${dash.budgetMonthlyUsed.toFixed(0)}% used)`
|
|
427
|
+
);
|
|
428
|
+
return lines.join("\n");
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const project = pm.switch(action);
|
|
432
|
+
ctx.rebuildSystemPrompt?.();
|
|
433
|
+
return `\u2713 Switched to "${project.name}" (${project.path})`;
|
|
434
|
+
} catch (err) {
|
|
435
|
+
return `\u2717 ${err instanceof Error ? err.message : String(err)}`;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "schedule",
|
|
441
|
+
aliases: ["sched", "cron"],
|
|
442
|
+
description: "Manage scheduled tasks for the active project",
|
|
443
|
+
usage: "/schedule [list|add|history]",
|
|
444
|
+
execute: async (args) => {
|
|
445
|
+
const parts = args.trim().split(/\s+/);
|
|
446
|
+
const action = parts[0] || "list";
|
|
447
|
+
if (action === "list" || !action) {
|
|
448
|
+
const { getDb } = await import("@brainst0rm/db");
|
|
449
|
+
const db = getDb();
|
|
450
|
+
const tasks = db.prepare(
|
|
451
|
+
"SELECT * FROM scheduled_tasks WHERE status = 'active' ORDER BY name"
|
|
452
|
+
).all();
|
|
453
|
+
if (tasks.length === 0) {
|
|
454
|
+
return 'No scheduled tasks. Use: storm schedule add "<prompt>" --project <name> --cron "0 9 * * *"';
|
|
455
|
+
}
|
|
456
|
+
const lines = tasks.map((t) => {
|
|
457
|
+
const cron = t.cron_expression || "one-shot";
|
|
458
|
+
const mutations = t.allow_mutations ? "read+write" : "read-only";
|
|
459
|
+
return ` ${t.name.padEnd(25)} ${cron.padEnd(15)} ${mutations.padEnd(12)} $${(t.budget_limit ?? 0).toFixed(2)}`;
|
|
460
|
+
});
|
|
461
|
+
return `Scheduled Tasks:
|
|
462
|
+
${lines.join("\n")}`;
|
|
463
|
+
}
|
|
464
|
+
if (action === "history") {
|
|
465
|
+
const { getDb } = await import("@brainst0rm/db");
|
|
466
|
+
const db = getDb();
|
|
467
|
+
const runs = db.prepare(
|
|
468
|
+
"SELECT r.*, t.name as task_name FROM scheduled_task_runs r JOIN scheduled_tasks t ON r.task_id = t.id ORDER BY r.created_at DESC LIMIT 10"
|
|
469
|
+
).all();
|
|
470
|
+
if (runs.length === 0) return "No task run history yet.";
|
|
471
|
+
const lines = runs.map((r) => {
|
|
472
|
+
const status = r.status === "completed" ? "\u2713" : r.status === "failed" ? "\u2717" : r.status;
|
|
473
|
+
const cost = `$${r.cost.toFixed(4)}`;
|
|
474
|
+
const date = new Date(r.created_at * 1e3).toLocaleDateString();
|
|
475
|
+
return ` ${status} ${(r.task_name ?? "").padEnd(20)} ${cost.padEnd(10)} ${date}`;
|
|
476
|
+
});
|
|
477
|
+
return `Recent Runs:
|
|
478
|
+
${lines.join("\n")}`;
|
|
479
|
+
}
|
|
480
|
+
return `Unknown action "${action}". Usage: /schedule [list|history]`;
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "orchestrate",
|
|
485
|
+
aliases: ["orch"],
|
|
486
|
+
description: "Coordinate work across multiple projects",
|
|
487
|
+
usage: '/orchestrate "<description>" [project1,project2,...]',
|
|
488
|
+
execute: async (args) => {
|
|
489
|
+
const { OrchestrationEngine } = await import("./dist-GNHTH2DH.js");
|
|
490
|
+
const { ProjectManager } = await import("./dist-WLTQTLFO.js");
|
|
491
|
+
const { getDb } = await import("@brainst0rm/db");
|
|
492
|
+
const db = getDb();
|
|
493
|
+
const engine = new OrchestrationEngine(db);
|
|
494
|
+
const pm = new ProjectManager(db);
|
|
495
|
+
if (!args.trim()) {
|
|
496
|
+
const runs = engine.listRecent(5);
|
|
497
|
+
if (runs.length === 0) {
|
|
498
|
+
return 'No orchestrations yet. Usage: /orchestrate "do something" project1,project2';
|
|
499
|
+
}
|
|
500
|
+
const lines2 = runs.map((r) => {
|
|
501
|
+
const icon = r.status === "completed" ? "\u2713" : r.status === "failed" ? "\u2717" : "\u25CF";
|
|
502
|
+
return ` ${icon} ${r.name.slice(0, 50)} \u2014 ${r.status} ($${r.totalCost.toFixed(4)})`;
|
|
503
|
+
});
|
|
504
|
+
return `Recent orchestrations:
|
|
505
|
+
${lines2.join("\n")}`;
|
|
506
|
+
}
|
|
507
|
+
const match = args.match(/^"([^"]+)"\s+(.+)$/) ?? args.match(/^(.+?)\s+([\w,-]+)$/);
|
|
508
|
+
if (!match) {
|
|
509
|
+
return 'Usage: /orchestrate "description" project1,project2';
|
|
510
|
+
}
|
|
511
|
+
const description = match[1];
|
|
512
|
+
const projectNames = match[2].split(",").map((s) => s.trim());
|
|
513
|
+
const lines = [
|
|
514
|
+
`Orchestrating: "${description}"`,
|
|
515
|
+
`Projects: ${projectNames.join(", ")}`,
|
|
516
|
+
""
|
|
517
|
+
];
|
|
518
|
+
for await (const event of engine.run({ description, projectNames })) {
|
|
519
|
+
if (event.type === "task-started") {
|
|
520
|
+
lines.push(`\u25CF ${event.project.name} \u2014 starting...`);
|
|
521
|
+
} else if (event.type === "task-completed") {
|
|
522
|
+
lines.push(`\u2713 ${event.project.name} \u2014 $${event.cost.toFixed(4)}`);
|
|
523
|
+
} else if (event.type === "task-failed") {
|
|
524
|
+
lines.push(`\u2717 ${event.project.name} \u2014 ${event.error}`);
|
|
525
|
+
} else if (event.type === "orchestration-completed") {
|
|
526
|
+
lines.push("", `Complete: $${event.run.totalCost.toFixed(4)} total`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return lines.join("\n");
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
name: "intelligence",
|
|
534
|
+
aliases: ["intel"],
|
|
535
|
+
description: "Show what BrainstormRouter has learned",
|
|
536
|
+
usage: "/intelligence [--json]",
|
|
537
|
+
execute: async (args, _ctx) => {
|
|
538
|
+
const { createGatewayClient, createIntelligenceClient } = await import("@brainst0rm/gateway");
|
|
539
|
+
const gw = createGatewayClient();
|
|
540
|
+
if (!gw) return "No BRAINSTORM_API_KEY set.";
|
|
541
|
+
const intel = createIntelligenceClient();
|
|
542
|
+
const asJson = args.includes("--json");
|
|
543
|
+
const [leaderboard, usage, waste, forecast] = await Promise.all([
|
|
544
|
+
gw.getLeaderboard().catch(() => []),
|
|
545
|
+
gw.getUsageSummary("weekly").catch(() => null),
|
|
546
|
+
gw.getWasteInsights().catch(() => null),
|
|
547
|
+
gw.getForecast().catch(() => null)
|
|
548
|
+
]);
|
|
549
|
+
if (asJson) {
|
|
550
|
+
return JSON.stringify({ leaderboard, usage, waste, forecast }, null, 2);
|
|
551
|
+
}
|
|
552
|
+
const lines = [];
|
|
553
|
+
lines.push("BrainstormRouter Intelligence Report");
|
|
554
|
+
lines.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
555
|
+
const ud = usage?.data?.[0];
|
|
556
|
+
const reqs = ud?.requestCount ?? 0;
|
|
557
|
+
lines.push(
|
|
558
|
+
`
|
|
559
|
+
Requests: ${reqs.toLocaleString()} | Cost: $${(ud?.totalCostUsd ?? 0).toFixed(2)}`
|
|
560
|
+
);
|
|
561
|
+
const real = leaderboard.filter(
|
|
562
|
+
(m) => m.id && !m.id.startsWith("cache/")
|
|
563
|
+
);
|
|
564
|
+
if (real.length > 0) {
|
|
565
|
+
lines.push("\nTop Models:");
|
|
566
|
+
for (const m of real.slice(0, 5)) {
|
|
567
|
+
const name = m.model_id ?? m.id ?? "?";
|
|
568
|
+
const reward = m.reward_score != null ? (m.reward_score * 100).toFixed(0) + "%" : "n/a";
|
|
569
|
+
lines.push(
|
|
570
|
+
` ${name} \u2014 reward:${reward} (${m.sample_count ?? 0} samples)`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const fc = forecast?.forecast;
|
|
575
|
+
if (fc) {
|
|
576
|
+
const trend = fc.trend === "increasing" ? "\u2191" : fc.trend === "decreasing" ? "\u2193" : "\u2192";
|
|
577
|
+
lines.push(
|
|
578
|
+
`
|
|
579
|
+
Forecast: $${(fc.avgDailySpendUsd ?? 0).toFixed(2)}/day ${trend}`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
const w = waste;
|
|
583
|
+
if (w?.estimatedWasteUsd > 0) {
|
|
584
|
+
lines.push(`
|
|
585
|
+
Recoverable waste: $${w.estimatedWasteUsd.toFixed(4)}`);
|
|
586
|
+
}
|
|
587
|
+
return lines.join("\n");
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "plan",
|
|
592
|
+
aliases: [],
|
|
593
|
+
description: "Toggle plan mode \u2014 agent describes changes before executing",
|
|
594
|
+
usage: "/plan",
|
|
595
|
+
execute: async (_args, ctx) => {
|
|
596
|
+
const current = ctx.getMode?.() ?? "auto";
|
|
597
|
+
if (current === "plan") {
|
|
598
|
+
ctx.setMode?.("auto");
|
|
599
|
+
return "Plan mode OFF \u2014 agent will execute directly.";
|
|
600
|
+
}
|
|
601
|
+
ctx.setMode?.("plan");
|
|
602
|
+
return "Plan mode ON \u2014 agent will describe changes before executing. Approve to proceed.";
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: "efficiency",
|
|
607
|
+
aliases: ["eff"],
|
|
608
|
+
description: "Show token efficiency and cost savings from routing",
|
|
609
|
+
usage: "/efficiency",
|
|
610
|
+
execute: async (_args, ctx) => {
|
|
611
|
+
const tokens = ctx.getTokenCount?.() ?? { input: 0, output: 0 };
|
|
612
|
+
const cost = ctx.getSessionCost?.() ?? 0;
|
|
613
|
+
const totalTokens = tokens.input + tokens.output;
|
|
614
|
+
const opusCostPer1M = 75;
|
|
615
|
+
const sonnetCostPer1M = 15;
|
|
616
|
+
const haikuCostPer1M = 4;
|
|
617
|
+
const opusCost = totalTokens / 1e6 * opusCostPer1M;
|
|
618
|
+
const sonnetCost = totalTokens / 1e6 * sonnetCostPer1M;
|
|
619
|
+
const haikuCost = totalTokens / 1e6 * haikuCostPer1M;
|
|
620
|
+
const savings = opusCost > 0 ? Math.round((1 - cost / opusCost) * 100) : 0;
|
|
621
|
+
const lines = [
|
|
622
|
+
"Token Efficiency Report",
|
|
623
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
624
|
+
"",
|
|
625
|
+
`Tokens used: ${totalTokens.toLocaleString()} (${tokens.input.toLocaleString()} in / ${tokens.output.toLocaleString()} out)`,
|
|
626
|
+
`Actual cost: $${cost.toFixed(4)} (with routing)`,
|
|
627
|
+
"",
|
|
628
|
+
"If you used a single model for everything:",
|
|
629
|
+
` Opus only: $${opusCost.toFixed(4)}`,
|
|
630
|
+
` Sonnet only: $${sonnetCost.toFixed(4)}`,
|
|
631
|
+
` Haiku only: $${haikuCost.toFixed(4)}`,
|
|
632
|
+
"",
|
|
633
|
+
`Savings vs Opus: ${savings}%`
|
|
634
|
+
];
|
|
635
|
+
return lines.join("\n");
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
];
|
|
639
|
+
function createRoleCommand(roleId) {
|
|
640
|
+
const role = ROLES[roleId];
|
|
641
|
+
return {
|
|
642
|
+
name: roleId,
|
|
643
|
+
aliases: [],
|
|
644
|
+
description: role.description,
|
|
645
|
+
usage: `/${roleId} [model-number]`,
|
|
646
|
+
execute: async (args, ctx) => {
|
|
647
|
+
let modelId;
|
|
648
|
+
if (args) {
|
|
649
|
+
modelId = getModelForRole(roleId, parseInt(args, 10));
|
|
650
|
+
} else if (ctx.prompt) {
|
|
651
|
+
const selected = await ctx.prompt(
|
|
652
|
+
`${role.icon} ${role.displayName} \u2014 pick model`,
|
|
653
|
+
role.modelChoices.map((m) => ({
|
|
654
|
+
label: m.label,
|
|
655
|
+
value: m.modelId,
|
|
656
|
+
description: `${m.cost} per 1M tokens`,
|
|
657
|
+
recommended: m.default
|
|
658
|
+
}))
|
|
659
|
+
);
|
|
660
|
+
modelId = selected;
|
|
661
|
+
} else {
|
|
662
|
+
return formatModelMenu(roleId);
|
|
663
|
+
}
|
|
664
|
+
ctx.setModel?.(modelId);
|
|
665
|
+
ctx.setOutputStyle?.(role.outputStyle);
|
|
666
|
+
ctx.setMode?.(role.permissionMode);
|
|
667
|
+
ctx.setStrategy?.(role.routingStrategy);
|
|
668
|
+
const { getRolePrompt } = await import("./roles-2DGF4PZU.js");
|
|
669
|
+
ctx.rebuildSystemPrompt?.(getRolePrompt(roleId, modelId));
|
|
670
|
+
ctx.setActiveRole?.(roleId);
|
|
671
|
+
return formatRoleConfirmation(roleId, modelId);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
for (const roleId of Object.keys(ROLES)) {
|
|
676
|
+
commands.push(createRoleCommand(roleId));
|
|
677
|
+
}
|
|
678
|
+
commands.push({
|
|
679
|
+
name: "role",
|
|
680
|
+
aliases: [],
|
|
681
|
+
description: "Show current role or list available roles",
|
|
682
|
+
usage: "/role",
|
|
683
|
+
execute: (_args, ctx) => {
|
|
684
|
+
const current = ctx.getActiveRole?.();
|
|
685
|
+
if (current && ROLES[current]) {
|
|
686
|
+
const role = ROLES[current];
|
|
687
|
+
const model = ctx.getModel?.() ?? "auto";
|
|
688
|
+
const lines2 = [
|
|
689
|
+
`${role.icon} ${role.displayName} (active)`,
|
|
690
|
+
` Model: ${model}`,
|
|
691
|
+
` Tools: ${role.allowedTools ? `only: ${role.allowedTools.join(", ")}` : role.blockedTools ? `all except: ${role.blockedTools.join(", ")}` : "all"}`,
|
|
692
|
+
` Style: ${role.outputStyle}`,
|
|
693
|
+
` Strategy: ${role.routingStrategy}`,
|
|
694
|
+
` Permission: ${role.permissionMode}`,
|
|
695
|
+
``,
|
|
696
|
+
`Models: ${role.modelChoices.map((m, i) => `${i + 1}.${m.label}`).join(", ")}`,
|
|
697
|
+
`Switch: /${current} N \u2502 /default to reset`
|
|
698
|
+
];
|
|
699
|
+
return lines2.join("\n");
|
|
700
|
+
}
|
|
701
|
+
const lines = Object.values(ROLES).map(
|
|
702
|
+
(r) => ` /${r.id.padEnd(18)} ${r.icon} ${r.displayName} \u2014 ${r.description}`
|
|
703
|
+
);
|
|
704
|
+
return `Available roles:
|
|
705
|
+
${lines.join("\n")}
|
|
706
|
+
|
|
707
|
+
Usage: /<role> [model-number]`;
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
commands.push({
|
|
711
|
+
name: "default",
|
|
712
|
+
aliases: ["reset"],
|
|
713
|
+
description: "Reset to default session state (no role)",
|
|
714
|
+
usage: "/default",
|
|
715
|
+
execute: (_args, ctx) => {
|
|
716
|
+
ctx.setActiveRole?.(void 0);
|
|
717
|
+
ctx.rebuildSystemPrompt?.();
|
|
718
|
+
ctx.setMode?.("confirm");
|
|
719
|
+
ctx.setOutputStyle?.("concise");
|
|
720
|
+
ctx.setStrategy?.("combined");
|
|
721
|
+
return "Session reset to defaults.";
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
commands.push({
|
|
725
|
+
name: "context",
|
|
726
|
+
aliases: ["ctx"],
|
|
727
|
+
description: "Show context window token breakdown",
|
|
728
|
+
usage: "/context",
|
|
729
|
+
execute: (_args, ctx) => {
|
|
730
|
+
const tokens = ctx.getTokenCount?.() ?? { input: 0, output: 0 };
|
|
731
|
+
const total = tokens.input + tokens.output;
|
|
732
|
+
const limit = ctx.getContextWindow?.() ?? 128e3;
|
|
733
|
+
const percent = Math.round(total / limit * 100);
|
|
734
|
+
const barWidth = 20;
|
|
735
|
+
const filled = Math.round(percent / 100 * barWidth);
|
|
736
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
737
|
+
const lines = [
|
|
738
|
+
"Context Window Usage",
|
|
739
|
+
"",
|
|
740
|
+
` [${bar}] ${percent}%`,
|
|
741
|
+
"",
|
|
742
|
+
` Input tokens: ${tokens.input.toLocaleString()}`,
|
|
743
|
+
` Output tokens: ${tokens.output.toLocaleString()}`,
|
|
744
|
+
` Total: ${total.toLocaleString()} / ${limit.toLocaleString()}`,
|
|
745
|
+
` Remaining: ${(limit - total).toLocaleString()}`,
|
|
746
|
+
"",
|
|
747
|
+
percent >= 80 ? " \u26A0 Context is high. Run /compact to free space." : percent >= 60 ? " Consider running /compact soon." : " Context usage is healthy."
|
|
748
|
+
];
|
|
749
|
+
return lines.join("\n");
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
commands.push({
|
|
753
|
+
name: "undo",
|
|
754
|
+
aliases: [],
|
|
755
|
+
description: "Remove the last user message and assistant response",
|
|
756
|
+
usage: "/undo",
|
|
757
|
+
execute: (_args, ctx) => {
|
|
758
|
+
const removed = ctx.undoLastTurn?.() ?? 0;
|
|
759
|
+
if (removed === 0) return "Nothing to undo.";
|
|
760
|
+
return `Removed ${removed} message${removed > 1 ? "s" : ""}.`;
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
commands.push({
|
|
764
|
+
name: "changelog",
|
|
765
|
+
aliases: ["whatsnew"],
|
|
766
|
+
description: "Show recent features and changes",
|
|
767
|
+
usage: "/changelog",
|
|
768
|
+
execute: () => {
|
|
769
|
+
return [
|
|
770
|
+
"What's New in Brainstorm",
|
|
771
|
+
"",
|
|
772
|
+
"v11 \u2014 Claude Code Parity",
|
|
773
|
+
" /context Token breakdown with visual gauge",
|
|
774
|
+
" /insights Session intelligence + BR waste tips",
|
|
775
|
+
" /undo Remove last turn",
|
|
776
|
+
" /build Multi-model workflow wizard",
|
|
777
|
+
" Shift+Tab Cycle permission modes",
|
|
778
|
+
" SelectPrompt Model asks you to pick from options",
|
|
779
|
+
" Autocomplete Type / to see command suggestions",
|
|
780
|
+
" Multi-line End line with \\ to continue",
|
|
781
|
+
" Error badges [NETWORK] [BUDGET] [AUTH] with hints",
|
|
782
|
+
" ? Shortcut overlay (in non-chat modes)",
|
|
783
|
+
"",
|
|
784
|
+
"v10 \u2014 DeerFlow Gaps",
|
|
785
|
+
" Artifacts Workflow outputs persist to disk",
|
|
786
|
+
" Temporal System prompt includes current date",
|
|
787
|
+
" Style Detects comment style, JSDoc, line length",
|
|
788
|
+
" Test parsing vitest/jest/pytest structured results",
|
|
789
|
+
"",
|
|
790
|
+
"v9 \u2014 Build Wizard",
|
|
791
|
+
" /build desc Auto-detect workflow + assign models",
|
|
792
|
+
"",
|
|
793
|
+
"v7 \u2014 Dashboard",
|
|
794
|
+
" 4 modes: Chat [1] Dashboard [2] Models [3] Config [4]",
|
|
795
|
+
" BR leaderboard, waste detection, guardian audit",
|
|
796
|
+
"",
|
|
797
|
+
"v6 \u2014 Roles",
|
|
798
|
+
" /architect /sr-developer /jr-developer /qa"
|
|
799
|
+
].join("\n");
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
commands.push({
|
|
803
|
+
name: "insights",
|
|
804
|
+
aliases: [],
|
|
805
|
+
description: "Session intelligence \u2014 what Brainstorm learned",
|
|
806
|
+
usage: "/insights",
|
|
807
|
+
execute: async (_args, ctx) => {
|
|
808
|
+
const cost = ctx.getSessionCost?.() ?? 0;
|
|
809
|
+
const tokens = ctx.getTokenCount?.() ?? { input: 0, output: 0 };
|
|
810
|
+
const model = ctx.getModel?.() ?? "auto";
|
|
811
|
+
const strategy = ctx.getStrategy?.() ?? "combined";
|
|
812
|
+
const role = ctx.getActiveRole?.();
|
|
813
|
+
const lines = [
|
|
814
|
+
"Session Insights",
|
|
815
|
+
"",
|
|
816
|
+
` Model: ${model}`,
|
|
817
|
+
` Strategy: ${strategy}`,
|
|
818
|
+
` Cost: $${cost.toFixed(4)}`,
|
|
819
|
+
` Tokens: ${tokens.input.toLocaleString()} in / ${tokens.output.toLocaleString()} out`
|
|
820
|
+
];
|
|
821
|
+
if (role) lines.push(` Role: ${role}`);
|
|
822
|
+
const totalTokens = tokens.input + tokens.output;
|
|
823
|
+
if (totalTokens > 0) {
|
|
824
|
+
const costPer1k = cost / totalTokens * 1e3;
|
|
825
|
+
lines.push("");
|
|
826
|
+
lines.push(` Cost efficiency: $${costPer1k.toFixed(4)} per 1K tokens`);
|
|
827
|
+
}
|
|
828
|
+
if (ctx.gateway) {
|
|
829
|
+
try {
|
|
830
|
+
const waste = await ctx.gateway.getWasteInsights();
|
|
831
|
+
if (waste?.suggestions?.length > 0) {
|
|
832
|
+
lines.push("");
|
|
833
|
+
lines.push(" Optimization suggestions:");
|
|
834
|
+
for (const s of waste.suggestions.slice(0, 3)) {
|
|
835
|
+
lines.push(
|
|
836
|
+
` \u2192 ${s.description} (save ~$${s.savings_usd?.toFixed(2) ?? "?"})`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
} catch {
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return lines.join("\n");
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
commands.push({
|
|
847
|
+
name: "build",
|
|
848
|
+
aliases: ["b"],
|
|
849
|
+
description: "Multi-model workflow wizard \u2014 assemble a dev team",
|
|
850
|
+
usage: "/build [description]",
|
|
851
|
+
execute: async (args, ctx) => {
|
|
852
|
+
if (!args) {
|
|
853
|
+
return "Usage: /build <what you want to build>\nExample: /build add OAuth login";
|
|
854
|
+
}
|
|
855
|
+
let state = createWizardState();
|
|
856
|
+
state = processDescription(state, args);
|
|
857
|
+
if (!state.workflow) {
|
|
858
|
+
return `Could not detect workflow type for: "${args}"`;
|
|
859
|
+
}
|
|
860
|
+
if (ctx.prompt) {
|
|
861
|
+
const action = await ctx.prompt(
|
|
862
|
+
`Detected: ${state.detectedPreset} (${state.assignments.length} steps)`,
|
|
863
|
+
[
|
|
864
|
+
{
|
|
865
|
+
label: "Go with defaults",
|
|
866
|
+
value: "defaults",
|
|
867
|
+
description: formatPipeline(state),
|
|
868
|
+
recommended: true
|
|
869
|
+
},
|
|
870
|
+
{ label: "Customize models", value: "customize" },
|
|
871
|
+
{ label: "Cancel", value: "cancel" }
|
|
872
|
+
]
|
|
873
|
+
);
|
|
874
|
+
if (action === "cancel") return "Build cancelled.";
|
|
875
|
+
if (action === "customize") {
|
|
876
|
+
for (let i = 0; i < state.assignments.length; i++) {
|
|
877
|
+
const a = state.assignments[i];
|
|
878
|
+
const choices = getModelChoicesForStep(a.stepRole);
|
|
879
|
+
const selected = await ctx.prompt(
|
|
880
|
+
`Step ${i + 1}: ${a.stepRole}`,
|
|
881
|
+
choices.map((m) => ({
|
|
882
|
+
label: m.label,
|
|
883
|
+
value: m.modelId,
|
|
884
|
+
description: `${m.cost} per 1M tokens`,
|
|
885
|
+
recommended: m.modelId === a.modelId
|
|
886
|
+
}))
|
|
887
|
+
);
|
|
888
|
+
const choice = choices.find((m) => m.modelId === selected);
|
|
889
|
+
if (choice) state = updateAssignment(state, i, choice);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return `${formatPipeline(state)}
|
|
893
|
+
|
|
894
|
+
Ready to execute. Use /build-go to start.`;
|
|
895
|
+
}
|
|
896
|
+
_pendingWizard = state;
|
|
897
|
+
return `${formatPipeline(state)}
|
|
898
|
+
|
|
899
|
+
/build-go to run \u2502 /build-customize for options`;
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
var _pendingWizard = null;
|
|
903
|
+
commands.push({
|
|
904
|
+
name: "build-go",
|
|
905
|
+
aliases: ["bg"],
|
|
906
|
+
description: "Execute the pending build workflow",
|
|
907
|
+
usage: "/build-go",
|
|
908
|
+
execute: async (_args, ctx) => {
|
|
909
|
+
if (!_pendingWizard || !_pendingWizard.workflow) {
|
|
910
|
+
return "No pending build. Use /build <description> first.";
|
|
911
|
+
}
|
|
912
|
+
const state = _pendingWizard;
|
|
913
|
+
_pendingWizard = null;
|
|
914
|
+
const { stepModelOverrides } = buildWorkflowOverrides(state);
|
|
915
|
+
const lines = [`Executing: ${state.detectedPreset}`, ""];
|
|
916
|
+
for (const a of state.assignments) {
|
|
917
|
+
lines.push(
|
|
918
|
+
` ${ROLE_ICONS_SLASH[a.stepRole] ?? "\u2699"} ${a.stepRole}: ${a.modelLabel}`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
lines.push("");
|
|
922
|
+
lines.push(`Estimated cost: ~$${state.totalCost.toFixed(4)}`);
|
|
923
|
+
lines.push("");
|
|
924
|
+
lines.push("Workflow started \u2014 results will appear in chat.");
|
|
925
|
+
return lines.join("\n");
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
var ROLE_ICONS_SLASH = {
|
|
929
|
+
architect: "\u{1F3D7}",
|
|
930
|
+
coder: "\u{1F468}\u200D\u{1F4BB}",
|
|
931
|
+
reviewer: "\u{1F50D}",
|
|
932
|
+
debugger: "\u{1F527}",
|
|
933
|
+
analyst: "\u{1F4CA}"
|
|
934
|
+
};
|
|
935
|
+
commands.push({
|
|
936
|
+
name: "build-customize",
|
|
937
|
+
aliases: ["bc"],
|
|
938
|
+
description: "Show model options for each pipeline step",
|
|
939
|
+
usage: "/build-customize",
|
|
940
|
+
execute: (_args, ctx) => {
|
|
941
|
+
if (!_pendingWizard || !_pendingWizard.workflow) {
|
|
942
|
+
return "No pending build. Use /build <description> first.";
|
|
943
|
+
}
|
|
944
|
+
const lines = ["Model options per step:", ""];
|
|
945
|
+
for (let i = 0; i < _pendingWizard.assignments.length; i++) {
|
|
946
|
+
const a = _pendingWizard.assignments[i];
|
|
947
|
+
const choices = getModelChoicesForStep(a.stepRole);
|
|
948
|
+
lines.push(`${i + 1}. ${a.stepRole} [${a.modelLabel}]`);
|
|
949
|
+
choices.forEach((m, j) => {
|
|
950
|
+
const cur = m.modelId === a.modelId ? " \u2190" : "";
|
|
951
|
+
lines.push(` ${j + 1}. ${m.label} ${m.cost}${cur}`);
|
|
952
|
+
});
|
|
953
|
+
lines.push(` /build-set ${i + 1} N`);
|
|
954
|
+
lines.push("");
|
|
955
|
+
}
|
|
956
|
+
return lines.join("\n");
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
commands.push({
|
|
960
|
+
name: "build-set",
|
|
961
|
+
aliases: ["bs"],
|
|
962
|
+
description: "Set model for a pipeline step",
|
|
963
|
+
usage: "/build-set <step> <model-number>",
|
|
964
|
+
execute: (_args, ctx) => {
|
|
965
|
+
if (!_pendingWizard || !_pendingWizard.workflow) {
|
|
966
|
+
return "No pending build. Use /build <description> first.";
|
|
967
|
+
}
|
|
968
|
+
const parts = _args.split(/\s+/);
|
|
969
|
+
const stepIdx = parseInt(parts[0], 10) - 1;
|
|
970
|
+
const modelIdx = parseInt(parts[1], 10) - 1;
|
|
971
|
+
if (isNaN(stepIdx) || stepIdx < 0 || stepIdx >= _pendingWizard.assignments.length) {
|
|
972
|
+
return `Invalid step. Use 1-${_pendingWizard.assignments.length}.`;
|
|
973
|
+
}
|
|
974
|
+
const a = _pendingWizard.assignments[stepIdx];
|
|
975
|
+
const choices = getModelChoicesForStep(a.stepRole);
|
|
976
|
+
if (isNaN(modelIdx) || modelIdx < 0 || modelIdx >= choices.length) {
|
|
977
|
+
return `Invalid model. Use 1-${choices.length}.`;
|
|
978
|
+
}
|
|
979
|
+
_pendingWizard = updateAssignment(
|
|
980
|
+
_pendingWizard,
|
|
981
|
+
stepIdx,
|
|
982
|
+
choices[modelIdx]
|
|
983
|
+
);
|
|
984
|
+
const updated = _pendingWizard.assignments[stepIdx];
|
|
985
|
+
return `${a.stepRole} \u2192 ${updated.modelLabel}
|
|
986
|
+
|
|
987
|
+
${formatPipeline(_pendingWizard)}
|
|
988
|
+
|
|
989
|
+
/build-go to run`;
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
commands.push({
|
|
993
|
+
name: "plan",
|
|
994
|
+
aliases: [],
|
|
995
|
+
description: "Toggle plan mode \u2014 agent describes changes before executing",
|
|
996
|
+
usage: "/plan",
|
|
997
|
+
execute: (_args, ctx) => {
|
|
998
|
+
const current = ctx.getMode?.() ?? "confirm";
|
|
999
|
+
if (current === "plan") {
|
|
1000
|
+
ctx.setMode?.("confirm");
|
|
1001
|
+
return "Plan mode OFF \u2014 agent will execute changes directly (with confirmation).";
|
|
1002
|
+
}
|
|
1003
|
+
ctx.setMode?.("plan");
|
|
1004
|
+
return "Plan mode ON \u2014 agent will describe changes before executing. Approve to proceed.";
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
commands.push({
|
|
1008
|
+
name: "efficiency",
|
|
1009
|
+
aliases: ["eff"],
|
|
1010
|
+
description: "Show token efficiency and routing savings",
|
|
1011
|
+
usage: "/efficiency",
|
|
1012
|
+
execute: (_args, ctx) => {
|
|
1013
|
+
const tokens = ctx.getTokenCount?.() ?? { input: 0, output: 0 };
|
|
1014
|
+
const cost = ctx.getSessionCost?.() ?? 0;
|
|
1015
|
+
const totalTokens = tokens.input + tokens.output;
|
|
1016
|
+
const opusInputCost = tokens.input / 1e6 * 15;
|
|
1017
|
+
const opusOutputCost = tokens.output / 1e6 * 75;
|
|
1018
|
+
const opusCost = opusInputCost + opusOutputCost;
|
|
1019
|
+
const savings = opusCost > 0 ? (opusCost - cost) / opusCost * 100 : 0;
|
|
1020
|
+
const lines = [
|
|
1021
|
+
"Token Efficiency Report",
|
|
1022
|
+
"",
|
|
1023
|
+
` Input tokens: ${tokens.input.toLocaleString()}`,
|
|
1024
|
+
` Output tokens: ${tokens.output.toLocaleString()}`,
|
|
1025
|
+
` Total tokens: ${totalTokens.toLocaleString()}`,
|
|
1026
|
+
"",
|
|
1027
|
+
` Session cost: $${cost.toFixed(4)}`
|
|
1028
|
+
];
|
|
1029
|
+
if (totalTokens > 0) {
|
|
1030
|
+
lines.push(
|
|
1031
|
+
` Cost per 1K: $${(cost / totalTokens * 1e3).toFixed(4)}`
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
lines.push("");
|
|
1035
|
+
lines.push(` If you used Opus for everything: $${opusCost.toFixed(4)}`);
|
|
1036
|
+
lines.push(` With routing: $${cost.toFixed(4)}`);
|
|
1037
|
+
lines.push(
|
|
1038
|
+
` Savings: ${savings > 0 ? savings.toFixed(1) : "0"}%`
|
|
1039
|
+
);
|
|
1040
|
+
if (savings > 50) {
|
|
1041
|
+
lines.push("");
|
|
1042
|
+
lines.push(" Routing is saving you significant cost.");
|
|
1043
|
+
} else if (savings > 0) {
|
|
1044
|
+
lines.push("");
|
|
1045
|
+
lines.push(
|
|
1046
|
+
" Tip: Use /strategy cost-first for cheaper tasks to increase savings."
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
return lines.join("\n");
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
commands.push({
|
|
1053
|
+
name: "history",
|
|
1054
|
+
aliases: ["hist"],
|
|
1055
|
+
description: "Show recent input history",
|
|
1056
|
+
usage: "/history",
|
|
1057
|
+
execute: (_args, ctx) => {
|
|
1058
|
+
return "Input history:\n Use \u2191/\u2193 arrow keys to navigate previous inputs.\n History persists across sessions in ~/.brainstorm/input-history.json";
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
commands.push({
|
|
1062
|
+
name: "recommend",
|
|
1063
|
+
aliases: ["rec"],
|
|
1064
|
+
description: "Get model recommendation from BrainstormRouter",
|
|
1065
|
+
usage: "/recommend [task-type]",
|
|
1066
|
+
execute: async (args, ctx) => {
|
|
1067
|
+
if (!ctx.gateway) return "No BrainstormRouter API key configured.";
|
|
1068
|
+
try {
|
|
1069
|
+
const { IntelligenceAPIClient } = await import("@brainst0rm/gateway");
|
|
1070
|
+
const key = process.env._BR_RESOLVED_KEY ?? process.env.BRAINSTORM_API_KEY;
|
|
1071
|
+
if (!key) return "No BR API key available.";
|
|
1072
|
+
const intel = new IntelligenceAPIClient(
|
|
1073
|
+
"https://api.brainstormrouter.com",
|
|
1074
|
+
key
|
|
1075
|
+
);
|
|
1076
|
+
const taskType = args || "code-generation";
|
|
1077
|
+
const recs = await intel.getRecommendations(taskType, "typescript");
|
|
1078
|
+
if (!recs || recs.length === 0)
|
|
1079
|
+
return `No recommendations for task type: ${taskType}`;
|
|
1080
|
+
const lines = recs.slice(0, 3).map((r, i) => {
|
|
1081
|
+
return ` ${i + 1}. ${r.recommendedModel ?? r.model} (${Math.round((r.confidence ?? r.score ?? 0) * 100)}% confidence)
|
|
1082
|
+
${r.reasoning ?? ""}`;
|
|
1083
|
+
});
|
|
1084
|
+
return `Model recommendations for "${taskType}":
|
|
1085
|
+
${lines.join("\n")}
|
|
1086
|
+
|
|
1087
|
+
Use: /model <id> to switch`;
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
return `Recommendation failed: ${err.message}`;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
commands.push({
|
|
1094
|
+
name: "stats",
|
|
1095
|
+
aliases: [],
|
|
1096
|
+
description: "Show session analytics and BR usage summary",
|
|
1097
|
+
usage: "/stats",
|
|
1098
|
+
execute: async (_args, ctx) => {
|
|
1099
|
+
const cost = ctx.getSessionCost?.() ?? 0;
|
|
1100
|
+
const tokens = ctx.getTokenCount?.() ?? { input: 0, output: 0 };
|
|
1101
|
+
const model = ctx.getModel?.() ?? "auto";
|
|
1102
|
+
const role = ctx.getActiveRole?.();
|
|
1103
|
+
const strategy = ctx.getStrategy?.() ?? "combined";
|
|
1104
|
+
const lines = [
|
|
1105
|
+
"Session Stats",
|
|
1106
|
+
` Cost: $${cost.toFixed(4)}`,
|
|
1107
|
+
` Tokens: ${tokens.input.toLocaleString()} in / ${tokens.output.toLocaleString()} out`,
|
|
1108
|
+
` Model: ${model}`,
|
|
1109
|
+
` Strategy: ${strategy}`
|
|
1110
|
+
];
|
|
1111
|
+
if (role) lines.push(` Role: ${role}`);
|
|
1112
|
+
if (ctx.gateway) {
|
|
1113
|
+
try {
|
|
1114
|
+
const summary = await ctx.gateway.getUsageSummary("daily");
|
|
1115
|
+
if (summary) {
|
|
1116
|
+
lines.push("");
|
|
1117
|
+
lines.push("BrainstormRouter (today)");
|
|
1118
|
+
lines.push(` Requests: ${summary.total_requests ?? 0}`);
|
|
1119
|
+
lines.push(
|
|
1120
|
+
` Cost: $${(summary.total_cost_usd ?? 0).toFixed(4)}`
|
|
1121
|
+
);
|
|
1122
|
+
if (summary.by_model?.length > 0) {
|
|
1123
|
+
lines.push(" By model:");
|
|
1124
|
+
for (const m of summary.by_model.slice(0, 5)) {
|
|
1125
|
+
lines.push(
|
|
1126
|
+
` ${m.model}: $${m.cost_usd?.toFixed(4) ?? "0"} (${m.request_count ?? 0} reqs)`
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
} catch {
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return lines.join("\n");
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
commands.push({
|
|
1138
|
+
name: "compare",
|
|
1139
|
+
aliases: [],
|
|
1140
|
+
description: "Compare two models side by side",
|
|
1141
|
+
usage: "/compare <model1> <model2>",
|
|
1142
|
+
execute: (_args, ctx) => {
|
|
1143
|
+
const parts = _args.split(/\s+/);
|
|
1144
|
+
if (parts.length < 2)
|
|
1145
|
+
return "Usage: /compare model1 model2\nExample: /compare anthropic/claude-sonnet-4-6 deepseek/deepseek-chat";
|
|
1146
|
+
return `Model comparison for: ${parts[0]} vs ${parts[1]}
|
|
1147
|
+
(Switch to Models mode [Esc \u2192 3] for detailed comparison with gauges)`;
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
commands.push({
|
|
1151
|
+
name: "architect-edit",
|
|
1152
|
+
aliases: ["ae", "dual"],
|
|
1153
|
+
description: "Dual-model mode: reasoning model plans, fast model applies (Aider-style)",
|
|
1154
|
+
usage: "/architect-edit [task description]",
|
|
1155
|
+
execute: async (args, ctx) => {
|
|
1156
|
+
if (!args.trim()) {
|
|
1157
|
+
return "Usage: /architect-edit <task description>\n\nA reasoning model (Opus/GPT-5.4) will plan the change, then a fast model (Sonnet/GPT-4.1) will apply it.";
|
|
1158
|
+
}
|
|
1159
|
+
const { ROLES: ROLES2, getRolePrompt } = await import("./roles-2DGF4PZU.js");
|
|
1160
|
+
const architect = ROLES2.architect;
|
|
1161
|
+
const planModel = architect.modelChoices[0]?.models[0] ?? "anthropic/claude-opus-4.6";
|
|
1162
|
+
ctx.setModel?.(planModel);
|
|
1163
|
+
ctx.setMode?.("plan");
|
|
1164
|
+
ctx.rebuildSystemPrompt?.(
|
|
1165
|
+
getRolePrompt("architect", planModel) + `
|
|
1166
|
+
|
|
1167
|
+
## Architect-Edit Mode
|
|
1168
|
+
|
|
1169
|
+
You are in Phase 1 (PLAN). Your job is to analyze the codebase and produce a detailed, actionable implementation plan. Do NOT make any changes \u2014 only read files and produce the plan. Include specific file paths, function names, and code snippets. After you present the plan, the user will approve it and a fast coding model will execute it.
|
|
1170
|
+
|
|
1171
|
+
Task: ${args.trim()}`
|
|
1172
|
+
);
|
|
1173
|
+
return [
|
|
1174
|
+
`\u{1F3D7}\uFE0F Architect-Edit mode activated`,
|
|
1175
|
+
``,
|
|
1176
|
+
` Phase 1 (PLAN): ${planModel}`,
|
|
1177
|
+
` Phase 2 (EDIT): will auto-switch to fast model after approval`,
|
|
1178
|
+
``,
|
|
1179
|
+
`Describe your changes and the architect will plan them.`,
|
|
1180
|
+
`After approving the plan, type /ae-apply to switch to the coding model.`
|
|
1181
|
+
].join("\n");
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
commands.push({
|
|
1185
|
+
name: "ae-apply",
|
|
1186
|
+
aliases: ["apply"],
|
|
1187
|
+
description: "Switch from architect plan phase to coding apply phase",
|
|
1188
|
+
usage: "/ae-apply",
|
|
1189
|
+
execute: async (_args, ctx) => {
|
|
1190
|
+
const { ROLES: ROLES2 } = await import("./roles-2DGF4PZU.js");
|
|
1191
|
+
const dev = ROLES2["sr-developer"];
|
|
1192
|
+
const codeModel = dev.modelChoices[0]?.models[0] ?? "anthropic/claude-sonnet-4.6";
|
|
1193
|
+
ctx.setModel?.(codeModel);
|
|
1194
|
+
ctx.setMode?.("auto");
|
|
1195
|
+
ctx.rebuildSystemPrompt?.();
|
|
1196
|
+
return [
|
|
1197
|
+
`\u26A1 Switched to coding model: ${codeModel}`,
|
|
1198
|
+
``,
|
|
1199
|
+
`The plan from the architect is in your conversation history.`,
|
|
1200
|
+
`Now implement it. Auto-verify after each edit.`
|
|
1201
|
+
].join("\n");
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
commands.push({
|
|
1205
|
+
name: "recipe",
|
|
1206
|
+
aliases: ["recipes"],
|
|
1207
|
+
description: "List, run, or init shareable YAML workflow recipes",
|
|
1208
|
+
usage: "/recipe [list|run <name>|init]",
|
|
1209
|
+
execute: async (args, ctx) => {
|
|
1210
|
+
const { listRecipes, loadRecipe, initRecipeDir } = await import("@brainst0rm/workflow");
|
|
1211
|
+
const cwd = process.cwd();
|
|
1212
|
+
const parts = args.trim().split(/\s+/);
|
|
1213
|
+
const subcommand = parts[0]?.toLowerCase() ?? "list";
|
|
1214
|
+
if (subcommand === "init") {
|
|
1215
|
+
const dir = initRecipeDir(cwd);
|
|
1216
|
+
return `Recipe directory initialized: ${dir}
|
|
1217
|
+
An example recipe was created. Edit it or add new .yaml files.`;
|
|
1218
|
+
}
|
|
1219
|
+
if (subcommand === "run") {
|
|
1220
|
+
const name = parts[1];
|
|
1221
|
+
if (!name) return "Usage: /recipe run <name>";
|
|
1222
|
+
const recipe = loadRecipe(cwd, name);
|
|
1223
|
+
if (!recipe)
|
|
1224
|
+
return `Recipe '${name}' not found. Run /recipe list to see available recipes.`;
|
|
1225
|
+
return [
|
|
1226
|
+
`Recipe loaded: ${recipe.name}`,
|
|
1227
|
+
` ${recipe.description}`,
|
|
1228
|
+
` Steps: ${recipe.steps.map((s) => s.id).join(" \u2192 ")}`,
|
|
1229
|
+
` Max iterations: ${recipe.maxIterations}`,
|
|
1230
|
+
``,
|
|
1231
|
+
`To execute, describe the task and the workflow engine will use this recipe.`
|
|
1232
|
+
].join("\n");
|
|
1233
|
+
}
|
|
1234
|
+
const recipes = listRecipes(cwd);
|
|
1235
|
+
if (recipes.length === 0) {
|
|
1236
|
+
return "No recipes found. Run /recipe init to create the recipe directory with an example.";
|
|
1237
|
+
}
|
|
1238
|
+
const lines = recipes.map(
|
|
1239
|
+
(r) => ` ${r.id.padEnd(20)} ${r.name} (${r.source}) \u2014 ${r.description}`
|
|
1240
|
+
);
|
|
1241
|
+
return `Available recipes:
|
|
1242
|
+
${lines.join("\n")}
|
|
1243
|
+
|
|
1244
|
+
Usage: /recipe run <name>`;
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
commands.push({
|
|
1248
|
+
name: "voice",
|
|
1249
|
+
aliases: ["mic"],
|
|
1250
|
+
description: "Record voice input and transcribe via Whisper (requires sox)",
|
|
1251
|
+
usage: "/voice",
|
|
1252
|
+
execute: async () => {
|
|
1253
|
+
const { AudioRecorder } = await import("./recorder-SPYYF4DL.js");
|
|
1254
|
+
if (!AudioRecorder.isAvailable()) {
|
|
1255
|
+
return "Voice input requires `sox`. Install: brew install sox (macOS) or apt install sox (Linux).";
|
|
1256
|
+
}
|
|
1257
|
+
const recorder = new AudioRecorder();
|
|
1258
|
+
const outputPath = recorder.start();
|
|
1259
|
+
return [
|
|
1260
|
+
"\u{1F399}\uFE0F Recording... Press Enter to stop.",
|
|
1261
|
+
"",
|
|
1262
|
+
` Audio file: ${outputPath}`,
|
|
1263
|
+
` Transcription will be sent via BrainstormRouter Whisper API.`,
|
|
1264
|
+
"",
|
|
1265
|
+
" Note: Full voice loop (record \u2192 transcribe \u2192 send) is coming in a future update.",
|
|
1266
|
+
" For now, the audio file is saved for manual transcription."
|
|
1267
|
+
].join("\n");
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
var commandMap = /* @__PURE__ */ new Map();
|
|
1271
|
+
for (const cmd of commands) {
|
|
1272
|
+
commandMap.set(cmd.name, cmd);
|
|
1273
|
+
for (const alias of cmd.aliases) {
|
|
1274
|
+
commandMap.set(alias, cmd);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function isSlashCommand(input) {
|
|
1278
|
+
const trimmed = input.trim();
|
|
1279
|
+
if (!trimmed.startsWith("/")) return false;
|
|
1280
|
+
const name = trimmed.slice(1).split(/\s+/)[0].toLowerCase();
|
|
1281
|
+
return commandMap.has(name);
|
|
1282
|
+
}
|
|
1283
|
+
async function executeSlashCommand(input, ctx) {
|
|
1284
|
+
const trimmed = input.trim();
|
|
1285
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
1286
|
+
const name = parts[0].toLowerCase();
|
|
1287
|
+
const args = parts.slice(1).join(" ");
|
|
1288
|
+
const cmd = commandMap.get(name);
|
|
1289
|
+
if (!cmd) {
|
|
1290
|
+
return `Unknown command: /${name}. Type /help for available commands.`;
|
|
1291
|
+
}
|
|
1292
|
+
return cmd.execute(args, ctx, name);
|
|
1293
|
+
}
|
|
1294
|
+
function getSlashCommands() {
|
|
1295
|
+
return commands.map(({ name, description, usage }) => ({
|
|
1296
|
+
name,
|
|
1297
|
+
description,
|
|
1298
|
+
usage
|
|
1299
|
+
}));
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
export {
|
|
1303
|
+
isSlashCommand,
|
|
1304
|
+
executeSlashCommand,
|
|
1305
|
+
getSlashCommands
|
|
1306
|
+
};
|
|
1307
|
+
//# sourceMappingURL=chunk-55ITCWZZ.js.map
|