@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
@@ -0,0 +1,2794 @@
1
+ import {
2
+ MarkdownRenderer
3
+ } from "./chunk-GJXEX2A3.js";
4
+ import {
5
+ executeSlashCommand,
6
+ getSlashCommands,
7
+ isSlashCommand
8
+ } from "./chunk-55ITCWZZ.js";
9
+ import "./chunk-YWXOPUDW.js";
10
+ import "./chunk-7D4SUZUM.js";
11
+
12
+ // src/components/App.tsx
13
+ import { useState as useState9, useRef as useRef2 } from "react";
14
+ import { Box as Box17, useApp as useApp2, useInput as useInput8 } from "ink";
15
+
16
+ // src/hooks/useMode.ts
17
+ import { useState, useCallback } from "react";
18
+ var MODE_ORDER = [
19
+ "chat",
20
+ "dashboard",
21
+ "models",
22
+ "config",
23
+ "planning"
24
+ ];
25
+ var MODE_LABELS = {
26
+ chat: { label: "Chat", key: "1", color: "green" },
27
+ dashboard: { label: "Dashboard", key: "2", color: "blue" },
28
+ models: { label: "Models", key: "3", color: "yellow" },
29
+ config: { label: "Config", key: "4", color: "magenta" },
30
+ planning: { label: "Planning", key: "5", color: "cyan" }
31
+ };
32
+ function useMode(initial = "chat") {
33
+ const [mode, setMode] = useState(initial);
34
+ const cycleMode = useCallback(() => {
35
+ setMode((prev) => {
36
+ const idx = MODE_ORDER.indexOf(prev);
37
+ return MODE_ORDER[(idx + 1) % MODE_ORDER.length];
38
+ });
39
+ }, []);
40
+ const setModeByKey = useCallback((key) => {
41
+ const idx = parseInt(key, 10) - 1;
42
+ if (idx >= 0 && idx < MODE_ORDER.length) {
43
+ setMode(MODE_ORDER[idx]);
44
+ return true;
45
+ }
46
+ return false;
47
+ }, []);
48
+ return { mode, setMode, cycleMode, setModeByKey };
49
+ }
50
+
51
+ // src/hooks/useBRData.ts
52
+ import { useState as useState2, useCallback as useCallback2 } from "react";
53
+ var EMPTY_DATA = {
54
+ leaderboard: [],
55
+ waste: null,
56
+ forecast: null,
57
+ audit: [],
58
+ dailyTrend: [],
59
+ lastFetched: 0,
60
+ loading: false,
61
+ error: null
62
+ };
63
+ function useBRData(gateway) {
64
+ const [data, setData] = useState2(EMPTY_DATA);
65
+ const refresh = useCallback2(async () => {
66
+ if (!gateway) {
67
+ setData((prev) => ({
68
+ ...prev,
69
+ error: "No BrainstormRouter API key configured"
70
+ }));
71
+ return;
72
+ }
73
+ setData((prev) => ({ ...prev, loading: true, error: null }));
74
+ try {
75
+ const [leaderboard, waste, forecast, audit, daily] = await Promise.allSettled([
76
+ gateway.getLeaderboard(),
77
+ gateway.getWasteInsights(),
78
+ gateway.getForecast(),
79
+ gateway.getCompletionAudit("24h"),
80
+ gateway.getDailyInsights()
81
+ ]);
82
+ setData({
83
+ leaderboard: leaderboard.status === "fulfilled" ? leaderboard.value : [],
84
+ waste: waste.status === "fulfilled" ? waste.value : null,
85
+ forecast: forecast.status === "fulfilled" ? forecast.value : null,
86
+ audit: audit.status === "fulfilled" ? audit.value.slice(0, 10) : [],
87
+ dailyTrend: daily.status === "fulfilled" ? daily.value.slice(0, 7) : [],
88
+ lastFetched: Date.now(),
89
+ loading: false,
90
+ error: null
91
+ });
92
+ } catch (err) {
93
+ setData((prev) => ({ ...prev, loading: false, error: err.message }));
94
+ }
95
+ }, [gateway]);
96
+ return { data, refresh };
97
+ }
98
+
99
+ // src/components/ModeBar.tsx
100
+ import { Box, Text } from "ink";
101
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
102
+ function ModeBar({
103
+ activeMode,
104
+ model,
105
+ cost,
106
+ role,
107
+ guardianStatus
108
+ }) {
109
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, children: [
110
+ /* @__PURE__ */ jsx(Box, { children: Object.entries(MODE_LABELS).map(([id, meta]) => {
111
+ const isActive = id === activeMode;
112
+ return /* @__PURE__ */ jsxs(Box, { marginRight: 1, children: [
113
+ /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: !isActive, children: "[" }),
114
+ /* @__PURE__ */ jsx(
115
+ Text,
116
+ {
117
+ color: isActive ? meta.color : "gray",
118
+ bold: isActive,
119
+ dimColor: !isActive,
120
+ children: meta.key
121
+ }
122
+ ),
123
+ /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: !isActive, children: "]" }),
124
+ /* @__PURE__ */ jsxs(
125
+ Text,
126
+ {
127
+ color: isActive ? meta.color : "gray",
128
+ bold: isActive,
129
+ dimColor: !isActive,
130
+ children: [
131
+ " ",
132
+ meta.label
133
+ ]
134
+ }
135
+ )
136
+ ] }, id);
137
+ }) }),
138
+ /* @__PURE__ */ jsxs(Box, { children: [
139
+ role && /* @__PURE__ */ jsxs(Fragment, { children: [
140
+ /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: role }),
141
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " \u2502 " })
142
+ ] }),
143
+ model && /* @__PURE__ */ jsxs(Fragment, { children: [
144
+ /* @__PURE__ */ jsx(Text, { color: "green", children: model }),
145
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " \u2502 " })
146
+ ] }),
147
+ /* @__PURE__ */ jsxs(Text, { color: cost && cost > 0.01 ? "yellow" : "green", children: [
148
+ "$",
149
+ (cost ?? 0).toFixed(4)
150
+ ] }),
151
+ guardianStatus && /* @__PURE__ */ jsxs(Fragment, { children: [
152
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " \u2502 " }),
153
+ /* @__PURE__ */ jsx(
154
+ Text,
155
+ {
156
+ color: guardianStatus === "safe" ? "green" : guardianStatus === "flagged" ? "yellow" : "red",
157
+ children: guardianStatus === "safe" ? "\u25CF" : "\u26A0"
158
+ }
159
+ )
160
+ ] })
161
+ ] })
162
+ ] });
163
+ }
164
+
165
+ // src/components/KeyHint.tsx
166
+ import { Box as Box2, Text as Text2 } from "ink";
167
+ import { jsx as jsx2 } from "react/jsx-runtime";
168
+ var HINTS = {
169
+ chat: "Esc dashboard \u2502 \u2191\u2193 history \u2502 Shift+\u2191\u2193 scroll \u2502 Ctrl+D\xD72 exit",
170
+ dashboard: "1-4 switch \u2502 Tab cycle \u2502 r refresh \u2502 Esc chat \u2502 Ctrl+D\xD72 exit",
171
+ models: "1-4 switch \u2502 \u2191\u2193 navigate \u2502 Enter select \u2502 Esc chat",
172
+ config: "1-5 switch \u2502 Esc chat \u2502 Ctrl+D\xD72 exit",
173
+ planning: "1-5 switch \u2502 \u2191\u2193/jk navigate \u2502 Enter expand \u2502 [] switch plan \u2502 Esc chat"
174
+ };
175
+ var PROCESSING_HINT = "Esc abort \u2502 Shift+\u2191\u2193 scroll";
176
+ function KeyHint({ mode, isProcessing }) {
177
+ return /* @__PURE__ */ jsx2(Box2, { paddingX: 2, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: isProcessing ? PROCESSING_HINT : HINTS[mode] }) });
178
+ }
179
+
180
+ // src/components/ChatApp.tsx
181
+ import {
182
+ useState as useState5,
183
+ useCallback as useCallback3,
184
+ useMemo as useMemo3,
185
+ useEffect,
186
+ useRef
187
+ } from "react";
188
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput3 } from "ink";
189
+ import TextInput from "ink-text-input";
190
+
191
+ // src/components/MessageList.tsx
192
+ import React, { useMemo } from "react";
193
+ import { Box as Box3, Text as Text3 } from "ink";
194
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
195
+ function MessageList({
196
+ messages,
197
+ maxHeight,
198
+ scrollOffset = 0
199
+ }) {
200
+ const visibleMessages = useMemo(() => {
201
+ if (!maxHeight || messages.length <= 5) return messages;
202
+ const estimatedVisible = Math.max(5, Math.floor(maxHeight / 3));
203
+ const startIdx = Math.max(
204
+ 0,
205
+ messages.length - estimatedVisible - scrollOffset
206
+ );
207
+ const endIdx = messages.length - scrollOffset;
208
+ return messages.slice(startIdx, endIdx > 0 ? endIdx : void 0);
209
+ }, [messages, maxHeight, scrollOffset]);
210
+ const hiddenAbove = messages.length - visibleMessages.length - scrollOffset;
211
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
212
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs2(Text3, { color: "gray", children: [
213
+ " ",
214
+ "\u2191 ",
215
+ hiddenAbove,
216
+ " earlier message",
217
+ hiddenAbove > 1 ? "s" : "",
218
+ " ",
219
+ "(Shift+\u2191 to scroll)"
220
+ ] }),
221
+ visibleMessages.map((msg, i) => /* @__PURE__ */ jsx3(MessageBubble, { message: msg }, i))
222
+ ] });
223
+ }
224
+ var MessageBubble = React.memo(function MessageBubble2({
225
+ message
226
+ }) {
227
+ switch (message.role) {
228
+ case "user":
229
+ return /* @__PURE__ */ jsxs2(
230
+ Box3,
231
+ {
232
+ marginBottom: 1,
233
+ borderStyle: "single",
234
+ borderColor: "blue",
235
+ borderLeft: true,
236
+ borderRight: false,
237
+ borderTop: false,
238
+ borderBottom: false,
239
+ paddingLeft: 1,
240
+ children: [
241
+ /* @__PURE__ */ jsxs2(Text3, { color: "blue", bold: true, children: [
242
+ "you",
243
+ " "
244
+ ] }),
245
+ /* @__PURE__ */ jsx3(Text3, { children: message.content })
246
+ ]
247
+ }
248
+ );
249
+ case "assistant":
250
+ return /* @__PURE__ */ jsxs2(
251
+ Box3,
252
+ {
253
+ flexDirection: "column",
254
+ marginBottom: 1,
255
+ borderStyle: "single",
256
+ borderColor: "green",
257
+ borderLeft: true,
258
+ borderRight: false,
259
+ borderTop: false,
260
+ borderBottom: false,
261
+ paddingLeft: 1,
262
+ children: [
263
+ /* @__PURE__ */ jsxs2(Box3, { children: [
264
+ /* @__PURE__ */ jsxs2(Text3, { color: "green", bold: true, children: [
265
+ "brainstorm",
266
+ " "
267
+ ] }),
268
+ message.model && /* @__PURE__ */ jsxs2(Text3, { color: "gray", dimColor: true, children: [
269
+ "[",
270
+ message.model,
271
+ "]",
272
+ " "
273
+ ] })
274
+ ] }),
275
+ /* @__PURE__ */ jsx3(Box3, { paddingLeft: 0, children: /* @__PURE__ */ jsx3(MarkdownRenderer, { content: message.content }) }),
276
+ message.cost !== void 0 && message.cost > 0 && /* @__PURE__ */ jsxs2(Text3, { color: "gray", dimColor: true, children: [
277
+ " ",
278
+ "$",
279
+ message.cost.toFixed(4)
280
+ ] })
281
+ ]
282
+ }
283
+ );
284
+ case "reasoning":
285
+ return /* @__PURE__ */ jsx3(Box3, { marginBottom: 0, paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text3, { color: "gray", dimColor: true, italic: true, children: [
286
+ "\u25B8",
287
+ " ",
288
+ message.content.length > 200 ? message.content.slice(0, 200) + "..." : message.content
289
+ ] }) });
290
+ case "routing": {
291
+ const content = message.content;
292
+ let icon = "\u2192";
293
+ let color = "gray";
294
+ if (content.startsWith("\u21BB")) {
295
+ icon = "";
296
+ color = "yellow";
297
+ } else if (content.startsWith("\u26A0")) {
298
+ icon = "";
299
+ color = "yellow";
300
+ } else if (content.includes("tool:") || content.includes("subagent")) {
301
+ icon = "\u2699";
302
+ } else if (content.includes("compacted")) {
303
+ icon = "\u25C7";
304
+ } else if (content.includes("[bg]")) {
305
+ icon = "\u25C6";
306
+ }
307
+ return /* @__PURE__ */ jsx3(Box3, { marginBottom: 0, paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text3, { color, dimColor: true, children: [
308
+ icon ? `${icon} ` : "",
309
+ content
310
+ ] }) });
311
+ }
312
+ case "error":
313
+ return /* @__PURE__ */ jsxs2(
314
+ Box3,
315
+ {
316
+ flexDirection: "column",
317
+ marginBottom: 1,
318
+ borderStyle: "single",
319
+ borderColor: "red",
320
+ borderLeft: true,
321
+ borderRight: false,
322
+ borderTop: false,
323
+ borderBottom: false,
324
+ paddingLeft: 1,
325
+ children: [
326
+ /* @__PURE__ */ jsxs2(Text3, { color: "red", bold: true, children: [
327
+ "error",
328
+ " "
329
+ ] }),
330
+ /* @__PURE__ */ jsx3(Text3, { children: message.content })
331
+ ]
332
+ }
333
+ );
334
+ default:
335
+ return null;
336
+ }
337
+ });
338
+
339
+ // src/components/TaskList.tsx
340
+ import { Box as Box4, Text as Text4 } from "ink";
341
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
342
+ var STATUS_ICONS = {
343
+ pending: { icon: "\u25CB", color: "gray" },
344
+ in_progress: { icon: "\u25C9", color: "yellow" },
345
+ completed: { icon: "\u25CF", color: "green" },
346
+ failed: { icon: "\u2717", color: "red" }
347
+ };
348
+ function TaskList({ tasks }) {
349
+ if (tasks.length === 0) return null;
350
+ const completed = tasks.filter((t) => t.status === "completed").length;
351
+ const failed = tasks.filter((t) => t.status === "failed").length;
352
+ const total = tasks.length;
353
+ const allDone = completed + failed === total;
354
+ if (allDone && completed === total) return null;
355
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingX: 1, marginBottom: 0, children: [
356
+ /* @__PURE__ */ jsxs3(Text4, { color: "gray", dimColor: true, children: [
357
+ " ",
358
+ "tasks (",
359
+ completed,
360
+ "/",
361
+ total,
362
+ " complete",
363
+ failed > 0 ? `, ${failed} failed` : "",
364
+ ")"
365
+ ] }),
366
+ tasks.map((task) => {
367
+ const { icon, color } = STATUS_ICONS[task.status] ?? STATUS_ICONS.pending;
368
+ const isDone = task.status === "completed";
369
+ return /* @__PURE__ */ jsxs3(Box4, { children: [
370
+ /* @__PURE__ */ jsx4(Text4, { color, children: ` ${icon} ` }),
371
+ /* @__PURE__ */ jsx4(Text4, { color: isDone ? "gray" : void 0, dimColor: isDone, children: task.description })
372
+ ] }, task.id);
373
+ })
374
+ ] });
375
+ }
376
+
377
+ // src/components/StreamingMessage.tsx
378
+ import React2 from "react";
379
+ import { Box as Box5, Text as Text5 } from "ink";
380
+ import Spinner from "ink-spinner";
381
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
382
+ var PHASE_LABELS = {
383
+ classifying: "Analyzing",
384
+ routing: "Selecting model",
385
+ connecting: "Connecting",
386
+ streaming: "Streaming"
387
+ };
388
+ var StreamingMessage = React2.memo(function StreamingMessage2({
389
+ content,
390
+ isStreaming,
391
+ phase,
392
+ model
393
+ }) {
394
+ if (!content && isStreaming) {
395
+ const label = phase ? PHASE_LABELS[phase] ?? phase : "Thinking";
396
+ return /* @__PURE__ */ jsxs4(Box5, { paddingLeft: 1, children: [
397
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: /* @__PURE__ */ jsx5(Spinner, { type: "dots" }) }),
398
+ /* @__PURE__ */ jsxs4(Text5, { color: "gray", dimColor: true, children: [
399
+ " ",
400
+ label,
401
+ model ? ` \xB7 ${model}` : "",
402
+ "..."
403
+ ] })
404
+ ] });
405
+ }
406
+ if (content && isStreaming) {
407
+ const MAX_STREAM_RENDER = 2e3;
408
+ const truncated = content.length > MAX_STREAM_RENDER;
409
+ let visibleContent = truncated ? content.slice(-MAX_STREAM_RENDER) : content;
410
+ if (truncated) {
411
+ const backtickCount = (visibleContent.match(/```/g) || []).length;
412
+ if (backtickCount % 2 === 1) {
413
+ const lastOpen = visibleContent.lastIndexOf("```");
414
+ if (lastOpen >= 0) {
415
+ if (lastOpen > 50) {
416
+ visibleContent = visibleContent.slice(0, lastOpen);
417
+ } else {
418
+ visibleContent += "\n```";
419
+ }
420
+ }
421
+ }
422
+ }
423
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
424
+ /* @__PURE__ */ jsxs4(Box5, { children: [
425
+ /* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "brainstorm " }),
426
+ model && /* @__PURE__ */ jsxs4(Text5, { color: "gray", dimColor: true, children: [
427
+ "[",
428
+ model,
429
+ "]",
430
+ " "
431
+ ] }),
432
+ truncated && /* @__PURE__ */ jsxs4(Text5, { color: "gray", dimColor: true, children: [
433
+ "(",
434
+ content.length,
435
+ " chars, showing tail)"
436
+ ] })
437
+ ] }),
438
+ /* @__PURE__ */ jsxs4(Box5, { paddingLeft: 0, children: [
439
+ /* @__PURE__ */ jsx5(MarkdownRenderer, { content: visibleContent }),
440
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "\u258C" })
441
+ ] })
442
+ ] });
443
+ }
444
+ return null;
445
+ });
446
+
447
+ // src/components/ToolCallDisplay.tsx
448
+ import { Box as Box6, Text as Text6 } from "ink";
449
+ import Spinner2 from "ink-spinner";
450
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
451
+ function summarizeArgs(toolName, args) {
452
+ switch (toolName) {
453
+ case "file_read":
454
+ case "file_write":
455
+ case "file_edit":
456
+ return String(args.file_path ?? args.path ?? "").split("/").slice(-2).join("/");
457
+ case "multi_edit":
458
+ case "batch_edit":
459
+ return `${Array.isArray(args.edits) ? args.edits.length : "?"} edits`;
460
+ case "shell":
461
+ return String(args.command ?? "").slice(0, 60);
462
+ case "grep":
463
+ return `/${args.pattern ?? ""}/ ${args.path ?? ""}`.slice(0, 50);
464
+ case "glob":
465
+ return String(args.pattern ?? "").slice(0, 50);
466
+ case "web_search":
467
+ return String(args.query ?? "").slice(0, 50);
468
+ case "web_fetch":
469
+ return String(args.url ?? "").slice(0, 50);
470
+ case "git_commit":
471
+ return String(args.message ?? "").slice(0, 50);
472
+ case "subagent":
473
+ return `[${args.type ?? "general"}] ${String(args.task ?? "").slice(0, 40)}`;
474
+ default:
475
+ return "";
476
+ }
477
+ }
478
+ function formatDuration(ms) {
479
+ if (ms < 1e3) return `${ms}ms`;
480
+ return `${(ms / 1e3).toFixed(1)}s`;
481
+ }
482
+ function ToolCallDisplay({ tool }) {
483
+ const argSummary = summarizeArgs(tool.toolName, tool.args);
484
+ if (tool.status === "running") {
485
+ const elapsed = Date.now() - tool.startTime;
486
+ return /* @__PURE__ */ jsxs5(Box6, { paddingLeft: 2, children: [
487
+ /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: /* @__PURE__ */ jsx6(Spinner2, { type: "dots" }) }),
488
+ /* @__PURE__ */ jsxs5(Text6, { color: "yellow", bold: true, children: [
489
+ " ",
490
+ tool.toolName
491
+ ] }),
492
+ argSummary && /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
493
+ " ",
494
+ argSummary
495
+ ] }),
496
+ /* @__PURE__ */ jsxs5(Text6, { color: "gray", dimColor: true, children: [
497
+ " ",
498
+ "(",
499
+ formatDuration(elapsed),
500
+ ")"
501
+ ] })
502
+ ] });
503
+ }
504
+ const icon = tool.ok !== false ? "\u2713" : "\u2717";
505
+ const color = tool.ok !== false ? "green" : "red";
506
+ return /* @__PURE__ */ jsxs5(Box6, { paddingLeft: 2, children: [
507
+ /* @__PURE__ */ jsx6(Text6, { color, children: icon }),
508
+ /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
509
+ " ",
510
+ tool.toolName
511
+ ] }),
512
+ argSummary && /* @__PURE__ */ jsxs5(Text6, { color: "gray", dimColor: true, children: [
513
+ " ",
514
+ argSummary
515
+ ] }),
516
+ tool.duration !== void 0 && /* @__PURE__ */ jsxs5(Text6, { color: "gray", dimColor: true, children: [
517
+ " ",
518
+ "(",
519
+ formatDuration(tool.duration),
520
+ ")"
521
+ ] })
522
+ ] });
523
+ }
524
+ function ToolCallList({ tools }) {
525
+ if (tools.length === 0) return null;
526
+ const running = tools.filter((t) => t.status === "running");
527
+ const completed = tools.filter((t) => t.status !== "running");
528
+ const hiddenCount = Math.max(0, completed.length - 2);
529
+ const recentCompleted = completed.slice(-2);
530
+ const visible = [...recentCompleted, ...running];
531
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 1, children: [
532
+ hiddenCount > 0 && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 2, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
533
+ hiddenCount,
534
+ " earlier tool call",
535
+ hiddenCount > 1 ? "s" : "",
536
+ " ",
537
+ "completed"
538
+ ] }) }),
539
+ visible.map((tool) => /* @__PURE__ */ jsx6(ToolCallDisplay, { tool }, tool.id))
540
+ ] });
541
+ }
542
+
543
+ // src/components/SelectPrompt.tsx
544
+ import { useState as useState3 } from "react";
545
+ import { Box as Box7, Text as Text7, useInput } from "ink";
546
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
547
+ function SelectPrompt({
548
+ message,
549
+ options,
550
+ onSelect,
551
+ onCancel,
552
+ multiSelect = false,
553
+ onMultiSelect
554
+ }) {
555
+ const [cursor, setCursor] = useState3(0);
556
+ const [selected, setSelected] = useState3(/* @__PURE__ */ new Set());
557
+ useInput((input, key) => {
558
+ if (key.downArrow || input === "j") {
559
+ setCursor((prev) => Math.min(prev + 1, options.length - 1));
560
+ }
561
+ if (key.upArrow || input === "k") {
562
+ setCursor((prev) => Math.max(prev - 1, 0));
563
+ }
564
+ if (key.escape) {
565
+ onCancel?.();
566
+ }
567
+ if (key.return) {
568
+ if (multiSelect && onMultiSelect) {
569
+ onMultiSelect(Array.from(selected));
570
+ } else {
571
+ onSelect(options[cursor].value);
572
+ }
573
+ }
574
+ if (input === " " && multiSelect) {
575
+ const val = options[cursor].value;
576
+ setSelected((prev) => {
577
+ const next = new Set(prev);
578
+ if (next.has(val)) next.delete(val);
579
+ else next.add(val);
580
+ return next;
581
+ });
582
+ }
583
+ });
584
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
585
+ /* @__PURE__ */ jsxs6(Box7, { marginBottom: 1, children: [
586
+ /* @__PURE__ */ jsxs6(Text7, { color: "cyan", bold: true, children: [
587
+ "\u25C6",
588
+ " "
589
+ ] }),
590
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: message })
591
+ ] }),
592
+ options.map((opt, i) => {
593
+ const isCursor = i === cursor;
594
+ const isSelected = selected.has(opt.value);
595
+ const indicator = multiSelect ? isSelected ? "\u25C9" : "\u25CB" : isCursor ? "\u25B8" : " ";
596
+ const indicatorColor = multiSelect ? isSelected ? "green" : "gray" : isCursor ? "cyan" : "gray";
597
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
598
+ /* @__PURE__ */ jsxs6(Box7, { children: [
599
+ /* @__PURE__ */ jsxs6(Text7, { color: indicatorColor, children: [
600
+ indicator,
601
+ " "
602
+ ] }),
603
+ /* @__PURE__ */ jsx7(Text7, { color: isCursor ? "white" : "gray", bold: isCursor, children: opt.label }),
604
+ opt.recommended && /* @__PURE__ */ jsxs6(Text7, { color: "green", dimColor: true, children: [
605
+ " ",
606
+ "(recommended)"
607
+ ] })
608
+ ] }),
609
+ opt.description && isCursor && /* @__PURE__ */ jsx7(Box7, { paddingLeft: 3, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: opt.description }) })
610
+ ] }, opt.value);
611
+ }),
612
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: multiSelect ? "\u2191\u2193 navigate \u2502 Space toggle \u2502 Enter confirm \u2502 Esc cancel" : "\u2191\u2193 navigate \u2502 Enter select \u2502 Esc cancel" }) })
613
+ ] });
614
+ }
615
+
616
+ // src/components/Autocomplete.tsx
617
+ import { useState as useState4, useMemo as useMemo2 } from "react";
618
+ import { Box as Box8, Text as Text8, useInput as useInput2 } from "ink";
619
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
620
+ function Autocomplete({
621
+ query,
622
+ items,
623
+ onAccept,
624
+ onDismiss,
625
+ maxVisible = 8
626
+ }) {
627
+ const [cursor, setCursor] = useState4(0);
628
+ const filtered = useMemo2(() => {
629
+ const q = query.toLowerCase();
630
+ return items.filter((item) => item.label.toLowerCase().includes(q)).slice(0, maxVisible);
631
+ }, [query, items, maxVisible]);
632
+ useInput2((input, key) => {
633
+ if (key.downArrow) {
634
+ setCursor((prev) => Math.min(prev + 1, filtered.length - 1));
635
+ return;
636
+ }
637
+ if (key.upArrow) {
638
+ setCursor((prev) => Math.max(prev - 1, 0));
639
+ return;
640
+ }
641
+ if (key.tab || key.return) {
642
+ if (filtered[cursor]) {
643
+ onAccept(filtered[cursor].label);
644
+ }
645
+ return;
646
+ }
647
+ if (key.escape) {
648
+ onDismiss();
649
+ return;
650
+ }
651
+ });
652
+ if (filtered.length === 0) return null;
653
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", paddingX: 2, marginBottom: 0, children: filtered.map((item, i) => {
654
+ const isActive = i === cursor;
655
+ return /* @__PURE__ */ jsxs7(Box8, { children: [
656
+ /* @__PURE__ */ jsx8(Text8, { color: isActive ? "cyan" : "gray", children: isActive ? "\u25B8 " : " " }),
657
+ /* @__PURE__ */ jsxs7(Text8, { color: isActive ? "white" : "gray", bold: isActive, children: [
658
+ item.prefix ?? "",
659
+ item.label
660
+ ] }),
661
+ item.description && /* @__PURE__ */ jsxs7(Text8, { color: "gray", dimColor: !isActive, children: [
662
+ " ",
663
+ item.description
664
+ ] })
665
+ ] }, item.label);
666
+ }) });
667
+ }
668
+
669
+ // src/keybindings.ts
670
+ var DEFAULT_KEYBINDINGS = [
671
+ {
672
+ action: "abort",
673
+ description: "Interrupt current operation",
674
+ match: (_input, key) => key.escape
675
+ },
676
+ {
677
+ action: "exit",
678
+ description: "Exit Brainstorm",
679
+ // Ink sends empty string for Ctrl+D when input is empty (standard EOF)
680
+ match: (input, key) => key.ctrl && (input === "d" || input === "")
681
+ },
682
+ {
683
+ action: "clear-screen",
684
+ description: "Clear terminal screen",
685
+ match: (input, key) => key.ctrl && (input === "l" || input === "\f")
686
+ },
687
+ {
688
+ action: "clear-chat",
689
+ description: "Clear conversation history",
690
+ match: (input, key) => key.ctrl && (input === "k" || input === "\v")
691
+ },
692
+ {
693
+ action: "cycle-mode",
694
+ description: "Cycle permission mode (auto \u2192 confirm \u2192 plan)",
695
+ match: (_input, key) => key.shift && key.tab
696
+ }
697
+ ];
698
+ function resolveKeyAction(input, key, bindings = DEFAULT_KEYBINDINGS) {
699
+ for (const binding of bindings) {
700
+ if (binding.match(input, key)) return binding.action;
701
+ }
702
+ return null;
703
+ }
704
+
705
+ // src/input-history.ts
706
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "fs";
707
+ import { join } from "path";
708
+ import { homedir } from "os";
709
+ var HISTORY_DIR = join(homedir(), ".brainstorm");
710
+ var HISTORY_FILE = join(HISTORY_DIR, "input-history.json");
711
+ var MAX_MEMORY = 100;
712
+ var MAX_PERSIST = 500;
713
+ var InputHistory = class {
714
+ entries = [];
715
+ cursor = -1;
716
+ draft = "";
717
+ constructor() {
718
+ this.load();
719
+ }
720
+ /**
721
+ * Add an input to history. Deduplicates consecutive identical entries.
722
+ */
723
+ push(input) {
724
+ const trimmed = input.trim();
725
+ if (!trimmed) return;
726
+ if (this.entries.length > 0 && this.entries[this.entries.length - 1] === trimmed) {
727
+ this.resetCursor();
728
+ return;
729
+ }
730
+ this.entries.push(trimmed);
731
+ if (this.entries.length > MAX_MEMORY) {
732
+ this.entries = this.entries.slice(-MAX_MEMORY);
733
+ }
734
+ this.resetCursor();
735
+ this.save();
736
+ }
737
+ /**
738
+ * Navigate up (older). Returns the entry to display, or null if at the end.
739
+ */
740
+ up(currentInput) {
741
+ if (this.entries.length === 0) return null;
742
+ if (this.cursor === -1) {
743
+ this.draft = currentInput;
744
+ }
745
+ const nextCursor = this.cursor === -1 ? this.entries.length - 1 : this.cursor - 1;
746
+ if (nextCursor < 0) return null;
747
+ this.cursor = nextCursor;
748
+ return this.entries[this.cursor];
749
+ }
750
+ /**
751
+ * Navigate down (newer). Returns the entry to display, or the draft if at bottom.
752
+ */
753
+ down() {
754
+ if (this.cursor === -1) return null;
755
+ this.cursor += 1;
756
+ if (this.cursor >= this.entries.length) {
757
+ this.cursor = -1;
758
+ return this.draft;
759
+ }
760
+ return this.entries[this.cursor];
761
+ }
762
+ /**
763
+ * Reset cursor position (called after submitting input).
764
+ */
765
+ resetCursor() {
766
+ this.cursor = -1;
767
+ this.draft = "";
768
+ }
769
+ /**
770
+ * Get all entries (for debugging/export).
771
+ */
772
+ getAll() {
773
+ return [...this.entries];
774
+ }
775
+ load() {
776
+ try {
777
+ if (existsSync(HISTORY_FILE)) {
778
+ const data = JSON.parse(readFileSync(HISTORY_FILE, "utf-8"));
779
+ if (Array.isArray(data)) {
780
+ this.entries = data.slice(-MAX_MEMORY);
781
+ }
782
+ }
783
+ } catch {
784
+ this.entries = [];
785
+ }
786
+ }
787
+ save() {
788
+ try {
789
+ if (!existsSync(HISTORY_DIR)) mkdirSync(HISTORY_DIR, { recursive: true });
790
+ let fullHistory = [];
791
+ try {
792
+ if (existsSync(HISTORY_FILE)) {
793
+ const data = JSON.parse(readFileSync(HISTORY_FILE, "utf-8"));
794
+ if (Array.isArray(data)) fullHistory = data;
795
+ }
796
+ } catch {
797
+ }
798
+ const merged = [...fullHistory];
799
+ for (const entry of this.entries) {
800
+ if (merged[merged.length - 1] !== entry) {
801
+ merged.push(entry);
802
+ }
803
+ }
804
+ const trimmed = merged.slice(-MAX_PERSIST);
805
+ const tmpFile = HISTORY_FILE + ".tmp";
806
+ writeFileSync(tmpFile, JSON.stringify(trimmed), "utf-8");
807
+ renameSync(tmpFile, HISTORY_FILE);
808
+ } catch {
809
+ }
810
+ }
811
+ };
812
+
813
+ // src/components/ChatApp.tsx
814
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
815
+ function ChatApp({
816
+ strategy,
817
+ modelCount,
818
+ onSendMessage,
819
+ onAbort,
820
+ isActive = true,
821
+ slashCallbacks
822
+ }) {
823
+ const { exit } = useApp();
824
+ const [messages, setMessages] = useState5([]);
825
+ const [input, setInput] = useState5("");
826
+ const [streamingText, setStreamingText] = useState5(
827
+ void 0
828
+ );
829
+ const [currentModel, setCurrentModel] = useState5(
830
+ void 0
831
+ );
832
+ const [sessionCost, setSessionCost] = useState5(0);
833
+ const [isProcessing, setIsProcessing] = useState5(false);
834
+ const [tasks, setTasks] = useState5([]);
835
+ const [tokenCount, setTokenCount] = useState5({ input: 0, output: 0 });
836
+ const [thinkingPhase, setThinkingPhase] = useState5(
837
+ void 0
838
+ );
839
+ const [activeTools, setActiveTools] = useState5([]);
840
+ const [scrollOffset, setScrollOffset] = useState5(0);
841
+ const [askUserPrompt, setAskUserPrompt] = useState5(null);
842
+ const [showAutocomplete, setShowAutocomplete] = useState5(false);
843
+ const [promptQueue, setPromptQueue] = useState5([]);
844
+ const [history] = useState5(() => new InputHistory());
845
+ const abortTimeoutRef = useRef(null);
846
+ const toolIdCounter = useRef(0);
847
+ const pushPrompt = useCallback3(
848
+ (question, options) => {
849
+ return new Promise((resolve) => {
850
+ setPromptQueue((prev) => [...prev, { question, options, resolve }]);
851
+ });
852
+ },
853
+ []
854
+ );
855
+ const slashItems = useMemo3(() => {
856
+ return getSlashCommands().map((cmd) => ({
857
+ label: cmd.name,
858
+ description: cmd.description,
859
+ prefix: "/"
860
+ }));
861
+ }, []);
862
+ useEffect(() => {
863
+ const handler = (data) => {
864
+ if (data?.question && data?.options) {
865
+ setAskUserPrompt({
866
+ question: data.question,
867
+ options: data.options.map((o) => ({
868
+ label: o.label ?? o,
869
+ value: o.label ?? o,
870
+ description: o.description,
871
+ recommended: o.recommended
872
+ }))
873
+ });
874
+ }
875
+ };
876
+ process.on("brainstorm:ask-user", handler);
877
+ return () => {
878
+ process.removeListener("brainstorm:ask-user", handler);
879
+ };
880
+ }, []);
881
+ useInput3((inputChar, key) => {
882
+ if (!isActive) return;
883
+ if (key.upArrow && key.shift) {
884
+ setScrollOffset(
885
+ (prev) => Math.min(prev + 3, Math.max(0, messages.length - 3))
886
+ );
887
+ return;
888
+ }
889
+ if (key.downArrow && key.shift) {
890
+ setScrollOffset((prev) => Math.max(0, prev - 3));
891
+ return;
892
+ }
893
+ if (key.upArrow && !isProcessing) {
894
+ const prev = history.up(input);
895
+ if (prev !== null) setInput(prev);
896
+ return;
897
+ }
898
+ if (key.downArrow && !isProcessing) {
899
+ const next = history.down();
900
+ if (next !== null) setInput(next);
901
+ return;
902
+ }
903
+ const action = resolveKeyAction(inputChar, key);
904
+ if (!action) return;
905
+ switch (action) {
906
+ case "abort":
907
+ if (isProcessing) {
908
+ onAbort?.();
909
+ if (abortTimeoutRef.current) clearTimeout(abortTimeoutRef.current);
910
+ abortTimeoutRef.current = setTimeout(() => {
911
+ setIsProcessing(false);
912
+ setStreamingText(void 0);
913
+ setThinkingPhase(void 0);
914
+ abortTimeoutRef.current = null;
915
+ }, 5e3);
916
+ }
917
+ break;
918
+ case "exit":
919
+ exit();
920
+ break;
921
+ case "clear-screen":
922
+ process.stdout.write("\x1B[2J\x1B[0f");
923
+ break;
924
+ case "clear-chat":
925
+ setMessages([]);
926
+ setStreamingText(void 0);
927
+ break;
928
+ case "cycle-mode": {
929
+ const modes = ["auto", "confirm", "plan"];
930
+ const current = slashCallbacks?.getMode?.() ?? "confirm";
931
+ const idx = modes.indexOf(current);
932
+ const next = modes[(idx + 1) % modes.length];
933
+ slashCallbacks?.setMode?.(next);
934
+ const labels = {
935
+ auto: "auto (all tools allowed)",
936
+ confirm: "confirm (ask before writes)",
937
+ plan: "plan (read-only)"
938
+ };
939
+ setMessages((prev) => [
940
+ ...prev,
941
+ { role: "routing", content: `Mode: ${labels[next] ?? next}` }
942
+ ]);
943
+ break;
944
+ }
945
+ }
946
+ });
947
+ const slashCtx = useMemo3(
948
+ () => ({
949
+ getModel: () => currentModel,
950
+ getSessionCost: () => sessionCost,
951
+ getTokenCount: () => tokenCount,
952
+ exit: () => exit(),
953
+ clearHistory: () => {
954
+ setMessages([]);
955
+ setStreamingText(void 0);
956
+ },
957
+ setModel: (model) => {
958
+ slashCallbacks?.setModel?.(model);
959
+ const name = model.split("/").pop() ?? model;
960
+ setCurrentModel(name);
961
+ },
962
+ setStrategy: slashCallbacks?.setStrategy,
963
+ getStrategy: slashCallbacks?.getStrategy,
964
+ setMode: slashCallbacks?.setMode,
965
+ getMode: slashCallbacks?.getMode,
966
+ setOutputStyle: slashCallbacks?.setOutputStyle,
967
+ getOutputStyle: slashCallbacks?.getOutputStyle,
968
+ getBudget: slashCallbacks?.getBudget,
969
+ compact: slashCallbacks?.compact,
970
+ getContextWindow: slashCallbacks?.getContextWindow,
971
+ prompt: pushPrompt,
972
+ dream: slashCallbacks?.dream,
973
+ vault: slashCallbacks?.vault,
974
+ rebuildSystemPrompt: slashCallbacks?.rebuildSystemPrompt,
975
+ undoLastTurn: () => {
976
+ let removed = 0;
977
+ setMessages((prev) => {
978
+ const lastUserIdx = prev.findLastIndex((m) => m.role === "user");
979
+ if (lastUserIdx < 0) return prev;
980
+ removed = prev.length - lastUserIdx;
981
+ return prev.slice(0, lastUserIdx);
982
+ });
983
+ return removed;
984
+ },
985
+ getActiveRole: slashCallbacks?.getActiveRole,
986
+ setActiveRole: slashCallbacks?.setActiveRole,
987
+ gateway: slashCallbacks?.gateway
988
+ }),
989
+ [currentModel, sessionCost, tokenCount, exit, slashCallbacks]
990
+ );
991
+ const handleSubmit = useCallback3(
992
+ async (text) => {
993
+ if (isProcessing) return;
994
+ if (!text.trim()) {
995
+ setMessages((prev) => [
996
+ ...prev,
997
+ {
998
+ role: "routing",
999
+ content: "Type a message or use /help for commands"
1000
+ }
1001
+ ]);
1002
+ return;
1003
+ }
1004
+ history.push(text.trim());
1005
+ if (isSlashCommand(text)) {
1006
+ setInput("");
1007
+ const result = await executeSlashCommand(text, slashCtx);
1008
+ setMessages((prev) => [
1009
+ ...prev,
1010
+ { role: "assistant", content: result }
1011
+ ]);
1012
+ return;
1013
+ }
1014
+ setInput("");
1015
+ setMessages((prev) => [...prev, { role: "user", content: text.trim() }]);
1016
+ setIsProcessing(true);
1017
+ setStreamingText("");
1018
+ setTasks([]);
1019
+ setActiveTools([]);
1020
+ setScrollOffset(0);
1021
+ let fullResponse = "";
1022
+ let model;
1023
+ let cost = 0;
1024
+ const costBefore = sessionCost;
1025
+ try {
1026
+ for await (const event of onSendMessage(text.trim())) {
1027
+ switch (event.type) {
1028
+ case "thinking":
1029
+ setThinkingPhase(event.phase);
1030
+ break;
1031
+ case "routing":
1032
+ model = event.decision.model.name;
1033
+ setCurrentModel(model);
1034
+ setThinkingPhase(void 0);
1035
+ const est = event.decision.estimatedCost;
1036
+ const estStr = est > 0 ? ` ~$${est.toFixed(3)}` : "";
1037
+ const fb = event.decision.fallbacks?.length ?? 0;
1038
+ const fbStr = fb > 0 ? ` (${fb} fallback${fb > 1 ? "s" : ""})` : "";
1039
+ setMessages((prev) => [
1040
+ ...prev,
1041
+ {
1042
+ role: "routing",
1043
+ content: `\u2192 ${model} via ${event.decision.strategy}${estStr}${fbStr}`
1044
+ }
1045
+ ]);
1046
+ break;
1047
+ case "text-delta":
1048
+ setThinkingPhase(void 0);
1049
+ fullResponse += event.delta;
1050
+ setStreamingText(fullResponse);
1051
+ break;
1052
+ case "reasoning":
1053
+ setMessages((prev) => [
1054
+ ...prev,
1055
+ { role: "reasoning", content: event.content }
1056
+ ]);
1057
+ break;
1058
+ case "tool-call-start":
1059
+ setActiveTools((prev) => [
1060
+ ...prev,
1061
+ {
1062
+ id: `tc-${++toolIdCounter.current}-${event.toolName}`,
1063
+ toolName: event.toolName,
1064
+ args: event.args ?? {},
1065
+ status: "running",
1066
+ startTime: Date.now()
1067
+ }
1068
+ ]);
1069
+ break;
1070
+ case "tool-call-result":
1071
+ setActiveTools((prev) => {
1072
+ const idx = prev.findLastIndex(
1073
+ (t) => t.status === "running" && t.toolName === event.toolName
1074
+ );
1075
+ if (idx < 0) return prev;
1076
+ const updated = [...prev];
1077
+ updated[idx] = {
1078
+ ...updated[idx],
1079
+ status: "done",
1080
+ duration: Date.now() - updated[idx].startTime,
1081
+ ok: true
1082
+ };
1083
+ return updated;
1084
+ });
1085
+ break;
1086
+ case "compaction":
1087
+ setMessages((prev) => [
1088
+ ...prev,
1089
+ {
1090
+ role: "routing",
1091
+ content: `context compacted \u2014 ${event.removed} messages summarized (${event.tokensBefore.toLocaleString()} \u2192 ${event.tokensAfter.toLocaleString()} tokens)`
1092
+ }
1093
+ ]);
1094
+ break;
1095
+ case "subagent-result":
1096
+ setMessages((prev) => [
1097
+ ...prev,
1098
+ {
1099
+ role: "routing",
1100
+ content: `subagent [${event.subagentType}] \u2192 ${event.model} ($${event.cost.toFixed(4)}, ${event.toolCalls.length} tool calls)`
1101
+ }
1102
+ ]);
1103
+ break;
1104
+ case "task-created":
1105
+ setTasks((prev) => [...prev, event.task]);
1106
+ break;
1107
+ case "task-updated":
1108
+ setTasks(
1109
+ (prev) => prev.map((t) => t.id === event.task.id ? event.task : t)
1110
+ );
1111
+ break;
1112
+ case "background-complete":
1113
+ setMessages((prev) => [
1114
+ ...prev,
1115
+ {
1116
+ role: "routing",
1117
+ content: `[bg] ${event.taskId} completed (exit ${event.exitCode}): ${event.command.slice(0, 60)}`
1118
+ }
1119
+ ]);
1120
+ break;
1121
+ case "model-retry":
1122
+ setCurrentModel(event.toModel);
1123
+ setMessages((prev) => [
1124
+ ...prev,
1125
+ {
1126
+ role: "routing",
1127
+ content: `\u21BB retry: ${event.fromModel} \u2192 ${event.toModel} (${event.reason})`
1128
+ }
1129
+ ]);
1130
+ break;
1131
+ case "fallback-exhausted":
1132
+ setMessages((prev) => [
1133
+ ...prev,
1134
+ {
1135
+ role: "routing",
1136
+ content: `\u26A0 all models failed: ${event.modelsTried.join(", ")}`
1137
+ }
1138
+ ]);
1139
+ break;
1140
+ case "budget-warning":
1141
+ setMessages((prev) => [
1142
+ ...prev,
1143
+ {
1144
+ role: "routing",
1145
+ content: `\u26A0 budget: $${event.used.toFixed(4)} / $${event.limit.toFixed(4)} ($${event.remaining.toFixed(4)} remaining)`
1146
+ }
1147
+ ]);
1148
+ break;
1149
+ case "context-budget":
1150
+ setMessages((prev) => [
1151
+ ...prev,
1152
+ {
1153
+ role: "routing",
1154
+ content: `context: ${event.percent}% (${event.used.toLocaleString()} / ${event.limit.toLocaleString()} tokens)`
1155
+ }
1156
+ ]);
1157
+ break;
1158
+ case "loop-warning":
1159
+ break;
1160
+ case "empty-response":
1161
+ setMessages((prev) => [
1162
+ ...prev,
1163
+ {
1164
+ role: "routing",
1165
+ content: `\u26A0 empty response from ${event.modelId}`
1166
+ }
1167
+ ]);
1168
+ break;
1169
+ case "gateway-feedback":
1170
+ break;
1171
+ case "tool-output-partial":
1172
+ break;
1173
+ case "interrupted":
1174
+ setMessages((prev) => [
1175
+ ...prev,
1176
+ { role: "routing", content: "interrupted" }
1177
+ ]);
1178
+ break;
1179
+ case "done":
1180
+ cost = event.totalCost - costBefore;
1181
+ setSessionCost(event.totalCost);
1182
+ if (event.totalTokens) setTokenCount(event.totalTokens);
1183
+ break;
1184
+ case "error": {
1185
+ const msg = event.error.message ?? "";
1186
+ const category = msg.includes("fetch") || msg.includes("ECONNREFUSED") ? "NETWORK" : msg.includes("Budget") || msg.includes("budget") ? "BUDGET" : msg.includes("Unauthorized") || msg.includes("401") ? "AUTH" : msg.includes("No models") ? "MODEL" : "ERROR";
1187
+ const hint = category === "NETWORK" ? "\nCheck your internet connection." : category === "BUDGET" ? "\nRun /budget to check. Adjust in config.toml." : category === "AUTH" ? "\nRun /vault list to check API keys." : category === "MODEL" ? "\nRun /model to switch or check available models." : "";
1188
+ setMessages((prev) => [
1189
+ ...prev,
1190
+ {
1191
+ role: "error",
1192
+ content: `[${category}] ${msg}${hint}`,
1193
+ model
1194
+ }
1195
+ ]);
1196
+ break;
1197
+ }
1198
+ }
1199
+ }
1200
+ } catch (err) {
1201
+ fullResponse = `Error: ${err.message}`;
1202
+ } finally {
1203
+ setStreamingText(void 0);
1204
+ setThinkingPhase(void 0);
1205
+ setIsProcessing(false);
1206
+ }
1207
+ setMessages((prev) => [
1208
+ ...prev,
1209
+ {
1210
+ role: "assistant",
1211
+ content: fullResponse || "(No response received)",
1212
+ model,
1213
+ cost
1214
+ }
1215
+ ]);
1216
+ },
1217
+ [isProcessing, onSendMessage, exit, slashCtx]
1218
+ );
1219
+ const termHeight = process.stdout.rows || 24;
1220
+ const footerHeight = 4;
1221
+ const toolsHeight = activeTools.filter((t) => t.status === "running").length > 0 ? 4 : 0;
1222
+ const tasksHeight = tasks.length > 0 ? Math.min(tasks.length + 1, 5) : 0;
1223
+ const messageHeight = Math.max(
1224
+ 5,
1225
+ termHeight - 2 - footerHeight - toolsHeight - tasksHeight
1226
+ );
1227
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", flexGrow: 1, children: [
1228
+ /* @__PURE__ */ jsx9(
1229
+ MessageList,
1230
+ {
1231
+ messages,
1232
+ maxHeight: messageHeight,
1233
+ scrollOffset
1234
+ }
1235
+ ),
1236
+ (streamingText !== void 0 || thinkingPhase) && /* @__PURE__ */ jsx9(
1237
+ StreamingMessage,
1238
+ {
1239
+ content: streamingText ?? "",
1240
+ isStreaming: isProcessing,
1241
+ phase: thinkingPhase,
1242
+ model: currentModel
1243
+ }
1244
+ ),
1245
+ /* @__PURE__ */ jsx9(ToolCallList, { tools: activeTools }),
1246
+ tasks.length > 0 && /* @__PURE__ */ jsx9(TaskList, { tasks }),
1247
+ showAutocomplete && !isProcessing && !askUserPrompt && /* @__PURE__ */ jsx9(
1248
+ Autocomplete,
1249
+ {
1250
+ query: input.slice(1),
1251
+ items: slashItems,
1252
+ onAccept: (label) => {
1253
+ setInput(`/${label} `);
1254
+ setShowAutocomplete(false);
1255
+ },
1256
+ onDismiss: () => setShowAutocomplete(false)
1257
+ }
1258
+ ),
1259
+ askUserPrompt && /* @__PURE__ */ jsx9(
1260
+ SelectPrompt,
1261
+ {
1262
+ message: askUserPrompt.question,
1263
+ options: askUserPrompt.options,
1264
+ onSelect: async (value) => {
1265
+ const { resolveAskUser } = await import("@brainst0rm/tools");
1266
+ resolveAskUser(value);
1267
+ setAskUserPrompt(null);
1268
+ setMessages((prev) => [
1269
+ ...prev,
1270
+ { role: "routing", content: `Selected: ${value}` }
1271
+ ]);
1272
+ },
1273
+ onCancel: async () => {
1274
+ const { resolveAskUser } = await import("@brainst0rm/tools");
1275
+ resolveAskUser(askUserPrompt.options[0]?.value ?? "");
1276
+ setAskUserPrompt(null);
1277
+ }
1278
+ }
1279
+ ),
1280
+ promptQueue.length > 0 && !askUserPrompt && /* @__PURE__ */ jsx9(
1281
+ SelectPrompt,
1282
+ {
1283
+ message: promptQueue[0].question,
1284
+ options: promptQueue[0].options,
1285
+ onSelect: (value) => {
1286
+ const current = promptQueue[0];
1287
+ setPromptQueue((prev) => prev.slice(1));
1288
+ setMessages((prev) => [
1289
+ ...prev,
1290
+ { role: "routing", content: `\u2192 ${value}` }
1291
+ ]);
1292
+ current.resolve(value);
1293
+ },
1294
+ onCancel: () => {
1295
+ const current = promptQueue[0];
1296
+ setPromptQueue([]);
1297
+ current.resolve(current.options[0]?.value ?? "");
1298
+ }
1299
+ }
1300
+ ),
1301
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
1302
+ /* @__PURE__ */ jsxs8(
1303
+ Box9,
1304
+ {
1305
+ borderStyle: "single",
1306
+ borderColor: isProcessing ? "gray" : "cyan",
1307
+ paddingX: 1,
1308
+ children: [
1309
+ /* @__PURE__ */ jsx9(Text9, { color: isProcessing ? "gray" : "cyan", bold: true, children: "> " }),
1310
+ /* @__PURE__ */ jsx9(
1311
+ TextInput,
1312
+ {
1313
+ value: input,
1314
+ onChange: (val) => {
1315
+ setInput(val);
1316
+ setShowAutocomplete(
1317
+ val.startsWith("/") && val.length > 1 && !val.includes(" ")
1318
+ );
1319
+ },
1320
+ onSubmit: (val) => {
1321
+ setShowAutocomplete(false);
1322
+ if (val.endsWith("\\")) {
1323
+ setInput(val.slice(0, -1) + "\n");
1324
+ return;
1325
+ }
1326
+ handleSubmit(val);
1327
+ },
1328
+ placeholder: isProcessing ? "Thinking..." : "Type a message... (/ commands, @file to include)"
1329
+ }
1330
+ )
1331
+ ]
1332
+ }
1333
+ ),
1334
+ /* @__PURE__ */ jsx9(Box9, { paddingX: 2, children: /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: isProcessing ? "Esc abort \u2502 Shift+\u2191\u2193 scroll" : "/help \u2502 Shift+Tab mode \u2502 \u2191\u2193 history \u2502 Ctrl+D exit" }) })
1335
+ ] })
1336
+ ] });
1337
+ }
1338
+
1339
+ // src/components/modes/DashboardMode.tsx
1340
+ import { useEffect as useEffect2 } from "react";
1341
+ import { Box as Box11, Text as Text12 } from "ink";
1342
+
1343
+ // src/components/viz/Gauge.tsx
1344
+ import { Box as Box10, Text as Text10 } from "ink";
1345
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1346
+ var DEFAULT_COLOR = (v) => {
1347
+ if (v >= 85) return "red";
1348
+ if (v >= 60) return "yellow";
1349
+ return "green";
1350
+ };
1351
+ function Gauge({
1352
+ value,
1353
+ width = 16,
1354
+ label,
1355
+ showPercent = true,
1356
+ colorFn = DEFAULT_COLOR
1357
+ }) {
1358
+ const clamped = Math.max(0, Math.min(100, value));
1359
+ const filled = Math.round(clamped / 100 * width);
1360
+ const empty = width - filled;
1361
+ const color = colorFn(clamped);
1362
+ return /* @__PURE__ */ jsxs9(Box10, { children: [
1363
+ label && /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
1364
+ label,
1365
+ " "
1366
+ ] }),
1367
+ /* @__PURE__ */ jsx10(Text10, { color, children: "\u2588".repeat(filled) }),
1368
+ /* @__PURE__ */ jsx10(Text10, { color: "gray", dimColor: true, children: "\u2591".repeat(empty) }),
1369
+ showPercent && /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
1370
+ " ",
1371
+ Math.round(clamped),
1372
+ "%"
1373
+ ] })
1374
+ ] });
1375
+ }
1376
+
1377
+ // src/components/viz/Sparkline.tsx
1378
+ import { Text as Text11 } from "ink";
1379
+ import { jsx as jsx11 } from "react/jsx-runtime";
1380
+ var BARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
1381
+ function Sparkline({ data, color = "green", width }) {
1382
+ if (data.length === 0)
1383
+ return /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: "\u2500" });
1384
+ let values = data;
1385
+ if (width && data.length > width) {
1386
+ const step = data.length / width;
1387
+ values = Array.from(
1388
+ { length: width },
1389
+ (_, i) => data[Math.floor(i * step)]
1390
+ );
1391
+ }
1392
+ const min = Math.min(...values);
1393
+ const max = Math.max(...values);
1394
+ const range = max - min || 1;
1395
+ const bars = values.map((v) => {
1396
+ const idx = Math.round((v - min) / range * (BARS.length - 1));
1397
+ return BARS[idx];
1398
+ });
1399
+ return /* @__PURE__ */ jsx11(Text11, { color, children: bars.join("") });
1400
+ }
1401
+
1402
+ // src/theme.ts
1403
+ function getProviderColor(provider) {
1404
+ const p = provider.toLowerCase();
1405
+ if (p.includes("anthropic") || p.includes("claude")) return "magenta";
1406
+ if (p.includes("openai") || p.includes("gpt") || p.includes("o3"))
1407
+ return "yellow";
1408
+ if (p.includes("google") || p.includes("gemini")) return "blue";
1409
+ if (p.includes("deepseek")) return "cyan";
1410
+ if (p.includes("local") || p.includes("ollama") || p.includes("lmstudio"))
1411
+ return "white";
1412
+ return "gray";
1413
+ }
1414
+ function getRoleColor(role) {
1415
+ switch (role) {
1416
+ case "architect":
1417
+ return "magenta";
1418
+ case "product-manager":
1419
+ return "blue";
1420
+ case "sr-developer":
1421
+ return "green";
1422
+ case "jr-developer":
1423
+ return "yellow";
1424
+ case "qa":
1425
+ return "red";
1426
+ default:
1427
+ return "gray";
1428
+ }
1429
+ }
1430
+
1431
+ // src/components/modes/DashboardMode.tsx
1432
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1433
+ function formatElapsed(ms) {
1434
+ const mins = Math.floor(ms / 6e4);
1435
+ if (mins < 1) return "<1m";
1436
+ if (mins < 60) return `${mins}m`;
1437
+ return `${Math.floor(mins / 60)}h ${mins % 60}m`;
1438
+ }
1439
+ function formatTokens(n) {
1440
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
1441
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
1442
+ return String(n);
1443
+ }
1444
+ function timeAgo(ts) {
1445
+ const secs = Math.floor((Date.now() - ts) / 1e3);
1446
+ if (secs < 60) return `${secs}s ago`;
1447
+ return `${Math.floor(secs / 60)}m ago`;
1448
+ }
1449
+ function DashboardMode({
1450
+ sessionCost,
1451
+ tokenCount,
1452
+ modelCount,
1453
+ routingHistory,
1454
+ toolStats,
1455
+ turnCount,
1456
+ sessionStart,
1457
+ brData,
1458
+ onRefreshBR
1459
+ }) {
1460
+ const elapsed = Date.now() - sessionStart;
1461
+ const costPerHour = elapsed > 6e4 ? sessionCost / elapsed * 36e5 : 0;
1462
+ useEffect2(() => {
1463
+ if (onRefreshBR && (!brData || brData.lastFetched === 0)) {
1464
+ onRefreshBR();
1465
+ }
1466
+ }, []);
1467
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
1468
+ /* @__PURE__ */ jsxs10(
1469
+ Box11,
1470
+ {
1471
+ borderStyle: "round",
1472
+ borderColor: "gray",
1473
+ paddingX: 1,
1474
+ flexDirection: "row",
1475
+ justifyContent: "space-between",
1476
+ children: [
1477
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
1478
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Session" }),
1479
+ /* @__PURE__ */ jsxs10(Text12, { color: "yellow", bold: true, children: [
1480
+ "$",
1481
+ sessionCost.toFixed(4)
1482
+ ] })
1483
+ ] }),
1484
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
1485
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Tokens" }),
1486
+ /* @__PURE__ */ jsxs10(Text12, { children: [
1487
+ formatTokens(tokenCount.input),
1488
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "\u2191" }),
1489
+ " ",
1490
+ formatTokens(tokenCount.output),
1491
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "\u2193" })
1492
+ ] })
1493
+ ] }),
1494
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
1495
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Turns" }),
1496
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: turnCount })
1497
+ ] }),
1498
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
1499
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Elapsed" }),
1500
+ /* @__PURE__ */ jsx12(Text12, { children: formatElapsed(elapsed) })
1501
+ ] }),
1502
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
1503
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "$/hour" }),
1504
+ /* @__PURE__ */ jsxs10(
1505
+ Text12,
1506
+ {
1507
+ color: costPerHour > 5 ? "red" : costPerHour > 1 ? "yellow" : "green",
1508
+ children: [
1509
+ "$",
1510
+ costPerHour.toFixed(2)
1511
+ ]
1512
+ }
1513
+ )
1514
+ ] }),
1515
+ brData?.forecast && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
1516
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Forecast" }),
1517
+ /* @__PURE__ */ jsxs10(Text12, { color: brData.forecast.will_exceed ? "red" : "green", bold: true, children: [
1518
+ "$",
1519
+ brData.forecast.projected_spend.toFixed(2)
1520
+ ] })
1521
+ ] })
1522
+ ]
1523
+ }
1524
+ ),
1525
+ /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "row", flexGrow: 1, children: [
1526
+ /* @__PURE__ */ jsxs10(
1527
+ Box11,
1528
+ {
1529
+ borderStyle: "round",
1530
+ borderColor: "gray",
1531
+ flexGrow: 1,
1532
+ paddingX: 1,
1533
+ flexDirection: "column",
1534
+ children: [
1535
+ /* @__PURE__ */ jsxs10(Text12, { bold: true, color: "green", children: [
1536
+ " ",
1537
+ "Routing Log"
1538
+ ] }),
1539
+ routingHistory.length === 0 ? /* @__PURE__ */ jsxs10(Text12, { color: "gray", dimColor: true, children: [
1540
+ " ",
1541
+ "Send a message to start."
1542
+ ] }) : routingHistory.slice(0, 6).map((entry, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
1543
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", dimColor: true, children: timeAgo(entry.timestamp).padEnd(8) }),
1544
+ /* @__PURE__ */ jsx12(Text12, { color: getProviderColor(entry.model), bold: true, children: entry.model.padEnd(18) }),
1545
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: entry.strategy })
1546
+ ] }, i)),
1547
+ brData && brData.leaderboard.length > 0 && /* @__PURE__ */ jsxs10(Fragment2, { children: [
1548
+ /* @__PURE__ */ jsx12(Text12, { children: " " }),
1549
+ /* @__PURE__ */ jsxs10(Text12, { bold: true, color: "yellow", children: [
1550
+ " ",
1551
+ "Leaderboard"
1552
+ ] }),
1553
+ brData.leaderboard.slice(0, 5).map((entry, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
1554
+ /* @__PURE__ */ jsxs10(Text12, { color: "gray", children: [
1555
+ String(i + 1).padStart(2),
1556
+ ". "
1557
+ ] }),
1558
+ /* @__PURE__ */ jsx12(Text12, { color: getProviderColor(entry.provider), bold: true, children: entry.model.split("/").pop()?.padEnd(18) ?? entry.model.padEnd(18) }),
1559
+ /* @__PURE__ */ jsxs10(Text12, { color: "gray", children: [
1560
+ "Q",
1561
+ entry.quality_rank,
1562
+ " S",
1563
+ entry.speed_rank,
1564
+ " V",
1565
+ entry.value_rank
1566
+ ] })
1567
+ ] }, i))
1568
+ ] })
1569
+ ]
1570
+ }
1571
+ ),
1572
+ /* @__PURE__ */ jsxs10(
1573
+ Box11,
1574
+ {
1575
+ borderStyle: "round",
1576
+ borderColor: "gray",
1577
+ flexGrow: 1,
1578
+ paddingX: 1,
1579
+ marginLeft: 1,
1580
+ flexDirection: "column",
1581
+ children: [
1582
+ /* @__PURE__ */ jsxs10(Text12, { bold: true, color: "cyan", children: [
1583
+ " ",
1584
+ "Tool Health"
1585
+ ] }),
1586
+ toolStats.length === 0 ? /* @__PURE__ */ jsxs10(Text12, { color: "gray", dimColor: true, children: [
1587
+ " ",
1588
+ "No tool calls yet."
1589
+ ] }) : toolStats.sort((a, b) => b.calls - a.calls).slice(0, 8).map((tool) => {
1590
+ const rate = tool.calls > 0 ? Math.round(tool.successes / tool.calls * 100) : 0;
1591
+ const color = rate >= 90 ? "green" : rate >= 70 ? "yellow" : "red";
1592
+ return /* @__PURE__ */ jsxs10(Box11, { children: [
1593
+ /* @__PURE__ */ jsxs10(Text12, { color, children: [
1594
+ rate >= 90 ? "\u25CF" : rate >= 70 ? "\u25D0" : "\u25CB",
1595
+ " "
1596
+ ] }),
1597
+ /* @__PURE__ */ jsx12(Text12, { children: tool.name.padEnd(14) }),
1598
+ /* @__PURE__ */ jsxs10(Text12, { color: "gray", children: [
1599
+ String(tool.calls).padStart(3),
1600
+ " "
1601
+ ] }),
1602
+ /* @__PURE__ */ jsx12(Gauge, { value: rate, width: 8, showPercent: false }),
1603
+ /* @__PURE__ */ jsxs10(Text12, { color, children: [
1604
+ " ",
1605
+ rate,
1606
+ "%"
1607
+ ] })
1608
+ ] }, tool.name);
1609
+ }),
1610
+ brData?.waste && brData.waste.total_waste_usd > 0 && /* @__PURE__ */ jsxs10(Fragment2, { children: [
1611
+ /* @__PURE__ */ jsx12(Text12, { children: " " }),
1612
+ /* @__PURE__ */ jsxs10(Text12, { bold: true, color: "red", children: [
1613
+ " ",
1614
+ "Waste: $",
1615
+ brData.waste.total_waste_usd.toFixed(2)
1616
+ ] }),
1617
+ brData.waste.suggestions.slice(0, 3).map((s, i) => /* @__PURE__ */ jsxs10(Text12, { color: "gray", dimColor: true, children: [
1618
+ " ",
1619
+ s.description.slice(0, 50)
1620
+ ] }, i))
1621
+ ] })
1622
+ ]
1623
+ }
1624
+ ),
1625
+ /* @__PURE__ */ jsxs10(
1626
+ Box11,
1627
+ {
1628
+ borderStyle: "round",
1629
+ borderColor: "gray",
1630
+ flexGrow: 1,
1631
+ paddingX: 1,
1632
+ marginLeft: 1,
1633
+ flexDirection: "column",
1634
+ children: [
1635
+ /* @__PURE__ */ jsxs10(Text12, { bold: true, color: "magenta", children: [
1636
+ " ",
1637
+ "Guardian Audit"
1638
+ ] }),
1639
+ !brData || brData.audit.length === 0 ? /* @__PURE__ */ jsxs10(Text12, { color: "gray", dimColor: true, children: [
1640
+ " ",
1641
+ "No audit data. Press r to refresh."
1642
+ ] }) : brData.audit.slice(0, 6).map((entry, i) => {
1643
+ const statusColor2 = entry.guardian_status === "safe" ? "green" : entry.guardian_status === "flagged" ? "yellow" : "red";
1644
+ return /* @__PURE__ */ jsxs10(Box11, { children: [
1645
+ /* @__PURE__ */ jsxs10(Text12, { color: statusColor2, children: [
1646
+ entry.guardian_status === "safe" ? "\u25CF" : "\u26A0",
1647
+ " "
1648
+ ] }),
1649
+ /* @__PURE__ */ jsx12(Text12, { color: getProviderColor(entry.model), children: entry.model.split("/").pop()?.padEnd(14) ?? "" }),
1650
+ /* @__PURE__ */ jsxs10(Text12, { color: "gray", children: [
1651
+ "$",
1652
+ entry.cost_usd.toFixed(4)
1653
+ ] })
1654
+ ] }, i);
1655
+ }),
1656
+ brData && brData.dailyTrend.length > 0 && /* @__PURE__ */ jsxs10(Fragment2, { children: [
1657
+ /* @__PURE__ */ jsx12(Text12, { children: " " }),
1658
+ /* @__PURE__ */ jsxs10(Text12, { bold: true, color: "blue", children: [
1659
+ " ",
1660
+ "7-Day Trend"
1661
+ ] }),
1662
+ /* @__PURE__ */ jsxs10(Box11, { children: [
1663
+ /* @__PURE__ */ jsx12(
1664
+ Sparkline,
1665
+ {
1666
+ data: brData.dailyTrend.map((d) => d.cost_usd),
1667
+ color: "yellow",
1668
+ width: 20
1669
+ }
1670
+ ),
1671
+ /* @__PURE__ */ jsxs10(Text12, { color: "gray", children: [
1672
+ " ",
1673
+ "$",
1674
+ brData.dailyTrend.reduce((s, d) => s + d.cost_usd, 0).toFixed(2),
1675
+ " ",
1676
+ "total"
1677
+ ] })
1678
+ ] })
1679
+ ] })
1680
+ ]
1681
+ }
1682
+ )
1683
+ ] }),
1684
+ /* @__PURE__ */ jsx12(Box11, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text12, { color: "gray", dimColor: true, children: brData?.loading ? "Loading BR data..." : brData?.error ? `BR: ${brData.error}` : `r refresh \u2502 ${modelCount.local}L/${modelCount.cloud}C` }) })
1685
+ ] });
1686
+ }
1687
+
1688
+ // src/components/modes/ModelsMode.tsx
1689
+ import { useState as useState6 } from "react";
1690
+ import { Box as Box12, Text as Text13, useInput as useInput4 } from "ink";
1691
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1692
+ var QUALITY_BARS = {
1693
+ 1: { label: "Excellent", value: 100 },
1694
+ 2: { label: "Good", value: 66 },
1695
+ 3: { label: "Basic", value: 33 }
1696
+ };
1697
+ var SPEED_BARS = {
1698
+ 1: { label: "Fast", value: 100 },
1699
+ 2: { label: "Medium", value: 66 },
1700
+ 3: { label: "Slow", value: 33 }
1701
+ };
1702
+ function ModelsMode({
1703
+ models,
1704
+ currentModelId,
1705
+ onSelectModel
1706
+ }) {
1707
+ const initialIdx = currentModelId ? Math.max(
1708
+ 0,
1709
+ models.findIndex(
1710
+ (m) => m.id === currentModelId || m.name === currentModelId
1711
+ )
1712
+ ) : 0;
1713
+ const [selectedIdx, setSelectedIdx] = useState6(initialIdx);
1714
+ useInput4((input, key) => {
1715
+ if (key.downArrow || input === "j") {
1716
+ setSelectedIdx((prev) => Math.min(prev + 1, models.length - 1));
1717
+ }
1718
+ if (key.upArrow || input === "k") {
1719
+ setSelectedIdx((prev) => Math.max(prev - 1, 0));
1720
+ }
1721
+ if (key.return && onSelectModel && models[selectedIdx]) {
1722
+ onSelectModel(models[selectedIdx].id);
1723
+ }
1724
+ });
1725
+ const selected = models[selectedIdx];
1726
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", flexGrow: 1, paddingX: 1, children: [
1727
+ /* @__PURE__ */ jsxs11(
1728
+ Box12,
1729
+ {
1730
+ borderStyle: "round",
1731
+ borderColor: "gray",
1732
+ flexDirection: "column",
1733
+ paddingX: 1,
1734
+ width: "60%",
1735
+ children: [
1736
+ /* @__PURE__ */ jsxs11(Text13, { bold: true, color: "yellow", children: [
1737
+ " ",
1738
+ "Model Explorer"
1739
+ ] }),
1740
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, flexDirection: "column", children: models.map((m, i) => {
1741
+ const isSelected = i === selectedIdx;
1742
+ const provColor = getProviderColor(m.provider);
1743
+ return /* @__PURE__ */ jsxs11(Box12, { children: [
1744
+ /* @__PURE__ */ jsx13(Text13, { color: isSelected ? "white" : "gray", children: isSelected ? " \u25B8 " : " " }),
1745
+ /* @__PURE__ */ jsx13(Text13, { color: m.status === "available" ? "green" : "red", children: m.status === "available" ? "\u25CF " : "\u25CB " }),
1746
+ /* @__PURE__ */ jsx13(Text13, { color: provColor, bold: isSelected, children: m.name.padEnd(24) }),
1747
+ /* @__PURE__ */ jsxs11(Text13, { color: "gray", children: [
1748
+ "$",
1749
+ m.pricing.input,
1750
+ "/$",
1751
+ m.pricing.output
1752
+ ] })
1753
+ ] }, m.id);
1754
+ }) }),
1755
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text13, { color: "gray", dimColor: true, children: [
1756
+ models.length,
1757
+ " models \u2502 j/k navigate \u2502 Enter select"
1758
+ ] }) })
1759
+ ]
1760
+ }
1761
+ ),
1762
+ /* @__PURE__ */ jsx13(
1763
+ Box12,
1764
+ {
1765
+ borderStyle: "round",
1766
+ borderColor: "gray",
1767
+ flexDirection: "column",
1768
+ paddingX: 1,
1769
+ marginLeft: 1,
1770
+ width: "40%",
1771
+ children: selected ? /* @__PURE__ */ jsxs11(Fragment3, { children: [
1772
+ /* @__PURE__ */ jsxs11(Text13, { bold: true, color: getProviderColor(selected.provider), children: [
1773
+ " ",
1774
+ selected.name
1775
+ ] }),
1776
+ /* @__PURE__ */ jsxs11(Text13, { color: "gray", dimColor: true, children: [
1777
+ " ",
1778
+ selected.id
1779
+ ] }),
1780
+ /* @__PURE__ */ jsxs11(Box12, { marginTop: 1, flexDirection: "column", children: [
1781
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Provider" }),
1782
+ /* @__PURE__ */ jsxs11(Text13, { color: getProviderColor(selected.provider), bold: true, children: [
1783
+ " ",
1784
+ selected.provider
1785
+ ] }),
1786
+ /* @__PURE__ */ jsxs11(Box12, { marginTop: 1, children: [
1787
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Quality " }),
1788
+ /* @__PURE__ */ jsx13(
1789
+ Gauge,
1790
+ {
1791
+ value: QUALITY_BARS[selected.qualityTier]?.value ?? 50,
1792
+ width: 8,
1793
+ showPercent: false,
1794
+ colorFn: () => "yellow"
1795
+ }
1796
+ ),
1797
+ /* @__PURE__ */ jsxs11(Text13, { color: "gray", children: [
1798
+ " ",
1799
+ QUALITY_BARS[selected.qualityTier]?.label ?? "?"
1800
+ ] })
1801
+ ] }),
1802
+ /* @__PURE__ */ jsxs11(Box12, { children: [
1803
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Speed " }),
1804
+ /* @__PURE__ */ jsx13(
1805
+ Gauge,
1806
+ {
1807
+ value: SPEED_BARS[selected.speedTier]?.value ?? 50,
1808
+ width: 8,
1809
+ showPercent: false,
1810
+ colorFn: () => "cyan"
1811
+ }
1812
+ ),
1813
+ /* @__PURE__ */ jsxs11(Text13, { color: "gray", children: [
1814
+ " ",
1815
+ SPEED_BARS[selected.speedTier]?.label ?? "?"
1816
+ ] })
1817
+ ] }),
1818
+ /* @__PURE__ */ jsxs11(Box12, { marginTop: 1, flexDirection: "column", children: [
1819
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Pricing (per 1M tokens)" }),
1820
+ /* @__PURE__ */ jsxs11(Text13, { children: [
1821
+ " ",
1822
+ "Input: ",
1823
+ /* @__PURE__ */ jsxs11(Text13, { color: "yellow", children: [
1824
+ "$",
1825
+ selected.pricing.input
1826
+ ] })
1827
+ ] }),
1828
+ /* @__PURE__ */ jsxs11(Text13, { children: [
1829
+ " ",
1830
+ "Output: ",
1831
+ /* @__PURE__ */ jsxs11(Text13, { color: "yellow", children: [
1832
+ "$",
1833
+ selected.pricing.output
1834
+ ] })
1835
+ ] })
1836
+ ] }),
1837
+ /* @__PURE__ */ jsxs11(Box12, { marginTop: 1, children: [
1838
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Status: " }),
1839
+ /* @__PURE__ */ jsx13(
1840
+ Text13,
1841
+ {
1842
+ color: selected.status === "available" ? "green" : "red",
1843
+ bold: true,
1844
+ children: selected.status
1845
+ }
1846
+ )
1847
+ ] })
1848
+ ] })
1849
+ ] }) : /* @__PURE__ */ jsx13(Text13, { color: "gray", dimColor: true, children: "No model selected" })
1850
+ }
1851
+ )
1852
+ ] });
1853
+ }
1854
+
1855
+ // src/components/modes/ConfigMode.tsx
1856
+ import { Box as Box13, Text as Text14 } from "ink";
1857
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1858
+ function ConfigItem({
1859
+ label,
1860
+ value,
1861
+ color
1862
+ }) {
1863
+ return /* @__PURE__ */ jsxs12(Box13, { children: [
1864
+ /* @__PURE__ */ jsxs12(Text14, { color: "gray", children: [
1865
+ " ",
1866
+ label.padEnd(18)
1867
+ ] }),
1868
+ /* @__PURE__ */ jsx14(Text14, { color: color ?? "white", bold: true, children: value })
1869
+ ] });
1870
+ }
1871
+ function formatAge(isoDate) {
1872
+ const ms = Date.now() - new Date(isoDate).getTime();
1873
+ const days = Math.floor(ms / 864e5);
1874
+ if (days === 0) return "today";
1875
+ if (days === 1) return "1 day ago";
1876
+ if (days < 30) return `${days} days ago`;
1877
+ if (days < 365) return `${Math.floor(days / 30)} months ago`;
1878
+ return `${Math.floor(days / 365)} years ago`;
1879
+ }
1880
+ var KEY_LABELS = {
1881
+ BRAINSTORM_API_KEY: {
1882
+ label: "BrainstormRouter",
1883
+ provider: "brainstorm",
1884
+ color: "green"
1885
+ },
1886
+ ANTHROPIC_API_KEY: {
1887
+ label: "Anthropic",
1888
+ provider: "anthropic",
1889
+ color: "magenta"
1890
+ },
1891
+ OPENAI_API_KEY: { label: "OpenAI", provider: "openai", color: "yellow" },
1892
+ GOOGLE_GENERATIVE_AI_API_KEY: {
1893
+ label: "Google AI",
1894
+ provider: "google",
1895
+ color: "blue"
1896
+ },
1897
+ DEEPSEEK_API_KEY: { label: "DeepSeek", provider: "deepseek", color: "cyan" },
1898
+ MOONSHOT_API_KEY: {
1899
+ label: "Moonshot (Kimi)",
1900
+ provider: "moonshot",
1901
+ color: "white"
1902
+ },
1903
+ BRAINSTORM_ADMIN_KEY: {
1904
+ label: "BR Admin",
1905
+ provider: "brainstorm",
1906
+ color: "red"
1907
+ }
1908
+ };
1909
+ function ConfigMode({
1910
+ strategy,
1911
+ permissionMode,
1912
+ outputStyle,
1913
+ sandbox,
1914
+ role,
1915
+ modelCount,
1916
+ turnCount,
1917
+ sessionCost,
1918
+ vaultInfo,
1919
+ memoryInfo
1920
+ }) {
1921
+ const modeColor = permissionMode === "auto" ? "green" : permissionMode === "plan" ? "cyan" : "yellow";
1922
+ return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", flexGrow: 1, children: [
1923
+ /* @__PURE__ */ jsxs12(
1924
+ Box13,
1925
+ {
1926
+ borderStyle: "round",
1927
+ borderColor: "gray",
1928
+ flexDirection: "column",
1929
+ paddingX: 1,
1930
+ width: "50%",
1931
+ children: [
1932
+ /* @__PURE__ */ jsxs12(Text14, { bold: true, color: "magenta", children: [
1933
+ " ",
1934
+ "Active Configuration"
1935
+ ] }),
1936
+ /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1937
+ /* @__PURE__ */ jsxs12(Text14, { children: [
1938
+ " ",
1939
+ /* @__PURE__ */ jsx14(Text14, { color: "green", bold: true, children: "\u25CF" }),
1940
+ " ",
1941
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Routing" })
1942
+ ] }),
1943
+ /* @__PURE__ */ jsx14(ConfigItem, { label: "Strategy", value: strategy }),
1944
+ /* @__PURE__ */ jsx14(
1945
+ ConfigItem,
1946
+ {
1947
+ label: "Permission",
1948
+ value: permissionMode,
1949
+ color: modeColor
1950
+ }
1951
+ ),
1952
+ /* @__PURE__ */ jsx14(ConfigItem, { label: "Output style", value: outputStyle }),
1953
+ role && /* @__PURE__ */ jsx14(
1954
+ ConfigItem,
1955
+ {
1956
+ label: "Active role",
1957
+ value: role,
1958
+ color: getRoleColor(role)
1959
+ }
1960
+ )
1961
+ ] }),
1962
+ /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1963
+ /* @__PURE__ */ jsxs12(Text14, { children: [
1964
+ " ",
1965
+ /* @__PURE__ */ jsx14(Text14, { color: "yellow", bold: true, children: "\u25CF" }),
1966
+ " ",
1967
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Shell" })
1968
+ ] }),
1969
+ /* @__PURE__ */ jsx14(
1970
+ ConfigItem,
1971
+ {
1972
+ label: "Sandbox",
1973
+ value: sandbox,
1974
+ color: sandbox === "container" ? "cyan" : sandbox === "restricted" ? "yellow" : "gray"
1975
+ }
1976
+ )
1977
+ ] }),
1978
+ /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1979
+ /* @__PURE__ */ jsxs12(Text14, { children: [
1980
+ " ",
1981
+ /* @__PURE__ */ jsx14(Text14, { color: "blue", bold: true, children: "\u25CF" }),
1982
+ " ",
1983
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Session" })
1984
+ ] }),
1985
+ turnCount !== void 0 && /* @__PURE__ */ jsx14(ConfigItem, { label: "Turns", value: String(turnCount) }),
1986
+ sessionCost !== void 0 && /* @__PURE__ */ jsx14(
1987
+ ConfigItem,
1988
+ {
1989
+ label: "Cost",
1990
+ value: `$${sessionCost.toFixed(4)}`,
1991
+ color: "yellow"
1992
+ }
1993
+ ),
1994
+ modelCount && /* @__PURE__ */ jsx14(
1995
+ ConfigItem,
1996
+ {
1997
+ label: "Models",
1998
+ value: `${modelCount.local} local, ${modelCount.cloud} cloud`
1999
+ }
2000
+ )
2001
+ ] })
2002
+ ]
2003
+ }
2004
+ ),
2005
+ /* @__PURE__ */ jsxs12(
2006
+ Box13,
2007
+ {
2008
+ borderStyle: "round",
2009
+ borderColor: "gray",
2010
+ flexDirection: "column",
2011
+ paddingX: 1,
2012
+ marginLeft: 1,
2013
+ width: "50%",
2014
+ children: [
2015
+ /* @__PURE__ */ jsxs12(Text14, { bold: true, color: "cyan", children: [
2016
+ " ",
2017
+ "Vault & API Keys"
2018
+ ] }),
2019
+ vaultInfo ? /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
2020
+ /* @__PURE__ */ jsxs12(Box13, { children: [
2021
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " Status " }),
2022
+ /* @__PURE__ */ jsx14(
2023
+ Text14,
2024
+ {
2025
+ color: vaultInfo.isOpen ? "green" : vaultInfo.exists ? "yellow" : "red",
2026
+ bold: true,
2027
+ children: vaultInfo.isOpen ? "\u25CF unlocked" : vaultInfo.exists ? "\u25CF locked" : "\u25CB not initialized"
2028
+ }
2029
+ )
2030
+ ] }),
2031
+ vaultInfo.createdAt && /* @__PURE__ */ jsxs12(Box13, { children: [
2032
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " Created " }),
2033
+ /* @__PURE__ */ jsx14(Text14, { children: formatAge(vaultInfo.createdAt) })
2034
+ ] }),
2035
+ /* @__PURE__ */ jsxs12(Box13, { children: [
2036
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " 1Password " }),
2037
+ /* @__PURE__ */ jsx14(Text14, { color: vaultInfo.opAvailable ? "green" : "gray", children: vaultInfo.opAvailable ? "\u25CF connected" : "\u25CB not available" })
2038
+ ] }),
2039
+ /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
2040
+ /* @__PURE__ */ jsxs12(Text14, { children: [
2041
+ " ",
2042
+ /* @__PURE__ */ jsxs12(Text14, { bold: true, children: [
2043
+ "Resolved Keys (",
2044
+ vaultInfo.resolvedKeys.length,
2045
+ ")"
2046
+ ] })
2047
+ ] }),
2048
+ vaultInfo.resolvedKeys.length === 0 ? /* @__PURE__ */ jsxs12(Text14, { color: "gray", dimColor: true, children: [
2049
+ " ",
2050
+ "No keys resolved. Use /vault add or set env vars."
2051
+ ] }) : vaultInfo.resolvedKeys.map((key) => {
2052
+ const info = KEY_LABELS[key];
2053
+ return /* @__PURE__ */ jsxs12(Box13, { children: [
2054
+ /* @__PURE__ */ jsx14(Text14, { color: info?.color ?? "gray", children: " \u25CF " }),
2055
+ /* @__PURE__ */ jsx14(Text14, { children: (info?.label ?? key).padEnd(20) }),
2056
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: key })
2057
+ ] }, key);
2058
+ })
2059
+ ] }),
2060
+ vaultInfo.isOpen && vaultInfo.keys.length > 0 && /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
2061
+ /* @__PURE__ */ jsxs12(Text14, { children: [
2062
+ " ",
2063
+ /* @__PURE__ */ jsxs12(Text14, { bold: true, children: [
2064
+ "Vault Keys (",
2065
+ vaultInfo.keyCount,
2066
+ ")"
2067
+ ] })
2068
+ ] }),
2069
+ vaultInfo.keys.map((key) => {
2070
+ const info = KEY_LABELS[key];
2071
+ return /* @__PURE__ */ jsxs12(Box13, { children: [
2072
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " \u25C6 " }),
2073
+ /* @__PURE__ */ jsx14(Text14, { children: (info?.label ?? key).padEnd(20) }),
2074
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: key })
2075
+ ] }, key);
2076
+ })
2077
+ ] })
2078
+ ] }) : /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
2079
+ /* @__PURE__ */ jsxs12(Text14, { color: "gray", dimColor: true, children: [
2080
+ " ",
2081
+ "Vault info not available."
2082
+ ] }),
2083
+ /* @__PURE__ */ jsxs12(Text14, { color: "gray", dimColor: true, children: [
2084
+ " ",
2085
+ "Run `brainstorm vault init` to create."
2086
+ ] })
2087
+ ] }),
2088
+ memoryInfo && /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
2089
+ /* @__PURE__ */ jsxs12(Text14, { children: [
2090
+ " ",
2091
+ /* @__PURE__ */ jsxs12(Text14, { bold: true, children: [
2092
+ "Memory (",
2093
+ memoryInfo.localCount,
2094
+ " entries)"
2095
+ ] })
2096
+ ] }),
2097
+ Object.entries(memoryInfo.types).map(([type, count]) => /* @__PURE__ */ jsxs12(Text14, { color: "gray", children: [
2098
+ " ",
2099
+ type.padEnd(12),
2100
+ " ",
2101
+ count
2102
+ ] }, type)),
2103
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " /dream to consolidate" })
2104
+ ] }),
2105
+ /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
2106
+ /* @__PURE__ */ jsxs12(Text14, { children: [
2107
+ " ",
2108
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Commands" })
2109
+ ] }),
2110
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " /vault list Show stored keys" }),
2111
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " /role Show available roles" }),
2112
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " /recommend Get model suggestion" }),
2113
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " /stats Session analytics" })
2114
+ ] })
2115
+ ]
2116
+ }
2117
+ )
2118
+ ] }) });
2119
+ }
2120
+
2121
+ // src/components/modes/PlanningMode.tsx
2122
+ import { useState as useState8, useEffect as useEffect4 } from "react";
2123
+ import { Box as Box15, Text as Text16, useInput as useInput6 } from "ink";
2124
+
2125
+ // src/components/planning/PlanTree.tsx
2126
+ import { useState as useState7, useEffect as useEffect3 } from "react";
2127
+ import { Box as Box14, Text as Text15, useInput as useInput5 } from "ink";
2128
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2129
+ function statusIcon(status) {
2130
+ switch (status) {
2131
+ case "completed":
2132
+ return "\u2713";
2133
+ case "in_progress":
2134
+ return "\u25D0";
2135
+ case "failed":
2136
+ return "\u2717";
2137
+ case "blocked":
2138
+ return "\u25AA";
2139
+ case "skipped":
2140
+ return "\u25CB";
2141
+ default:
2142
+ return "\u25CB";
2143
+ }
2144
+ }
2145
+ function statusColor(status) {
2146
+ switch (status) {
2147
+ case "completed":
2148
+ return "green";
2149
+ case "in_progress":
2150
+ return "yellow";
2151
+ case "failed":
2152
+ return "red";
2153
+ case "blocked":
2154
+ return "gray";
2155
+ default:
2156
+ return "gray";
2157
+ }
2158
+ }
2159
+ function flattenPlan(plan, expanded) {
2160
+ const nodes = [];
2161
+ for (const phase of plan.phases) {
2162
+ nodes.push({
2163
+ id: phase.id,
2164
+ label: phase.name,
2165
+ status: phase.status,
2166
+ depth: 0,
2167
+ type: "phase",
2168
+ progress: `${phase.completedCount}/${phase.taskCount}`,
2169
+ expandable: true
2170
+ });
2171
+ if (!expanded.has(phase.id)) continue;
2172
+ for (const sprint of phase.sprints) {
2173
+ nodes.push({
2174
+ id: sprint.id,
2175
+ label: sprint.name,
2176
+ status: sprint.status,
2177
+ depth: 1,
2178
+ type: "sprint",
2179
+ expandable: true
2180
+ });
2181
+ if (!expanded.has(sprint.id)) continue;
2182
+ for (const task of sprint.tasks) {
2183
+ nodes.push({
2184
+ id: task.id,
2185
+ label: task.description,
2186
+ status: task.status,
2187
+ depth: 2,
2188
+ type: "task",
2189
+ cost: task.cost,
2190
+ skill: task.assignedSkill,
2191
+ expandable: false
2192
+ });
2193
+ }
2194
+ }
2195
+ }
2196
+ return nodes;
2197
+ }
2198
+ function PlanTree({
2199
+ plan,
2200
+ selectedId,
2201
+ onSelect,
2202
+ maxHeight = 20,
2203
+ isActive = true
2204
+ }) {
2205
+ const [expanded, setExpanded] = useState7(() => {
2206
+ const initial = /* @__PURE__ */ new Set();
2207
+ for (const phase of plan.phases) {
2208
+ if (phase.status === "in_progress" || phase.status === "pending") {
2209
+ initial.add(phase.id);
2210
+ for (const sprint of phase.sprints) {
2211
+ if (sprint.status === "in_progress" || sprint.status === "pending") {
2212
+ initial.add(sprint.id);
2213
+ }
2214
+ }
2215
+ }
2216
+ }
2217
+ return initial;
2218
+ });
2219
+ const [cursor, setCursor] = useState7(0);
2220
+ const nodes = flattenPlan(plan, expanded);
2221
+ useInput5((input, key) => {
2222
+ if (!isActive) return;
2223
+ if (key.downArrow || input === "j") {
2224
+ setCursor((prev) => Math.min(prev + 1, nodes.length - 1));
2225
+ }
2226
+ if (key.upArrow || input === "k") {
2227
+ setCursor((prev) => Math.max(prev - 1, 0));
2228
+ }
2229
+ if (key.return) {
2230
+ const node = nodes[cursor];
2231
+ if (!node) return;
2232
+ if (node.expandable) {
2233
+ setExpanded((prev) => {
2234
+ const next = new Set(prev);
2235
+ if (next.has(node.id)) next.delete(node.id);
2236
+ else next.add(node.id);
2237
+ return next;
2238
+ });
2239
+ }
2240
+ onSelect(node.id, node.type);
2241
+ }
2242
+ });
2243
+ const safeCursor = Math.min(cursor, nodes.length - 1);
2244
+ const selectedNode = nodes[safeCursor];
2245
+ useEffect3(() => {
2246
+ if (selectedNode && selectedNode.id !== selectedId) {
2247
+ onSelect(selectedNode.id, selectedNode.type);
2248
+ }
2249
+ }, [selectedNode?.id]);
2250
+ const scrollOffset = Math.max(0, safeCursor - maxHeight + 3);
2251
+ const visibleNodes = nodes.slice(scrollOffset, scrollOffset + maxHeight);
2252
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
2253
+ /* @__PURE__ */ jsx15(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text15, { bold: true, color: "cyan", children: plan.name }) }),
2254
+ /* @__PURE__ */ jsx15(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsxs13(Text15, { color: "gray", children: [
2255
+ plan.completedTasks,
2256
+ "/",
2257
+ plan.totalTasks,
2258
+ " tasks"
2259
+ ] }) }),
2260
+ visibleNodes.map((node, i) => {
2261
+ const actualIdx = scrollOffset + i;
2262
+ const isCursor = actualIdx === safeCursor;
2263
+ const indent = " ".repeat(node.depth);
2264
+ const expandIcon = node.expandable ? expanded.has(node.id) ? "\u25BC " : "\u25B6 " : " ";
2265
+ return /* @__PURE__ */ jsxs13(Box14, { children: [
2266
+ /* @__PURE__ */ jsx15(Text15, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u2192 " : " " }),
2267
+ /* @__PURE__ */ jsx15(Text15, { dimColor: !isCursor, children: indent }),
2268
+ /* @__PURE__ */ jsxs13(Text15, { color: statusColor(node.status), children: [
2269
+ statusIcon(node.status),
2270
+ " "
2271
+ ] }),
2272
+ /* @__PURE__ */ jsx15(Text15, { dimColor: !isCursor, children: expandIcon }),
2273
+ /* @__PURE__ */ jsx15(
2274
+ Text15,
2275
+ {
2276
+ color: isCursor ? "white" : "gray",
2277
+ bold: isCursor,
2278
+ wrap: "truncate",
2279
+ children: node.label.slice(0, 35)
2280
+ }
2281
+ ),
2282
+ node.progress && /* @__PURE__ */ jsxs13(Text15, { color: "gray", dimColor: true, children: [
2283
+ " ",
2284
+ node.progress
2285
+ ] }),
2286
+ node.skill && /* @__PURE__ */ jsxs13(Text15, { color: "cyan", dimColor: true, children: [
2287
+ " ",
2288
+ "[",
2289
+ node.skill,
2290
+ "]"
2291
+ ] }),
2292
+ node.cost !== void 0 && node.cost > 0 && /* @__PURE__ */ jsxs13(Text15, { color: "gray", dimColor: true, children: [
2293
+ " ",
2294
+ "$",
2295
+ node.cost.toFixed(2)
2296
+ ] })
2297
+ ] }, node.id);
2298
+ }),
2299
+ scrollOffset > 0 && /* @__PURE__ */ jsxs13(Text15, { color: "gray", dimColor: true, children: [
2300
+ " ",
2301
+ "\u2191 ",
2302
+ scrollOffset,
2303
+ " more"
2304
+ ] }),
2305
+ scrollOffset + maxHeight < nodes.length && /* @__PURE__ */ jsxs13(Text15, { color: "gray", dimColor: true, children: [
2306
+ " ",
2307
+ "\u2193 ",
2308
+ nodes.length - scrollOffset - maxHeight,
2309
+ " more"
2310
+ ] })
2311
+ ] });
2312
+ }
2313
+
2314
+ // src/components/modes/PlanningMode.tsx
2315
+ import { parsePlanContent } from "@brainst0rm/core";
2316
+ import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
2317
+ import { join as join2 } from "path";
2318
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2319
+ function loadPlans(projectPath) {
2320
+ const plans = [];
2321
+ const planDirs = [
2322
+ join2(projectPath, ".claude", "plans"),
2323
+ join2(projectPath, ".brainstorm", "plans")
2324
+ ];
2325
+ for (const dir of planDirs) {
2326
+ if (!existsSync2(dir)) continue;
2327
+ for (const file of readdirSync(dir)) {
2328
+ if (!file.endsWith(".plan.md")) continue;
2329
+ try {
2330
+ const filePath = join2(dir, file);
2331
+ const content = readFileSync2(filePath, "utf-8");
2332
+ plans.push(parsePlanContent(content, filePath));
2333
+ } catch {
2334
+ }
2335
+ }
2336
+ }
2337
+ return plans;
2338
+ }
2339
+ function PlanningMode({ projectPath }) {
2340
+ const [plans, setPlans] = useState8([]);
2341
+ const [activePlanIdx, setActivePlanIdx] = useState8(0);
2342
+ const [selectedId, setSelectedId] = useState8(null);
2343
+ const [selectedType, setSelectedType] = useState8(null);
2344
+ useEffect4(() => {
2345
+ const cwd = projectPath || process.cwd();
2346
+ setPlans(loadPlans(cwd));
2347
+ }, [projectPath]);
2348
+ useInput6((input, key) => {
2349
+ if (input === "[" && plans.length > 1) {
2350
+ setActivePlanIdx((prev) => Math.max(0, prev - 1));
2351
+ }
2352
+ if (input === "]" && plans.length > 1) {
2353
+ setActivePlanIdx((prev) => Math.min(plans.length - 1, prev + 1));
2354
+ }
2355
+ });
2356
+ if (plans.length === 0) {
2357
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
2358
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: "cyan", children: "Planning" }),
2359
+ /* @__PURE__ */ jsx16(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "No .plan.md files found. Create one at .claude/plans/my-plan.plan.md" }) }),
2360
+ /* @__PURE__ */ jsx16(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Format: YAML frontmatter + ## Phases + ### Sprints + - [x] Tasks" }) }),
2361
+ /* @__PURE__ */ jsx16(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text16, { color: "gray", dimColor: true, children: [
2362
+ "Execute: storm plan execute ",
2363
+ "<path>",
2364
+ " [--dry-run] [--auto]"
2365
+ ] }) })
2366
+ ] });
2367
+ }
2368
+ const activePlan = plans[activePlanIdx];
2369
+ let selectedPhase = null;
2370
+ let selectedTask = null;
2371
+ if (selectedId && activePlan) {
2372
+ for (const phase of activePlan.phases) {
2373
+ if (phase.id === selectedId) {
2374
+ selectedPhase = phase;
2375
+ break;
2376
+ }
2377
+ for (const sprint of phase.sprints) {
2378
+ if (sprint.id === selectedId) {
2379
+ selectedPhase = phase;
2380
+ break;
2381
+ }
2382
+ for (const task of sprint.tasks) {
2383
+ if (task.id === selectedId) {
2384
+ selectedTask = task;
2385
+ selectedPhase = phase;
2386
+ break;
2387
+ }
2388
+ }
2389
+ }
2390
+ }
2391
+ }
2392
+ const progress = activePlan.totalTasks > 0 ? Math.round(activePlan.completedTasks / activePlan.totalTasks * 100) : 0;
2393
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2394
+ plans.length > 1 && /* @__PURE__ */ jsxs14(Box15, { marginBottom: 1, children: [
2395
+ /* @__PURE__ */ jsxs14(Text16, { color: "gray", dimColor: true, children: [
2396
+ "[ ] switch plan:",
2397
+ " "
2398
+ ] }),
2399
+ plans.map((p, i) => /* @__PURE__ */ jsx16(Box15, { marginRight: 1, children: /* @__PURE__ */ jsx16(
2400
+ Text16,
2401
+ {
2402
+ color: i === activePlanIdx ? "cyan" : "gray",
2403
+ bold: i === activePlanIdx,
2404
+ children: p.name.slice(0, 20)
2405
+ }
2406
+ ) }, p.id))
2407
+ ] }),
2408
+ /* @__PURE__ */ jsxs14(Box15, { marginBottom: 1, children: [
2409
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "Progress: " }),
2410
+ /* @__PURE__ */ jsxs14(
2411
+ Text16,
2412
+ {
2413
+ color: progress >= 100 ? "green" : progress > 0 ? "yellow" : "gray",
2414
+ children: [
2415
+ "\u2588".repeat(Math.round(progress / 5)),
2416
+ "\u2591".repeat(20 - Math.round(progress / 5))
2417
+ ]
2418
+ }
2419
+ ),
2420
+ /* @__PURE__ */ jsxs14(Text16, { color: "gray", children: [
2421
+ " ",
2422
+ progress,
2423
+ "% "
2424
+ ] }),
2425
+ /* @__PURE__ */ jsxs14(Text16, { color: "gray", dimColor: true, children: [
2426
+ "(",
2427
+ activePlan.completedTasks,
2428
+ "/",
2429
+ activePlan.totalTasks,
2430
+ ")"
2431
+ ] })
2432
+ ] }),
2433
+ /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", flexGrow: 1, children: [
2434
+ /* @__PURE__ */ jsx16(Box15, { width: "45%", flexDirection: "column", children: /* @__PURE__ */ jsx16(
2435
+ PlanTree,
2436
+ {
2437
+ plan: activePlan,
2438
+ selectedId,
2439
+ onSelect: (id, type) => {
2440
+ setSelectedId(id);
2441
+ setSelectedType(type);
2442
+ }
2443
+ }
2444
+ ) }),
2445
+ /* @__PURE__ */ jsx16(Box15, { width: 1, marginX: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "\u2502" }) }),
2446
+ /* @__PURE__ */ jsxs14(Box15, { width: "50%", flexDirection: "column", children: [
2447
+ selectedPhase && selectedType === "phase" && /* @__PURE__ */ jsx16(PhaseDetail, { phase: selectedPhase }),
2448
+ selectedTask && selectedType === "task" && /* @__PURE__ */ jsx16(TaskDetail, { task: selectedTask }),
2449
+ !selectedId && /* @__PURE__ */ jsx16(Box15, { children: /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "Select a node to see details" }) })
2450
+ ] })
2451
+ ] })
2452
+ ] });
2453
+ }
2454
+ function PhaseDetail({ phase }) {
2455
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
2456
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: "white", children: phase.name }),
2457
+ /* @__PURE__ */ jsxs14(Text16, { color: "gray", dimColor: true, children: [
2458
+ phase.completedCount,
2459
+ "/",
2460
+ phase.taskCount,
2461
+ " tasks"
2462
+ ] }),
2463
+ /* @__PURE__ */ jsx16(Box15, { marginTop: 1, flexDirection: "column", children: phase.sprints.map((sprint) => /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
2464
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", children: sprint.name }),
2465
+ sprint.tasks.map((task) => {
2466
+ const icon = task.status === "completed" ? "\u2713" : task.status === "in_progress" ? "\u25D0" : "\u25CB";
2467
+ const color = task.status === "completed" ? "green" : task.status === "in_progress" ? "yellow" : "gray";
2468
+ return /* @__PURE__ */ jsxs14(Box15, { paddingLeft: 2, children: [
2469
+ /* @__PURE__ */ jsxs14(Text16, { color, children: [
2470
+ icon,
2471
+ " "
2472
+ ] }),
2473
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", wrap: "truncate", children: task.description.slice(0, 45) }),
2474
+ task.cost !== void 0 && task.cost > 0 && /* @__PURE__ */ jsxs14(Text16, { color: "gray", dimColor: true, children: [
2475
+ " ",
2476
+ "$",
2477
+ task.cost.toFixed(2)
2478
+ ] })
2479
+ ] }, task.id);
2480
+ })
2481
+ ] }, sprint.id)) })
2482
+ ] });
2483
+ }
2484
+ function TaskDetail({ task }) {
2485
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
2486
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: "white", children: task.description }),
2487
+ /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
2488
+ /* @__PURE__ */ jsxs14(Text16, { color: "gray", children: [
2489
+ "Status:",
2490
+ " ",
2491
+ /* @__PURE__ */ jsx16(Text16, { color: task.status === "completed" ? "green" : "yellow", children: task.status })
2492
+ ] }),
2493
+ task.assignedSkill && /* @__PURE__ */ jsxs14(Text16, { color: "gray", children: [
2494
+ "Skill: ",
2495
+ /* @__PURE__ */ jsx16(Text16, { color: "cyan", children: task.assignedSkill })
2496
+ ] }),
2497
+ task.modelUsed && /* @__PURE__ */ jsxs14(Text16, { color: "gray", children: [
2498
+ "Model: ",
2499
+ /* @__PURE__ */ jsx16(Text16, { color: "green", children: task.modelUsed })
2500
+ ] }),
2501
+ task.cost !== void 0 && task.cost > 0 && /* @__PURE__ */ jsxs14(Text16, { color: "gray", children: [
2502
+ "Cost: ",
2503
+ /* @__PURE__ */ jsxs14(Text16, { color: "yellow", children: [
2504
+ "$",
2505
+ task.cost.toFixed(4)
2506
+ ] })
2507
+ ] })
2508
+ ] })
2509
+ ] });
2510
+ }
2511
+
2512
+ // src/components/ShortcutOverlay.tsx
2513
+ import { Box as Box16, Text as Text17, useInput as useInput7 } from "ink";
2514
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2515
+ function ShortcutOverlay({ onDismiss }) {
2516
+ useInput7(() => {
2517
+ onDismiss();
2518
+ });
2519
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
2520
+ /* @__PURE__ */ jsx17(Text17, { bold: true, color: "cyan", children: "Keyboard Shortcuts" }),
2521
+ /* @__PURE__ */ jsx17(Text17, { children: " " }),
2522
+ /* @__PURE__ */ jsx17(Text17, { bold: true, children: "Navigation" }),
2523
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " Esc Toggle between Chat and Dashboard" }),
2524
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " 1-4 Switch modes (in non-chat modes)" }),
2525
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " Tab Cycle modes (in non-chat modes)" }),
2526
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " Ctrl+D\xD72 Exit application" }),
2527
+ /* @__PURE__ */ jsx17(Text17, { children: " " }),
2528
+ /* @__PURE__ */ jsx17(Text17, { bold: true, children: "Chat Mode" }),
2529
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " Enter Send message" }),
2530
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " \\+Enter Multi-line (backslash continuation)" }),
2531
+ /* @__PURE__ */ jsxs15(Text17, { color: "gray", children: [
2532
+ " ",
2533
+ "Shift+Tab Cycle permission mode (auto/confirm/plan)"
2534
+ ] }),
2535
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " \u2191\u2193 Input history" }),
2536
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " Shift+\u2191\u2193 Scroll message history" }),
2537
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " / Show command autocomplete" }),
2538
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " @file Include file in context" }),
2539
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " Esc Abort (while processing) / Dashboard (idle)" }),
2540
+ /* @__PURE__ */ jsx17(Text17, { children: " " }),
2541
+ /* @__PURE__ */ jsx17(Text17, { bold: true, children: "Models Mode" }),
2542
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " \u2191\u2193 / j/k Navigate model list" }),
2543
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " Enter Select model for session" }),
2544
+ /* @__PURE__ */ jsx17(Text17, { children: " " }),
2545
+ /* @__PURE__ */ jsx17(Text17, { bold: true, children: "Dashboard Mode" }),
2546
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " r Refresh BrainstormRouter data" }),
2547
+ /* @__PURE__ */ jsx17(Text17, { children: " " }),
2548
+ /* @__PURE__ */ jsx17(Text17, { bold: true, children: "Key Commands" }),
2549
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " /help Command reference" }),
2550
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " /role Switch roles (architect, sr-dev, qa)" }),
2551
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " /build Multi-model workflow wizard" }),
2552
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " /context Token breakdown" }),
2553
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " /insights Session intelligence" }),
2554
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: " /undo Remove last turn" }),
2555
+ /* @__PURE__ */ jsx17(Text17, { children: " " }),
2556
+ /* @__PURE__ */ jsx17(Text17, { color: "gray", children: "Press any key to dismiss" })
2557
+ ] });
2558
+ }
2559
+
2560
+ // src/components/App.tsx
2561
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2562
+ function App(props) {
2563
+ const { exit } = useApp2();
2564
+ const { mode, setMode, cycleMode, setModeByKey } = useMode("chat");
2565
+ const [sessionCost, setSessionCost] = useState9(0);
2566
+ const [tokenCount, setTokenCount] = useState9({ input: 0, output: 0 });
2567
+ const [currentModel, setCurrentModel] = useState9();
2568
+ const [currentRole, setCurrentRole] = useState9();
2569
+ const [isProcessing, setIsProcessing] = useState9(false);
2570
+ const [routingHistory, setRoutingHistory] = useState9([]);
2571
+ const [toolStats, setToolStats] = useState9(/* @__PURE__ */ new Map());
2572
+ const [turnCount, setTurnCount] = useState9(0);
2573
+ const [sessionStart] = useState9(Date.now());
2574
+ const { data: brData, refresh: refreshBR } = useBRData(props.gateway ?? null);
2575
+ const [lastCtrlD, setLastCtrlD] = useState9(0);
2576
+ const [guardianStatus, setGuardianStatus] = useState9();
2577
+ const [showShortcuts, setShowShortcuts] = useState9(false);
2578
+ const abortTimeoutRef = useRef2(null);
2579
+ useInput8((input, key) => {
2580
+ if (key.escape) {
2581
+ if (mode === "chat") {
2582
+ if (isProcessing) {
2583
+ props.onAbort?.();
2584
+ if (abortTimeoutRef.current) clearTimeout(abortTimeoutRef.current);
2585
+ abortTimeoutRef.current = setTimeout(() => {
2586
+ setIsProcessing(false);
2587
+ abortTimeoutRef.current = null;
2588
+ }, 5e3);
2589
+ } else {
2590
+ setMode("dashboard");
2591
+ }
2592
+ } else {
2593
+ setMode("chat");
2594
+ }
2595
+ return;
2596
+ }
2597
+ if (input === "?" && mode !== "chat") {
2598
+ setShowShortcuts(true);
2599
+ return;
2600
+ }
2601
+ if (mode !== "chat") {
2602
+ if (setModeByKey(input)) return;
2603
+ if (key.tab) {
2604
+ cycleMode();
2605
+ return;
2606
+ }
2607
+ if (input === "r" && mode === "dashboard") {
2608
+ refreshBR();
2609
+ return;
2610
+ }
2611
+ }
2612
+ if (input === "d" && key.ctrl) {
2613
+ const now = Date.now();
2614
+ if (lastCtrlD > 0 && now - lastCtrlD < 2e3) {
2615
+ exit();
2616
+ } else {
2617
+ setLastCtrlD(now);
2618
+ }
2619
+ return;
2620
+ }
2621
+ });
2622
+ const termHeight = process.stdout.rows || 24;
2623
+ const wrappedSlashCallbacks = {
2624
+ ...props.slashCallbacks,
2625
+ gateway: props.gateway,
2626
+ setModel: (model) => {
2627
+ props.slashCallbacks?.setModel?.(model);
2628
+ const name = model.split("/").pop() ?? model;
2629
+ setCurrentModel(name);
2630
+ },
2631
+ setActiveRole: (role) => {
2632
+ props.slashCallbacks?.setActiveRole?.(role);
2633
+ setCurrentRole(role);
2634
+ }
2635
+ };
2636
+ function wrappedSendMessage(text) {
2637
+ const gen = props.onSendMessage(text);
2638
+ let lastRequestId;
2639
+ let lastModelUsed;
2640
+ return (async function* () {
2641
+ setIsProcessing(true);
2642
+ try {
2643
+ for await (const event of gen) {
2644
+ if (event.type === "routing") {
2645
+ lastModelUsed = event.decision.model.id ?? event.decision.model.name;
2646
+ setCurrentModel(event.decision.model.name);
2647
+ setRoutingHistory((prev) => [
2648
+ {
2649
+ model: event.decision.model.name,
2650
+ strategy: event.decision.strategy,
2651
+ reason: event.decision.reason,
2652
+ timestamp: Date.now()
2653
+ },
2654
+ ...prev.slice(0, 9)
2655
+ ]);
2656
+ }
2657
+ if (event.type === "tool-call-start") {
2658
+ setToolStats((prev) => {
2659
+ const next = new Map(prev);
2660
+ const existing = next.get(event.toolName) ?? {
2661
+ name: event.toolName,
2662
+ calls: 0,
2663
+ successes: 0
2664
+ };
2665
+ next.set(event.toolName, {
2666
+ ...existing,
2667
+ calls: existing.calls + 1
2668
+ });
2669
+ return next;
2670
+ });
2671
+ }
2672
+ if (event.type === "tool-call-result") {
2673
+ setToolStats((prev) => {
2674
+ const next = new Map(prev);
2675
+ const existing = next.get(event.toolName);
2676
+ if (existing) {
2677
+ next.set(event.toolName, {
2678
+ ...existing,
2679
+ successes: existing.successes + 1
2680
+ });
2681
+ }
2682
+ return next;
2683
+ });
2684
+ }
2685
+ if (event.type === "gateway-feedback") {
2686
+ lastRequestId = event.feedback?.requestId;
2687
+ const actualCost = event.feedback?.actualCost;
2688
+ if (typeof actualCost === "number" && actualCost > 0) {
2689
+ setSessionCost((prev) => Math.max(prev, actualCost));
2690
+ }
2691
+ const guardian = event.feedback?.guardianStatus;
2692
+ if (guardian) setGuardianStatus(guardian);
2693
+ }
2694
+ if (event.type === "done") {
2695
+ setSessionCost(event.totalCost);
2696
+ if (event.totalTokens) setTokenCount(event.totalTokens);
2697
+ setTurnCount((prev) => prev + 1);
2698
+ if (props.gateway && lastRequestId) {
2699
+ props.gateway.reportOutcome(lastRequestId, {
2700
+ success: true,
2701
+ signals: {},
2702
+ model_used: lastModelUsed
2703
+ }).catch(() => {
2704
+ });
2705
+ }
2706
+ }
2707
+ yield event;
2708
+ }
2709
+ } finally {
2710
+ setIsProcessing(false);
2711
+ }
2712
+ })();
2713
+ }
2714
+ return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", height: termHeight, children: [
2715
+ /* @__PURE__ */ jsx18(
2716
+ ModeBar,
2717
+ {
2718
+ activeMode: mode,
2719
+ model: currentModel,
2720
+ cost: sessionCost,
2721
+ role: currentRole,
2722
+ guardianStatus
2723
+ }
2724
+ ),
2725
+ /* @__PURE__ */ jsx18(
2726
+ Box17,
2727
+ {
2728
+ flexDirection: "column",
2729
+ flexGrow: mode === "chat" ? 1 : 0,
2730
+ display: mode === "chat" ? "flex" : "none",
2731
+ children: /* @__PURE__ */ jsx18(
2732
+ ChatApp,
2733
+ {
2734
+ strategy: props.strategy,
2735
+ modelCount: props.modelCount,
2736
+ onSendMessage: wrappedSendMessage,
2737
+ onAbort: props.onAbort,
2738
+ isActive: mode === "chat",
2739
+ slashCallbacks: wrappedSlashCallbacks
2740
+ }
2741
+ )
2742
+ }
2743
+ ),
2744
+ mode === "dashboard" && /* @__PURE__ */ jsx18(
2745
+ DashboardMode,
2746
+ {
2747
+ sessionCost,
2748
+ tokenCount,
2749
+ modelCount: props.modelCount,
2750
+ routingHistory,
2751
+ toolStats: Array.from(toolStats.values()),
2752
+ turnCount,
2753
+ sessionStart,
2754
+ brData,
2755
+ onRefreshBR: refreshBR
2756
+ }
2757
+ ),
2758
+ mode === "models" && /* @__PURE__ */ jsx18(
2759
+ ModelsMode,
2760
+ {
2761
+ models: props.models ?? [],
2762
+ currentModelId: currentModel,
2763
+ onSelectModel: (id) => {
2764
+ props.slashCallbacks?.setModel?.(id);
2765
+ const name = id.split("/").pop() ?? id;
2766
+ setCurrentModel(name);
2767
+ setMode("chat");
2768
+ }
2769
+ }
2770
+ ),
2771
+ mode === "config" && /* @__PURE__ */ jsx18(
2772
+ ConfigMode,
2773
+ {
2774
+ strategy: props.configInfo?.strategy ?? props.strategy,
2775
+ permissionMode: props.configInfo?.permissionMode ?? "confirm",
2776
+ outputStyle: props.configInfo?.outputStyle ?? "concise",
2777
+ sandbox: props.configInfo?.sandbox ?? "none",
2778
+ role: currentRole,
2779
+ modelCount: props.modelCount,
2780
+ turnCount,
2781
+ sessionCost,
2782
+ vaultInfo: props.vaultInfo,
2783
+ memoryInfo: props.memoryInfo
2784
+ }
2785
+ ),
2786
+ mode === "planning" && /* @__PURE__ */ jsx18(PlanningMode, {}),
2787
+ /* @__PURE__ */ jsx18(KeyHint, { mode, isProcessing }),
2788
+ showShortcuts && /* @__PURE__ */ jsx18(ShortcutOverlay, { onDismiss: () => setShowShortcuts(false) })
2789
+ ] });
2790
+ }
2791
+ export {
2792
+ App
2793
+ };
2794
+ //# sourceMappingURL=App-DPXJYXKH.js.map