deepagentsdk 0.11.1 → 0.12.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 (103) hide show
  1. package/dist/adapters/elements/index.cjs +324 -0
  2. package/dist/adapters/elements/index.cjs.map +1 -0
  3. package/dist/adapters/elements/index.d.cts +212 -0
  4. package/dist/adapters/elements/index.d.mts +212 -0
  5. package/dist/adapters/elements/index.mjs +320 -0
  6. package/dist/adapters/elements/index.mjs.map +1 -0
  7. package/dist/agent-CrH-He58.mjs +2974 -0
  8. package/dist/agent-CrH-He58.mjs.map +1 -0
  9. package/dist/agent-Cuks-Idh.cjs +3396 -0
  10. package/dist/agent-Cuks-Idh.cjs.map +1 -0
  11. package/dist/chunk-CbDLau6x.cjs +34 -0
  12. package/dist/cli/index.cjs +3162 -0
  13. package/dist/cli/index.cjs.map +1 -0
  14. package/dist/cli/index.d.cts +1 -0
  15. package/dist/cli/index.d.mts +1 -0
  16. package/dist/cli/index.mjs +3120 -0
  17. package/dist/cli/index.mjs.map +1 -0
  18. package/dist/file-saver-BJCqMIb5.mjs +655 -0
  19. package/dist/file-saver-BJCqMIb5.mjs.map +1 -0
  20. package/dist/file-saver-C6O2LAvg.cjs +679 -0
  21. package/dist/file-saver-C6O2LAvg.cjs.map +1 -0
  22. package/dist/index.cjs +1471 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +1581 -0
  25. package/dist/index.d.mts +1581 -0
  26. package/dist/index.mjs +1371 -0
  27. package/dist/index.mjs.map +1 -0
  28. package/dist/load-79a2H4m0.cjs +163 -0
  29. package/dist/load-79a2H4m0.cjs.map +1 -0
  30. package/dist/load-94gjHorc.mjs +3 -0
  31. package/dist/load-B6CA5js_.mjs +142 -0
  32. package/dist/load-B6CA5js_.mjs.map +1 -0
  33. package/dist/load-C2qVmZMp.cjs +3 -0
  34. package/dist/types-4g9UvXal.d.mts +1151 -0
  35. package/dist/types-IulnvhFg.d.cts +1151 -0
  36. package/package.json +26 -12
  37. package/src/adapters/elements/index.ts +0 -27
  38. package/src/adapters/elements/messageAdapter.ts +0 -165
  39. package/src/adapters/elements/statusAdapter.ts +0 -39
  40. package/src/adapters/elements/types.ts +0 -97
  41. package/src/adapters/elements/useElementsAdapter.ts +0 -261
  42. package/src/agent.ts +0 -1258
  43. package/src/backends/composite.ts +0 -273
  44. package/src/backends/filesystem.ts +0 -692
  45. package/src/backends/index.ts +0 -22
  46. package/src/backends/local-sandbox.ts +0 -175
  47. package/src/backends/persistent.ts +0 -593
  48. package/src/backends/sandbox.ts +0 -510
  49. package/src/backends/state.ts +0 -244
  50. package/src/backends/utils.ts +0 -287
  51. package/src/checkpointer/file-saver.ts +0 -98
  52. package/src/checkpointer/index.ts +0 -5
  53. package/src/checkpointer/kv-saver.ts +0 -82
  54. package/src/checkpointer/memory-saver.ts +0 -82
  55. package/src/checkpointer/types.ts +0 -125
  56. package/src/cli/components/ApiKeyInput.tsx +0 -300
  57. package/src/cli/components/FilePreview.tsx +0 -237
  58. package/src/cli/components/Input.tsx +0 -277
  59. package/src/cli/components/Message.tsx +0 -93
  60. package/src/cli/components/ModelSelection.tsx +0 -338
  61. package/src/cli/components/SlashMenu.tsx +0 -101
  62. package/src/cli/components/StatusBar.tsx +0 -89
  63. package/src/cli/components/Subagent.tsx +0 -91
  64. package/src/cli/components/TodoList.tsx +0 -133
  65. package/src/cli/components/ToolApproval.tsx +0 -70
  66. package/src/cli/components/ToolCall.tsx +0 -144
  67. package/src/cli/components/ToolCallSummary.tsx +0 -175
  68. package/src/cli/components/Welcome.tsx +0 -75
  69. package/src/cli/components/index.ts +0 -24
  70. package/src/cli/hooks/index.ts +0 -12
  71. package/src/cli/hooks/useAgent.ts +0 -933
  72. package/src/cli/index.tsx +0 -1066
  73. package/src/cli/theme.ts +0 -205
  74. package/src/cli/utils/model-list.ts +0 -365
  75. package/src/constants/errors.ts +0 -29
  76. package/src/constants/limits.ts +0 -195
  77. package/src/index.ts +0 -176
  78. package/src/middleware/agent-memory.ts +0 -330
  79. package/src/prompts.ts +0 -196
  80. package/src/skills/index.ts +0 -2
  81. package/src/skills/load.ts +0 -191
  82. package/src/skills/types.ts +0 -53
  83. package/src/tools/execute.ts +0 -167
  84. package/src/tools/filesystem.ts +0 -418
  85. package/src/tools/index.ts +0 -39
  86. package/src/tools/subagent.ts +0 -443
  87. package/src/tools/todos.ts +0 -101
  88. package/src/tools/web.ts +0 -567
  89. package/src/types/backend.ts +0 -177
  90. package/src/types/core.ts +0 -220
  91. package/src/types/events.ts +0 -430
  92. package/src/types/index.ts +0 -94
  93. package/src/types/structured-output.ts +0 -43
  94. package/src/types/subagent.ts +0 -96
  95. package/src/types.ts +0 -22
  96. package/src/utils/approval.ts +0 -213
  97. package/src/utils/events.ts +0 -416
  98. package/src/utils/eviction.ts +0 -181
  99. package/src/utils/index.ts +0 -34
  100. package/src/utils/model-parser.ts +0 -38
  101. package/src/utils/patch-tool-calls.ts +0 -233
  102. package/src/utils/project-detection.ts +0 -32
  103. package/src/utils/summarization.ts +0 -254
