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