deepagentsdk 0.11.1 → 0.13.0

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