@@ -0,0 +1,3162 @@
1
+ #!/usr/bin/env bun
2
+ const require_chunk = require('../chunk-CbDLau6x.cjs');
3
+ const require_agent = require('../agent-Cuks-Idh.cjs');
4
+ const require_file_saver = require('../file-saver-C6O2LAvg.cjs');
5
+ let react = require("react");
6
+ react = require_chunk.__toESM(react);
7
+ let ink = require("ink");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let _inkjs_ui = require("@inkjs/ui");
10
+
11
+ //#region src/cli/hooks/useAgent.ts
12
+ /**
13
+ * Hook for managing agent streaming and events.
14
+ */
15
+ let eventCounter = 0;
16
+ function createEventId() {
17
+ return `event-${++eventCounter}`;
18
+ }
19
+ const DEFAULT_CLI_INTERRUPT_ON = {
20
+ execute: true,
21
+ write_file: true,
22
+ edit_file: true,
23
+ web_search: true,
24
+ fetch_url: true
25
+ };
26
+ function useAgent(options) {
27
+ const [status, setStatus] = (0, react.useState)("idle");
28
+ const [streamingText, setStreamingText] = (0, react.useState)("");
29
+ const [lastCompletedText, setLastCompletedText] = (0, react.useState)("");
30
+ const [events, setEvents] = (0, react.useState)([]);
31
+ const [state, setState] = (0, react.useState)({
32
+ todos: [],
33
+ files: {}
34
+ });
35
+ const [messages, setMessages] = (0, react.useState)([]);
36
+ const [toolCalls, setToolCalls] = (0, react.useState)([]);
37
+ const [error, setError] = (0, react.useState)(null);
38
+ const [currentModel, setCurrentModel] = (0, react.useState)(options.model);
39
+ (0, react.useEffect)(() => {
40
+ const loadSession = async () => {
41
+ if (!options.sessionId || !options.checkpointer) return;
42
+ const checkpoint = await options.checkpointer.load(options.sessionId);
43
+ if (checkpoint) {
44
+ setState(checkpoint.state);
45
+ setMessages(checkpoint.messages);
46
+ messagesRef.current = checkpoint.messages;
47
+ addEvent({
48
+ type: "checkpoint-loaded",
49
+ threadId: options.sessionId,
50
+ step: checkpoint.step,
51
+ messagesCount: checkpoint.messages.length
52
+ });
53
+ }
54
+ };
55
+ loadSession().catch(console.error);
56
+ }, [options.sessionId]);
57
+ const [promptCachingEnabled, setPromptCachingEnabled] = (0, react.useState)(options.enablePromptCaching ?? false);
58
+ const [evictionLimit, setEvictionLimit] = (0, react.useState)(options.toolResultEvictionLimit ?? 0);
59
+ const [summarizationEnabled, setSummarizationEnabled] = (0, react.useState)(options.summarization?.enabled ?? false);
60
+ const [summarizationConfig, setSummarizationConfig] = (0, react.useState)(options.summarization);
61
+ const [autoApproveEnabled, setAutoApproveEnabled] = (0, react.useState)(false);
62
+ const [pendingApproval, setPendingApproval] = (0, react.useState)(null);
63
+ const approvalResolverRef = (0, react.useRef)(null);
64
+ const abortControllerRef = (0, react.useRef)(null);
65
+ const accumulatedTextRef = (0, react.useRef)("");
66
+ const totalTextRef = (0, react.useRef)("");
67
+ const messagesRef = (0, react.useRef)([]);
68
+ const toolCallsRef = (0, react.useRef)([]);
69
+ const pendingToolCallsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
70
+ const features = {
71
+ promptCaching: promptCachingEnabled,
72
+ eviction: evictionLimit > 0,
73
+ summarization: summarizationEnabled
74
+ };
75
+ const agentRef = (0, react.useRef)(require_agent.createDeepAgent({
76
+ model: require_file_saver.parseModelString(currentModel),
77
+ maxSteps: options.maxSteps,
78
+ systemPrompt: options.systemPrompt,
79
+ backend: options.backend,
80
+ enablePromptCaching: promptCachingEnabled,
81
+ toolResultEvictionLimit: evictionLimit,
82
+ summarization: summarizationConfig,
83
+ interruptOn: autoApproveEnabled ? void 0 : options.interruptOn ?? DEFAULT_CLI_INTERRUPT_ON,
84
+ checkpointer: options.checkpointer
85
+ }));
86
+ const addEvent = (0, react.useCallback)((event) => {
87
+ setEvents((prev) => [...prev, {
88
+ id: createEventId(),
89
+ type: event.type,
90
+ event,
91
+ timestamp: /* @__PURE__ */ new Date()
92
+ }]);
93
+ }, []);
94
+ const flushTextSegment = (0, react.useCallback)(() => {
95
+ if (accumulatedTextRef.current.trim()) {
96
+ addEvent({
97
+ type: "text-segment",
98
+ text: accumulatedTextRef.current
99
+ });
100
+ accumulatedTextRef.current = "";
101
+ setStreamingText("");
102
+ }
103
+ }, [addEvent]);
104
+ /**
105
+ * Common pattern: flush text, set status to "tool-call", add event
106
+ */
107
+ const handleToolEvent = (event, ctx) => {
108
+ ctx.flushTextSegment();
109
+ ctx.setStatus("tool-call");
110
+ ctx.addEvent(event);
111
+ };
112
+ /**
113
+ * Handle text streaming events.
114
+ * Accumulates text and updates streaming display.
115
+ */
116
+ const handleTextEvent = (event, ctx) => {
117
+ if (event.type !== "text") return;
118
+ ctx.setStatus("streaming");
119
+ ctx.accumulatedTextRef.current += event.text;
120
+ ctx.totalTextRef.current += event.text;
121
+ setStreamingText(ctx.accumulatedTextRef.current);
122
+ };
123
+ /**
124
+ * Handle step-start events.
125
+ * Marks beginning of a new reasoning step.
126
+ */
127
+ const handleStepStartEvent = (event, ctx) => {
128
+ if (event.type !== "step-start") return;
129
+ if (event.stepNumber > 1) ctx.addEvent(event);
130
+ };
131
+ /**
132
+ * Handle tool-call events.
133
+ * Tracks pending tool calls until results arrive.
134
+ */
135
+ const handleToolCallEvent = (event, ctx) => {
136
+ if (event.type !== "tool-call") return;
137
+ ctx.flushTextSegment();
138
+ ctx.setStatus("tool-call");
139
+ const pendingToolCall = {
140
+ toolName: event.toolName,
141
+ args: event.args,
142
+ status: "success"
143
+ };
144
+ ctx.pendingToolCallsRef.current.set(event.toolCallId, pendingToolCall);
145
+ ctx.addEvent(event);
146
+ };
147
+ /**
148
+ * Handle tool-result events.
149
+ * Updates pending tool call with result and moves to completed list.
150
+ */
151
+ const handleToolResultEvent = (event, ctx) => {
152
+ if (event.type !== "tool-result") return;
153
+ const completedToolCall = ctx.pendingToolCallsRef.current.get(event.toolCallId);
154
+ if (completedToolCall) {
155
+ completedToolCall.result = event.result;
156
+ ctx.toolCallsRef.current.push(completedToolCall);
157
+ ctx.setToolCalls([...ctx.toolCallsRef.current]);
158
+ ctx.pendingToolCallsRef.current.delete(event.toolCallId);
159
+ }
160
+ ctx.addEvent(event);
161
+ };
162
+ /**
163
+ * Handle todos-changed events.
164
+ * Updates the todos list in state.
165
+ */
166
+ const handleTodosChangedEvent = (event, ctx) => {
167
+ if (event.type !== "todos-changed") return;
168
+ ctx.flushTextSegment();
169
+ ctx.setStatus("tool-call");
170
+ ctx.setState((prev) => ({
171
+ ...prev,
172
+ todos: event.todos
173
+ }));
174
+ ctx.addEvent(event);
175
+ };
176
+ /**
177
+ * Handle file-write-start events.
178
+ */
179
+ const handleFileWriteStartEvent = (event, ctx) => {
180
+ if (event.type !== "file-write-start") return;
181
+ handleToolEvent(event, ctx);
182
+ };
183
+ /**
184
+ * Handle file-written events.
185
+ */
186
+ const handleFileWrittenEvent = (event, ctx) => {
187
+ if (event.type !== "file-written") return;
188
+ ctx.setStatus("tool-call");
189
+ ctx.addEvent(event);
190
+ };
191
+ /**
192
+ * Handle file-edited events.
193
+ */
194
+ const handleFileEditedEvent = (event, ctx) => {
195
+ if (event.type !== "file-edited") return;
196
+ ctx.setStatus("tool-call");
197
+ ctx.addEvent(event);
198
+ };
199
+ /**
200
+ * Handle file-read events.
201
+ */
202
+ const handleFileReadEvent = (event, ctx) => {
203
+ if (event.type !== "file-read") return;
204
+ handleToolEvent(event, ctx);
205
+ };
206
+ /**
207
+ * Handle ls events.
208
+ */
209
+ const handleLsEvent = (event, ctx) => {
210
+ if (event.type !== "ls") return;
211
+ handleToolEvent(event, ctx);
212
+ };
213
+ /**
214
+ * Handle glob events.
215
+ */
216
+ const handleGlobEvent = (event, ctx) => {
217
+ if (event.type !== "glob") return;
218
+ handleToolEvent(event, ctx);
219
+ };
220
+ /**
221
+ * Handle grep events.
222
+ */
223
+ const handleGrepEvent = (event, ctx) => {
224
+ if (event.type !== "grep") return;
225
+ handleToolEvent(event, ctx);
226
+ };
227
+ /**
228
+ * Handle web-search-start events.
229
+ */
230
+ const handleWebSearchStartEvent = (event, ctx) => {
231
+ if (event.type !== "web-search-start") return;
232
+ handleToolEvent(event, ctx);
233
+ };
234
+ /**
235
+ * Handle web-search-finish events.
236
+ */
237
+ const handleWebSearchFinishEvent = (event, ctx) => {
238
+ if (event.type !== "web-search-finish") return;
239
+ ctx.setStatus("tool-call");
240
+ ctx.addEvent(event);
241
+ };
242
+ /**
243
+ * Handle http-request-start events.
244
+ */
245
+ const handleHttpRequestStartEvent = (event, ctx) => {
246
+ if (event.type !== "http-request-start") return;
247
+ handleToolEvent(event, ctx);
248
+ };
249
+ /**
250
+ * Handle http-request-finish events.
251
+ */
252
+ const handleHttpRequestFinishEvent = (event, ctx) => {
253
+ if (event.type !== "http-request-finish") return;
254
+ ctx.setStatus("tool-call");
255
+ ctx.addEvent(event);
256
+ };
257
+ /**
258
+ * Handle fetch-url-start events.
259
+ */
260
+ const handleFetchUrlStartEvent = (event, ctx) => {
261
+ if (event.type !== "fetch-url-start") return;
262
+ handleToolEvent(event, ctx);
263
+ };
264
+ /**
265
+ * Handle fetch-url-finish events.
266
+ */
267
+ const handleFetchUrlFinishEvent = (event, ctx) => {
268
+ if (event.type !== "fetch-url-finish") return;
269
+ ctx.setStatus("tool-call");
270
+ ctx.addEvent(event);
271
+ };
272
+ /**
273
+ * Handle subagent-start events.
274
+ */
275
+ const handleSubagentStartEvent = (event, ctx) => {
276
+ if (event.type !== "subagent-start") return;
277
+ ctx.setStatus("subagent");
278
+ ctx.addEvent(event);
279
+ };
280
+ /**
281
+ * Handle subagent-finish events.
282
+ */
283
+ const handleSubagentFinishEvent = (event, ctx) => {
284
+ if (event.type !== "subagent-finish") return;
285
+ ctx.addEvent(event);
286
+ };
287
+ /**
288
+ * Handle approval-requested events.
289
+ * Already handled in onApprovalRequest callback, so no-op here.
290
+ */
291
+ const handleApprovalRequestedEvent = (event, ctx) => {};
292
+ /**
293
+ * Handle approval-response events.
294
+ * Already handled in respondToApproval callback, so no-op here.
295
+ */
296
+ const handleApprovalResponseEvent = (event, ctx) => {};
297
+ /**
298
+ * Handle done events.
299
+ * Flushes remaining text and updates final state.
300
+ */
301
+ const handleDoneEvent = (event, ctx) => {
302
+ if (event.type !== "done") return;
303
+ ctx.flushTextSegment();
304
+ ctx.setStatus("done");
305
+ ctx.setState(event.state);
306
+ if (event.messages) {
307
+ ctx.setMessages(event.messages);
308
+ ctx.messagesRef.current = event.messages;
309
+ }
310
+ ctx.addEvent(event);
311
+ };
312
+ /**
313
+ * Handle error events.
314
+ * Flushes remaining text and marks pending tool calls as failed.
315
+ */
316
+ const handleErrorEvent = (event, ctx) => {
317
+ if (event.type !== "error") return;
318
+ ctx.flushTextSegment();
319
+ ctx.setStatus("error");
320
+ ctx.setError(event.error);
321
+ for (const [id, tc] of ctx.pendingToolCallsRef.current) {
322
+ tc.status = "error";
323
+ ctx.toolCallsRef.current.push(tc);
324
+ }
325
+ ctx.pendingToolCallsRef.current.clear();
326
+ ctx.setToolCalls([...ctx.toolCallsRef.current]);
327
+ ctx.addEvent(event);
328
+ };
329
+ /**
330
+ * Event handler map.
331
+ * Maps event types to their handler functions.
332
+ */
333
+ const EVENT_HANDLERS = {
334
+ "text": handleTextEvent,
335
+ "step-start": handleStepStartEvent,
336
+ "tool-call": handleToolCallEvent,
337
+ "tool-result": handleToolResultEvent,
338
+ "todos-changed": handleTodosChangedEvent,
339
+ "file-write-start": handleFileWriteStartEvent,
340
+ "file-written": handleFileWrittenEvent,
341
+ "file-edited": handleFileEditedEvent,
342
+ "file-read": handleFileReadEvent,
343
+ "ls": handleLsEvent,
344
+ "glob": handleGlobEvent,
345
+ "grep": handleGrepEvent,
346
+ "web-search-start": handleWebSearchStartEvent,
347
+ "web-search-finish": handleWebSearchFinishEvent,
348
+ "http-request-start": handleHttpRequestStartEvent,
349
+ "http-request-finish": handleHttpRequestFinishEvent,
350
+ "fetch-url-start": handleFetchUrlStartEvent,
351
+ "fetch-url-finish": handleFetchUrlFinishEvent,
352
+ "subagent-start": handleSubagentStartEvent,
353
+ "subagent-finish": handleSubagentFinishEvent,
354
+ "approval-requested": handleApprovalRequestedEvent,
355
+ "approval-response": handleApprovalResponseEvent,
356
+ "done": handleDoneEvent,
357
+ "error": handleErrorEvent
358
+ };
359
+ const sendPrompt = (0, react.useCallback)(async (prompt) => {
360
+ setStatus("thinking");
361
+ setStreamingText("");
362
+ setToolCalls([]);
363
+ setError(null);
364
+ accumulatedTextRef.current = "";
365
+ totalTextRef.current = "";
366
+ toolCallsRef.current = [];
367
+ pendingToolCallsRef.current.clear();
368
+ addEvent({
369
+ type: "user-message",
370
+ content: prompt
371
+ });
372
+ messagesRef.current = messages;
373
+ abortControllerRef.current = new AbortController();
374
+ const inputMessages = messagesRef.current.length > 0 ? [...messagesRef.current, {
375
+ role: "user",
376
+ content: prompt
377
+ }] : [{
378
+ role: "user",
379
+ content: prompt
380
+ }];
381
+ try {
382
+ for await (const event of agentRef.current.streamWithEvents({
383
+ messages: inputMessages,
384
+ state,
385
+ threadId: options.sessionId,
386
+ abortSignal: abortControllerRef.current.signal,
387
+ onApprovalRequest: async (request) => {
388
+ if (autoApproveEnabled) {
389
+ addEvent({
390
+ type: "approval-requested",
391
+ ...request
392
+ });
393
+ addEvent({
394
+ type: "approval-response",
395
+ approvalId: request.approvalId,
396
+ approved: true
397
+ });
398
+ return true;
399
+ }
400
+ setPendingApproval({
401
+ approvalId: request.approvalId,
402
+ toolName: request.toolName,
403
+ args: request.args
404
+ });
405
+ addEvent({
406
+ type: "approval-requested",
407
+ ...request
408
+ });
409
+ return new Promise((resolve) => {
410
+ approvalResolverRef.current = resolve;
411
+ });
412
+ }
413
+ })) {
414
+ const eventHandlerContext = {
415
+ setStatus,
416
+ setState,
417
+ setMessages,
418
+ setToolCalls,
419
+ setError,
420
+ addEvent,
421
+ flushTextSegment,
422
+ accumulatedTextRef,
423
+ totalTextRef,
424
+ toolCallsRef,
425
+ pendingToolCallsRef,
426
+ messagesRef
427
+ };
428
+ const handler = EVENT_HANDLERS[event.type];
429
+ if (handler) handler(event, eventHandlerContext);
430
+ }
431
+ const finalText = totalTextRef.current;
432
+ const finalToolCalls = [...toolCallsRef.current];
433
+ setLastCompletedText(finalText);
434
+ setStatus("idle");
435
+ return {
436
+ text: finalText,
437
+ toolCalls: finalToolCalls
438
+ };
439
+ } catch (err) {
440
+ if (err.name === "AbortError") {
441
+ flushTextSegment();
442
+ setStatus("idle");
443
+ return {
444
+ text: totalTextRef.current,
445
+ toolCalls: toolCallsRef.current
446
+ };
447
+ } else {
448
+ flushTextSegment();
449
+ setStatus("error");
450
+ setError(err);
451
+ return {
452
+ text: "",
453
+ toolCalls: []
454
+ };
455
+ }
456
+ } finally {
457
+ abortControllerRef.current = null;
458
+ }
459
+ }, [
460
+ state,
461
+ messages,
462
+ addEvent,
463
+ flushTextSegment,
464
+ autoApproveEnabled
465
+ ]);
466
+ const abort = (0, react.useCallback)(() => {
467
+ if (abortControllerRef.current) {
468
+ abortControllerRef.current.abort();
469
+ setStatus("idle");
470
+ }
471
+ }, []);
472
+ const clear = (0, react.useCallback)(() => {
473
+ setEvents([]);
474
+ setStreamingText("");
475
+ setLastCompletedText("");
476
+ setMessages([]);
477
+ setToolCalls([]);
478
+ messagesRef.current = [];
479
+ toolCallsRef.current = [];
480
+ pendingToolCallsRef.current.clear();
481
+ setError(null);
482
+ setStatus("idle");
483
+ }, []);
484
+ const clearStreamingText = (0, react.useCallback)(() => {
485
+ setStreamingText("");
486
+ setEvents([]);
487
+ }, []);
488
+ const clearEvents = (0, react.useCallback)(() => {
489
+ setEvents([]);
490
+ }, []);
491
+ const recreateAgent = (0, react.useCallback)((overrides = {}) => {
492
+ const newModel = overrides.model ?? currentModel;
493
+ const newPromptCaching = overrides.promptCaching ?? promptCachingEnabled;
494
+ const newEvictionLimit = overrides.evictionLimit ?? evictionLimit;
495
+ const newSummarization = overrides.summarization ?? summarizationConfig;
496
+ let newInterruptOn;
497
+ if (overrides.interruptOn === null) newInterruptOn = void 0;
498
+ else if (overrides.interruptOn !== void 0) newInterruptOn = overrides.interruptOn;
499
+ else newInterruptOn = autoApproveEnabled ? void 0 : options.interruptOn ?? DEFAULT_CLI_INTERRUPT_ON;
500
+ agentRef.current = require_agent.createDeepAgent({
501
+ model: require_file_saver.parseModelString(newModel),
502
+ maxSteps: options.maxSteps,
503
+ systemPrompt: options.systemPrompt,
504
+ backend: options.backend,
505
+ enablePromptCaching: newPromptCaching,
506
+ toolResultEvictionLimit: newEvictionLimit,
507
+ summarization: newSummarization,
508
+ interruptOn: newInterruptOn
509
+ });
510
+ }, [
511
+ currentModel,
512
+ promptCachingEnabled,
513
+ evictionLimit,
514
+ summarizationConfig,
515
+ autoApproveEnabled,
516
+ options.maxSteps,
517
+ options.systemPrompt,
518
+ options.backend,
519
+ options.interruptOn
520
+ ]);
521
+ const setModel = (0, react.useCallback)((model) => {
522
+ setCurrentModel(model);
523
+ recreateAgent({ model });
524
+ }, [recreateAgent]);
525
+ const setPromptCaching = (0, react.useCallback)((enabled) => {
526
+ setPromptCachingEnabled(enabled);
527
+ recreateAgent({ promptCaching: enabled });
528
+ }, [recreateAgent]);
529
+ const setEviction = (0, react.useCallback)((enabled) => {
530
+ const newLimit = enabled ? options.toolResultEvictionLimit || 2e4 : 0;
531
+ setEvictionLimit(newLimit);
532
+ recreateAgent({ evictionLimit: newLimit });
533
+ }, [recreateAgent, options.toolResultEvictionLimit]);
534
+ const setSummarization = (0, react.useCallback)((enabled) => {
535
+ setSummarizationEnabled(enabled);
536
+ const newConfig = enabled ? {
537
+ enabled: true,
538
+ tokenThreshold: options.summarization?.tokenThreshold,
539
+ keepMessages: options.summarization?.keepMessages
540
+ } : void 0;
541
+ setSummarizationConfig(newConfig);
542
+ recreateAgent({ summarization: newConfig });
543
+ }, [recreateAgent, options.summarization]);
544
+ const respondToApproval = (0, react.useCallback)((approved) => {
545
+ if (approvalResolverRef.current) {
546
+ approvalResolverRef.current(approved);
547
+ approvalResolverRef.current = null;
548
+ const currentApproval = pendingApproval;
549
+ setPendingApproval(null);
550
+ if (currentApproval) addEvent({
551
+ type: "approval-response",
552
+ approvalId: currentApproval.approvalId,
553
+ approved
554
+ });
555
+ }
556
+ }, [addEvent]);
557
+ return {
558
+ status,
559
+ streamingText,
560
+ lastCompletedText,
561
+ events,
562
+ state,
563
+ messages,
564
+ toolCalls,
565
+ error,
566
+ sendPrompt,
567
+ abort,
568
+ clear,
569
+ clearStreamingText,
570
+ clearEvents,
571
+ setModel,
572
+ currentModel,
573
+ features,
574
+ setPromptCaching,
575
+ setEviction,
576
+ setSummarization,
577
+ pendingApproval,
578
+ respondToApproval,
579
+ autoApproveEnabled,
580
+ setAutoApprove: (0, react.useCallback)((enabled) => {
581
+ setAutoApproveEnabled(enabled);
582
+ if (enabled && approvalResolverRef.current) respondToApproval(true);
583
+ recreateAgent({ interruptOn: enabled ? null : options.interruptOn ?? DEFAULT_CLI_INTERRUPT_ON });
584
+ }, [
585
+ recreateAgent,
586
+ options.interruptOn,
587
+ respondToApproval
588
+ ])
589
+ };
590
+ }
591
+
592
+ //#endregion
593
+ //#region src/cli/theme.ts
594
+ /**
595
+ * Theme constants for the Ink CLI.
596
+ * Colors, emoji, and styling values.
597
+ */
598
+ /**
599
+ * Color palette for the CLI.
600
+ * Uses chalk-compatible color names.
601
+ */
602
+ const colors = {
603
+ primary: "cyan",
604
+ secondary: "magenta",
605
+ accent: "yellow",
606
+ success: "green",
607
+ error: "red",
608
+ warning: "yellow",
609
+ info: "blue",
610
+ muted: "gray",
611
+ highlight: "white",
612
+ tool: "magenta",
613
+ file: "blue",
614
+ user: "cyan",
615
+ agent: "green"
616
+ };
617
+ /**
618
+ * Status emoji for todo items and events.
619
+ */
620
+ const emoji = {
621
+ pending: "⏳",
622
+ in_progress: "🔄",
623
+ completed: "✅",
624
+ cancelled: "❌",
625
+ robot: "🤖",
626
+ thinking: "💭",
627
+ tool: "🔧",
628
+ file: "📁",
629
+ edit: "✏️",
630
+ todo: "📋",
631
+ done: "🎉",
632
+ error: "💥",
633
+ warning: "⚠️",
634
+ info: "ℹ️",
635
+ user: "👤",
636
+ subagent: "🤝",
637
+ key: "🔑",
638
+ model: "🧠"
639
+ };
640
+ const SLASH_COMMANDS = [
641
+ {
642
+ command: "/todos",
643
+ aliases: ["/t", "/todo"],
644
+ description: "Show current todo list"
645
+ },
646
+ {
647
+ command: "/files",
648
+ aliases: ["/f", "/file"],
649
+ description: "Show files in working directory"
650
+ },
651
+ {
652
+ command: "/read",
653
+ aliases: ["/r"],
654
+ description: "Read a file (usage: /read <path>)"
655
+ },
656
+ {
657
+ command: "/apikey",
658
+ aliases: ["/key", "/api"],
659
+ description: "Manage API keys (interactive)"
660
+ },
661
+ {
662
+ command: "/model",
663
+ aliases: [],
664
+ description: "Show available models or change model (usage: /model [model-name])"
665
+ },
666
+ {
667
+ command: "/features",
668
+ aliases: ["/feat"],
669
+ description: "Show enabled features (caching, eviction, summarization)"
670
+ },
671
+ {
672
+ command: "/tokens",
673
+ aliases: ["/tok"],
674
+ description: "Show estimated token count for conversation"
675
+ },
676
+ {
677
+ command: "/cache",
678
+ aliases: [],
679
+ description: "Toggle prompt caching (usage: /cache on|off)"
680
+ },
681
+ {
682
+ command: "/eviction",
683
+ aliases: ["/evict"],
684
+ description: "Toggle tool result eviction (usage: /eviction on|off)"
685
+ },
686
+ {
687
+ command: "/summarize",
688
+ aliases: ["/sum"],
689
+ description: "Toggle auto-summarization (usage: /summarize on|off)"
690
+ },
691
+ {
692
+ command: "/approve",
693
+ aliases: [],
694
+ description: "Toggle auto-approve mode for tool executions"
695
+ },
696
+ {
697
+ command: "/clear",
698
+ aliases: ["/c"],
699
+ description: "Clear chat history"
700
+ },
701
+ {
702
+ command: "/sessions",
703
+ aliases: ["/session-list"],
704
+ description: "List saved sessions"
705
+ },
706
+ {
707
+ command: "/session",
708
+ aliases: [],
709
+ description: "Session management (usage: /session clear)"
710
+ },
711
+ {
712
+ command: "/help",
713
+ aliases: ["/h", "/?"],
714
+ description: "Show help"
715
+ },
716
+ {
717
+ command: "/quit",
718
+ aliases: ["/q", "/exit"],
719
+ description: "Exit the CLI"
720
+ }
721
+ ];
722
+ /**
723
+ * Filter commands by prefix.
724
+ */
725
+ function filterCommands(prefix) {
726
+ if (!prefix) return SLASH_COMMANDS;
727
+ const normalizedPrefix = prefix.startsWith("/") ? prefix : `/${prefix}`;
728
+ return SLASH_COMMANDS.filter((cmd) => {
729
+ if (cmd.command.toLowerCase().startsWith(normalizedPrefix.toLowerCase())) return true;
730
+ return cmd.aliases.some((alias) => alias.toLowerCase().startsWith(normalizedPrefix.toLowerCase()));
731
+ });
732
+ }
733
+ /**
734
+ * Parse a slash command from input.
735
+ */
736
+ function parseCommand(input) {
737
+ const trimmed = input.trim();
738
+ if (!trimmed.startsWith("/")) return { isCommand: false };
739
+ const parts = trimmed.slice(1).split(/\s+/);
740
+ return {
741
+ isCommand: true,
742
+ command: parts[0]?.toLowerCase(),
743
+ args: parts.slice(1).join(" ") || void 0
744
+ };
745
+ }
746
+
747
+ //#endregion
748
+ //#region src/cli/components/Welcome.tsx
749
+ /**
750
+ * Welcome banner component for the CLI.
751
+ * Clean, minimal design inspired by Claude Code and OpenAI Codex.
752
+ */
753
+ function Welcome({ model, workDir }) {
754
+ const displayDir = workDir ? workDir.replace(process.env.HOME || "", "~") : process.cwd().replace(process.env.HOME || "", "~");
755
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
756
+ flexDirection: "column",
757
+ borderStyle: "single",
758
+ borderColor: colors.muted,
759
+ paddingX: 2,
760
+ paddingY: 1,
761
+ marginBottom: 1,
762
+ children: [
763
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
764
+ bold: true,
765
+ color: colors.primary,
766
+ children: [">", "_"]
767
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
768
+ bold: true,
769
+ children: " Deep Agent"
770
+ })] }),
771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
772
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
774
+ dimColor: true,
775
+ children: "model:"
776
+ }),
777
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [" ", model || "anthropic/claude-haiku-4-5-20251001"] }),
778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
779
+ dimColor: true,
780
+ children: " /model to change"
781
+ })
782
+ ] }),
783
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
784
+ dimColor: true,
785
+ children: "directory:"
786
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [" ", displayDir] })] })
787
+ ]
788
+ });
789
+ }
790
+ /**
791
+ * Compact help text shown below the welcome banner.
792
+ */
793
+ function WelcomeHint() {
794
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
795
+ flexDirection: "column",
796
+ marginBottom: 1,
797
+ children: [
798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
799
+ dimColor: true,
800
+ children: "To get started, describe a task or try one of these commands:"
801
+ }),
802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
804
+ flexDirection: "column",
805
+ children: [
806
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
807
+ color: colors.info,
808
+ children: "/help"
809
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
810
+ dimColor: true,
811
+ children: " - show available commands"
812
+ })] }),
813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
814
+ color: colors.info,
815
+ children: "/features"
816
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
817
+ dimColor: true,
818
+ children: " - show enabled features"
819
+ })] }),
820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
821
+ color: colors.info,
822
+ children: "/model"
823
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
824
+ dimColor: true,
825
+ children: " - change the model"
826
+ })] })
827
+ ]
828
+ })
829
+ ]
830
+ });
831
+ }
832
+
833
+ //#endregion
834
+ //#region src/cli/components/SlashMenu.tsx
835
+ /**
836
+ * Slash command autocomplete menu component.
837
+ */
838
+ function SlashMenu({ filter, maxItems = 8 }) {
839
+ if (!filter.startsWith("/")) return null;
840
+ const filtered = filterCommands(filter);
841
+ if (filtered.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
842
+ paddingLeft: 2,
843
+ marginTop: 1,
844
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
845
+ dimColor: true,
846
+ children: "No matching commands"
847
+ })
848
+ });
849
+ const displayItems = filtered.slice(0, maxItems);
850
+ const hasMore = filtered.length > maxItems;
851
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
852
+ flexDirection: "column",
853
+ marginTop: 1,
854
+ paddingLeft: 2,
855
+ children: [displayItems.map((cmd) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlashMenuItem, { command: cmd }, cmd.command)), hasMore && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
856
+ dimColor: true,
857
+ children: [
858
+ "... and ",
859
+ filtered.length - maxItems,
860
+ " more"
861
+ ]
862
+ })]
863
+ });
864
+ }
865
+ function SlashMenuItem({ command }) {
866
+ const aliases = command.aliases.length > 0 ? ` (${command.aliases.join(", ")})` : "";
867
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
868
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
869
+ color: colors.info,
870
+ children: command.command
871
+ }),
872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
873
+ dimColor: true,
874
+ children: aliases
875
+ }),
876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
877
+ dimColor: true,
878
+ children: [" - ", command.description]
879
+ })
880
+ ] });
881
+ }
882
+ /**
883
+ * Full slash menu panel for /help command.
884
+ */
885
+ function SlashMenuPanel() {
886
+ const commands = filterCommands();
887
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
888
+ flexDirection: "column",
889
+ borderStyle: "single",
890
+ borderColor: colors.muted,
891
+ paddingX: 2,
892
+ paddingY: 1,
893
+ marginY: 1,
894
+ children: [
895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
896
+ bold: true,
897
+ color: colors.info,
898
+ children: "Available Commands"
899
+ }),
900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
901
+ commands.map((cmd) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
902
+ flexDirection: "column",
903
+ marginBottom: 1,
904
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
905
+ color: colors.info,
906
+ children: cmd.command
907
+ }), cmd.aliases.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
908
+ dimColor: true,
909
+ children: [
910
+ " (",
911
+ cmd.aliases.join(", "),
912
+ ")"
913
+ ]
914
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
915
+ paddingLeft: 2,
916
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
917
+ dimColor: true,
918
+ children: cmd.description
919
+ })
920
+ })]
921
+ }, cmd.command))
922
+ ]
923
+ });
924
+ }
925
+
926
+ //#endregion
927
+ //#region src/cli/components/Input.tsx
928
+ /**
929
+ * Text input component with slash command suggestions.
930
+ * Clean, minimal design inspired by Claude Code and OpenAI Codex.
931
+ */
932
+ const inputHistory = [];
933
+ const MAX_HISTORY = 100;
934
+ function Input({ onSubmit, disabled = false, placeholder = "Plan, search, build anything" }) {
935
+ const [value, setValue] = (0, react.useState)("");
936
+ const [cursorPos, setCursorPos] = (0, react.useState)(0);
937
+ const showMenu = value.startsWith("/") && !disabled;
938
+ const [historyIndex, setHistoryIndex] = (0, react.useState)(-1);
939
+ const savedInputRef = (0, react.useRef)("");
940
+ const deleteWord = () => {
941
+ if (cursorPos === 0) return;
942
+ let end = cursorPos;
943
+ while (end > 0 && value[end - 1] === " ") end--;
944
+ while (end > 0 && value[end - 1] !== " ") end--;
945
+ setValue(value.slice(0, end) + value.slice(cursorPos));
946
+ setCursorPos(end);
947
+ };
948
+ (0, ink.useInput)((input, key) => {
949
+ if (disabled) return;
950
+ if (key.return) {
951
+ if (value.trim()) {
952
+ if (inputHistory.length === 0 || inputHistory[0] !== value) {
953
+ inputHistory.unshift(value);
954
+ if (inputHistory.length > MAX_HISTORY) inputHistory.pop();
955
+ }
956
+ onSubmit(value);
957
+ setValue("");
958
+ setCursorPos(0);
959
+ setHistoryIndex(-1);
960
+ savedInputRef.current = "";
961
+ }
962
+ return;
963
+ }
964
+ if (key.upArrow) {
965
+ if (inputHistory.length === 0) return;
966
+ if (historyIndex === -1) savedInputRef.current = value;
967
+ const newIndex = Math.min(historyIndex + 1, inputHistory.length - 1);
968
+ if (newIndex !== historyIndex) {
969
+ const historyValue = inputHistory[newIndex];
970
+ if (historyValue !== void 0) {
971
+ setHistoryIndex(newIndex);
972
+ setValue(historyValue);
973
+ setCursorPos(historyValue.length);
974
+ }
975
+ }
976
+ return;
977
+ }
978
+ if (key.downArrow) {
979
+ if (historyIndex === -1) return;
980
+ const newIndex = historyIndex - 1;
981
+ if (newIndex === -1) {
982
+ setHistoryIndex(-1);
983
+ setValue(savedInputRef.current);
984
+ setCursorPos(savedInputRef.current.length);
985
+ } else {
986
+ const historyValue = inputHistory[newIndex];
987
+ if (historyValue !== void 0) {
988
+ setHistoryIndex(newIndex);
989
+ setValue(historyValue);
990
+ setCursorPos(historyValue.length);
991
+ }
992
+ }
993
+ return;
994
+ }
995
+ if (key.leftArrow) {
996
+ if (key.meta || key.ctrl) {
997
+ let pos = cursorPos;
998
+ while (pos > 0 && value[pos - 1] === " ") pos--;
999
+ while (pos > 0 && value[pos - 1] !== " ") pos--;
1000
+ setCursorPos(pos);
1001
+ } else setCursorPos((prev) => Math.max(0, prev - 1));
1002
+ return;
1003
+ }
1004
+ if (key.rightArrow) {
1005
+ if (key.meta || key.ctrl) {
1006
+ let pos = cursorPos;
1007
+ while (pos < value.length && value[pos] !== " ") pos++;
1008
+ while (pos < value.length && value[pos] === " ") pos++;
1009
+ setCursorPos(pos);
1010
+ } else setCursorPos((prev) => Math.min(value.length, prev + 1));
1011
+ return;
1012
+ }
1013
+ if (key.ctrl && input === "a") {
1014
+ setCursorPos(0);
1015
+ return;
1016
+ }
1017
+ if (key.ctrl && input === "e") {
1018
+ setCursorPos(value.length);
1019
+ return;
1020
+ }
1021
+ if ((key.backspace || key.delete) && key.meta) {
1022
+ deleteWord();
1023
+ return;
1024
+ }
1025
+ if (key.ctrl && input === "w") {
1026
+ deleteWord();
1027
+ return;
1028
+ }
1029
+ if (key.ctrl && input === "u") {
1030
+ setValue(value.slice(cursorPos));
1031
+ setCursorPos(0);
1032
+ return;
1033
+ }
1034
+ if (key.ctrl && input === "k") {
1035
+ setValue(value.slice(0, cursorPos));
1036
+ return;
1037
+ }
1038
+ if (key.backspace || key.delete) {
1039
+ if (cursorPos > 0) {
1040
+ setValue((prev) => prev.slice(0, cursorPos - 1) + prev.slice(cursorPos));
1041
+ setCursorPos((prev) => prev - 1);
1042
+ }
1043
+ return;
1044
+ }
1045
+ if (key.tab && value.startsWith("/")) return;
1046
+ if (key.ctrl || key.meta || key.escape || key.tab) return;
1047
+ if (input) {
1048
+ const printable = input.split("").filter((char) => char >= " " || char === " ").join("");
1049
+ if (printable) {
1050
+ if (historyIndex !== -1) {
1051
+ setHistoryIndex(-1);
1052
+ savedInputRef.current = "";
1053
+ }
1054
+ setValue((prev) => prev.slice(0, cursorPos) + printable + prev.slice(cursorPos));
1055
+ setCursorPos((prev) => prev + printable.length);
1056
+ }
1057
+ }
1058
+ }, { isActive: !disabled });
1059
+ const renderTextWithCursor = () => {
1060
+ if (!value) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1061
+ color: colors.primary,
1062
+ children: "▌"
1063
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1064
+ dimColor: true,
1065
+ children: placeholder
1066
+ })] });
1067
+ const beforeCursor = value.slice(0, cursorPos);
1068
+ const afterCursor = value.slice(cursorPos);
1069
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [
1070
+ beforeCursor,
1071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1072
+ color: colors.primary,
1073
+ children: "▌"
1074
+ }),
1075
+ afterCursor
1076
+ ] });
1077
+ };
1078
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1079
+ flexDirection: "column",
1080
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1081
+ color: colors.muted,
1082
+ children: "→ "
1083
+ }), disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1084
+ dimColor: true,
1085
+ children: "..."
1086
+ }) : renderTextWithCursor()] }), showMenu && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlashMenu, { filter: value })]
1087
+ });
1088
+ }
1089
+
1090
+ //#endregion
1091
+ //#region src/cli/components/ToolCallSummary.tsx
1092
+ /**
1093
+ * Collapsible tool call summary component.
1094
+ * Shows a collapsed summary of tool calls that can be expanded to see details.
1095
+ */
1096
+
1097
+ //#endregion
1098
+ //#region src/cli/components/Message.tsx
1099
+ /**
1100
+ * Message display component for user and assistant messages.
1101
+ * Clean, minimal design inspired by Claude Code and OpenAI Codex.
1102
+ */
1103
+
1104
+ //#endregion
1105
+ //#region src/cli/components/TodoList.tsx
1106
+ /**
1107
+ * Todo list panel component.
1108
+ */
1109
+ function TodoList({ todos, showPanel = true }) {
1110
+ const completed = todos.filter((t) => t.status === "completed").length;
1111
+ const inProgress = todos.filter((t) => t.status === "in_progress").length;
1112
+ const pending = todos.filter((t) => t.status === "pending").length;
1113
+ const total = todos.length;
1114
+ const content = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1115
+ flexDirection: "column",
1116
+ children: [
1117
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1118
+ marginBottom: 1,
1119
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1120
+ bold: true,
1121
+ color: colors.info,
1122
+ children: [emoji.todo, " Todo List"]
1123
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1124
+ dimColor: true,
1125
+ children: [
1126
+ " ",
1127
+ "(",
1128
+ completed,
1129
+ "/",
1130
+ total,
1131
+ " done)"
1132
+ ]
1133
+ })]
1134
+ }),
1135
+ todos.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1136
+ paddingLeft: 2,
1137
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1138
+ dimColor: true,
1139
+ children: "No todos yet."
1140
+ })
1141
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1142
+ flexDirection: "column",
1143
+ children: todos.map((todo) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TodoItemRow, { todo }, todo.id))
1144
+ }),
1145
+ todos.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1146
+ marginTop: 1,
1147
+ gap: 2,
1148
+ children: [
1149
+ inProgress > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1150
+ color: colors.warning,
1151
+ children: [inProgress, " in progress"]
1152
+ }),
1153
+ pending > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1154
+ dimColor: true,
1155
+ children: [pending, " pending"]
1156
+ }),
1157
+ completed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1158
+ color: colors.success,
1159
+ children: [completed, " completed"]
1160
+ })
1161
+ ]
1162
+ })
1163
+ ]
1164
+ });
1165
+ if (!showPanel) return content;
1166
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1167
+ flexDirection: "column",
1168
+ borderStyle: "single",
1169
+ borderColor: colors.muted,
1170
+ paddingX: 2,
1171
+ paddingY: 1,
1172
+ marginY: 1,
1173
+ children: content
1174
+ });
1175
+ }
1176
+ function TodoItemRow({ todo }) {
1177
+ const statusEmoji = emoji[todo.status] || "•";
1178
+ const isFinished = todo.status === "completed" || todo.status === "cancelled";
1179
+ const badgeColor = {
1180
+ pending: "gray",
1181
+ in_progress: "yellow",
1182
+ completed: "green",
1183
+ cancelled: "red"
1184
+ }[todo.status];
1185
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1186
+ paddingLeft: 2,
1187
+ children: [
1188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [statusEmoji, " "] }),
1189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_inkjs_ui.Badge, {
1190
+ color: badgeColor,
1191
+ children: todo.status.replace("_", " ")
1192
+ }),
1193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: " " }),
1194
+ isFinished ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1195
+ strikethrough: true,
1196
+ dimColor: true,
1197
+ children: todo.content
1198
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: todo.content })
1199
+ ]
1200
+ });
1201
+ }
1202
+
1203
+ //#endregion
1204
+ //#region src/cli/components/FilePreview.tsx
1205
+ /**
1206
+ * File preview component with line numbers.
1207
+ */
1208
+ function FilePreview({ path, content, maxLines = 20, isWrite = false }) {
1209
+ const lines = content.split("\n");
1210
+ const totalLines = lines.length;
1211
+ const displayLines = lines.slice(0, maxLines);
1212
+ const truncated = totalLines > maxLines;
1213
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1214
+ flexDirection: "column",
1215
+ borderStyle: "single",
1216
+ borderColor: colors.muted,
1217
+ marginY: 1,
1218
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1219
+ paddingX: 2,
1220
+ paddingY: 1,
1221
+ borderBottom: true,
1222
+ children: [
1223
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1224
+ color: colors.info,
1225
+ children: [
1226
+ emoji.file,
1227
+ " ",
1228
+ isWrite ? "Writing:" : "Reading:",
1229
+ " "
1230
+ ]
1231
+ }),
1232
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1233
+ color: colors.file,
1234
+ children: path
1235
+ }),
1236
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1237
+ dimColor: true,
1238
+ children: [
1239
+ " (",
1240
+ totalLines,
1241
+ " lines)"
1242
+ ]
1243
+ })
1244
+ ]
1245
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1246
+ flexDirection: "column",
1247
+ paddingX: 2,
1248
+ paddingY: 1,
1249
+ children: [displayLines.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1250
+ dimColor: true,
1251
+ children: [String(index + 1).padStart(4, " "), " "]
1252
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: truncateLine(line, 70) })] }, index)), truncated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1253
+ marginTop: 1,
1254
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1255
+ dimColor: true,
1256
+ children: [
1257
+ "... ",
1258
+ totalLines - maxLines,
1259
+ " more lines ..."
1260
+ ]
1261
+ })
1262
+ })]
1263
+ })]
1264
+ });
1265
+ }
1266
+ /**
1267
+ * Truncate a line if too long.
1268
+ */
1269
+ function truncateLine(line, maxLength) {
1270
+ if (line.length <= maxLength) return line;
1271
+ return line.substring(0, maxLength - 3) + "...";
1272
+ }
1273
+ function FileWritten({ path }) {
1274
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1275
+ color: colors.success,
1276
+ children: "✓ Wrote: "
1277
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1278
+ color: colors.file,
1279
+ children: path
1280
+ })] });
1281
+ }
1282
+ function FileEdited({ path, occurrences }) {
1283
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
1284
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1285
+ color: colors.success,
1286
+ children: [emoji.edit, " Edited: "]
1287
+ }),
1288
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1289
+ color: colors.file,
1290
+ children: path
1291
+ }),
1292
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1293
+ dimColor: true,
1294
+ children: [
1295
+ " ",
1296
+ "(",
1297
+ occurrences,
1298
+ " change",
1299
+ occurrences === 1 ? "" : "s",
1300
+ ")"
1301
+ ]
1302
+ })
1303
+ ] });
1304
+ }
1305
+ function FileRead({ path, lines }) {
1306
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
1307
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1308
+ color: colors.info,
1309
+ children: "📖 Read: "
1310
+ }),
1311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1312
+ color: colors.file,
1313
+ children: path
1314
+ }),
1315
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1316
+ dimColor: true,
1317
+ children: [
1318
+ " (",
1319
+ lines,
1320
+ " lines)"
1321
+ ]
1322
+ })
1323
+ ] });
1324
+ }
1325
+ function LsResult({ path, count }) {
1326
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
1327
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1328
+ color: colors.info,
1329
+ children: "📂 Listed: "
1330
+ }),
1331
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1332
+ color: colors.file,
1333
+ children: path
1334
+ }),
1335
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1336
+ dimColor: true,
1337
+ children: [
1338
+ " (",
1339
+ count,
1340
+ " item",
1341
+ count === 1 ? "" : "s",
1342
+ ")"
1343
+ ]
1344
+ })
1345
+ ] });
1346
+ }
1347
+ function GlobResult({ pattern, count }) {
1348
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
1349
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1350
+ color: colors.info,
1351
+ children: "🔍 Glob: "
1352
+ }),
1353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1354
+ color: colors.tool,
1355
+ children: pattern
1356
+ }),
1357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1358
+ dimColor: true,
1359
+ children: [
1360
+ " (",
1361
+ count,
1362
+ " match",
1363
+ count === 1 ? "" : "es",
1364
+ ")"
1365
+ ]
1366
+ })
1367
+ ] });
1368
+ }
1369
+ function GrepResult({ pattern, count }) {
1370
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
1371
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1372
+ color: colors.info,
1373
+ children: "🔎 Grep: "
1374
+ }),
1375
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1376
+ color: colors.tool,
1377
+ children: pattern
1378
+ }),
1379
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1380
+ dimColor: true,
1381
+ children: [
1382
+ " (",
1383
+ count,
1384
+ " match",
1385
+ count === 1 ? "" : "es",
1386
+ ")"
1387
+ ]
1388
+ })
1389
+ ] });
1390
+ }
1391
+ function FileList({ files, workDir }) {
1392
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1393
+ flexDirection: "column",
1394
+ borderStyle: "single",
1395
+ borderColor: colors.muted,
1396
+ paddingX: 2,
1397
+ paddingY: 1,
1398
+ marginY: 1,
1399
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1400
+ marginBottom: 1,
1401
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1402
+ bold: true,
1403
+ color: colors.info,
1404
+ children: [emoji.file, " Files"]
1405
+ }), workDir && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1406
+ dimColor: true,
1407
+ children: [" in ", workDir]
1408
+ })]
1409
+ }), files.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1410
+ paddingLeft: 2,
1411
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1412
+ dimColor: true,
1413
+ children: "No files found."
1414
+ })
1415
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1416
+ flexDirection: "column",
1417
+ children: files.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1418
+ paddingLeft: 2,
1419
+ children: [
1420
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [file.is_dir ? "📁" : "📄", " "] }),
1421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1422
+ color: colors.file,
1423
+ children: file.path
1424
+ }),
1425
+ file.size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1426
+ dimColor: true,
1427
+ children: [
1428
+ " (",
1429
+ file.size,
1430
+ " bytes)"
1431
+ ]
1432
+ })
1433
+ ]
1434
+ }, file.path))
1435
+ })]
1436
+ });
1437
+ }
1438
+
1439
+ //#endregion
1440
+ //#region src/cli/components/ToolCall.tsx
1441
+ /**
1442
+ * Tool call display component with spinner.
1443
+ * Clean, minimal design.
1444
+ */
1445
+ function ToolCall({ toolName, isExecuting = true }) {
1446
+ if (isExecuting) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_inkjs_ui.Spinner, { label: toolName }) });
1447
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1448
+ color: colors.success,
1449
+ children: "✓"
1450
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [" ", toolName] })] });
1451
+ }
1452
+ /**
1453
+ * Thinking indicator with animated spinner.
1454
+ */
1455
+ function ThinkingIndicator() {
1456
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1457
+ color: colors.warning,
1458
+ children: "● "
1459
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_inkjs_ui.Spinner, { label: "" })] });
1460
+ }
1461
+ function DoneIndicator({ todosCompleted, todosTotal, filesCount }) {
1462
+ const hasTodos = todosTotal > 0;
1463
+ const hasFiles = filesCount > 0;
1464
+ if (!hasTodos && !hasFiles) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
1465
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1466
+ dimColor: true,
1467
+ children: [
1468
+ hasTodos && `${todosCompleted}/${todosTotal} tasks`,
1469
+ hasTodos && hasFiles && " · ",
1470
+ hasFiles && `${filesCount} files`
1471
+ ]
1472
+ }) });
1473
+ }
1474
+ function ErrorDisplay({ error }) {
1475
+ const message = error instanceof Error ? error.message : error;
1476
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1477
+ color: colors.error,
1478
+ children: ["✗ ", message]
1479
+ }) });
1480
+ }
1481
+
1482
+ //#endregion
1483
+ //#region src/cli/components/Subagent.tsx
1484
+ /**
1485
+ * Subagent status display components.
1486
+ */
1487
+ function SubagentStart({ name, task, maxTaskLength = 60 }) {
1488
+ const shortTask = task.length > maxTaskLength ? task.substring(0, maxTaskLength) + "..." : task;
1489
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1490
+ flexDirection: "column",
1491
+ marginY: 1,
1492
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_inkjs_ui.Spinner, { label: `${emoji.subagent} Starting subagent: ${name}` }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1493
+ paddingLeft: 4,
1494
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1495
+ dimColor: true,
1496
+ children: ["└─ ", shortTask]
1497
+ })
1498
+ })]
1499
+ });
1500
+ }
1501
+ function SubagentFinish({ name }) {
1502
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1503
+ marginY: 1,
1504
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_inkjs_ui.StatusMessage, {
1505
+ variant: "success",
1506
+ children: [
1507
+ emoji.subagent,
1508
+ " Subagent ",
1509
+ name,
1510
+ " completed"
1511
+ ]
1512
+ })
1513
+ });
1514
+ }
1515
+
1516
+ //#endregion
1517
+ //#region src/cli/components/StatusBar.tsx
1518
+ /**
1519
+ * Compact status bar component.
1520
+ * Clean, minimal design inspired by Claude Code and OpenAI Codex.
1521
+ */
1522
+ function StatusBar({ workDir, model, status = "idle", features, autoApproveEnabled = false, sessionId }) {
1523
+ const shortModel = model.split("/").pop() || model;
1524
+ const getStatusDisplay = () => {
1525
+ switch (status) {
1526
+ case "thinking": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1527
+ color: colors.warning,
1528
+ children: "●"
1529
+ });
1530
+ case "streaming": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1531
+ color: colors.success,
1532
+ children: "●"
1533
+ });
1534
+ case "tool-call": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1535
+ color: colors.tool,
1536
+ children: "●"
1537
+ });
1538
+ case "subagent": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1539
+ color: colors.secondary,
1540
+ children: "●"
1541
+ });
1542
+ case "error": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1543
+ color: colors.error,
1544
+ children: "●"
1545
+ });
1546
+ case "done": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1547
+ color: colors.success,
1548
+ children: "●"
1549
+ });
1550
+ default: return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1551
+ dimColor: true,
1552
+ children: "○"
1553
+ });
1554
+ }
1555
+ };
1556
+ const featureBadges = [];
1557
+ if (features?.promptCaching) featureBadges.push("⚡");
1558
+ if (features?.eviction) featureBadges.push("📦");
1559
+ if (features?.summarization) featureBadges.push("📝");
1560
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
1561
+ marginTop: 1,
1562
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1563
+ dimColor: true,
1564
+ children: [
1565
+ getStatusDisplay(),
1566
+ " ",
1567
+ shortModel,
1568
+ featureBadges.length > 0 && ` ${featureBadges.join(" ")}`,
1569
+ " · ",
1570
+ autoApproveEnabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1571
+ color: colors.success,
1572
+ children: "🟢 Auto-approve"
1573
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1574
+ color: colors.warning,
1575
+ children: "🔴 Safe mode"
1576
+ }),
1577
+ sessionId && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" · ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1578
+ dimColor: true,
1579
+ children: ["Session: ", sessionId]
1580
+ })] }),
1581
+ " · ",
1582
+ "? for shortcuts"
1583
+ ]
1584
+ })
1585
+ });
1586
+ }
1587
+
1588
+ //#endregion
1589
+ //#region src/cli/utils/model-list.ts
1590
+ const modelCache = {};
1591
+ const CACHE_TTL_MS = 1440 * 60 * 1e3;
1592
+ /**
1593
+ * Simple hash function for API key to detect changes.
1594
+ */
1595
+ function hashApiKey(key) {
1596
+ if (!key) return "";
1597
+ return `${key.substring(0, 10)}...${key.substring(key.length - 4)}`;
1598
+ }
1599
+ /**
1600
+ * Check if cache is valid for a provider.
1601
+ */
1602
+ function isCacheValid(provider) {
1603
+ const entry = modelCache[provider];
1604
+ if (!entry) return false;
1605
+ if (Date.now() - entry.timestamp > CACHE_TTL_MS) return false;
1606
+ const currentKeyHash = provider === "anthropic" ? hashApiKey(process.env.ANTHROPIC_API_KEY) : hashApiKey(process.env.OPENAI_API_KEY);
1607
+ return entry.apiKeyHash === currentKeyHash;
1608
+ }
1609
+ /**
1610
+ * Get cached models for a provider.
1611
+ */
1612
+ function getCachedModels(provider) {
1613
+ if (isCacheValid(provider)) return modelCache[provider].models;
1614
+ return null;
1615
+ }
1616
+ /**
1617
+ * Set cached models for a provider.
1618
+ */
1619
+ function setCachedModels(provider, models) {
1620
+ const apiKeyHash = provider === "anthropic" ? hashApiKey(process.env.ANTHROPIC_API_KEY) : hashApiKey(process.env.OPENAI_API_KEY);
1621
+ modelCache[provider] = {
1622
+ models,
1623
+ timestamp: Date.now(),
1624
+ apiKeyHash
1625
+ };
1626
+ }
1627
+ /**
1628
+ * Detect which API keys are available in the environment.
1629
+ */
1630
+ function detectAvailableProviders() {
1631
+ return {
1632
+ anthropic: !!process.env.ANTHROPIC_API_KEY,
1633
+ openai: !!process.env.OPENAI_API_KEY
1634
+ };
1635
+ }
1636
+ /**
1637
+ * Fetch available models from Anthropic API.
1638
+ * Returns empty array if no API key is set.
1639
+ */
1640
+ async function fetchAnthropicModels() {
1641
+ const apiKey = process.env.ANTHROPIC_API_KEY;
1642
+ if (!apiKey) return {
1643
+ models: [],
1644
+ error: "No Anthropic API key configured"
1645
+ };
1646
+ const cached = getCachedModels("anthropic");
1647
+ if (cached) return { models: cached };
1648
+ try {
1649
+ const response = await fetch("https://api.anthropic.com/v1/models?limit=100", {
1650
+ method: "GET",
1651
+ headers: {
1652
+ "X-Api-Key": apiKey,
1653
+ "anthropic-version": "2023-06-01"
1654
+ }
1655
+ });
1656
+ if (!response.ok) {
1657
+ const errorText = await response.text();
1658
+ if (response.status === 401) return {
1659
+ models: [],
1660
+ error: "Invalid Anthropic API key"
1661
+ };
1662
+ return {
1663
+ models: [],
1664
+ error: `Anthropic API error: ${response.status} ${errorText}`
1665
+ };
1666
+ }
1667
+ const models = (await response.json()).data.map((model) => ({
1668
+ id: `anthropic/${model.id}`,
1669
+ name: model.id,
1670
+ provider: "anthropic",
1671
+ description: model.display_name,
1672
+ createdAt: model.created_at
1673
+ }));
1674
+ models.sort((a, b) => {
1675
+ if (!a.createdAt || !b.createdAt) return 0;
1676
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
1677
+ });
1678
+ setCachedModels("anthropic", models);
1679
+ return { models };
1680
+ } catch (error) {
1681
+ return {
1682
+ models: [],
1683
+ error: `Failed to fetch Anthropic models: ${error instanceof Error ? error.message : String(error)}`
1684
+ };
1685
+ }
1686
+ }
1687
+ /**
1688
+ * Fetch available models from OpenAI API.
1689
+ * Returns empty array if no API key is set.
1690
+ */
1691
+ async function fetchOpenAIModels() {
1692
+ const apiKey = process.env.OPENAI_API_KEY;
1693
+ if (!apiKey) return {
1694
+ models: [],
1695
+ error: "No OpenAI API key configured"
1696
+ };
1697
+ const cached = getCachedModels("openai");
1698
+ if (cached) return { models: cached };
1699
+ try {
1700
+ const response = await fetch("https://api.openai.com/v1/models", {
1701
+ method: "GET",
1702
+ headers: { Authorization: `Bearer ${apiKey}` }
1703
+ });
1704
+ if (!response.ok) {
1705
+ const errorText = await response.text();
1706
+ if (response.status === 401) return {
1707
+ models: [],
1708
+ error: "Invalid OpenAI API key"
1709
+ };
1710
+ return {
1711
+ models: [],
1712
+ error: `OpenAI API error: ${response.status} ${errorText}`
1713
+ };
1714
+ }
1715
+ const models = (await response.json()).data.filter((model) => model.id.startsWith("gpt-") || model.id.startsWith("o1") || model.id.startsWith("o3") || model.id.includes("chatgpt")).map((model) => ({
1716
+ id: `openai/${model.id}`,
1717
+ name: model.id,
1718
+ provider: "openai",
1719
+ description: getOpenAIModelDescription(model.id),
1720
+ createdAt: (/* @__PURE__ */ new Date(model.created * 1e3)).toISOString()
1721
+ }));
1722
+ models.sort((a, b) => {
1723
+ if (!a.createdAt || !b.createdAt) return 0;
1724
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
1725
+ });
1726
+ setCachedModels("openai", models);
1727
+ return { models };
1728
+ } catch (error) {
1729
+ return {
1730
+ models: [],
1731
+ error: `Failed to fetch OpenAI models: ${error instanceof Error ? error.message : String(error)}`
1732
+ };
1733
+ }
1734
+ }
1735
+ /**
1736
+ * Generate a description for OpenAI models based on their ID.
1737
+ */
1738
+ function getOpenAIModelDescription(modelId) {
1739
+ if (modelId.includes("gpt-4o-mini")) return "Fast and affordable GPT-4 variant";
1740
+ if (modelId.includes("gpt-4o")) return "Latest GPT-4 optimized model";
1741
+ if (modelId.includes("gpt-4-turbo")) return "GPT-4 Turbo";
1742
+ if (modelId.includes("gpt-4")) return "GPT-4";
1743
+ if (modelId.includes("gpt-3.5-turbo")) return "Fast and affordable";
1744
+ if (modelId.startsWith("o1")) return "OpenAI o1 reasoning model";
1745
+ if (modelId.startsWith("o3")) return "OpenAI o3 reasoning model";
1746
+ return "";
1747
+ }
1748
+ /**
1749
+ * Get list of available models from all configured providers.
1750
+ * Only fetches from providers that have API keys configured.
1751
+ */
1752
+ async function getAvailableModels() {
1753
+ const providers = detectAvailableProviders();
1754
+ const allModels = [];
1755
+ const errors = [];
1756
+ const promises = [];
1757
+ if (providers.anthropic) promises.push(fetchAnthropicModels().then((result) => {
1758
+ allModels.push(...result.models);
1759
+ if (result.error) errors.push({
1760
+ provider: "Anthropic",
1761
+ error: result.error
1762
+ });
1763
+ }));
1764
+ if (providers.openai) promises.push(fetchOpenAIModels().then((result) => {
1765
+ allModels.push(...result.models);
1766
+ if (result.error) errors.push({
1767
+ provider: "OpenAI",
1768
+ error: result.error
1769
+ });
1770
+ }));
1771
+ await Promise.all(promises);
1772
+ return {
1773
+ models: allModels,
1774
+ errors,
1775
+ loading: false
1776
+ };
1777
+ }
1778
+ /**
1779
+ * Get models grouped by provider.
1780
+ */
1781
+ async function getModelsByProvider() {
1782
+ const result = await getAvailableModels();
1783
+ const grouped = { errors: result.errors };
1784
+ for (const model of result.models) {
1785
+ if (!grouped[model.provider]) grouped[model.provider] = [];
1786
+ grouped[model.provider].push(model);
1787
+ }
1788
+ return grouped;
1789
+ }
1790
+
1791
+ //#endregion
1792
+ //#region src/cli/components/ModelSelection.tsx
1793
+ /**
1794
+ * Model Selection Panel - Interactive model selection with arrow keys.
1795
+ */
1796
+ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1797
+ const providers = detectAvailableProviders();
1798
+ const hasAnyKey = providers.anthropic || providers.openai;
1799
+ const [state, setState] = (0, react.useState)({
1800
+ loading: true,
1801
+ anthropicModels: [],
1802
+ openaiModels: [],
1803
+ errors: []
1804
+ });
1805
+ const [selectedIndex, setSelectedIndex] = (0, react.useState)(0);
1806
+ const allModels = (0, react.useMemo)(() => {
1807
+ const models = [];
1808
+ if (state.anthropicModels.length > 0) models.push(...state.anthropicModels);
1809
+ if (state.openaiModels.length > 0) models.push(...state.openaiModels);
1810
+ return models;
1811
+ }, [state.anthropicModels, state.openaiModels]);
1812
+ (0, react.useEffect)(() => {
1813
+ if (allModels.length > 0 && currentModel) {
1814
+ const currentIndex = allModels.findIndex((m) => isCurrentModel(currentModel, m));
1815
+ if (currentIndex >= 0) setSelectedIndex(currentIndex);
1816
+ }
1817
+ }, [allModels, currentModel]);
1818
+ (0, ink.useInput)((input, key) => {
1819
+ if (state.loading) return;
1820
+ if (key.upArrow) setSelectedIndex((prev) => prev > 0 ? prev - 1 : allModels.length - 1);
1821
+ else if (key.downArrow) setSelectedIndex((prev) => prev < allModels.length - 1 ? prev + 1 : 0);
1822
+ else if (key.return) {
1823
+ const selectedModel = allModels[selectedIndex];
1824
+ if (selectedModel) {
1825
+ onModelSelect?.(selectedModel.id);
1826
+ onClose?.();
1827
+ }
1828
+ } else if (key.escape) onClose?.();
1829
+ });
1830
+ (0, react.useEffect)(() => {
1831
+ if (!hasAnyKey) {
1832
+ setState({
1833
+ loading: false,
1834
+ anthropicModels: [],
1835
+ openaiModels: [],
1836
+ errors: []
1837
+ });
1838
+ return;
1839
+ }
1840
+ let cancelled = false;
1841
+ async function loadModels() {
1842
+ try {
1843
+ const result = await getModelsByProvider();
1844
+ if (!cancelled) setState({
1845
+ loading: false,
1846
+ anthropicModels: result.anthropic || [],
1847
+ openaiModels: result.openai || [],
1848
+ errors: result.errors
1849
+ });
1850
+ } catch (error) {
1851
+ if (!cancelled) setState({
1852
+ loading: false,
1853
+ anthropicModels: [],
1854
+ openaiModels: [],
1855
+ errors: [{
1856
+ provider: "Unknown",
1857
+ error: String(error)
1858
+ }]
1859
+ });
1860
+ }
1861
+ }
1862
+ loadModels();
1863
+ return () => {
1864
+ cancelled = true;
1865
+ };
1866
+ }, [hasAnyKey]);
1867
+ if (!hasAnyKey) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1868
+ flexDirection: "column",
1869
+ borderStyle: "single",
1870
+ borderColor: colors.warning,
1871
+ paddingX: 2,
1872
+ paddingY: 1,
1873
+ marginY: 1,
1874
+ children: [
1875
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1876
+ bold: true,
1877
+ color: colors.warning,
1878
+ children: "⚠️ No API Keys Found"
1879
+ }),
1880
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
1881
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Add an API key first to see available models." }),
1882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
1883
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1884
+ color: colors.primary,
1885
+ children: "Run /apikey to add your API key"
1886
+ }),
1887
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
1888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1889
+ dimColor: true,
1890
+ children: "Supported providers:"
1891
+ }),
1892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1893
+ dimColor: true,
1894
+ children: " • Anthropic (Claude)"
1895
+ }),
1896
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1897
+ dimColor: true,
1898
+ children: " • OpenAI (GPT)"
1899
+ })
1900
+ ]
1901
+ });
1902
+ if (state.loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1903
+ flexDirection: "column",
1904
+ borderStyle: "single",
1905
+ borderColor: colors.muted,
1906
+ paddingX: 2,
1907
+ paddingY: 1,
1908
+ marginY: 1,
1909
+ children: [
1910
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1911
+ bold: true,
1912
+ color: colors.info,
1913
+ children: [emoji.model, " Select Model"]
1914
+ }),
1915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
1916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_inkjs_ui.Spinner, { label: "Fetching models from API..." }) })
1917
+ ]
1918
+ });
1919
+ if (!(allModels.length > 0) && state.errors.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1920
+ flexDirection: "column",
1921
+ borderStyle: "single",
1922
+ borderColor: colors.error,
1923
+ paddingX: 2,
1924
+ paddingY: 1,
1925
+ marginY: 1,
1926
+ children: [
1927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1928
+ bold: true,
1929
+ color: colors.error,
1930
+ children: [emoji.error, " Failed to Fetch Models"]
1931
+ }),
1932
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
1933
+ state.errors.map((err, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1934
+ color: colors.error,
1935
+ children: [
1936
+ err.provider,
1937
+ ": ",
1938
+ err.error
1939
+ ]
1940
+ }, i)),
1941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
1942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1943
+ dimColor: true,
1944
+ children: "Check your API key and try again with /apikey"
1945
+ })
1946
+ ]
1947
+ });
1948
+ let anthropicOffset = 0;
1949
+ let openaiOffset = state.anthropicModels.length;
1950
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
1951
+ flexDirection: "column",
1952
+ borderStyle: "single",
1953
+ borderColor: colors.primary,
1954
+ paddingX: 2,
1955
+ paddingY: 1,
1956
+ marginY: 1,
1957
+ children: [
1958
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1959
+ bold: true,
1960
+ color: colors.info,
1961
+ children: [emoji.model, " Select Model"]
1962
+ }),
1963
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
1964
+ state.errors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state.errors.map((err, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
1965
+ color: colors.warning,
1966
+ children: [
1967
+ emoji.warning,
1968
+ " ",
1969
+ err.provider,
1970
+ ": ",
1971
+ err.error
1972
+ ]
1973
+ }, i)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 })] }),
1974
+ state.anthropicModels.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1975
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1976
+ bold: true,
1977
+ color: colors.primary,
1978
+ children: "Anthropic Claude"
1979
+ }),
1980
+ state.anthropicModels.map((model, index) => {
1981
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelItem, {
1982
+ model,
1983
+ isSelected: anthropicOffset + index === selectedIndex,
1984
+ isCurrent: isCurrentModel(currentModel, model)
1985
+ }, model.id);
1986
+ }),
1987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 })
1988
+ ] }),
1989
+ state.openaiModels.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1990
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
1991
+ bold: true,
1992
+ color: colors.primary,
1993
+ children: "OpenAI GPT"
1994
+ }),
1995
+ state.openaiModels.map((model, index) => {
1996
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelItem, {
1997
+ model,
1998
+ isSelected: openaiOffset + index === selectedIndex,
1999
+ isCurrent: isCurrentModel(currentModel, model)
2000
+ }, model.id);
2001
+ }),
2002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 })
2003
+ ] }),
2004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2005
+ dimColor: true,
2006
+ children: "↑/↓ Navigate • Enter Select • Esc Cancel"
2007
+ })
2008
+ ]
2009
+ });
2010
+ }
2011
+ /**
2012
+ * Check if a model matches the current model.
2013
+ */
2014
+ function isCurrentModel(currentModel, model) {
2015
+ if (!currentModel) return false;
2016
+ return currentModel === model.id || currentModel === model.name || currentModel === `${model.provider}/${model.name}` || currentModel.startsWith(`${model.provider}/`) && currentModel === model.id;
2017
+ }
2018
+ function ModelItem({ model, isSelected, isCurrent }) {
2019
+ let indicator = " ";
2020
+ let textColor = void 0;
2021
+ let isBold = false;
2022
+ if (isSelected) {
2023
+ indicator = "▸ ";
2024
+ textColor = colors.primary;
2025
+ isBold = true;
2026
+ }
2027
+ if (isCurrent) {
2028
+ indicator = isSelected ? "▸✓" : " ✓";
2029
+ textColor = isSelected ? colors.primary : colors.success;
2030
+ }
2031
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2032
+ marginLeft: 1,
2033
+ children: [
2034
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2035
+ color: isSelected ? colors.primary : isCurrent ? colors.success : void 0,
2036
+ children: indicator
2037
+ }),
2038
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2039
+ color: textColor,
2040
+ bold: isBold,
2041
+ children: model.id
2042
+ }),
2043
+ model.description && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2044
+ dimColor: true,
2045
+ children: " - "
2046
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2047
+ dimColor: true,
2048
+ children: model.description
2049
+ })] })
2050
+ ]
2051
+ });
2052
+ }
2053
+
2054
+ //#endregion
2055
+ //#region src/cli/components/ApiKeyInput.tsx
2056
+ /**
2057
+ * API Key Input Panel - Interactive provider selection and key input.
2058
+ * Shows current status and allows adding/updating keys.
2059
+ */
2060
+ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2061
+ const [step, setStep] = (0, react.useState)("select-provider");
2062
+ const [selectedProvider, setSelectedProvider] = (0, react.useState)(null);
2063
+ const [apiKey, setApiKey] = (0, react.useState)("");
2064
+ const [error, setError] = (0, react.useState)(null);
2065
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
2066
+ const openaiKey = process.env.OPENAI_API_KEY;
2067
+ const maskKey = (key) => {
2068
+ if (!key) return null;
2069
+ if (key.length <= 8) return "•".repeat(key.length);
2070
+ return key.substring(0, 10) + "..." + key.substring(key.length - 4);
2071
+ };
2072
+ (0, ink.useInput)((input, key) => {
2073
+ if (step === "select-provider") {
2074
+ if (input === "1" || input.toLowerCase() === "a") {
2075
+ setSelectedProvider("anthropic");
2076
+ setStep("enter-key");
2077
+ setError(null);
2078
+ if (anthropicKey) setApiKey(anthropicKey);
2079
+ } else if (input === "2" || input.toLowerCase() === "o") {
2080
+ setSelectedProvider("openai");
2081
+ setStep("enter-key");
2082
+ setError(null);
2083
+ if (openaiKey) setApiKey(openaiKey);
2084
+ } else if (key.escape) onClose?.();
2085
+ } else if (step === "enter-key") {
2086
+ if (key.escape) {
2087
+ setStep("select-provider");
2088
+ setApiKey("");
2089
+ setSelectedProvider(null);
2090
+ setError(null);
2091
+ } else if (key.return) {
2092
+ if (!apiKey.trim()) {
2093
+ setError("API key cannot be empty");
2094
+ return;
2095
+ }
2096
+ if (selectedProvider === "anthropic" && !apiKey.startsWith("sk-ant-")) {
2097
+ setError("Anthropic API keys typically start with 'sk-ant-'");
2098
+ return;
2099
+ }
2100
+ if (selectedProvider === "openai" && !apiKey.startsWith("sk-")) {
2101
+ setError("OpenAI API keys typically start with 'sk-'");
2102
+ return;
2103
+ }
2104
+ if (selectedProvider === "anthropic") process.env.ANTHROPIC_API_KEY = apiKey.trim();
2105
+ else if (selectedProvider === "openai") process.env.OPENAI_API_KEY = apiKey.trim();
2106
+ setStep("success");
2107
+ onKeySaved?.(selectedProvider, apiKey.trim());
2108
+ setTimeout(() => {
2109
+ setStep("select-provider");
2110
+ setApiKey("");
2111
+ setSelectedProvider(null);
2112
+ }, 1500);
2113
+ } else if (key.backspace || key.delete) {
2114
+ setApiKey((prev) => prev.slice(0, -1));
2115
+ setError(null);
2116
+ } else if (input && !key.ctrl && !key.meta) {
2117
+ setApiKey((prev) => prev + input);
2118
+ setError(null);
2119
+ }
2120
+ } else if (step === "success") {
2121
+ if (key.return || key.escape) {
2122
+ setStep("select-provider");
2123
+ setApiKey("");
2124
+ setSelectedProvider(null);
2125
+ }
2126
+ }
2127
+ });
2128
+ const maskKeyForInput = (key) => {
2129
+ if (key.length <= 8) return "•".repeat(key.length);
2130
+ return key.substring(0, 7) + "•".repeat(Math.min(key.length - 11, 20)) + key.substring(key.length - 4);
2131
+ };
2132
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2133
+ flexDirection: "column",
2134
+ borderStyle: "single",
2135
+ borderColor: colors.primary,
2136
+ paddingX: 2,
2137
+ paddingY: 1,
2138
+ marginY: 1,
2139
+ children: [
2140
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2141
+ bold: true,
2142
+ color: colors.info,
2143
+ children: [emoji.key, " API Key Management"]
2144
+ }),
2145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2146
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2147
+ bold: true,
2148
+ children: "Current Status:"
2149
+ }),
2150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2152
+ marginLeft: 2,
2153
+ children: anthropicKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2155
+ color: colors.success,
2156
+ children: "✓ "
2157
+ }),
2158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Anthropic: " }),
2159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2160
+ dimColor: true,
2161
+ children: maskKey(anthropicKey)
2162
+ })
2163
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2164
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2165
+ color: colors.warning,
2166
+ children: "✗ "
2167
+ }),
2168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Anthropic: " }),
2169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2170
+ dimColor: true,
2171
+ children: "not set"
2172
+ })
2173
+ ] })
2174
+ }),
2175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2176
+ marginLeft: 2,
2177
+ children: openaiKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2179
+ color: colors.success,
2180
+ children: "✓ "
2181
+ }),
2182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "OpenAI: " }),
2183
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2184
+ dimColor: true,
2185
+ children: maskKey(openaiKey)
2186
+ })
2187
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2189
+ color: colors.warning,
2190
+ children: "✗ "
2191
+ }),
2192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "OpenAI: " }),
2193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2194
+ dimColor: true,
2195
+ children: "not set"
2196
+ })
2197
+ ] })
2198
+ }),
2199
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2200
+ step === "select-provider" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2202
+ bold: true,
2203
+ children: "Add or Update Key:"
2204
+ }),
2205
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2206
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2207
+ marginLeft: 2,
2208
+ children: [
2209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2210
+ color: colors.primary,
2211
+ children: "[1]"
2212
+ }),
2213
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: " Anthropic (Claude)" }),
2214
+ anthropicKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2215
+ dimColor: true,
2216
+ children: " (overwrite)"
2217
+ })
2218
+ ]
2219
+ }),
2220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2221
+ marginLeft: 2,
2222
+ children: [
2223
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2224
+ color: colors.primary,
2225
+ children: "[2]"
2226
+ }),
2227
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: " OpenAI (GPT)" }),
2228
+ openaiKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2229
+ dimColor: true,
2230
+ children: " (overwrite)"
2231
+ })
2232
+ ]
2233
+ }),
2234
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2236
+ dimColor: true,
2237
+ children: "Press 1 or 2 to select, Esc to close"
2238
+ })
2239
+ ] }),
2240
+ step === "enter-key" && selectedProvider && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [
2242
+ "Enter your",
2243
+ " ",
2244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2245
+ color: colors.primary,
2246
+ children: selectedProvider === "anthropic" ? "Anthropic" : "OpenAI"
2247
+ }),
2248
+ " ",
2249
+ "API key:",
2250
+ selectedProvider === "anthropic" && anthropicKey && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2251
+ dimColor: true,
2252
+ children: [
2253
+ " (current: ",
2254
+ maskKey(anthropicKey),
2255
+ ")"
2256
+ ]
2257
+ }),
2258
+ selectedProvider === "openai" && openaiKey && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2259
+ dimColor: true,
2260
+ children: [
2261
+ " (current: ",
2262
+ maskKey(openaiKey),
2263
+ ")"
2264
+ ]
2265
+ })
2266
+ ] }),
2267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
2269
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2270
+ dimColor: true,
2271
+ children: [">", " "]
2272
+ }),
2273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: apiKey ? maskKeyForInput(apiKey) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2274
+ dimColor: true,
2275
+ children: "Paste your API key here..."
2276
+ }) }),
2277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2278
+ color: colors.primary,
2279
+ children: "█"
2280
+ })
2281
+ ] }),
2282
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2283
+ color: colors.error,
2284
+ children: [
2285
+ emoji.warning,
2286
+ " ",
2287
+ error
2288
+ ]
2289
+ })] }),
2290
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2292
+ dimColor: true,
2293
+ children: "Press Enter to save, Esc to go back"
2294
+ })
2295
+ ] }),
2296
+ step === "success" && selectedProvider && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2297
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2298
+ color: colors.success,
2299
+ children: [
2300
+ emoji.completed,
2301
+ " API key saved for",
2302
+ " ",
2303
+ selectedProvider === "anthropic" ? "Anthropic" : "OpenAI",
2304
+ "!"
2305
+ ]
2306
+ }),
2307
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2308
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2309
+ dimColor: true,
2310
+ children: "Press Enter or Esc to return to menu"
2311
+ })
2312
+ ] })
2313
+ ]
2314
+ });
2315
+ }
2316
+
2317
+ //#endregion
2318
+ //#region src/cli/components/ToolApproval.tsx
2319
+ /**
2320
+ * Tool approval component for interactive approval flow.
2321
+ */
2322
+ function ToolApproval({ toolName, args, onApprove, onDeny, onApproveAll }) {
2323
+ (0, ink.useInput)((input, key) => {
2324
+ if (input === "y" || input === "Y") onApprove();
2325
+ else if (input === "n" || input === "N" || key.escape) onDeny();
2326
+ else if ((input === "a" || input === "A") && onApproveAll) onApproveAll();
2327
+ });
2328
+ const argsDisplay = JSON.stringify(args, null, 2);
2329
+ const truncatedArgs = argsDisplay.length > 500 ? argsDisplay.slice(0, 500) + "\n... (truncated)" : argsDisplay;
2330
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2331
+ flexDirection: "column",
2332
+ borderStyle: "round",
2333
+ borderColor: "yellow",
2334
+ paddingX: 1,
2335
+ marginY: 1,
2336
+ children: [
2337
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2338
+ bold: true,
2339
+ color: "yellow",
2340
+ children: "🛑 Tool Approval Required"
2341
+ }),
2342
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: ["Tool: ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2343
+ bold: true,
2344
+ children: toolName
2345
+ })] }),
2346
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2347
+ marginTop: 1,
2348
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2349
+ dimColor: true,
2350
+ children: "Arguments:"
2351
+ })
2352
+ }),
2353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: truncatedArgs }),
2354
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2355
+ marginTop: 1,
2356
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [
2357
+ "Press ",
2358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2359
+ bold: true,
2360
+ color: "green",
2361
+ children: "[Y]"
2362
+ }),
2363
+ " to approve,",
2364
+ " ",
2365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2366
+ bold: true,
2367
+ color: "red",
2368
+ children: "[N]"
2369
+ }),
2370
+ " to deny",
2371
+ onApproveAll && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2372
+ ", ",
2373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2374
+ bold: true,
2375
+ color: "blue",
2376
+ children: "[A]"
2377
+ }),
2378
+ " to approve all (enable auto-approve)"
2379
+ ] })
2380
+ ] })
2381
+ })
2382
+ ]
2383
+ });
2384
+ }
2385
+
2386
+ //#endregion
2387
+ //#region src/cli/index.tsx
2388
+ /**
2389
+ * Deep Agent CLI - Interactive terminal interface using Ink.
2390
+ *
2391
+ * Usage:
2392
+ * ANTHROPIC_API_KEY=xxx bunx deep-agent-ink
2393
+ * ANTHROPIC_API_KEY=xxx bun src/cli-ink/index.tsx
2394
+ *
2395
+ * Or with options:
2396
+ * ANTHROPIC_API_KEY=xxx bunx deep-agent-ink --model anthropic/claude-sonnet-4-20250514
2397
+ */
2398
+ const DEFAULT_PROMPT_CACHING = true;
2399
+ const DEFAULT_EVICTION_LIMIT = require_agent.DEFAULT_EVICTION_TOKEN_LIMIT$1;
2400
+ const DEFAULT_SUMMARIZATION = true;
2401
+ const DEFAULT_SUMMARIZATION_THRESHOLD_VALUE = require_agent.DEFAULT_SUMMARIZATION_THRESHOLD$1;
2402
+ const DEFAULT_SUMMARIZATION_KEEP = require_agent.DEFAULT_KEEP_MESSAGES$1;
2403
+ function parseArgs() {
2404
+ const args = process.argv.slice(2);
2405
+ const options = {
2406
+ enablePromptCaching: DEFAULT_PROMPT_CACHING,
2407
+ toolResultEvictionLimit: DEFAULT_EVICTION_LIMIT,
2408
+ enableSummarization: DEFAULT_SUMMARIZATION,
2409
+ summarizationThreshold: DEFAULT_SUMMARIZATION_THRESHOLD_VALUE,
2410
+ summarizationKeepMessages: DEFAULT_SUMMARIZATION_KEEP
2411
+ };
2412
+ for (let i = 0; i < args.length; i++) {
2413
+ const arg = args[i];
2414
+ if (arg === "--model" || arg === "-m") options.model = args[++i];
2415
+ else if (arg === "--max-steps" || arg === "-s") {
2416
+ const val = args[++i];
2417
+ if (val) options.maxSteps = parseInt(val, 10);
2418
+ } else if (arg === "--prompt" || arg === "-p") options.systemPrompt = args[++i];
2419
+ else if (arg === "--dir" || arg === "-d") options.workDir = args[++i];
2420
+ else if (arg === "--cache" || arg === "--prompt-caching") options.enablePromptCaching = true;
2421
+ else if (arg === "--no-cache" || arg === "--no-prompt-caching") options.enablePromptCaching = false;
2422
+ else if (arg === "--eviction-limit" || arg === "-e") {
2423
+ const val = args[++i];
2424
+ if (val) options.toolResultEvictionLimit = parseInt(val, 10);
2425
+ } else if (arg === "--no-eviction") options.toolResultEvictionLimit = 0;
2426
+ else if (arg === "--summarize" || arg === "--auto-summarize") options.enableSummarization = true;
2427
+ else if (arg === "--no-summarize" || arg === "--no-auto-summarize") options.enableSummarization = false;
2428
+ else if (arg === "--summarize-threshold") {
2429
+ const val = args[++i];
2430
+ if (val) {
2431
+ options.summarizationThreshold = parseInt(val, 10);
2432
+ options.enableSummarization = true;
2433
+ }
2434
+ } else if (arg === "--summarize-keep") {
2435
+ const val = args[++i];
2436
+ if (val) {
2437
+ options.summarizationKeepMessages = parseInt(val, 10);
2438
+ options.enableSummarization = true;
2439
+ }
2440
+ } else if (arg === "--session") {
2441
+ const val = args[++i];
2442
+ if (val) options.session = val;
2443
+ } else if (arg && arg.startsWith("--session=")) {
2444
+ const sessionVal = arg.split("=")[1];
2445
+ if (sessionVal) options.session = sessionVal;
2446
+ } else if (arg === "--help" || arg === "-h") {
2447
+ printHelp();
2448
+ process.exit(0);
2449
+ }
2450
+ }
2451
+ return options;
2452
+ }
2453
+ const DEEP_AGENTS_ASCII = `
2454
+ █████╗ ██╗ ███████╗ ██████╗ ██╗ ██╗
2455
+ ██╔══██╗ ██║ ██╔════╝ ██╔══██╗ ██║ ██╔╝
2456
+ ███████║ ██║█████╗███████╗ ██║ ██║ █████╔╝
2457
+ ██╔══██║ ██║╚════╝╚════██║ ██║ ██║ ██╔═██╗
2458
+ ██║ ██║ ██║ ███████║ ██████╔╝ ██║ ██╗
2459
+ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝
2460
+
2461
+ ██████╗ ███████╗ ███████╗ ██████╗
2462
+ ██╔══██╗ ██╔════╝ ██╔════╝ ██╔══██╗
2463
+ ██║ ██║ █████╗ █████╗ ██████╔╝
2464
+ ██║ ██║ ██╔══╝ ██╔══╝ ██╔═══╝
2465
+ ██████╔╝ ███████╗ ███████╗ ██║
2466
+ ╚═════╝ ╚══════╝ ╚══════╝ ╚═╝
2467
+
2468
+ █████╗ ██████╗ ███████╗ ███╗ ██╗ ████████╗
2469
+ ██╔══██╗ ██╔════╝ ██╔════╝ ████╗ ██║ ╚══██╔══╝
2470
+ ███████║ ██║ ███╗ █████╗ ██╔██╗ ██║ ██║
2471
+ ██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║
2472
+ ██║ ██║ ╚██████╔╝ ███████╗ ██║ ╚████║ ██║
2473
+ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═══╝ ╚═╝
2474
+ `;
2475
+ function printHelp() {
2476
+ console.log(`
2477
+ ${DEEP_AGENTS_ASCII}
2478
+
2479
+ Usage:
2480
+ bun src/cli-ink/index.tsx [options]
2481
+
2482
+ Options:
2483
+ --model, -m <model> Model to use (default: anthropic/claude-haiku-4-5-20251001)
2484
+ --max-steps, -s <number> Maximum steps per generation (default: 100)
2485
+ --prompt, -p <prompt> Custom system prompt
2486
+ --dir, -d <directory> Working directory for file operations (default: current dir)
2487
+ --help, -h Show this help
2488
+
2489
+ Performance & Memory (all enabled by default):
2490
+ --no-cache Disable prompt caching (enabled by default for Anthropic)
2491
+ --no-eviction Disable tool result eviction (enabled by default: 20k tokens)
2492
+ --eviction-limit, -e <n> Set custom eviction token limit
2493
+ --no-summarize Disable auto-summarization (enabled by default)
2494
+ --summarize-threshold <n> Token threshold to trigger summarization (default: 170000)
2495
+ --summarize-keep <n> Number of recent messages to keep intact (default: 6)
2496
+
2497
+ Runtime Commands:
2498
+ /cache on|off Toggle prompt caching
2499
+ /eviction on|off Toggle tool result eviction
2500
+ /summarize on|off Toggle auto-summarization
2501
+ /features Show current feature status
2502
+
2503
+ API Keys:
2504
+ The CLI automatically loads API keys from:
2505
+ 1. Environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, TAVILY_API_KEY)
2506
+ 2. .env or .env.local file in the working directory
2507
+
2508
+ Example .env file:
2509
+ ANTHROPIC_API_KEY=sk-ant-...
2510
+ OPENAI_API_KEY=sk-...
2511
+ TAVILY_API_KEY=tvly-... # For web_search tool
2512
+
2513
+ Examples:
2514
+ bun src/cli-ink/index.tsx # uses .env file
2515
+ bun src/cli-ink/index.tsx --dir ./my-project # loads .env from ./my-project
2516
+ ANTHROPIC_API_KEY=xxx bun src/cli-ink/index.tsx # env var takes precedence
2517
+ bun src/cli-ink/index.tsx --model anthropic/claude-sonnet-4-20250514
2518
+ `);
2519
+ }
2520
+ function App({ options, backend }) {
2521
+ const { exit } = (0, ink.useApp)();
2522
+ const summarizationConfig = options.enableSummarization ? {
2523
+ enabled: true,
2524
+ tokenThreshold: options.summarizationThreshold,
2525
+ keepMessages: options.summarizationKeepMessages
2526
+ } : void 0;
2527
+ const checkpointer = options.session ? new require_file_saver.FileSaver({ dir: "./.checkpoints" }) : void 0;
2528
+ const agent = useAgent({
2529
+ model: options.model || "anthropic/claude-haiku-4-5-20251001",
2530
+ maxSteps: options.maxSteps || 100,
2531
+ systemPrompt: options.systemPrompt,
2532
+ backend,
2533
+ enablePromptCaching: options.enablePromptCaching,
2534
+ toolResultEvictionLimit: options.toolResultEvictionLimit,
2535
+ summarization: summarizationConfig,
2536
+ sessionId: options.session,
2537
+ checkpointer,
2538
+ interruptOn: {
2539
+ execute: true,
2540
+ write_file: true,
2541
+ edit_file: true
2542
+ }
2543
+ });
2544
+ const [messages, setMessages] = (0, react.useState)([]);
2545
+ const [showWelcome, setShowWelcome] = (0, react.useState)(true);
2546
+ const [panel, setPanel] = (0, react.useState)({ view: "none" });
2547
+ (0, ink.useInput)((input, key) => {
2548
+ if (key.ctrl && input === "c") if (agent.status !== "idle") agent.abort();
2549
+ else exit();
2550
+ if (key.ctrl && input === "d") exit();
2551
+ });
2552
+ const handleSubmit = (0, react.useCallback)(async (input) => {
2553
+ const trimmed = input.trim();
2554
+ if (!trimmed) return;
2555
+ if (showWelcome) setShowWelcome(false);
2556
+ const { isCommand, command, args } = parseCommand(trimmed);
2557
+ if (isCommand) {
2558
+ await handleCommand(command, args);
2559
+ return;
2560
+ }
2561
+ setPanel({ view: "none" });
2562
+ await agent.sendPrompt(trimmed);
2563
+ }, [showWelcome, agent]);
2564
+ const handleCommand = async (command, args) => {
2565
+ if (!command || command === "") {
2566
+ setPanel({ view: "help" });
2567
+ return;
2568
+ }
2569
+ switch (command) {
2570
+ case "todos":
2571
+ case "todo":
2572
+ case "t":
2573
+ setPanel({ view: "todos" });
2574
+ break;
2575
+ case "files":
2576
+ case "file":
2577
+ case "f":
2578
+ try {
2579
+ setPanel({
2580
+ view: "files",
2581
+ files: await backend.lsInfo(".")
2582
+ });
2583
+ } catch (err) {}
2584
+ break;
2585
+ case "read":
2586
+ case "r":
2587
+ if (!args) return;
2588
+ try {
2589
+ setPanel({
2590
+ view: "file-content",
2591
+ filePath: args,
2592
+ fileContent: await backend.read(args)
2593
+ });
2594
+ } catch (err) {}
2595
+ break;
2596
+ case "apikey":
2597
+ case "key":
2598
+ case "api":
2599
+ setPanel({ view: "apikey-input" });
2600
+ break;
2601
+ case "model":
2602
+ if (args) agent.setModel(args.trim());
2603
+ else setPanel({ view: "models" });
2604
+ break;
2605
+ case "features":
2606
+ case "feat":
2607
+ setPanel({ view: "features" });
2608
+ break;
2609
+ case "tokens":
2610
+ case "tok":
2611
+ setPanel({
2612
+ view: "tokens",
2613
+ tokenCount: require_agent.estimateMessagesTokens(agent.messages)
2614
+ });
2615
+ break;
2616
+ case "sessions":
2617
+ case "session-list":
2618
+ if (checkpointer) {
2619
+ const sessions = await checkpointer.list();
2620
+ if (sessions.length > 0) {
2621
+ const sessionList = sessions.map((s) => ` - ${s}`).join("\n");
2622
+ setMessages((prev) => [...prev, {
2623
+ id: `session-list-${Date.now()}`,
2624
+ role: "assistant",
2625
+ content: `Saved sessions:\n${sessionList}`,
2626
+ timestamp: /* @__PURE__ */ new Date()
2627
+ }]);
2628
+ } else setMessages((prev) => [...prev, {
2629
+ id: `session-list-empty-${Date.now()}`,
2630
+ role: "assistant",
2631
+ content: "No saved sessions",
2632
+ timestamp: /* @__PURE__ */ new Date()
2633
+ }]);
2634
+ } else setMessages((prev) => [...prev, {
2635
+ id: `session-error-${Date.now()}`,
2636
+ role: "assistant",
2637
+ content: "Checkpointing not enabled. Use --session to enable.",
2638
+ timestamp: /* @__PURE__ */ new Date()
2639
+ }]);
2640
+ break;
2641
+ case "session":
2642
+ if (!args) {
2643
+ setMessages((prev) => [...prev, {
2644
+ id: `session-usage-${Date.now()}`,
2645
+ role: "assistant",
2646
+ content: "Usage: /session clear",
2647
+ timestamp: /* @__PURE__ */ new Date()
2648
+ }]);
2649
+ return;
2650
+ }
2651
+ if (args.trim() === "clear" && options.session && checkpointer) {
2652
+ await checkpointer.delete(options.session);
2653
+ setMessages([]);
2654
+ agent.clear();
2655
+ setShowWelcome(true);
2656
+ setPanel({ view: "none" });
2657
+ setMessages((prev) => [...prev, {
2658
+ id: `session-cleared-${Date.now()}`,
2659
+ role: "assistant",
2660
+ content: "Session cleared.",
2661
+ timestamp: /* @__PURE__ */ new Date()
2662
+ }]);
2663
+ }
2664
+ break;
2665
+ case "clear":
2666
+ case "c":
2667
+ setMessages([]);
2668
+ agent.clear();
2669
+ setShowWelcome(true);
2670
+ setPanel({ view: "none" });
2671
+ break;
2672
+ case "cache":
2673
+ if (args === "on" || args === "true" || args === "1") agent.setPromptCaching(true);
2674
+ else if (args === "off" || args === "false" || args === "0") agent.setPromptCaching(false);
2675
+ else agent.setPromptCaching(!agent.features.promptCaching);
2676
+ setPanel({ view: "features" });
2677
+ break;
2678
+ case "eviction":
2679
+ case "evict":
2680
+ if (args === "on" || args === "true" || args === "1") agent.setEviction(true);
2681
+ else if (args === "off" || args === "false" || args === "0") agent.setEviction(false);
2682
+ else agent.setEviction(!agent.features.eviction);
2683
+ setPanel({ view: "features" });
2684
+ break;
2685
+ case "summarize":
2686
+ case "sum":
2687
+ if (args === "on" || args === "true" || args === "1") agent.setSummarization(true);
2688
+ else if (args === "off" || args === "false" || args === "0") agent.setSummarization(false);
2689
+ else agent.setSummarization(!agent.features.summarization);
2690
+ setPanel({ view: "features" });
2691
+ break;
2692
+ case "approve":
2693
+ const newValue = !agent.autoApproveEnabled;
2694
+ agent.setAutoApprove(newValue);
2695
+ return;
2696
+ case "help":
2697
+ case "h":
2698
+ case "?":
2699
+ setPanel({ view: "help" });
2700
+ break;
2701
+ case "quit":
2702
+ case "exit":
2703
+ case "q":
2704
+ exit();
2705
+ break;
2706
+ case "state":
2707
+ console.log(JSON.stringify(agent.state, null, 2));
2708
+ break;
2709
+ }
2710
+ };
2711
+ const isGenerating = agent.status !== "idle" && agent.status !== "done" && agent.status !== "error";
2712
+ const isInteractivePanel = panel.view === "apikey-input" || panel.view === "models";
2713
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2714
+ flexDirection: "column",
2715
+ padding: 1,
2716
+ children: [
2717
+ showWelcome && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Welcome, {
2718
+ model: agent.currentModel,
2719
+ workDir: options.workDir || process.cwd()
2720
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WelcomeHint, {})] }),
2721
+ panel.view === "help" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlashMenuPanel, {}),
2722
+ panel.view === "todos" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TodoList, { todos: agent.state.todos }),
2723
+ panel.view === "files" && panel.files && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileList, { files: panel.files }),
2724
+ panel.view === "file-content" && panel.filePath && panel.fileContent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilePreview, {
2725
+ path: panel.filePath,
2726
+ content: panel.fileContent
2727
+ }),
2728
+ panel.view === "apikey-input" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApiKeyInputPanel, {
2729
+ onKeySaved: () => {},
2730
+ onClose: () => setPanel({ view: "none" })
2731
+ }),
2732
+ panel.view === "features" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FeaturesPanel, {
2733
+ features: agent.features,
2734
+ options
2735
+ }),
2736
+ panel.view === "tokens" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TokensPanel, {
2737
+ tokenCount: panel.tokenCount || 0,
2738
+ messageCount: agent.messages.length
2739
+ }),
2740
+ panel.view === "models" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelSelectionPanel, {
2741
+ currentModel: agent.currentModel,
2742
+ onModelSelect: (modelId) => {
2743
+ agent.setModel(modelId);
2744
+ },
2745
+ onClose: () => setPanel({ view: "none" })
2746
+ }),
2747
+ agent.events.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2748
+ flexDirection: "column",
2749
+ children: agent.events.map((event) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EventRenderer, { event }, event.id))
2750
+ }),
2751
+ isGenerating && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2752
+ flexDirection: "column",
2753
+ children: [agent.streamingText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2754
+ marginY: 1,
2755
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2756
+ color: colors.success,
2757
+ children: "● "
2758
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [agent.streamingText, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2759
+ color: colors.muted,
2760
+ children: "▌"
2761
+ })] })] })
2762
+ }), (agent.status === "thinking" || agent.status === "tool-call") && !agent.streamingText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2763
+ marginY: 1,
2764
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThinkingIndicator, {})
2765
+ })]
2766
+ }),
2767
+ agent.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorDisplay, { error: agent.error }),
2768
+ agent.pendingApproval && !agent.autoApproveEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolApproval, {
2769
+ toolName: agent.pendingApproval.toolName,
2770
+ args: agent.pendingApproval.args,
2771
+ onApprove: () => agent.respondToApproval(true),
2772
+ onDeny: () => agent.respondToApproval(false),
2773
+ onApproveAll: () => {
2774
+ agent.setAutoApprove(true);
2775
+ agent.respondToApproval(true);
2776
+ }
2777
+ }),
2778
+ !isInteractivePanel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2779
+ marginTop: 1,
2780
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
2781
+ onSubmit: handleSubmit,
2782
+ disabled: isGenerating
2783
+ })
2784
+ }),
2785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusBar, {
2786
+ workDir: options.workDir || process.cwd(),
2787
+ model: agent.currentModel,
2788
+ status: agent.status,
2789
+ features: agent.features,
2790
+ autoApproveEnabled: agent.autoApproveEnabled,
2791
+ sessionId: options.session
2792
+ })
2793
+ ]
2794
+ });
2795
+ }
2796
+ const TOOLS_WITH_SPECIFIC_EVENTS = new Set([
2797
+ "read_file",
2798
+ "ls",
2799
+ "glob",
2800
+ "grep",
2801
+ "write_file",
2802
+ "edit_file",
2803
+ "write_todos",
2804
+ "web_search",
2805
+ "http_request",
2806
+ "fetch_url"
2807
+ ]);
2808
+ function EventRenderer({ event }) {
2809
+ const e = event.event;
2810
+ switch (e.type) {
2811
+ case "user-message": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2812
+ marginBottom: 1,
2813
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2814
+ color: colors.muted,
2815
+ bold: true,
2816
+ children: "> "
2817
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2818
+ bold: true,
2819
+ children: e.content
2820
+ })]
2821
+ });
2822
+ case "text-segment":
2823
+ if (!e.text.trim()) return null;
2824
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2825
+ marginY: 1,
2826
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2827
+ color: colors.success,
2828
+ children: "● "
2829
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: e.text })] })
2830
+ });
2831
+ case "step-start": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
2832
+ marginTop: 1,
2833
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2834
+ color: colors.muted,
2835
+ children: [
2836
+ "─── step ",
2837
+ e.stepNumber,
2838
+ " ───"
2839
+ ]
2840
+ })
2841
+ });
2842
+ case "tool-call":
2843
+ if (TOOLS_WITH_SPECIFIC_EVENTS.has(e.toolName)) return null;
2844
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCall, {
2845
+ toolName: e.toolName,
2846
+ isExecuting: true
2847
+ });
2848
+ case "todos-changed": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2849
+ color: colors.info,
2850
+ children: "📋 Todos: "
2851
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2852
+ dimColor: true,
2853
+ children: [
2854
+ e.todos.filter((t) => t.status === "completed").length,
2855
+ "/",
2856
+ e.todos.length,
2857
+ " completed"
2858
+ ]
2859
+ })] });
2860
+ case "file-write-start": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilePreview, {
2861
+ path: e.path,
2862
+ content: e.content,
2863
+ isWrite: true,
2864
+ maxLines: 10
2865
+ });
2866
+ case "file-written": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileWritten, { path: e.path });
2867
+ case "file-edited": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileEdited, {
2868
+ path: e.path,
2869
+ occurrences: e.occurrences
2870
+ });
2871
+ case "file-read": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileRead, {
2872
+ path: e.path,
2873
+ lines: e.lines
2874
+ });
2875
+ case "ls": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LsResult, {
2876
+ path: e.path,
2877
+ count: e.count
2878
+ });
2879
+ case "glob": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlobResult, {
2880
+ pattern: e.pattern,
2881
+ count: e.count
2882
+ });
2883
+ case "grep": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GrepResult, {
2884
+ pattern: e.pattern,
2885
+ count: e.count
2886
+ });
2887
+ case "web-search-start": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
2888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2889
+ color: colors.info,
2890
+ children: "🔍 "
2891
+ }),
2892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Searching web: " }),
2893
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2894
+ color: colors.muted,
2895
+ children: e.query
2896
+ })
2897
+ ] });
2898
+ case "web-search-finish": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
2899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2900
+ color: colors.success,
2901
+ children: "✓ "
2902
+ }),
2903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Found " }),
2904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2905
+ color: colors.info,
2906
+ children: e.resultCount
2907
+ }),
2908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: " results" })
2909
+ ] });
2910
+ case "http-request-start": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
2911
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2912
+ color: colors.info,
2913
+ children: "🌐 "
2914
+ }),
2915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [e.method, " "] }),
2916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2917
+ color: colors.muted,
2918
+ children: e.url
2919
+ })
2920
+ ] });
2921
+ case "http-request-finish": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
2922
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2923
+ color: e.statusCode >= 200 && e.statusCode < 300 ? colors.success : colors.error,
2924
+ children: [e.statusCode >= 200 && e.statusCode < 300 ? "✓" : "✗", " "]
2925
+ }),
2926
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Status: " }),
2927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2928
+ color: e.statusCode >= 200 && e.statusCode < 300 ? colors.success : colors.error,
2929
+ children: e.statusCode
2930
+ })
2931
+ ] });
2932
+ case "fetch-url-start": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
2933
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2934
+ color: colors.info,
2935
+ children: "📄 "
2936
+ }),
2937
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Fetching: " }),
2938
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2939
+ color: colors.muted,
2940
+ children: e.url
2941
+ })
2942
+ ] });
2943
+ case "fetch-url-finish": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
2944
+ color: e.success ? colors.success : colors.error,
2945
+ children: [e.success ? "✓" : "✗", " "]
2946
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: e.success ? "Content fetched" : "Failed to fetch" })] });
2947
+ case "subagent-start": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubagentStart, {
2948
+ name: e.name,
2949
+ task: e.task
2950
+ });
2951
+ case "subagent-finish": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubagentFinish, { name: e.name });
2952
+ case "done": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoneIndicator, {
2953
+ todosCompleted: e.state.todos.filter((t) => t.status === "completed").length,
2954
+ todosTotal: e.state.todos.length,
2955
+ filesCount: Object.keys(e.state.files).length
2956
+ });
2957
+ case "error": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorDisplay, { error: e.error });
2958
+ default: return null;
2959
+ }
2960
+ }
2961
+ function FeaturesPanel({ features, options }) {
2962
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
2963
+ flexDirection: "column",
2964
+ borderStyle: "single",
2965
+ borderColor: colors.muted,
2966
+ paddingX: 2,
2967
+ paddingY: 1,
2968
+ marginY: 1,
2969
+ children: [
2970
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2971
+ bold: true,
2972
+ color: colors.info,
2973
+ children: "⚙️ Feature Status"
2974
+ }),
2975
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
2976
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: features.promptCaching ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2977
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2978
+ color: colors.success,
2979
+ children: "✓ "
2980
+ }),
2981
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Prompt Caching: " }),
2982
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2983
+ color: colors.success,
2984
+ children: "enabled"
2985
+ })
2986
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2988
+ dimColor: true,
2989
+ children: "✗ "
2990
+ }),
2991
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Prompt Caching: " }),
2992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2993
+ dimColor: true,
2994
+ children: "disabled"
2995
+ })
2996
+ ] }) }),
2997
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: features.eviction ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2998
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
2999
+ color: colors.success,
3000
+ children: "✓ "
3001
+ }),
3002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Tool Eviction: " }),
3003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
3004
+ color: colors.success,
3005
+ children: [
3006
+ "enabled (",
3007
+ options.toolResultEvictionLimit,
3008
+ " tokens)"
3009
+ ]
3010
+ })
3011
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3013
+ dimColor: true,
3014
+ children: "✗ "
3015
+ }),
3016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Tool Eviction: " }),
3017
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3018
+ dimColor: true,
3019
+ children: "disabled"
3020
+ })
3021
+ ] }) }),
3022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: features.summarization ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3024
+ color: colors.success,
3025
+ children: "✓ "
3026
+ }),
3027
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Auto-Summarization: " }),
3028
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
3029
+ color: colors.success,
3030
+ children: [
3031
+ "enabled (",
3032
+ options.summarizationThreshold || require_agent.DEFAULT_SUMMARIZATION_THRESHOLD$1,
3033
+ " tokens, keep ",
3034
+ options.summarizationKeepMessages || require_agent.DEFAULT_KEEP_MESSAGES$1,
3035
+ " msgs)"
3036
+ ]
3037
+ })
3038
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3039
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3040
+ dimColor: true,
3041
+ children: "✗ "
3042
+ }),
3043
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Auto-Summarization: " }),
3044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3045
+ dimColor: true,
3046
+ children: "disabled"
3047
+ })
3048
+ ] }) }),
3049
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
3050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
3051
+ dimColor: true,
3052
+ children: [
3053
+ "Enable with: --cache --eviction-limit ",
3054
+ DEFAULT_EVICTION_LIMIT,
3055
+ " --summarize"
3056
+ ]
3057
+ })
3058
+ ]
3059
+ });
3060
+ }
3061
+ function TokensPanel({ tokenCount, messageCount }) {
3062
+ const formatNumber = (n) => n.toLocaleString();
3063
+ const percentage = Math.round(tokenCount / require_agent.CONTEXT_WINDOW * 100);
3064
+ let usageColor = colors.success;
3065
+ if (percentage > 80) usageColor = colors.error;
3066
+ else if (percentage > 50) usageColor = colors.warning;
3067
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
3068
+ flexDirection: "column",
3069
+ borderStyle: "single",
3070
+ borderColor: colors.muted,
3071
+ paddingX: 2,
3072
+ paddingY: 1,
3073
+ marginY: 1,
3074
+ children: [
3075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3076
+ bold: true,
3077
+ color: colors.info,
3078
+ children: "📊 Token Usage"
3079
+ }),
3080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
3081
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Messages: " }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3082
+ color: colors.primary,
3083
+ children: messageCount
3084
+ })] }),
3085
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Estimated Tokens: " }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3086
+ color: usageColor,
3087
+ children: formatNumber(tokenCount)
3088
+ })] }),
3089
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
3090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: "Context Usage: " }),
3091
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
3092
+ color: usageColor,
3093
+ children: [percentage, "%"]
3094
+ }),
3095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3096
+ dimColor: true,
3097
+ children: " (of ~200k)"
3098
+ })
3099
+ ] }),
3100
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { height: 1 }),
3101
+ percentage > 50 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
3102
+ color: colors.warning,
3103
+ children: "⚠️ Consider enabling --summarize to manage context"
3104
+ })
3105
+ ]
3106
+ });
3107
+ }
3108
+ /**
3109
+ * Load environment variables from .env file in the working directory.
3110
+ * Bun automatically loads .env from cwd, but we want to also check the
3111
+ * specified working directory if different.
3112
+ */
3113
+ async function loadEnvFile(workDir) {
3114
+ const envPaths = [`${workDir}/.env`, `${workDir}/.env.local`];
3115
+ const keysToCheck = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
3116
+ const result = {
3117
+ loaded: false,
3118
+ keysFound: []
3119
+ };
3120
+ for (const envPath of envPaths) try {
3121
+ const file = Bun.file(envPath);
3122
+ if (await file.exists()) {
3123
+ const lines = (await file.text()).split("\n");
3124
+ for (const line of lines) {
3125
+ const trimmed = line.trim();
3126
+ if (!trimmed || trimmed.startsWith("#")) continue;
3127
+ const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
3128
+ if (match) {
3129
+ const key = match[1];
3130
+ const rawValue = match[2];
3131
+ if (!key || rawValue === void 0) continue;
3132
+ let value = rawValue.trim();
3133
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
3134
+ if (!process.env[key] && value) {
3135
+ process.env[key] = value;
3136
+ if (keysToCheck.includes(key)) result.keysFound.push(key);
3137
+ }
3138
+ }
3139
+ }
3140
+ result.loaded = true;
3141
+ result.path = envPath;
3142
+ break;
3143
+ }
3144
+ } catch {}
3145
+ for (const key of keysToCheck) if (process.env[key] && !result.keysFound.includes(key)) {}
3146
+ return result;
3147
+ }
3148
+ async function main() {
3149
+ const options = parseArgs();
3150
+ const workDir = options.workDir || process.cwd();
3151
+ const envResult = await loadEnvFile(workDir);
3152
+ if (envResult.loaded && envResult.keysFound.length > 0) console.log(`\x1b[32m✓\x1b[0m Loaded API keys from ${envResult.path}: ${envResult.keysFound.join(", ")}`);
3153
+ if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) console.log(`\x1b[33m⚠\x1b[0m No API keys found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY in environment or .env file.`);
3154
+ (0, ink.render)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(App, {
3155
+ options,
3156
+ backend: new require_file_saver.LocalSandbox({ cwd: workDir })
3157
+ }));
3158
+ }
3159
+ main();
3160
+
3161
+ //#endregion
3162
+ //# sourceMappingURL=index.cjs.map