@harperfast/agent 0.16.5 → 0.16.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,2919 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ActionsProvider,
4
+ agentManager,
5
+ curryEmitToListeners,
6
+ defaultAnthropicCompactionModel,
7
+ defaultAnthropicModel,
8
+ defaultGoogleCompactionModel,
9
+ defaultGoogleModel,
10
+ defaultModelToken,
11
+ defaultOllamaCompactionModel,
12
+ defaultOllamaModel,
13
+ defaultOpenAICompactionModel,
14
+ defaultOpenAIModel,
15
+ emitToListeners,
16
+ excludeFalsy,
17
+ fetchOllamaModels,
18
+ handleExit,
19
+ isOpenAIModel,
20
+ isTrue,
21
+ lastEmittedValue,
22
+ rateLimitTracker,
23
+ resetTrackedState,
24
+ setupGlobalErrorHandlers,
25
+ trackedState,
26
+ updateEnv,
27
+ useActions,
28
+ useListener
29
+ } from "./chunk-LSVGOLBU.js";
30
+
31
+ // lifecycle/patchFetch.ts
32
+ var originalFetch = globalThis.fetch;
33
+ if (originalFetch) {
34
+ globalThis.fetch = async (...args) => {
35
+ const response = await originalFetch(...args);
36
+ const headers = {};
37
+ response.headers.forEach((value, key) => {
38
+ headers[key] = value;
39
+ });
40
+ const commonHeaders = [
41
+ "x-ratelimit-limit-requests",
42
+ "x-ratelimit-limit-tokens",
43
+ "x-ratelimit-remaining-requests",
44
+ "x-ratelimit-remaining-tokens",
45
+ "x-ratelimit-reset-requests",
46
+ "x-ratelimit-reset-tokens",
47
+ "anthropic-ratelimit-requests-limit",
48
+ "anthropic-ratelimit-requests-remaining",
49
+ "anthropic-ratelimit-requests-reset",
50
+ "anthropic-ratelimit-tokens-limit",
51
+ "anthropic-ratelimit-tokens-remaining",
52
+ "anthropic-ratelimit-tokens-reset",
53
+ "retry-after"
54
+ ];
55
+ for (const key of commonHeaders) {
56
+ if (!headers[key]) {
57
+ const val = response.headers.get(key);
58
+ if (val) {
59
+ headers[key] = val;
60
+ }
61
+ }
62
+ }
63
+ rateLimitTracker.updateFromHeaders(headers);
64
+ emitToListeners("SettingsUpdated", void 0);
65
+ const retryAfter = response.headers.get("retry-after");
66
+ if (retryAfter) {
67
+ const seconds = parseInt(retryAfter, 10);
68
+ if (!isNaN(seconds) && seconds > 0) {
69
+ emitToListeners("PushNewMessages", [{
70
+ type: "interrupted",
71
+ text: `Rate limit reached. Sleeping for ${seconds} seconds (Retry-After)...`,
72
+ version: 1
73
+ }]);
74
+ await new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
75
+ }
76
+ }
77
+ return response;
78
+ };
79
+ }
80
+
81
+ // cli.ts
82
+ import chalk5 from "chalk";
83
+
84
+ // ink/main.tsx
85
+ import { render, useApp } from "ink";
86
+ import "react";
87
+
88
+ // ink/components/ChatContent.tsx
89
+ import { Spinner as Spinner2 } from "@inkjs/ui";
90
+ import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
91
+ import React10, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo9, useRef as useRef2, useState as useState12 } from "react";
92
+
93
+ // ink/contexts/ChatContext.tsx
94
+ import { createContext, useContext, useMemo, useState } from "react";
95
+ import { jsx } from "react/jsx-runtime";
96
+ var ChatContext = createContext(void 0);
97
+ var useChat = () => {
98
+ const context = useContext(ChatContext);
99
+ if (!context) {
100
+ throw new Error("useChat must be used within a ChatProvider");
101
+ }
102
+ return context;
103
+ };
104
+ var initialMessages = [];
105
+ function getInitialMessages() {
106
+ if (initialMessages.length === 0) {
107
+ initialMessages = agentManager.initialMessages ? agentManager.initialMessages.map((m, index) => ({
108
+ ...m,
109
+ id: index,
110
+ version: m.version ?? 1
111
+ })) : [{
112
+ id: 0,
113
+ type: "agent",
114
+ text: 'What shall we build today? (Type "/clear" to reset, "/skills" to add skills, or "/exit" to quit)',
115
+ version: 1
116
+ }];
117
+ }
118
+ return initialMessages;
119
+ }
120
+ var ChatProvider = ({
121
+ children
122
+ }) => {
123
+ const [messages, setMessages] = useState(getInitialMessages());
124
+ const [userInputMode, setUserInputMode] = useState("waiting");
125
+ const [isThinking, setIsThinking] = useState(false);
126
+ const [isCompacting, setIsCompacting] = useState(false);
127
+ const [pullingState, setPullingState] = useState(null);
128
+ const [focusedArea, setFocusedArea] = useState("input");
129
+ useListener("PushNewMessages", (messages2) => {
130
+ setMessages((prev) => {
131
+ return prev.concat(
132
+ messages2.map((message, index) => ({ ...message, id: prev.length + index, version: 1 }))
133
+ );
134
+ });
135
+ }, []);
136
+ useListener("SetInputMode", (newInputMode) => {
137
+ setUserInputMode(newInputMode);
138
+ }, []);
139
+ useListener("SetThinking", (value2) => {
140
+ setIsThinking(Boolean(value2));
141
+ }, []);
142
+ useListener("SetCompacting", (value2) => {
143
+ setIsCompacting(Boolean(value2));
144
+ }, []);
145
+ useListener("SetPulling", (value2) => {
146
+ setPullingState(value2);
147
+ }, []);
148
+ useListener("InterruptThought", () => {
149
+ setIsThinking(false);
150
+ }, []);
151
+ useListener("UpdateLastMessageText", (text) => {
152
+ setMessages((prev) => {
153
+ if (prev.length === 0) {
154
+ return prev;
155
+ }
156
+ const lastIndex = [...prev].reverse().findIndex((m) => m.type === "agent");
157
+ if (lastIndex === -1) {
158
+ return prev;
159
+ }
160
+ const actualIndex = prev.length - 1 - lastIndex;
161
+ const last = prev[actualIndex];
162
+ const updated = [...prev];
163
+ updated[actualIndex] = {
164
+ ...last,
165
+ text: last.text + text,
166
+ version: last.version + 1
167
+ };
168
+ return updated;
169
+ });
170
+ }, []);
171
+ useListener("ClearChatHistory", () => {
172
+ agentManager.session?.clearSession();
173
+ setMessages([{
174
+ id: 0,
175
+ type: "agent",
176
+ text: "Chat cleared. What shall we build today?",
177
+ version: 1
178
+ }]);
179
+ }, []);
180
+ const value = useMemo(() => ({
181
+ messages,
182
+ userInputMode,
183
+ isThinking,
184
+ isCompacting,
185
+ pullingState,
186
+ focusedArea,
187
+ setFocusedArea
188
+ }), [messages, userInputMode, isThinking, isCompacting, pullingState, focusedArea]);
189
+ return /* @__PURE__ */ jsx(ChatContext.Provider, { value, children });
190
+ };
191
+
192
+ // ink/bindings/useMessageListener.ts
193
+ var hasShownInterruptHint = false;
194
+ function useMessageListener() {
195
+ const { userInputMode, isThinking } = useChat();
196
+ useListener("InterruptThought", () => {
197
+ agentManager.interrupt();
198
+ emitToListeners("PushNewMessages", [
199
+ {
200
+ type: "interrupted",
201
+ text: "- thought interrupted -",
202
+ version: 1
203
+ }
204
+ ]);
205
+ }, []);
206
+ useListener("PushNewMessages", async (messages) => {
207
+ for (const message of messages) {
208
+ if ((message.type === "user" || message.type === "prompt") && message.text) {
209
+ const lowerText = message.text.toLowerCase();
210
+ if (lowerText === "exit" || lowerText === "bye") {
211
+ await handleExit();
212
+ }
213
+ if (isThinking) {
214
+ if (!hasShownInterruptHint) {
215
+ hasShownInterruptHint = true;
216
+ emitToListeners("PushNewMessages", [{
217
+ type: "interrupted",
218
+ text: "- to interrupt the current thinking, press escape -",
219
+ version: 1
220
+ }]);
221
+ }
222
+ agentManager.enqueueUserInput(message.text);
223
+ message.handled = true;
224
+ } else if (!message.handled) {
225
+ void agentManager.runTask(message.text, message.type === "prompt");
226
+ }
227
+ }
228
+ }
229
+ }, [userInputMode, isThinking]);
230
+ }
231
+
232
+ // ink/constants/footerHeight.ts
233
+ var footerHeight = 2;
234
+
235
+ // ink/contexts/ApprovalContext.tsx
236
+ import { createContext as createContext2, useCallback, useContext as useContext2, useMemo as useMemo2, useState as useState2 } from "react";
237
+ import { jsx as jsx2 } from "react/jsx-runtime";
238
+ var ApprovalContext = createContext2(void 0);
239
+ var useApproval = () => {
240
+ const context = useContext2(ApprovalContext);
241
+ if (!context) {
242
+ throw new Error("useApproval must be used within an ApprovalProvider");
243
+ }
244
+ return context;
245
+ };
246
+ var ApprovalProvider = ({ children }) => {
247
+ const [payload, setPayload] = useState2(null);
248
+ const [toolInfos] = useState2(/* @__PURE__ */ new Map());
249
+ const registerToolInfo = useCallback(
250
+ (info) => {
251
+ toolInfos.set(info.callId, info);
252
+ },
253
+ [toolInfos]
254
+ );
255
+ useListener("OpenApprovalViewer", (p) => {
256
+ let finalPayload = { ...p, openedAt: Date.now() };
257
+ if (p.mode === "info" && p.callId) {
258
+ const info = toolInfos.get(p.callId);
259
+ if (info) {
260
+ finalPayload = { ...finalPayload, ...info };
261
+ }
262
+ } else if (p.callId) {
263
+ registerToolInfo({
264
+ callId: p.callId,
265
+ type: p.type,
266
+ path: p.path,
267
+ diff: p.diff,
268
+ code: p.code,
269
+ commands: p.commands
270
+ });
271
+ }
272
+ setPayload(finalPayload);
273
+ }, [registerToolInfo, toolInfos]);
274
+ useListener("CloseApprovalViewer", () => {
275
+ setPayload(null);
276
+ }, []);
277
+ const value = useMemo2(() => ({
278
+ payload,
279
+ setPayload,
280
+ registerToolInfo
281
+ }), [payload, registerToolInfo]);
282
+ return /* @__PURE__ */ jsx2(ApprovalContext.Provider, { value, children });
283
+ };
284
+
285
+ // ink/contexts/PlanContext.tsx
286
+ import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3, useState as useState3 } from "react";
287
+ import { jsx as jsx3 } from "react/jsx-runtime";
288
+ var PlanContext = createContext3({ planDescription: "", planItems: [], progress: 0 });
289
+ var usePlan = () => {
290
+ const context = useContext3(PlanContext);
291
+ if (!context) {
292
+ throw new Error("usePlan must be used within a PlanProvider");
293
+ }
294
+ return context;
295
+ };
296
+ var PlanProvider = ({
297
+ children
298
+ }) => {
299
+ const initialPlanDescription = lastEmittedValue("SetPlanDescription") || "";
300
+ const initialPlanItems = lastEmittedValue("SetPlanItems") || [];
301
+ const initialProgress = calculatePlanProgress(initialPlanItems);
302
+ const [planDescription, setPlanDescription] = useState3(initialPlanDescription);
303
+ const [planItems, setPlanItems] = useState3(initialPlanItems);
304
+ const [progress, setProgress] = useState3(initialProgress);
305
+ useListener("SetPlanDescription", (newGoal) => {
306
+ setPlanDescription(newGoal);
307
+ }, []);
308
+ useListener("SetPlanItems", (planItems2) => {
309
+ const progress2 = calculatePlanProgress(planItems2);
310
+ setPlanItems(planItems2);
311
+ setProgress(progress2);
312
+ }, []);
313
+ const value = useMemo3(() => ({
314
+ progress,
315
+ planDescription,
316
+ planItems
317
+ }), [progress, planDescription, planItems]);
318
+ return /* @__PURE__ */ jsx3(PlanContext.Provider, { value, children });
319
+ };
320
+ function calculatePlanProgress(planItems) {
321
+ if (!planItems?.length) {
322
+ return 0;
323
+ }
324
+ const completedCount = planItems.filter((item) => item.status === "done" || item.status === "not-needed").length;
325
+ return Math.round(completedCount / planItems.length * 100);
326
+ }
327
+
328
+ // ink/library/useTerminalSize.ts
329
+ import { useStdout } from "ink";
330
+ import { useEffect, useState as useState4 } from "react";
331
+ function useTerminalSize() {
332
+ const { stdout } = useStdout();
333
+ const [size, setSize] = useState4({
334
+ rows: stdout?.rows ?? 24,
335
+ columns: stdout?.columns ?? 80
336
+ });
337
+ useEffect(() => {
338
+ if (!stdout || !stdout.isTTY) {
339
+ return;
340
+ }
341
+ const handleResize = () => {
342
+ setSize({
343
+ rows: stdout.rows ?? 24,
344
+ columns: stdout.columns ?? 80
345
+ });
346
+ };
347
+ stdout.on("resize", handleResize);
348
+ return () => {
349
+ stdout.off("resize", handleResize);
350
+ };
351
+ }, [stdout]);
352
+ return size;
353
+ }
354
+
355
+ // ink/library/wrapText.ts
356
+ function wrapText(text, width) {
357
+ if (width <= 1) {
358
+ return text ? [text] : [""];
359
+ }
360
+ const hardLines = text.split("\n");
361
+ const allWrappedLines = [];
362
+ for (const hardLine of hardLines) {
363
+ if (hardLine.length === 0) {
364
+ allWrappedLines.push("");
365
+ continue;
366
+ }
367
+ const words = hardLine.split(/(\s+)/);
368
+ let currentLine = "";
369
+ for (const token of words) {
370
+ if (token.length === 0) {
371
+ continue;
372
+ }
373
+ if (currentLine.length + token.length <= width) {
374
+ currentLine += token;
375
+ } else {
376
+ if (currentLine) {
377
+ allWrappedLines.push(currentLine.trimEnd());
378
+ }
379
+ if (token.trim().length === 0) {
380
+ currentLine = "";
381
+ } else if (token.length > width) {
382
+ for (let i = 0; i < token.length; i += width) {
383
+ const chunk = token.slice(i, i + width);
384
+ if (chunk.length === width) {
385
+ allWrappedLines.push(chunk);
386
+ } else {
387
+ currentLine = chunk;
388
+ }
389
+ }
390
+ } else {
391
+ currentLine = token;
392
+ }
393
+ }
394
+ }
395
+ if (currentLine) {
396
+ allWrappedLines.push(currentLine.trimEnd());
397
+ }
398
+ }
399
+ return allWrappedLines.length ? allWrappedLines : [""];
400
+ }
401
+
402
+ // ink/components/ActionsView.tsx
403
+ import { Box as Box3, Text as Text3, useInput } from "ink";
404
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState6 } from "react";
405
+
406
+ // ink/components/ActionItemRow.tsx
407
+ import { Spinner } from "@inkjs/ui";
408
+ import { Box, Text } from "ink";
409
+ import { memo } from "react";
410
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
411
+ var ActionItemRow = memo(
412
+ ({ item, isSelected, isFocused, width }) => {
413
+ const statusColor = item.running ? "yellow" : item.status === "approved" || item.status === "succeeded" || typeof item.exitCode === "number" && item.exitCode === 0 ? "green" : item.status === "denied" || item.status === "failed" || typeof item.exitCode === "number" && item.exitCode !== 0 ? "red" : "cyan";
414
+ const selectionColor = isFocused ? "cyan" : "gray";
415
+ const pipe = /* @__PURE__ */ jsx4(Text, { color: isSelected ? selectionColor : "gray", bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " });
416
+ const statusLabel = item.running ? " " : item.kind === "approval" ? item.status === "approved" ? " approved" : item.status === "denied" ? " denied" : "" : typeof item.exitCode === "number" ? ` ${item.exitCode}` : "";
417
+ const statusLabelWidth = item.running ? 2 : statusLabel.length;
418
+ const pipeWidth = 3;
419
+ const nameWithSpace = `${item.title} `;
420
+ const availableForDetail = width - pipeWidth - nameWithSpace.length - statusLabelWidth;
421
+ const fullDetail = item.detail || "";
422
+ let displayedDetail = fullDetail;
423
+ if (availableForDetail < fullDetail.length) {
424
+ if (availableForDetail > 3) {
425
+ displayedDetail = fullDetail.slice(0, availableForDetail - 3) + "...";
426
+ } else {
427
+ displayedDetail = fullDetail.slice(0, Math.max(0, availableForDetail));
428
+ }
429
+ }
430
+ return /* @__PURE__ */ jsxs(Box, { children: [
431
+ pipe,
432
+ /* @__PURE__ */ jsxs(Box, { flexGrow: 1, children: [
433
+ /* @__PURE__ */ jsx4(Text, { color: statusColor, bold: true, children: item.title }),
434
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
435
+ " ",
436
+ displayedDetail
437
+ ] })
438
+ ] }),
439
+ /* @__PURE__ */ jsx4(Box, { children: item.running ? /* @__PURE__ */ jsxs(Box, { children: [
440
+ /* @__PURE__ */ jsx4(Text, { color: "yellow" }),
441
+ /* @__PURE__ */ jsx4(Spinner, { type: "dots" })
442
+ ] }) : /* @__PURE__ */ jsx4(Text, { color: statusColor, bold: true, children: statusLabel }) })
443
+ ] });
444
+ }
445
+ );
446
+
447
+ // ink/components/VirtualList.tsx
448
+ import { Box as Box2, Text as Text2 } from "ink";
449
+ import { forwardRef, useEffect as useEffect2, useImperativeHandle, useMemo as useMemo4, useRef, useState as useState5 } from "react";
450
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
451
+ function getDefaultKey(item, index) {
452
+ if (item && typeof item === "object") {
453
+ if ("id" in item && (typeof item.id === "string" || typeof item.id === "number")) {
454
+ return String(item.id);
455
+ }
456
+ if ("key" in item && (typeof item.key === "string" || typeof item.key === "number")) {
457
+ return String(item.key);
458
+ }
459
+ }
460
+ return String(index);
461
+ }
462
+ var VirtualListInner = (props, ref) => {
463
+ const {
464
+ items,
465
+ renderItem,
466
+ getItemHeight: getItemHeightProp,
467
+ itemHeight: fixedItemHeight,
468
+ selectedIndex = 0,
469
+ keyExtractor,
470
+ height: propHeight,
471
+ reservedLines = 0,
472
+ showOverflowIndicators = true,
473
+ overflowIndicatorThreshold = 1,
474
+ renderOverflowTop,
475
+ renderOverflowBottom,
476
+ renderFiller,
477
+ onViewportChange
478
+ } = props;
479
+ const { rows: terminalRows } = useTerminalSize();
480
+ const resolvedHeight = useMemo4(() => {
481
+ if (typeof propHeight === "number") {
482
+ return propHeight;
483
+ }
484
+ if (typeof propHeight === "string" && propHeight.endsWith("%")) {
485
+ const percent = parseFloat(propHeight) / 100;
486
+ return Math.floor(terminalRows * percent);
487
+ }
488
+ return Math.max(1, terminalRows - reservedLines);
489
+ }, [propHeight, terminalRows, reservedLines]);
490
+ const getItemHeight = useMemo4(() => {
491
+ if (getItemHeightProp) {
492
+ return getItemHeightProp;
493
+ }
494
+ if (fixedItemHeight) {
495
+ return () => fixedItemHeight;
496
+ }
497
+ return () => 1;
498
+ }, [getItemHeightProp, fixedItemHeight]);
499
+ const indicatorLines = showOverflowIndicators ? 2 : 0;
500
+ const availableHeight = Math.max(0, resolvedHeight - indicatorLines);
501
+ const [viewportOffset, setViewportOffset] = useState5(0);
502
+ const lastItemsLength = useRef(items.length);
503
+ const viewportOffsetRef = useRef(viewportOffset);
504
+ useEffect2(() => {
505
+ viewportOffsetRef.current = viewportOffset;
506
+ }, [viewportOffset]);
507
+ const clampedSelectedIndex = Math.max(0, Math.min(selectedIndex, items.length - 1));
508
+ const { visibleCount, visibleItems } = useMemo4(() => {
509
+ if (availableHeight <= 0) {
510
+ return { visibleCount: 0, visibleItems: [] };
511
+ }
512
+ let currentHeight = 0;
513
+ let count = 0;
514
+ const visibleItems2 = [];
515
+ for (let i = viewportOffset; i < items.length; i++) {
516
+ const h = getItemHeight(items[i], i);
517
+ if (currentHeight + h > availableHeight && count > 0) {
518
+ break;
519
+ }
520
+ currentHeight += h;
521
+ count++;
522
+ visibleItems2.push(items[i]);
523
+ }
524
+ return { visibleCount: count, visibleItems: visibleItems2 };
525
+ }, [items, viewportOffset, availableHeight, getItemHeight]);
526
+ useEffect2(() => {
527
+ if (items.length === 0 || availableHeight <= 0) {
528
+ lastItemsLength.current = items.length;
529
+ return;
530
+ }
531
+ const currentViewportOffset = viewportOffsetRef.current;
532
+ const isSelectingLast = clampedSelectedIndex === items.length - 1;
533
+ const wasAtBottom = lastItemsLength.current > 0 && currentViewportOffset + visibleCount >= lastItemsLength.current - 1;
534
+ lastItemsLength.current = items.length;
535
+ if (clampedSelectedIndex < currentViewportOffset) {
536
+ setViewportOffset(clampedSelectedIndex);
537
+ return;
538
+ }
539
+ let heightToSelected = 0;
540
+ let isVisible = false;
541
+ for (let i = currentViewportOffset; i <= clampedSelectedIndex; i++) {
542
+ const h = getItemHeight(items[i], i);
543
+ if (heightToSelected + h > availableHeight) {
544
+ isVisible = false;
545
+ break;
546
+ }
547
+ heightToSelected += h;
548
+ if (i === clampedSelectedIndex) {
549
+ isVisible = true;
550
+ }
551
+ }
552
+ if (!isVisible || isSelectingLast || wasAtBottom) {
553
+ let tempOffset = clampedSelectedIndex;
554
+ let hSum2 = 0;
555
+ while (tempOffset >= 0) {
556
+ const h = getItemHeight(items[tempOffset], tempOffset);
557
+ if (hSum2 + h > availableHeight) {
558
+ tempOffset++;
559
+ break;
560
+ }
561
+ hSum2 += h;
562
+ if (tempOffset === 0) {
563
+ break;
564
+ }
565
+ tempOffset--;
566
+ }
567
+ const finalOffset = Math.max(0, tempOffset);
568
+ if (finalOffset !== currentViewportOffset) {
569
+ setViewportOffset(finalOffset);
570
+ }
571
+ }
572
+ }, [clampedSelectedIndex, items, availableHeight, getItemHeight, visibleCount]);
573
+ const viewport = useMemo4(
574
+ () => ({
575
+ offset: viewportOffset,
576
+ visibleCount,
577
+ totalCount: items.length
578
+ }),
579
+ [viewportOffset, visibleCount, items.length]
580
+ );
581
+ const isInitialMount = useRef(true);
582
+ useEffect2(() => {
583
+ if (isInitialMount.current) {
584
+ if (items.length > 0 && availableHeight > 0) {
585
+ isInitialMount.current = false;
586
+ let tempOffset = items.length - 1;
587
+ let hSum2 = 0;
588
+ while (tempOffset >= 0) {
589
+ const h = getItemHeight(items[tempOffset], tempOffset);
590
+ if (hSum2 + h > availableHeight) {
591
+ tempOffset++;
592
+ break;
593
+ }
594
+ hSum2 += h;
595
+ if (tempOffset === 0) {
596
+ break;
597
+ }
598
+ tempOffset--;
599
+ }
600
+ setViewportOffset(Math.max(0, tempOffset));
601
+ }
602
+ }
603
+ }, [availableHeight, items.length, getItemHeight]);
604
+ useEffect2(() => {
605
+ onViewportChange?.(viewport);
606
+ }, [viewport, onViewportChange]);
607
+ useImperativeHandle(
608
+ ref,
609
+ () => ({
610
+ scrollToIndex: (index, alignment = "auto") => {
611
+ const clampedIndex = Math.max(0, Math.min(index, items.length - 1));
612
+ let newOffset = viewportOffset;
613
+ switch (alignment) {
614
+ case "top":
615
+ newOffset = clampedIndex;
616
+ break;
617
+ case "center": {
618
+ let hSum2 = 0;
619
+ let tempOffset = clampedIndex;
620
+ while (tempOffset >= 0 && hSum2 < availableHeight / 2) {
621
+ hSum2 += getItemHeight(items[tempOffset], tempOffset);
622
+ tempOffset--;
623
+ }
624
+ newOffset = tempOffset + 1;
625
+ break;
626
+ }
627
+ case "bottom": {
628
+ let hSum2 = 0;
629
+ let tempOffset = clampedIndex;
630
+ while (tempOffset >= 0 && hSum2 < availableHeight) {
631
+ const h = getItemHeight(items[tempOffset], tempOffset);
632
+ if (hSum2 + h > availableHeight) {
633
+ tempOffset++;
634
+ break;
635
+ }
636
+ hSum2 += h;
637
+ if (tempOffset === 0) {
638
+ break;
639
+ }
640
+ tempOffset--;
641
+ }
642
+ newOffset = tempOffset;
643
+ break;
644
+ }
645
+ case "auto":
646
+ default: {
647
+ if (clampedIndex < viewportOffset) {
648
+ newOffset = clampedIndex;
649
+ } else {
650
+ let heightToSelected = 0;
651
+ let isVisible = false;
652
+ for (let i = viewportOffset; i <= clampedIndex; i++) {
653
+ const h = getItemHeight(items[i], i);
654
+ if (heightToSelected + h > availableHeight) {
655
+ isVisible = false;
656
+ break;
657
+ }
658
+ heightToSelected += h;
659
+ if (i === clampedIndex) {
660
+ isVisible = true;
661
+ }
662
+ }
663
+ if (!isVisible) {
664
+ let hSum2 = 0;
665
+ let tempOffset = clampedIndex;
666
+ while (tempOffset >= 0) {
667
+ const h = getItemHeight(items[tempOffset], tempOffset);
668
+ if (hSum2 + h > availableHeight) {
669
+ tempOffset++;
670
+ break;
671
+ }
672
+ hSum2 += h;
673
+ if (tempOffset === 0) {
674
+ break;
675
+ }
676
+ tempOffset--;
677
+ }
678
+ newOffset = tempOffset;
679
+ }
680
+ }
681
+ }
682
+ }
683
+ setViewportOffset(Math.max(0, newOffset));
684
+ },
685
+ getViewport: () => viewport
686
+ }),
687
+ [items, viewportOffset, availableHeight, getItemHeight, viewport]
688
+ );
689
+ const overflowTop = viewportOffset;
690
+ let itemsFittingBelow = 0;
691
+ let hSum = 0;
692
+ for (let i = viewportOffset; i < items.length; i++) {
693
+ const h = getItemHeight(items[i], i);
694
+ if (hSum + h > availableHeight) {
695
+ break;
696
+ }
697
+ hSum += h;
698
+ itemsFittingBelow++;
699
+ }
700
+ const overflowBottom = Math.max(0, items.length - viewportOffset - itemsFittingBelow);
701
+ const defaultOverflowTop = (count) => {
702
+ if (count < overflowIndicatorThreshold) {
703
+ return null;
704
+ }
705
+ return /* @__PURE__ */ jsx5(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
706
+ "\u25B2 ",
707
+ count,
708
+ " more"
709
+ ] }) });
710
+ };
711
+ const defaultOverflowBottom = (count) => {
712
+ if (count < overflowIndicatorThreshold) {
713
+ return null;
714
+ }
715
+ return /* @__PURE__ */ jsx5(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
716
+ "\u25BC ",
717
+ count,
718
+ " more"
719
+ ] }) });
720
+ };
721
+ const topIndicator = renderOverflowTop ?? defaultOverflowTop;
722
+ const bottomIndicator = renderOverflowBottom ?? defaultOverflowBottom;
723
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: resolvedHeight, children: [
724
+ showOverflowIndicators && /* @__PURE__ */ jsx5(Box2, { height: 1, children: topIndicator(overflowTop) }),
725
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, children: [
726
+ visibleItems.map((item, idx) => {
727
+ const actualIndex = viewportOffset + idx;
728
+ const key = keyExtractor ? keyExtractor(item, actualIndex) : getDefaultKey(item, actualIndex);
729
+ const itemProps = {
730
+ item,
731
+ index: actualIndex,
732
+ isSelected: actualIndex === clampedSelectedIndex
733
+ };
734
+ return /* @__PURE__ */ jsx5(Box2, { height: getItemHeight(item, actualIndex), overflow: "hidden", children: renderItem(itemProps) }, key);
735
+ }),
736
+ renderFiller && hSum < availableHeight && /* @__PURE__ */ jsx5(Box2, { flexDirection: "column", flexGrow: 1, children: renderFiller(availableHeight - hSum) })
737
+ ] }),
738
+ showOverflowIndicators && /* @__PURE__ */ jsx5(Box2, { height: 1, children: bottomIndicator(overflowBottom) })
739
+ ] });
740
+ };
741
+ var VirtualList = forwardRef(VirtualListInner);
742
+
743
+ // ink/components/ActionsView.tsx
744
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
745
+ function ActionsView({ height, isFocused }) {
746
+ const { actions } = useActions();
747
+ const [selectedIndex, setSelectedIndex] = useState6(0);
748
+ const size = useTerminalSize();
749
+ const timelineWidth = Math.floor(size.columns * 0.65);
750
+ const statusWidth = size.columns - timelineWidth;
751
+ useEffect3(() => {
752
+ if (actions.length > 0) {
753
+ setSelectedIndex(actions.length - 1);
754
+ }
755
+ }, [actions.length]);
756
+ useInput((_, key) => {
757
+ if (!isFocused) {
758
+ return;
759
+ }
760
+ if (key.upArrow) {
761
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
762
+ }
763
+ if (key.downArrow) {
764
+ setSelectedIndex((prev) => Math.min(actions.length - 1, prev + 1));
765
+ }
766
+ if (key.return) {
767
+ const selected = actions[selectedIndex];
768
+ if (selected && (selected.kind === "apply_patch" || selected.kind === "approval" || selected.title === "shell" || selected.title === "code_interpreter")) {
769
+ if (selected.callId) {
770
+ emitToListeners("OpenApprovalViewer", {
771
+ type: selected.kind === "apply_patch" ? "update_file" : selected.title,
772
+ mode: "info",
773
+ callId: selected.callId,
774
+ actionId: selected.id
775
+ });
776
+ }
777
+ }
778
+ }
779
+ });
780
+ const renderOverflowTop = useCallback2((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
781
+ /* @__PURE__ */ jsx6(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
782
+ count > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
783
+ " ",
784
+ "\u25B2 ",
785
+ count,
786
+ " more"
787
+ ] })
788
+ ] }), []);
789
+ const renderOverflowBottom = useCallback2((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
790
+ /* @__PURE__ */ jsx6(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
791
+ count > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
792
+ " ",
793
+ "\u25BC ",
794
+ count,
795
+ " more"
796
+ ] })
797
+ ] }), []);
798
+ const renderFiller = useCallback2((h) => /* @__PURE__ */ jsx6(Box3, { flexDirection: "column", children: Array.from({ length: h }).map((_, i) => /* @__PURE__ */ jsx6(Box3, { children: /* @__PURE__ */ jsx6(Text3, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
799
+ const getItemHeight = useCallback2((_item) => 1, []);
800
+ if (actions.length === 0) {
801
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexGrow: 1, height, children: [
802
+ /* @__PURE__ */ jsx6(Box3, { children: /* @__PURE__ */ jsx6(Text3, { italic: true, color: "gray", children: "No actions yet." }) }),
803
+ Array.from({ length: height - 1 }).map((_, i) => /* @__PURE__ */ jsx6(Box3, { children: /* @__PURE__ */ jsx6(Text3, {}) }, i))
804
+ ] });
805
+ }
806
+ return /* @__PURE__ */ jsx6(Box3, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx6(
807
+ VirtualList,
808
+ {
809
+ items: actions,
810
+ getItemHeight,
811
+ height,
812
+ selectedIndex,
813
+ renderOverflowTop,
814
+ renderOverflowBottom,
815
+ renderFiller,
816
+ renderItem: ({ item, isSelected }) => /* @__PURE__ */ jsx6(ActionItemRow, { item, isSelected, isFocused, width: statusWidth - 2 })
817
+ }
818
+ ) });
819
+ }
820
+
821
+ // ink/components/CostView.tsx
822
+ import { Box as Box4, Text as Text4 } from "ink";
823
+ import "react";
824
+
825
+ // ink/contexts/CostContext.tsx
826
+ import { createContext as createContext4, useContext as useContext4, useMemo as useMemo5, useState as useState7 } from "react";
827
+ import { jsx as jsx7 } from "react/jsx-runtime";
828
+ var CostContext = createContext4(void 0);
829
+ var useCost = () => {
830
+ const context = useContext4(CostContext);
831
+ if (!context) {
832
+ throw new Error("useCost must be used within a CostProvider");
833
+ }
834
+ return context;
835
+ };
836
+ var CostProvider = ({
837
+ children
838
+ }) => {
839
+ const [cost, setCost] = useState7({
840
+ totalCost: 0,
841
+ inputTokens: 0,
842
+ outputTokens: 0,
843
+ cachedInputTokens: 0,
844
+ hasUnknownPrices: false
845
+ });
846
+ useListener("UpdateCost", (newCost) => {
847
+ setCost(newCost);
848
+ }, []);
849
+ const value = useMemo5(() => ({
850
+ cost
851
+ }), [cost]);
852
+ return /* @__PURE__ */ jsx7(CostContext.Provider, { value, children });
853
+ };
854
+
855
+ // ink/components/CostView.tsx
856
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
857
+ function CostView({ isDense = false }) {
858
+ const { cost } = useCost();
859
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 1, paddingTop: isDense ? 0 : 1, children: [
860
+ /* @__PURE__ */ jsx8(Box4, { marginBottom: isDense ? 0 : 1, children: /* @__PURE__ */ jsx8(Text4, { bold: true, underline: true, color: "cyan", children: "Session Cost Breakdown" }) }),
861
+ /* @__PURE__ */ jsxs4(Box4, { children: [
862
+ /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Total Cost:" }) }),
863
+ /* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
864
+ "$",
865
+ cost.totalCost.toFixed(4)
866
+ ] }),
867
+ cost.hasUnknownPrices && /* @__PURE__ */ jsx8(Text4, { color: "yellow", children: "(est.)" })
868
+ ] }),
869
+ /* @__PURE__ */ jsxs4(Box4, { children: [
870
+ /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Input Tokens:" }) }),
871
+ /* @__PURE__ */ jsx8(Text4, { children: cost.inputTokens.toLocaleString() })
872
+ ] }),
873
+ /* @__PURE__ */ jsxs4(Box4, { children: [
874
+ /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Cached Tokens:" }) }),
875
+ /* @__PURE__ */ jsx8(Text4, { color: "gray", children: cost.cachedInputTokens.toLocaleString() })
876
+ ] }),
877
+ /* @__PURE__ */ jsxs4(Box4, { children: [
878
+ /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Output Tokens:" }) }),
879
+ /* @__PURE__ */ jsx8(Text4, { children: cost.outputTokens.toLocaleString() })
880
+ ] })
881
+ ] });
882
+ }
883
+
884
+ // ink/components/MessageLineItem.tsx
885
+ import { Box as Box5, Text as Text5 } from "ink";
886
+ import { memo as memo2 } from "react";
887
+ import { Fragment, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
888
+ var MessageLineItem = memo2(
889
+ ({
890
+ item,
891
+ isSelected,
892
+ isFocused,
893
+ indent = 0
894
+ }) => {
895
+ const color = messageTypeToColor(item.type);
896
+ const label = item.type === "interrupted" ? "" : item.type.toUpperCase();
897
+ const selectionColor = isFocused ? "cyan" : "gray";
898
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
899
+ /* @__PURE__ */ jsx9(Text5, { color: isSelected ? selectionColor : "gray", dimColor: !isSelected, bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " }),
900
+ /* @__PURE__ */ jsx9(Box5, { paddingLeft: indent, children: item.isFirstLine ? /* @__PURE__ */ jsxs5(Text5, { children: [
901
+ label && /* @__PURE__ */ jsxs5(Text5, { bold: true, color, children: [
902
+ label,
903
+ item.type === "tool" ? ": " : item.isArgsLine ? " args: " : ": "
904
+ ] }),
905
+ item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx9(Text5, { children: item.text.startsWith(item.toolName) ? /* @__PURE__ */ jsxs5(Fragment, { children: [
906
+ /* @__PURE__ */ jsx9(Text5, { color: "white", bold: true, children: item.toolName }),
907
+ /* @__PURE__ */ jsx9(Text5, { dimColor: true, italic: true, children: item.text.slice(item.toolName.length) })
908
+ ] }) : /* @__PURE__ */ jsx9(Text5, { dimColor: true, italic: true, children: item.text.startsWith(" ") ? item.text : ` ${item.text}` }) }) : /* @__PURE__ */ jsx9(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text })
909
+ ] }) : /* @__PURE__ */ jsx9(Text5, { children: item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx9(Text5, { dimColor: true, italic: true, children: item.text }) : /* @__PURE__ */ jsx9(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text }) }) })
910
+ ] });
911
+ }
912
+ );
913
+ function messageTypeToColor(type) {
914
+ switch (type) {
915
+ case "prompt":
916
+ case "user":
917
+ return "green";
918
+ case "agent":
919
+ return "cyan";
920
+ case "interrupted":
921
+ return "gray";
922
+ default:
923
+ return "magenta";
924
+ }
925
+ }
926
+
927
+ // ink/components/PlanView.tsx
928
+ import { ProgressBar } from "@inkjs/ui";
929
+ import { Box as Box6, Text as Text6 } from "ink";
930
+ import "react";
931
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
932
+ function PlanView() {
933
+ const { planDescription, planItems, progress } = usePlan();
934
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
935
+ /* @__PURE__ */ jsx10(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx10(Text6, { italic: true, children: planDescription }) }),
936
+ /* @__PURE__ */ jsx10(Box6, { flexDirection: "column", flexGrow: 1, children: planItems.map((planItem) => /* @__PURE__ */ jsx10(Box6, { children: /* @__PURE__ */ jsxs6(
937
+ Text6,
938
+ {
939
+ color: planItem.status === "done" ? "green" : planItem.status === "in-progress" ? "yellow" : planItem.status === "not-needed" ? "gray" : "white",
940
+ dimColor: planItem.status === "not-needed",
941
+ children: [
942
+ planItem.status === "done" ? " \u25CF " : planItem.status === "in-progress" ? " \u25B6 " : planItem.status === "not-needed" ? " \u25CC " : " \u25CB ",
943
+ planItem.text
944
+ ]
945
+ }
946
+ ) }, planItem.id)) }),
947
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
948
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
949
+ "PROGRESS: ",
950
+ progress,
951
+ "%"
952
+ ] }),
953
+ /* @__PURE__ */ jsx10(ProgressBar, { value: progress })
954
+ ] })
955
+ ] });
956
+ }
957
+
958
+ // ink/components/SettingsView.tsx
959
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
960
+ import path from "path";
961
+ import { useEffect as useEffect4, useMemo as useMemo7, useState as useState9 } from "react";
962
+
963
+ // ink/contexts/SettingsContext.tsx
964
+ import { createContext as createContext5, useContext as useContext5, useMemo as useMemo6, useState as useState8 } from "react";
965
+ import { jsx as jsx11 } from "react/jsx-runtime";
966
+ var SettingsContext = createContext5(void 0);
967
+ var useSettings = () => {
968
+ const context = useContext5(SettingsContext);
969
+ if (!context) {
970
+ throw new Error("useSettings must be used within a SettingsProvider");
971
+ }
972
+ return context;
973
+ };
974
+ var SettingsProvider = ({
975
+ children
976
+ }) => {
977
+ const [version, setVersion] = useState8(0);
978
+ useListener("SettingsUpdated", () => {
979
+ setVersion((v) => v + 1);
980
+ }, []);
981
+ const value = useMemo6(() => ({
982
+ version,
983
+ model: trackedState.model,
984
+ compactionModel: trackedState.compactionModel,
985
+ sessionPath: trackedState.sessionPath,
986
+ cwd: trackedState.cwd,
987
+ useFlexTier: trackedState.useFlexTier,
988
+ currentTurn: trackedState.currentTurn,
989
+ maxTurns: trackedState.maxTurns,
990
+ maxCost: trackedState.maxCost,
991
+ autoApproveCodeInterpreter: trackedState.autoApproveCodeInterpreter,
992
+ autoApprovePatches: trackedState.autoApprovePatches,
993
+ autoApproveShell: trackedState.autoApproveShell,
994
+ monitorRateLimits: trackedState.monitorRateLimits,
995
+ rateLimitThreshold: trackedState.rateLimitThreshold,
996
+ rateLimitStatus: rateLimitTracker.getStatus()
997
+ }), [version]);
998
+ return /* @__PURE__ */ jsx11(SettingsContext.Provider, { value, children });
999
+ };
1000
+
1001
+ // ink/components/SettingsView.tsx
1002
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1003
+ function SettingsView({ isDense = false }) {
1004
+ const {
1005
+ model,
1006
+ compactionModel,
1007
+ sessionPath,
1008
+ cwd,
1009
+ useFlexTier,
1010
+ currentTurn,
1011
+ maxTurns,
1012
+ maxCost,
1013
+ autoApproveCodeInterpreter: initialAutoApproveCodeInterpreter,
1014
+ autoApprovePatches: initialAutoApprovePatches,
1015
+ autoApproveShell: initialAutoApproveShell,
1016
+ monitorRateLimits: initialMonitorRateLimits,
1017
+ rateLimitThreshold: initialRateLimitThreshold,
1018
+ rateLimitStatus
1019
+ } = useSettings();
1020
+ const { focusedArea } = useChat();
1021
+ const [autoApproveCodeInterpreter, setAutoApproveCodeInterpreter] = useState9(initialAutoApproveCodeInterpreter);
1022
+ const [autoApprovePatches, setAutoApprovePatches] = useState9(initialAutoApprovePatches);
1023
+ const [autoApproveShell, setAutoApproveShell] = useState9(initialAutoApproveShell);
1024
+ const [monitorRateLimits, setMonitorRateLimits] = useState9(initialMonitorRateLimits);
1025
+ useEffect4(() => {
1026
+ setAutoApproveCodeInterpreter(initialAutoApproveCodeInterpreter);
1027
+ }, [initialAutoApproveCodeInterpreter]);
1028
+ useEffect4(() => {
1029
+ setAutoApprovePatches(initialAutoApprovePatches);
1030
+ }, [initialAutoApprovePatches]);
1031
+ useEffect4(() => {
1032
+ setAutoApproveShell(initialAutoApproveShell);
1033
+ }, [initialAutoApproveShell]);
1034
+ useEffect4(() => {
1035
+ setMonitorRateLimits(initialMonitorRateLimits);
1036
+ }, [initialMonitorRateLimits]);
1037
+ const [selectedIndex, setSelectedIndex] = useState9(0);
1038
+ const selectableOptions = useMemo7(() => [
1039
+ {
1040
+ label: "Code Interpreter",
1041
+ value: autoApproveCodeInterpreter,
1042
+ envKey: "HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER",
1043
+ setter: setAutoApproveCodeInterpreter
1044
+ },
1045
+ {
1046
+ label: "File Patches",
1047
+ value: autoApprovePatches,
1048
+ envKey: "HARPER_AGENT_AUTO_APPROVE_PATCHES",
1049
+ setter: setAutoApprovePatches
1050
+ },
1051
+ {
1052
+ label: "Shell Commands",
1053
+ value: autoApproveShell,
1054
+ envKey: "HARPER_AGENT_AUTO_APPROVE_SHELL",
1055
+ setter: setAutoApproveShell
1056
+ },
1057
+ {
1058
+ label: "Monitor Rate Limits",
1059
+ value: monitorRateLimits,
1060
+ envKey: "HARPER_AGENT_MONITOR_RATE_LIMITS",
1061
+ setter: (val) => {
1062
+ setMonitorRateLimits(val);
1063
+ updateEnv("HARPER_AGENT_MONITOR_RATE_LIMITS", val ? "true" : "false");
1064
+ emitToListeners("SettingsUpdated", void 0);
1065
+ }
1066
+ },
1067
+ {
1068
+ label: "<edit settings>",
1069
+ isAction: true,
1070
+ action: bootstrapConfig
1071
+ }
1072
+ ], [autoApproveCodeInterpreter, autoApprovePatches, autoApproveShell, monitorRateLimits]);
1073
+ useInput2((_input, key) => {
1074
+ if (focusedArea !== "status") {
1075
+ return;
1076
+ }
1077
+ if (key.upArrow) {
1078
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : selectableOptions.length - 1);
1079
+ }
1080
+ if (key.downArrow) {
1081
+ setSelectedIndex((prev) => prev < selectableOptions.length - 1 ? prev + 1 : 0);
1082
+ }
1083
+ if (key.return || _input === " ") {
1084
+ const selected = selectableOptions[selectedIndex];
1085
+ if (!selected) {
1086
+ return;
1087
+ }
1088
+ if ("isAction" in selected && selected.isAction) {
1089
+ selected.action();
1090
+ } else if ("setter" in selected) {
1091
+ const newValue = !selected.value;
1092
+ selected.setter(newValue);
1093
+ updateEnv(selected.envKey, newValue ? "1" : "0");
1094
+ emitToListeners("SettingsUpdated", void 0);
1095
+ }
1096
+ }
1097
+ });
1098
+ const displayPath = useMemo7(() => {
1099
+ if (!sessionPath) {
1100
+ return null;
1101
+ }
1102
+ const relative = path.relative(cwd, sessionPath);
1103
+ if (!relative.startsWith("..") && !path.isAbsolute(relative)) {
1104
+ return `./${relative}`;
1105
+ }
1106
+ return sessionPath;
1107
+ }, [cwd, sessionPath]);
1108
+ const marginBottom = isDense ? 0 : 1;
1109
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, paddingLeft: 1, children: [
1110
+ /* @__PURE__ */ jsx12(Box7, { marginBottom, children: /* @__PURE__ */ jsx12(Text7, { bold: true, underline: true, color: "cyan", children: "Configuration" }) }),
1111
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
1112
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "Model:" }) }),
1113
+ /* @__PURE__ */ jsx12(Text7, { children: model })
1114
+ ] }),
1115
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
1116
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "Compaction Model:" }) }),
1117
+ /* @__PURE__ */ jsx12(Text7, { children: compactionModel })
1118
+ ] }),
1119
+ displayPath && /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
1120
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "Session Path:" }) }),
1121
+ /* @__PURE__ */ jsx12(Text7, { children: displayPath })
1122
+ ] }),
1123
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
1124
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "Flex Tier:" }) }),
1125
+ /* @__PURE__ */ jsx12(Text7, { children: useFlexTier ? "Yes" : "No" })
1126
+ ] }),
1127
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
1128
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "Current Turn:" }) }),
1129
+ /* @__PURE__ */ jsxs7(Text7, { children: [
1130
+ currentTurn,
1131
+ " / ",
1132
+ maxTurns
1133
+ ] })
1134
+ ] }),
1135
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
1136
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "Rate Limit @:" }) }),
1137
+ /* @__PURE__ */ jsxs7(Text7, { children: [
1138
+ initialRateLimitThreshold,
1139
+ "%"
1140
+ ] })
1141
+ ] }),
1142
+ rateLimitStatus && /* @__PURE__ */ jsxs7(Box7, { marginBottom, flexDirection: "column", children: [
1143
+ rateLimitStatus.retryAfter !== null && /* @__PURE__ */ jsxs7(Box7, { children: [
1144
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { color: "red", children: "Retry After:" }) }),
1145
+ /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
1146
+ rateLimitStatus.retryAfter,
1147
+ "s"
1148
+ ] })
1149
+ ] }),
1150
+ (rateLimitStatus.limitRequests !== null || rateLimitStatus.remainingRequests !== null) && /* @__PURE__ */ jsxs7(Box7, { children: [
1151
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "RPM Limit:" }) }),
1152
+ /* @__PURE__ */ jsxs7(Text7, { children: [
1153
+ rateLimitStatus.remainingRequests ?? "?",
1154
+ " / ",
1155
+ rateLimitStatus.limitRequests ?? "?",
1156
+ " (Reset:",
1157
+ " ",
1158
+ rateLimitStatus.resetRequests ?? "?",
1159
+ ")"
1160
+ ] })
1161
+ ] }),
1162
+ rateLimitStatus.remainingTokens !== null && /* @__PURE__ */ jsxs7(Box7, { children: [
1163
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "TPM Limit:" }) }),
1164
+ /* @__PURE__ */ jsxs7(Text7, { children: [
1165
+ rateLimitStatus.remainingTokens ?? "?",
1166
+ " / ",
1167
+ rateLimitStatus.limitTokens ?? "?",
1168
+ " (Reset:",
1169
+ " ",
1170
+ rateLimitStatus.resetTokens ?? "?",
1171
+ ")"
1172
+ ] })
1173
+ ] })
1174
+ ] }),
1175
+ maxCost !== null && /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
1176
+ /* @__PURE__ */ jsx12(Box7, { width: 20, children: /* @__PURE__ */ jsx12(Text7, { children: "Max Cost:" }) }),
1177
+ /* @__PURE__ */ jsxs7(Text7, { children: [
1178
+ "$",
1179
+ maxCost.toFixed(2)
1180
+ ] })
1181
+ ] }),
1182
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
1183
+ /* @__PURE__ */ jsx12(Text7, { bold: true, children: "Auto-approvals (up/down & space to toggle):" }),
1184
+ selectableOptions.map((option, index) => {
1185
+ const isSelected = index === selectedIndex && focusedArea === "status";
1186
+ return /* @__PURE__ */ jsx12(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : "white", children: [
1187
+ isSelected ? "> " : " ",
1188
+ option.label,
1189
+ "isAction" in option ? "" : `: ${option.value ? "ON" : "OFF"}`
1190
+ ] }) }, option.label);
1191
+ })
1192
+ ] })
1193
+ ] });
1194
+ }
1195
+
1196
+ // ink/components/UserInput.tsx
1197
+ import { Box as Box9, Text as Text9 } from "ink";
1198
+ import { exec } from "child_process";
1199
+ import { promisify } from "util";
1200
+ import { useCallback as useCallback3, useState as useState11 } from "react";
1201
+
1202
+ // ink/components/BlinkingTextInput.tsx
1203
+ import chalk from "chalk";
1204
+ import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
1205
+ import { useEffect as useEffect5, useMemo as useMemo8, useReducer, useState as useState10 } from "react";
1206
+ import { jsx as jsx13 } from "react/jsx-runtime";
1207
+ var reducer = (state, action) => {
1208
+ switch (action.type) {
1209
+ case "move-cursor-left":
1210
+ return {
1211
+ ...state,
1212
+ cursorOffset: Math.max(0, state.cursorOffset - 1)
1213
+ };
1214
+ case "move-cursor-right":
1215
+ return {
1216
+ ...state,
1217
+ cursorOffset: Math.min(state.value.length, state.cursorOffset + 1)
1218
+ };
1219
+ case "insert":
1220
+ return {
1221
+ ...state,
1222
+ previousValue: state.value,
1223
+ value: state.value.slice(0, state.cursorOffset) + action.text + state.value.slice(state.cursorOffset),
1224
+ cursorOffset: state.cursorOffset + action.text.length
1225
+ };
1226
+ case "newline":
1227
+ return {
1228
+ ...state,
1229
+ previousValue: state.value,
1230
+ value: state.value.slice(0, state.cursorOffset) + "\n" + state.value.slice(state.cursorOffset),
1231
+ cursorOffset: state.cursorOffset + 1
1232
+ };
1233
+ case "delete": {
1234
+ const newCursorOffset = Math.max(0, state.cursorOffset - 1);
1235
+ return {
1236
+ ...state,
1237
+ previousValue: state.value,
1238
+ value: state.value.slice(0, newCursorOffset) + state.value.slice(state.cursorOffset),
1239
+ cursorOffset: newCursorOffset
1240
+ };
1241
+ }
1242
+ default:
1243
+ return state;
1244
+ }
1245
+ };
1246
+ function BlinkingTextInput({
1247
+ isDisabled = false,
1248
+ defaultValue = "",
1249
+ placeholder = "",
1250
+ suggestions = [],
1251
+ onChange,
1252
+ onSubmit
1253
+ }) {
1254
+ const [state, dispatch] = useReducer(reducer, {
1255
+ value: defaultValue,
1256
+ previousValue: defaultValue,
1257
+ cursorOffset: defaultValue.length
1258
+ });
1259
+ const [isCursorVisible, setIsCursorVisible] = useState10(true);
1260
+ useEffect5(() => {
1261
+ if (isDisabled) {
1262
+ setIsCursorVisible(false);
1263
+ return;
1264
+ }
1265
+ const interval = setInterval(() => {
1266
+ setIsCursorVisible((prev) => !prev);
1267
+ }, 500);
1268
+ return () => clearInterval(interval);
1269
+ }, [isDisabled]);
1270
+ useEffect5(() => {
1271
+ if (state.value !== state.previousValue) {
1272
+ onChange?.(state.value);
1273
+ }
1274
+ }, [state.value, state.previousValue, onChange]);
1275
+ const suggestion = useMemo8(() => {
1276
+ if (state.value.length === 0) {
1277
+ return "";
1278
+ }
1279
+ return suggestions.find((s) => s.startsWith(state.value))?.slice(state.value.length) || "";
1280
+ }, [state.value, suggestions]);
1281
+ useInput3(
1282
+ (input, key) => {
1283
+ if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
1284
+ return;
1285
+ }
1286
+ setIsCursorVisible(true);
1287
+ if (key.return) {
1288
+ if (key.meta) {
1289
+ dispatch({ type: "newline" });
1290
+ return;
1291
+ }
1292
+ if (suggestion) {
1293
+ onSubmit?.(state.value + suggestion);
1294
+ } else {
1295
+ onSubmit?.(state.value);
1296
+ }
1297
+ return;
1298
+ }
1299
+ if (key.leftArrow) {
1300
+ dispatch({ type: "move-cursor-left" });
1301
+ } else if (key.rightArrow) {
1302
+ dispatch({ type: "move-cursor-right" });
1303
+ } else if (key.backspace || key.delete) {
1304
+ dispatch({ type: "delete" });
1305
+ } else if (input) {
1306
+ dispatch({ type: "insert", text: input });
1307
+ }
1308
+ },
1309
+ { isActive: !isDisabled }
1310
+ );
1311
+ const renderedValue = useMemo8(() => {
1312
+ if (isDisabled) {
1313
+ const lines2 = (state.value || (placeholder ? chalk.dim(placeholder) : "")).split("\n");
1314
+ return /* @__PURE__ */ jsx13(Box8, { flexDirection: "column", children: lines2.map((line, i) => /* @__PURE__ */ jsx13(Box8, { children: /* @__PURE__ */ jsx13(Text8, { children: line }) }, i)) });
1315
+ }
1316
+ if (state.value.length === 0) {
1317
+ let displayContent = "";
1318
+ if (placeholder && placeholder.length > 0) {
1319
+ displayContent = (isCursorVisible ? chalk.inverse(placeholder[0]) : placeholder[0]) + chalk.dim(placeholder.slice(1));
1320
+ } else {
1321
+ displayContent = isCursorVisible ? chalk.inverse(" ") : " ";
1322
+ }
1323
+ return /* @__PURE__ */ jsx13(Box8, { flexGrow: 1, minWidth: 1, children: /* @__PURE__ */ jsx13(Text8, { wrap: "end", children: displayContent }) });
1324
+ }
1325
+ const lines = state.value.split("\n");
1326
+ const result = [];
1327
+ let totalOffset = 0;
1328
+ lines.forEach((line, lineIndex) => {
1329
+ let lineContent = "";
1330
+ for (let i = 0; i < line.length; i++) {
1331
+ const char = line[i];
1332
+ if (totalOffset === state.cursorOffset) {
1333
+ lineContent += isCursorVisible ? chalk.inverse(char) : char;
1334
+ } else {
1335
+ lineContent += char;
1336
+ }
1337
+ totalOffset++;
1338
+ }
1339
+ if (totalOffset === state.cursorOffset) {
1340
+ if (lineIndex === lines.length - 1 && suggestion) {
1341
+ lineContent += (isCursorVisible ? chalk.inverse(suggestion[0]) : suggestion[0]) + chalk.dim(suggestion.slice(1));
1342
+ } else {
1343
+ lineContent += isCursorVisible ? chalk.inverse(" ") : " ";
1344
+ }
1345
+ } else if (lineIndex === lines.length - 1 && suggestion) {
1346
+ lineContent += chalk.dim(suggestion);
1347
+ }
1348
+ result.push(
1349
+ /* @__PURE__ */ jsx13(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx13(Text8, { wrap: "end", children: lineContent }) }, lineIndex)
1350
+ );
1351
+ if (lineIndex < lines.length - 1) {
1352
+ totalOffset++;
1353
+ }
1354
+ });
1355
+ return /* @__PURE__ */ jsx13(Box8, { flexDirection: "column", flexGrow: 1, children: result });
1356
+ }, [state.value, state.cursorOffset, suggestion, isCursorVisible, isDisabled, placeholder]);
1357
+ return /* @__PURE__ */ jsx13(Box8, { flexGrow: 1, minWidth: 1, children: renderedValue });
1358
+ }
1359
+
1360
+ // ink/components/UserInput.tsx
1361
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1362
+ var execAsync = promisify(exec);
1363
+ var modeSuggestion = {
1364
+ approved: ["/clear", "/skills", "/exit"],
1365
+ approving: ["yes", "approve", "no", "deny"],
1366
+ denied: [],
1367
+ waiting: ["/clear", "/skills", "/exit"]
1368
+ };
1369
+ function UserInput() {
1370
+ const { userInputMode, focusedArea } = useChat();
1371
+ const [resetKey, setResetKey] = useState11(0);
1372
+ const [, setBlankLines] = useState11(0);
1373
+ useListener("ClearUserInput", () => {
1374
+ setResetKey((prev) => prev + 1);
1375
+ }, []);
1376
+ const borderColor = focusedArea === "input" ? "cyan" : "gray";
1377
+ const placeholder = calculatePlaceholder(userInputMode);
1378
+ const onSubmitResetKey = useCallback3(async (value) => {
1379
+ if (value.length) {
1380
+ const trimmedValue = value.trim();
1381
+ if (trimmedValue === "/clear") {
1382
+ setResetKey((prev) => prev + 1);
1383
+ emitToListeners("ClearChatHistory", void 0);
1384
+ return;
1385
+ }
1386
+ if (trimmedValue === "/exit" || trimmedValue === "exit") {
1387
+ await handleExit();
1388
+ return;
1389
+ }
1390
+ if (trimmedValue === "/skills") {
1391
+ setResetKey((prev) => prev + 1);
1392
+ emitToListeners("PushNewMessages", [{
1393
+ type: "user",
1394
+ text: "/skills",
1395
+ version: 1
1396
+ }, {
1397
+ type: "agent",
1398
+ text: "Installing skills...",
1399
+ version: 1
1400
+ }]);
1401
+ try {
1402
+ const { stdout, stderr } = await execAsync("npx skills add harperfast/skills");
1403
+ emitToListeners(
1404
+ "UpdateLastMessageText",
1405
+ `
1406
+
1407
+ Skills installation result:
1408
+ ${stdout}${stderr ? `
1409
+ Errors:
1410
+ ${stderr}` : ""}`
1411
+ );
1412
+ } catch (error) {
1413
+ emitToListeners("UpdateLastMessageText", `
1414
+
1415
+ Failed to install skills: ${error.message}`);
1416
+ }
1417
+ return;
1418
+ }
1419
+ setResetKey((prev) => prev + 1);
1420
+ emitToListeners("PushNewMessages", [{ type: "user", text: trimmedValue, version: 1 }]);
1421
+ setBlankLines(0);
1422
+ } else {
1423
+ setBlankLines((value2) => {
1424
+ value2 += 1;
1425
+ if (value2 === 2) {
1426
+ void handleExit();
1427
+ }
1428
+ return value2;
1429
+ });
1430
+ }
1431
+ }, []);
1432
+ return /* @__PURE__ */ jsxs8(
1433
+ Box9,
1434
+ {
1435
+ minHeight: footerHeight,
1436
+ borderStyle: "bold",
1437
+ borderTop: false,
1438
+ borderColor,
1439
+ flexDirection: "row",
1440
+ alignItems: "flex-start",
1441
+ gap: 1,
1442
+ children: [
1443
+ /* @__PURE__ */ jsx14(Box9, { marginLeft: 1, height: 1, children: /* @__PURE__ */ jsx14(Text9, { bold: true, color: borderColor, children: "\u276F" }) }),
1444
+ /* @__PURE__ */ jsx14(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx14(
1445
+ BlinkingTextInput,
1446
+ {
1447
+ placeholder,
1448
+ onSubmit: onSubmitResetKey,
1449
+ suggestions: modeSuggestion[userInputMode],
1450
+ isDisabled: focusedArea !== "input"
1451
+ },
1452
+ resetKey
1453
+ ) })
1454
+ ]
1455
+ }
1456
+ );
1457
+ }
1458
+ function calculatePlaceholder(mode) {
1459
+ const prefix = " ";
1460
+ switch (mode) {
1461
+ case "denied":
1462
+ return prefix + "OK! Let's find another way.";
1463
+ case "approving":
1464
+ return prefix + "Do you approve? yes / no";
1465
+ default:
1466
+ case "approved":
1467
+ case "waiting":
1468
+ return "";
1469
+ }
1470
+ }
1471
+
1472
+ // ink/components/ChatContent.tsx
1473
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1474
+ function ChatContent() {
1475
+ const { messages, isThinking, isCompacting, pullingState, focusedArea, setFocusedArea } = useChat();
1476
+ const { payload } = useApproval();
1477
+ const size = useTerminalSize();
1478
+ useMessageListener();
1479
+ const [activeTab, setActiveTab] = useState12("settings");
1480
+ const [userHasSwitchedTab, setUserHasSwitchedTab] = useState12(false);
1481
+ const { planDescription, planItems } = usePlan();
1482
+ useEffect6(() => {
1483
+ if (!userHasSwitchedTab && activeTab === "settings") {
1484
+ const hasPlan = planDescription.trim().length > 0 || planItems.length > 0;
1485
+ if (hasPlan) {
1486
+ setActiveTab("planDescription");
1487
+ }
1488
+ }
1489
+ }, [planDescription, planItems, userHasSwitchedTab, activeTab]);
1490
+ const [selectedIndex, setSelectedIndex] = useState12(0);
1491
+ const wrapCache = useRef2(
1492
+ /* @__PURE__ */ new Map()
1493
+ );
1494
+ useInput4((input, key) => {
1495
+ if (key.tab) {
1496
+ const focusOrder = ["input", "timeline", "status"];
1497
+ const currentIndex = focusOrder.indexOf(focusedArea);
1498
+ if (key.shift || input === "\x1B[Z") {
1499
+ const nextIndex = (currentIndex - 1 + focusOrder.length) % focusOrder.length;
1500
+ setFocusedArea(focusOrder[nextIndex]);
1501
+ } else {
1502
+ const nextIndex = (currentIndex + 1) % focusOrder.length;
1503
+ setFocusedArea(focusOrder[nextIndex]);
1504
+ }
1505
+ return;
1506
+ }
1507
+ if (focusedArea === "timeline") {
1508
+ if (key.upArrow) {
1509
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
1510
+ }
1511
+ if (key.downArrow) {
1512
+ setSelectedIndex((prev) => Math.min(Math.max(0, lineItems.length - 1), prev + 1));
1513
+ }
1514
+ if (key.return) {
1515
+ const selected = lineItems[selectedIndex];
1516
+ if (selected && selected.type === "tool" && (selected.toolName === "apply_patch" || selected.toolName === "code_interpreter" || selected.toolName === "shell")) {
1517
+ const msg = messages.find((m) => m.id === selected.messageId);
1518
+ if (msg && msg.callId) {
1519
+ emitToListeners("OpenApprovalViewer", {
1520
+ type: selected.toolName,
1521
+ mode: "info",
1522
+ callId: msg.callId
1523
+ });
1524
+ }
1525
+ }
1526
+ }
1527
+ }
1528
+ if (focusedArea === "status") {
1529
+ if (key.leftArrow || key.rightArrow) {
1530
+ const tabNames = ["settings", "planDescription", "actions"];
1531
+ const currentIndex = tabNames.indexOf(activeTab);
1532
+ if (key.leftArrow) {
1533
+ const nextIndex = (currentIndex - 1 + tabNames.length) % tabNames.length;
1534
+ const newTab = tabNames[nextIndex];
1535
+ setActiveTab(newTab);
1536
+ setUserHasSwitchedTab(true);
1537
+ } else {
1538
+ const nextIndex = (currentIndex + 1) % tabNames.length;
1539
+ const newTab = tabNames[nextIndex];
1540
+ setActiveTab(newTab);
1541
+ setUserHasSwitchedTab(true);
1542
+ }
1543
+ }
1544
+ }
1545
+ if (key.ctrl && input === "x") {
1546
+ void handleExit();
1547
+ }
1548
+ if (key.escape && isThinking && focusedArea === "input" && !payload) {
1549
+ emitToListeners("InterruptThought", void 0);
1550
+ }
1551
+ });
1552
+ const contentHeight = size.rows - footerHeight;
1553
+ const timelineWidth = Math.floor(size.columns * 0.65);
1554
+ const statusWidth = size.columns - timelineWidth;
1555
+ const labelWidthFor = useCallback4((type) => {
1556
+ if (type === "agent") {
1557
+ return 7;
1558
+ }
1559
+ if (type === "prompt") {
1560
+ return 7;
1561
+ }
1562
+ if (type === "user") {
1563
+ return 6;
1564
+ }
1565
+ if (type === "interrupted") {
1566
+ return 0;
1567
+ }
1568
+ return 6;
1569
+ }, []);
1570
+ const availableTextWidth = timelineWidth - 4;
1571
+ const pullingHeight = pullingState ? 1 : 0;
1572
+ const lineItems = useMemo9(() => {
1573
+ const acc = [];
1574
+ for (const msg of messages) {
1575
+ const labelWidth = labelWidthFor(msg.type);
1576
+ const firstLineWidth = Math.max(1, availableTextWidth - labelWidth);
1577
+ let entry = wrapCache.current.get(msg.id);
1578
+ if (!entry || entry.version !== msg.version || entry.width !== firstLineWidth) {
1579
+ const textLines = wrapText(msg.text ?? "", firstLineWidth);
1580
+ const argsLines = msg.args ? wrapText(String(msg.args), firstLineWidth) : [];
1581
+ const msgLineItems = [];
1582
+ if (msg.type === "tool") {
1583
+ const toolName = msg.text ?? "";
1584
+ const toolArgs = msg.args ?? "";
1585
+ const toolNameWithSpace = `${toolName} `;
1586
+ const availableForArgs = firstLineWidth - toolNameWithSpace.length;
1587
+ let displayedArgs = toolArgs;
1588
+ if (availableForArgs < toolArgs.length) {
1589
+ if (availableForArgs > 3) {
1590
+ displayedArgs = toolArgs.slice(0, availableForArgs - 3) + "...";
1591
+ } else {
1592
+ displayedArgs = toolArgs.slice(0, Math.max(0, availableForArgs));
1593
+ }
1594
+ }
1595
+ msgLineItems.push({
1596
+ key: `${msg.id}:tool:0`,
1597
+ messageId: msg.id,
1598
+ type: msg.type,
1599
+ text: `${toolNameWithSpace}${displayedArgs}`,
1600
+ isFirstLine: true,
1601
+ toolName
1602
+ });
1603
+ } else {
1604
+ textLines.forEach((txt, idx) => {
1605
+ msgLineItems.push({
1606
+ key: `${msg.id}:text:${idx}`,
1607
+ messageId: msg.id,
1608
+ type: msg.type,
1609
+ text: txt,
1610
+ isFirstLine: idx === 0
1611
+ });
1612
+ });
1613
+ if (argsLines.length > 0) {
1614
+ argsLines.forEach((txt, idx) => {
1615
+ msgLineItems.push({
1616
+ key: `${msg.id}:args:${idx}`,
1617
+ messageId: msg.id,
1618
+ type: msg.type,
1619
+ text: txt,
1620
+ isFirstLine: idx === 0 && textLines.length === 0,
1621
+ isArgsLine: true
1622
+ });
1623
+ });
1624
+ }
1625
+ }
1626
+ entry = { version: msg.version, width: firstLineWidth, lineItems: msgLineItems };
1627
+ wrapCache.current.set(msg.id, entry);
1628
+ }
1629
+ acc.push(...entry.lineItems);
1630
+ }
1631
+ if (wrapCache.current.size > messages.length * 2) {
1632
+ const messageIds = new Set(messages.map((m) => m.id));
1633
+ for (const id of wrapCache.current.keys()) {
1634
+ if (!messageIds.has(id)) {
1635
+ wrapCache.current.delete(id);
1636
+ }
1637
+ }
1638
+ }
1639
+ return acc;
1640
+ }, [messages, availableTextWidth, labelWidthFor]);
1641
+ useEffect6(() => {
1642
+ if (lineItems.length > 0 && focusedArea !== "timeline") {
1643
+ setSelectedIndex(lineItems.length - 1);
1644
+ }
1645
+ }, [lineItems.length, focusedArea]);
1646
+ useEffect6(() => {
1647
+ if (lineItems.length > 0) {
1648
+ setSelectedIndex(lineItems.length - 1);
1649
+ }
1650
+ }, [size.rows, size.columns]);
1651
+ const tabs = useMemo9(() => [
1652
+ { name: "settings", label: "SETTINGS" },
1653
+ { name: "planDescription", label: "PLAN" },
1654
+ { name: "actions", label: "ACTIONS" }
1655
+ ], []);
1656
+ const timelineTitle = "TIMELINE:";
1657
+ const timelineHeaderWidth = timelineWidth - 1;
1658
+ const showSpinner = isCompacting || isThinking || Boolean(pullingState);
1659
+ const timelineDashes = timelineHeaderWidth - timelineTitle.length - (showSpinner ? 5 : 0);
1660
+ const tabsTotalWidth = tabs.reduce((acc, t) => acc + t.label.length + 2, 0) + (tabs.length - 1);
1661
+ const statusDashes = Math.max(0, statusWidth - tabsTotalWidth - 2);
1662
+ const timelineColor = focusedArea === "timeline" ? "cyan" : "gray";
1663
+ const statusColor = focusedArea === "status" ? "cyan" : "gray";
1664
+ const dividerColor = focusedArea === "timeline" || focusedArea === "status" ? "cyan" : "gray";
1665
+ const timelineBottomColor = focusedArea === "timeline" || focusedArea === "input" ? "cyan" : "gray";
1666
+ const statusBottomColor = focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
1667
+ const junctionLeftColor = focusedArea === "timeline" || focusedArea === "input" ? "cyan" : "gray";
1668
+ const junctionMiddleColor = focusedArea === "timeline" || focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
1669
+ const junctionRightColor = focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
1670
+ const timelinePipeFiller = useCallback4((count) => /* @__PURE__ */ jsx15(Box10, { flexDirection: "column", children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx15(Box10, { children: /* @__PURE__ */ jsx15(Text10, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
1671
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", height: size.rows, padding: 0, children: [
1672
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: 1, children: [
1673
+ /* @__PURE__ */ jsx15(Text10, { color: timelineColor, children: "\u256D" }),
1674
+ /* @__PURE__ */ jsx15(Text10, { bold: true, color: timelineColor, children: timelineTitle }),
1675
+ showSpinner && /* @__PURE__ */ jsx15(Box10, { paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx15(Spinner2, { type: "clock" }) }),
1676
+ /* @__PURE__ */ jsx15(Text10, { color: timelineColor, children: "\u2500".repeat(Math.max(0, timelineDashes)) }),
1677
+ /* @__PURE__ */ jsx15(Text10, { color: dividerColor, children: "\u252C" }),
1678
+ tabs.map((tab, i) => /* @__PURE__ */ jsxs9(React10.Fragment, { children: [
1679
+ /* @__PURE__ */ jsx15(
1680
+ Text10,
1681
+ {
1682
+ color: activeTab === tab.name ? "black" : statusColor,
1683
+ backgroundColor: activeTab === tab.name ? statusColor : "",
1684
+ bold: activeTab === tab.name,
1685
+ children: ` ${tab.label} `
1686
+ }
1687
+ ),
1688
+ i < tabs.length - 1 && /* @__PURE__ */ jsx15(Text10, { color: statusColor, children: "|" })
1689
+ ] }, tab.name)),
1690
+ /* @__PURE__ */ jsxs9(Text10, { color: statusColor, children: [
1691
+ "\u2500".repeat(statusDashes),
1692
+ "\u256E"
1693
+ ] })
1694
+ ] }),
1695
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: contentHeight - 2 - pullingHeight, children: [
1696
+ /* @__PURE__ */ jsx15(
1697
+ Box10,
1698
+ {
1699
+ flexDirection: "column",
1700
+ width: timelineWidth,
1701
+ paddingLeft: 0,
1702
+ paddingRight: 1,
1703
+ children: /* @__PURE__ */ jsx15(Box10, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx15(
1704
+ VirtualList,
1705
+ {
1706
+ items: lineItems,
1707
+ itemHeight: 1,
1708
+ height: contentHeight - 2 - pullingHeight,
1709
+ selectedIndex,
1710
+ renderOverflowTop: useCallback4((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
1711
+ /* @__PURE__ */ jsx15(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
1712
+ count > 0 && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1713
+ " ",
1714
+ "\u25B2 ",
1715
+ count,
1716
+ " more"
1717
+ ] })
1718
+ ] }), []),
1719
+ renderOverflowBottom: useCallback4((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
1720
+ /* @__PURE__ */ jsx15(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
1721
+ count > 0 && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1722
+ " ",
1723
+ "\u25BC ",
1724
+ count,
1725
+ " more"
1726
+ ] })
1727
+ ] }), []),
1728
+ renderFiller: timelinePipeFiller,
1729
+ keyExtractor: (it) => it.key,
1730
+ renderItem: useCallback4(
1731
+ ({ item, isSelected }) => /* @__PURE__ */ jsx15(
1732
+ MessageLineItem,
1733
+ {
1734
+ item,
1735
+ isSelected,
1736
+ isFocused: focusedArea === "timeline",
1737
+ indent: item.isFirstLine ? 0 : labelWidthFor(item.type)
1738
+ }
1739
+ ),
1740
+ [labelWidthFor, focusedArea]
1741
+ )
1742
+ }
1743
+ ) })
1744
+ }
1745
+ ),
1746
+ activeTab !== "shell" && activeTab !== "actions" && /* @__PURE__ */ jsx15(
1747
+ Box10,
1748
+ {
1749
+ flexDirection: "column",
1750
+ width: 1,
1751
+ borderStyle: "round",
1752
+ borderColor: dividerColor,
1753
+ borderTop: false,
1754
+ borderBottom: false,
1755
+ borderRight: false
1756
+ }
1757
+ ),
1758
+ /* @__PURE__ */ jsx15(
1759
+ Box10,
1760
+ {
1761
+ flexDirection: "column",
1762
+ width: activeTab === "shell" || activeTab === "actions" ? statusWidth : statusWidth - 1,
1763
+ borderStyle: "round",
1764
+ borderColor: statusColor,
1765
+ borderTop: false,
1766
+ borderBottom: false,
1767
+ borderLeft: false,
1768
+ paddingLeft: activeTab === "shell" || activeTab === "actions" ? 0 : 1,
1769
+ paddingRight: 1,
1770
+ children: /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", flexGrow: 1, marginTop: 0, children: [
1771
+ activeTab === "settings" && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
1772
+ /* @__PURE__ */ jsx15(CostView, { isDense: true }),
1773
+ /* @__PURE__ */ jsx15(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx15(SettingsView, { isDense: true }) })
1774
+ ] }),
1775
+ activeTab === "planDescription" && /* @__PURE__ */ jsx15(PlanView, {}),
1776
+ activeTab === "actions" && /* @__PURE__ */ jsx15(
1777
+ ActionsView,
1778
+ {
1779
+ height: contentHeight - 2 - pullingHeight,
1780
+ isFocused: focusedArea === "status"
1781
+ }
1782
+ )
1783
+ ] })
1784
+ }
1785
+ )
1786
+ ] }),
1787
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
1788
+ pullingState && /* @__PURE__ */ jsx15(Box10, { paddingLeft: 2, paddingRight: 2, marginBottom: 0, children: /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
1789
+ `\uF019 Downloading `,
1790
+ /* @__PURE__ */ jsx15(Text10, { bold: true, children: pullingState.modelName }),
1791
+ ` from Ollama... `,
1792
+ /* @__PURE__ */ jsx15(Text10, { dimColor: true, children: pullingState.status === "pulling manifest" ? "initializing" : pullingState.status }),
1793
+ pullingState.total > 0 && /* @__PURE__ */ jsxs9(Text10, { children: [
1794
+ ` [${"=".repeat(Math.floor(pullingState.completed / pullingState.total * 20))}${" ".repeat(20 - Math.floor(pullingState.completed / pullingState.total * 20))}] `,
1795
+ Math.round(pullingState.completed / pullingState.total * 100),
1796
+ "%"
1797
+ ] })
1798
+ ] }) }),
1799
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: 1, children: [
1800
+ /* @__PURE__ */ jsx15(Text10, { color: junctionLeftColor, children: "\u2522" }),
1801
+ /* @__PURE__ */ jsx15(Text10, { color: timelineBottomColor, children: "\u2501".repeat(timelineWidth - 1) }),
1802
+ /* @__PURE__ */ jsx15(Text10, { color: junctionMiddleColor, children: "\u2537" }),
1803
+ /* @__PURE__ */ jsx15(Text10, { color: statusBottomColor, children: "\u2501".repeat(Math.max(0, statusWidth - 2)) }),
1804
+ /* @__PURE__ */ jsx15(Text10, { color: junctionRightColor, children: "\u252A" })
1805
+ ] })
1806
+ ] }),
1807
+ /* @__PURE__ */ jsx15(UserInput, {})
1808
+ ] });
1809
+ }
1810
+
1811
+ // ink/components/DiffApprovalView.tsx
1812
+ import { Box as Box11, Text as Text11, useInput as useInput5 } from "ink";
1813
+ import { useMemo as useMemo10, useState as useState13 } from "react";
1814
+ import { Fragment as Fragment2, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1815
+ function DiffApprovalView() {
1816
+ const { payload } = useApproval();
1817
+ const size = useTerminalSize();
1818
+ const [scrollIndex, setScrollIndex] = useState13(0);
1819
+ const diffLines = useMemo10(() => {
1820
+ if (!payload?.diff) {
1821
+ return [];
1822
+ }
1823
+ return payload.diff.split("\n");
1824
+ }, [payload?.diff]);
1825
+ const wrappedLines = useMemo10(() => {
1826
+ const result = [];
1827
+ if (!payload) {
1828
+ return result;
1829
+ }
1830
+ if (payload.type === "delete_file") {
1831
+ result.push({ text: "Delete file: " + payload.path, color: "red" });
1832
+ return result;
1833
+ }
1834
+ if (payload.type === "overwrite_file") {
1835
+ for (const line of diffLines) {
1836
+ let color;
1837
+ if (line.startsWith("+")) {
1838
+ color = "green";
1839
+ } else if (line.startsWith("-")) {
1840
+ color = "red";
1841
+ }
1842
+ const wrapped = wrapText(line, size.columns - 4);
1843
+ for (const w of wrapped) {
1844
+ result.push({ text: w, color });
1845
+ }
1846
+ }
1847
+ return result;
1848
+ }
1849
+ if (payload.type === "code_interpreter" && payload.code) {
1850
+ const lines = payload.code.split("\n");
1851
+ for (const line of lines) {
1852
+ const wrapped = wrapText(line, size.columns - 4);
1853
+ for (const w of wrapped) {
1854
+ result.push({ text: w });
1855
+ }
1856
+ }
1857
+ return result;
1858
+ }
1859
+ if (payload.type === "shell" && payload.commands) {
1860
+ for (const cmd of payload.commands) {
1861
+ const wrapped = wrapText(`$ ${cmd}`, size.columns - 4);
1862
+ for (const w of wrapped) {
1863
+ result.push({ text: w, color: "yellow" });
1864
+ }
1865
+ }
1866
+ return result;
1867
+ }
1868
+ for (const line of diffLines) {
1869
+ let color;
1870
+ if (line.startsWith("+")) {
1871
+ color = "green";
1872
+ } else if (line.startsWith("-")) {
1873
+ color = "red";
1874
+ } else if (line.startsWith("@@")) {
1875
+ color = "cyan";
1876
+ }
1877
+ const wrapped = wrapText(line, size.columns - 4);
1878
+ for (const w of wrapped) {
1879
+ result.push({ text: w, color });
1880
+ }
1881
+ }
1882
+ return result;
1883
+ }, [diffLines, size.columns, payload]);
1884
+ const visibleHeight = size.rows - 6;
1885
+ useInput5((input, key) => {
1886
+ if (!payload) {
1887
+ return;
1888
+ }
1889
+ if (key.escape) {
1890
+ if (payload.mode === "ask") {
1891
+ emitToListeners("DenyCurrentApproval", void 0);
1892
+ }
1893
+ emitToListeners("CloseApprovalViewer", void 0);
1894
+ return;
1895
+ }
1896
+ if (key.return) {
1897
+ if (payload.mode === "ask") {
1898
+ const now = Date.now();
1899
+ if (payload.openedAt && now - payload.openedAt > 1e3) {
1900
+ emitToListeners("ApproveCurrentApproval", void 0);
1901
+ emitToListeners("CloseApprovalViewer", void 0);
1902
+ emitToListeners("ClearUserInput", void 0);
1903
+ }
1904
+ } else {
1905
+ emitToListeners("CloseApprovalViewer", void 0);
1906
+ }
1907
+ return;
1908
+ }
1909
+ if (payload.mode === "ask") {
1910
+ const now = Date.now();
1911
+ if (payload.openedAt && now - payload.openedAt > 1e3) {
1912
+ if (input === "y") {
1913
+ emitToListeners("ApproveCurrentApproval", void 0);
1914
+ emitToListeners("CloseApprovalViewer", void 0);
1915
+ emitToListeners("ClearUserInput", void 0);
1916
+ } else if (input === "n") {
1917
+ emitToListeners("DenyCurrentApproval", void 0);
1918
+ emitToListeners("CloseApprovalViewer", void 0);
1919
+ emitToListeners("ClearUserInput", void 0);
1920
+ }
1921
+ }
1922
+ }
1923
+ if (key.upArrow) {
1924
+ setScrollIndex((prev) => Math.max(0, prev - 1));
1925
+ }
1926
+ if (key.downArrow) {
1927
+ setScrollIndex((prev) => Math.min(Math.max(0, wrappedLines.length - visibleHeight), prev + 1));
1928
+ }
1929
+ if (key.pageUp) {
1930
+ setScrollIndex((prev) => Math.max(0, prev - visibleHeight));
1931
+ }
1932
+ if (key.pageDown) {
1933
+ setScrollIndex((prev) => Math.min(Math.max(0, wrappedLines.length - visibleHeight), prev + visibleHeight));
1934
+ }
1935
+ });
1936
+ if (!payload) {
1937
+ return null;
1938
+ }
1939
+ const canRespond = payload.mode === "ask" && payload.openedAt && Date.now() - payload.openedAt > 1e3;
1940
+ return /* @__PURE__ */ jsxs10(
1941
+ Box11,
1942
+ {
1943
+ position: "absolute",
1944
+ flexDirection: "column",
1945
+ width: size.columns,
1946
+ height: size.rows,
1947
+ backgroundColor: "black",
1948
+ borderStyle: "double",
1949
+ borderColor: "cyan",
1950
+ children: [
1951
+ /* @__PURE__ */ jsxs10(
1952
+ Box11,
1953
+ {
1954
+ paddingX: 1,
1955
+ borderStyle: "single",
1956
+ borderBottomColor: "gray",
1957
+ borderTop: false,
1958
+ borderLeft: false,
1959
+ borderRight: false,
1960
+ children: [
1961
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, color: "cyan", children: [
1962
+ payload.mode === "ask" ? "APPROVE " : "VIEW ",
1963
+ payload.type.toUpperCase().replace("_", " ")
1964
+ ] }),
1965
+ /* @__PURE__ */ jsx16(Text11, { color: "gray", children: "\u2502" }),
1966
+ payload.path && /* @__PURE__ */ jsxs10(Fragment2, { children: [
1967
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
1968
+ payload.type,
1969
+ ":"
1970
+ ] }),
1971
+ /* @__PURE__ */ jsx16(Text11, { children: payload.path })
1972
+ ] })
1973
+ ]
1974
+ }
1975
+ ),
1976
+ /* @__PURE__ */ jsx16(Box11, { flexGrow: 1, flexDirection: "column", paddingX: 1, children: wrappedLines.length === 0 ? /* @__PURE__ */ jsx16(Text11, { italic: true, color: "gray", children: "No diff content." }) : wrappedLines.slice(scrollIndex, scrollIndex + visibleHeight).map((line, i) => /* @__PURE__ */ jsx16(Text11, { color: line.color || "white", children: line.text }, i)) }),
1977
+ /* @__PURE__ */ jsxs10(
1978
+ Box11,
1979
+ {
1980
+ paddingX: 1,
1981
+ borderStyle: "single",
1982
+ borderTopColor: "gray",
1983
+ borderBottom: false,
1984
+ borderLeft: false,
1985
+ borderRight: false,
1986
+ children: [
1987
+ payload.mode === "ask" ? /* @__PURE__ */ jsxs10(Box11, { flexGrow: 1, children: [
1988
+ /* @__PURE__ */ jsx16(Text11, { color: canRespond ? "green" : "gray", children: "[Enter/y] Approve" }),
1989
+ /* @__PURE__ */ jsx16(Text11, {}),
1990
+ /* @__PURE__ */ jsx16(Text11, { color: canRespond ? "red" : "gray", children: "[Esc/n] Deny" }),
1991
+ !canRespond && /* @__PURE__ */ jsx16(Text11, { color: "yellow", children: "(Wait 1s...)" })
1992
+ ] }) : /* @__PURE__ */ jsx16(Box11, { flexGrow: 1, children: /* @__PURE__ */ jsx16(Text11, { color: "cyan", children: "[Enter/Esc] Close" }) }),
1993
+ /* @__PURE__ */ jsxs10(Text11, { color: "gray", children: [
1994
+ "Line ",
1995
+ scrollIndex + 1,
1996
+ "/",
1997
+ wrappedLines.length
1998
+ ] })
1999
+ ]
2000
+ }
2001
+ )
2002
+ ]
2003
+ }
2004
+ );
2005
+ }
2006
+
2007
+ // ink/configurationWizard/ConfigurationWizard.tsx
2008
+ import { Box as Box18, useInput as useInput10 } from "ink";
2009
+ import { Step, Stepper } from "ink-stepper";
2010
+ import { useEffect as useEffect12, useState as useState15 } from "react";
2011
+
2012
+ // utils/files/getEnvVarForProvider.ts
2013
+ function getEnvVarForProvider(provider) {
2014
+ switch (provider) {
2015
+ case "Anthropic":
2016
+ return "ANTHROPIC_API_KEY";
2017
+ case "Google":
2018
+ return "GOOGLE_GENERATIVE_AI_API_KEY";
2019
+ case "OpenAI":
2020
+ return "OPENAI_API_KEY";
2021
+ case "Ollama":
2022
+ return "OLLAMA_BASE_URL";
2023
+ }
2024
+ }
2025
+
2026
+ // utils/files/updateEnvKeyForProvider.ts
2027
+ function updateEnvKeyForProvider(provider, key) {
2028
+ const envVar = getEnvVarForProvider(provider);
2029
+ return updateEnv(envVar, key);
2030
+ }
2031
+
2032
+ // ink/configurationWizard/ApiKeyStep.tsx
2033
+ import { PasswordInput } from "@inkjs/ui";
2034
+ import { Box as Box12, Text as Text12, useInput as useInput6 } from "ink";
2035
+ import { useStepperInput } from "ink-stepper";
2036
+ import { useEffect as useEffect7 } from "react";
2037
+ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
2038
+ function ApiKeyStep({ provider, onConfirm, onBack }) {
2039
+ const { disableNavigation, enableNavigation } = useStepperInput();
2040
+ useEffect7(() => {
2041
+ disableNavigation();
2042
+ return () => enableNavigation();
2043
+ }, [disableNavigation, enableNavigation]);
2044
+ useInput6((input, key) => {
2045
+ if (key.escape) {
2046
+ onBack();
2047
+ }
2048
+ });
2049
+ const instructions = {
2050
+ OpenAI: "Get your key at: https://platform.openai.com/api-keys",
2051
+ Anthropic: "Get your key at: https://console.anthropic.com/settings/keys",
2052
+ Google: "Get your key at: https://aistudio.google.com/app/apikey",
2053
+ Ollama: ""
2054
+ };
2055
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
2056
+ /* @__PURE__ */ jsxs11(Text12, { children: [
2057
+ "Can you provide us with your ",
2058
+ provider,
2059
+ " API key?"
2060
+ ] }),
2061
+ /* @__PURE__ */ jsx17(Text12, { dimColor: true, children: instructions[provider] }),
2062
+ /* @__PURE__ */ jsx17(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx17(
2063
+ PasswordInput,
2064
+ {
2065
+ onSubmit: (v) => {
2066
+ if (v === "exit") {
2067
+ emitToListeners("ExitUI", void 0);
2068
+ } else {
2069
+ onConfirm(v);
2070
+ }
2071
+ }
2072
+ }
2073
+ ) }),
2074
+ /* @__PURE__ */ jsx17(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text12, { dimColor: true, children: "Press ESC to go back" }) })
2075
+ ] });
2076
+ }
2077
+
2078
+ // ink/configurationWizard/ApiUrlStep.tsx
2079
+ import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
2080
+ import { useStepperInput as useStepperInput2 } from "ink-stepper";
2081
+ import { useEffect as useEffect8 } from "react";
2082
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2083
+ function ApiUrlStep({ provider, onConfirm, onBack }) {
2084
+ const { disableNavigation, enableNavigation } = useStepperInput2();
2085
+ useEffect8(() => {
2086
+ disableNavigation();
2087
+ return () => enableNavigation();
2088
+ }, [disableNavigation, enableNavigation]);
2089
+ useInput7((input, key) => {
2090
+ if (key.escape) {
2091
+ onBack();
2092
+ }
2093
+ });
2094
+ const defaultApi = {
2095
+ OpenAI: "",
2096
+ Anthropic: "",
2097
+ Google: "",
2098
+ Ollama: "http://localhost:11434/api"
2099
+ };
2100
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
2101
+ /* @__PURE__ */ jsxs12(Text13, { children: [
2102
+ "Where are you hosting ",
2103
+ provider,
2104
+ "?"
2105
+ ] }),
2106
+ /* @__PURE__ */ jsx18(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx18(
2107
+ BlinkingTextInput,
2108
+ {
2109
+ placeholder: defaultApi[provider] || "",
2110
+ onSubmit: (v) => {
2111
+ if (v === "exit") {
2112
+ emitToListeners("ExitUI", void 0);
2113
+ } else {
2114
+ onConfirm(v || defaultApi[provider] || "");
2115
+ }
2116
+ }
2117
+ }
2118
+ ) }),
2119
+ /* @__PURE__ */ jsx18(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text13, { dimColor: true, children: "Press ESC to go back" }) })
2120
+ ] });
2121
+ }
2122
+
2123
+ // ink/configurationWizard/EnvironmentSettingsStep.tsx
2124
+ import { MultiSelect } from "@inkjs/ui";
2125
+ import { Box as Box14, Text as Text14, useInput as useInput8 } from "ink";
2126
+ import { useStepperInput as useStepperInput3 } from "ink-stepper";
2127
+ import { useEffect as useEffect9 } from "react";
2128
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
2129
+ var SETTINGS = [
2130
+ {
2131
+ label: "Save Harper agent memory locally",
2132
+ value: "HARPER_AGENT_SESSION",
2133
+ defaultValue: "./harper-agent-memory.json"
2134
+ },
2135
+ {
2136
+ label: "Automatically approve code interpreter execution",
2137
+ value: "HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER",
2138
+ defaultValue: "1"
2139
+ },
2140
+ {
2141
+ label: "Automatically approve file patches",
2142
+ value: "HARPER_AGENT_AUTO_APPROVE_PATCHES",
2143
+ defaultValue: "1"
2144
+ },
2145
+ {
2146
+ label: "Automatically approve shell commands",
2147
+ value: "HARPER_AGENT_AUTO_APPROVE_SHELL",
2148
+ defaultValue: "1"
2149
+ },
2150
+ {
2151
+ label: "Use flex tier for lower costs when possible",
2152
+ value: "HARPER_AGENT_FLEX_TIER",
2153
+ defaultValue: "true"
2154
+ }
2155
+ ];
2156
+ function EnvironmentSettingsStep({ onConfirm, onBack }) {
2157
+ const { disableNavigation, enableNavigation } = useStepperInput3();
2158
+ useEffect9(() => {
2159
+ disableNavigation();
2160
+ return () => enableNavigation();
2161
+ }, [disableNavigation, enableNavigation]);
2162
+ useInput8((_input, key) => {
2163
+ if (key.escape) {
2164
+ onBack();
2165
+ }
2166
+ });
2167
+ const options = SETTINGS.map((s) => ({
2168
+ label: s.label,
2169
+ value: s.value
2170
+ }));
2171
+ const defaultValues = SETTINGS.map((s) => s.value);
2172
+ const handleSubmit = (values) => {
2173
+ for (const setting of SETTINGS) {
2174
+ if (values.includes(setting.value)) {
2175
+ updateEnv(setting.value, setting.defaultValue);
2176
+ }
2177
+ }
2178
+ onConfirm();
2179
+ };
2180
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
2181
+ /* @__PURE__ */ jsx19(Text14, { children: "Additional Settings (all enabled by default):" }),
2182
+ /* @__PURE__ */ jsx19(
2183
+ MultiSelect,
2184
+ {
2185
+ options,
2186
+ defaultValue: defaultValues,
2187
+ onSubmit: handleSubmit
2188
+ }
2189
+ ),
2190
+ /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
2191
+ "Press ",
2192
+ "<space>",
2193
+ " to toggle, ",
2194
+ "<enter>",
2195
+ " to confirm."
2196
+ ] })
2197
+ ] });
2198
+ }
2199
+
2200
+ // ink/configurationWizard/modelsByProvider.ts
2201
+ var modelsByProvider = {
2202
+ OpenAI: [defaultOpenAIModel, "gpt-5.0", defaultOpenAICompactionModel],
2203
+ Anthropic: [defaultAnthropicModel, "claude-4-5-sonnet-latest", defaultAnthropicCompactionModel],
2204
+ Google: [defaultGoogleModel, "gemini-3-flash", "gemini-2.5-flash", defaultGoogleCompactionModel],
2205
+ Ollama: [defaultOllamaModel, "ollama-qwen3.5:27b", defaultOllamaCompactionModel]
2206
+ };
2207
+ var compactorModelsByProvider = {
2208
+ OpenAI: modelsByProvider.OpenAI.slice().reverse(),
2209
+ Anthropic: modelsByProvider.Anthropic.slice().reverse(),
2210
+ Google: modelsByProvider.Google.slice().reverse(),
2211
+ Ollama: modelsByProvider.Ollama.slice().reverse()
2212
+ };
2213
+
2214
+ // ink/configurationWizard/ModelSelectionStep.tsx
2215
+ import { Select } from "@inkjs/ui";
2216
+ import { Box as Box15, Text as Text15, useInput as useInput9 } from "ink";
2217
+ import { useStepperInput as useStepperInput4 } from "ink-stepper";
2218
+ import { useEffect as useEffect10, useState as useState14 } from "react";
2219
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
2220
+ function ModelSelectionStep({
2221
+ title,
2222
+ models,
2223
+ onConfirm,
2224
+ onBack
2225
+ }) {
2226
+ const { disableNavigation, enableNavigation } = useStepperInput4();
2227
+ const [isCustom, setIsCustom] = useState14(false);
2228
+ useEffect10(() => {
2229
+ disableNavigation();
2230
+ return () => enableNavigation();
2231
+ }, [isCustom, disableNavigation, enableNavigation]);
2232
+ useInput9((input, key) => {
2233
+ if (key.escape) {
2234
+ if (isCustom) {
2235
+ setIsCustom(false);
2236
+ } else {
2237
+ onBack();
2238
+ }
2239
+ }
2240
+ });
2241
+ if (isCustom) {
2242
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
2243
+ /* @__PURE__ */ jsxs14(Text15, { children: [
2244
+ "Enter custom model name for: ",
2245
+ title
2246
+ ] }),
2247
+ /* @__PURE__ */ jsx20(
2248
+ BlinkingTextInput,
2249
+ {
2250
+ onSubmit: (v) => {
2251
+ if (v === "exit") {
2252
+ emitToListeners("ExitUI", void 0);
2253
+ } else {
2254
+ onConfirm(v);
2255
+ }
2256
+ }
2257
+ }
2258
+ ),
2259
+ /* @__PURE__ */ jsx20(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text15, { dimColor: true, children: "Press ESC to go back to list" }) })
2260
+ ] });
2261
+ }
2262
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
2263
+ /* @__PURE__ */ jsx20(Text15, { children: title }),
2264
+ /* @__PURE__ */ jsx20(
2265
+ Select,
2266
+ {
2267
+ options: [
2268
+ ...models.map((m) => ({ label: m, value: m })),
2269
+ { label: "Other...", value: "other" }
2270
+ ],
2271
+ onChange: (v) => {
2272
+ if (v === "other") {
2273
+ setIsCustom(true);
2274
+ } else {
2275
+ onConfirm(v);
2276
+ }
2277
+ }
2278
+ }
2279
+ ),
2280
+ /* @__PURE__ */ jsx20(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text15, { dimColor: true, children: "Press ESC to go back" }) })
2281
+ ] });
2282
+ }
2283
+
2284
+ // ink/configurationWizard/ProviderStep.tsx
2285
+ import { Select as Select2 } from "@inkjs/ui";
2286
+ import { Box as Box16, Text as Text16 } from "ink";
2287
+ import { useStepperInput as useStepperInput5 } from "ink-stepper";
2288
+ import { useEffect as useEffect11 } from "react";
2289
+
2290
+ // ink/configurationWizard/providers.ts
2291
+ var providers = [
2292
+ { label: "OpenAI", value: "OpenAI" },
2293
+ { label: "Anthropic", value: "Anthropic" },
2294
+ { label: "Google", value: "Google" },
2295
+ { label: "Ollama", value: "Ollama" }
2296
+ ];
2297
+
2298
+ // ink/configurationWizard/ProviderStep.tsx
2299
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
2300
+ function ProviderStep({ onConfirm }) {
2301
+ const { disableNavigation, enableNavigation } = useStepperInput5();
2302
+ useEffect11(() => {
2303
+ disableNavigation();
2304
+ return () => enableNavigation();
2305
+ }, [disableNavigation, enableNavigation]);
2306
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
2307
+ /* @__PURE__ */ jsx21(Text16, { children: "What model provider would you like to use today?" }),
2308
+ /* @__PURE__ */ jsx21(
2309
+ Select2,
2310
+ {
2311
+ options: providers,
2312
+ onChange: (v) => onConfirm(v)
2313
+ }
2314
+ )
2315
+ ] });
2316
+ }
2317
+
2318
+ // ink/configurationWizard/StepperProgress.tsx
2319
+ import { Box as Box17, Text as Text17 } from "ink";
2320
+ import { Fragment as Fragment3 } from "react";
2321
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
2322
+ var markers = {
2323
+ completed: " \u2713 ",
2324
+ current: " \u25CF ",
2325
+ pending: " \u25CB "
2326
+ };
2327
+ var SEGMENT_WIDTH = 12;
2328
+ function StepperProgress({ steps, currentStep }) {
2329
+ return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
2330
+ /* @__PURE__ */ jsx22(Box17, { children: steps.map((step) => {
2331
+ return /* @__PURE__ */ jsx22(Box17, { width: SEGMENT_WIDTH, justifyContent: "center", children: /* @__PURE__ */ jsx22(
2332
+ Text17,
2333
+ {
2334
+ color: step.completed ? "green" : step.current ? "cyan" : "gray",
2335
+ bold: step.current,
2336
+ dimColor: !step.completed && !step.current,
2337
+ children: step.name
2338
+ }
2339
+ ) }, step.name);
2340
+ }) }),
2341
+ /* @__PURE__ */ jsx22(Box17, { children: steps.map((step, idx) => {
2342
+ const marker = step.completed ? markers.completed : step.current ? markers.current : markers.pending;
2343
+ const beforeLineColor = step.completed || idx <= currentStep ? "green" : "gray";
2344
+ const afterLineColor = step.completed ? "green" : "gray";
2345
+ const markerColor = step.completed ? "green" : step.current ? "cyan" : "gray";
2346
+ return /* @__PURE__ */ jsxs16(Fragment3, { children: [
2347
+ /* @__PURE__ */ jsx22(Text17, { color: beforeLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 2) }),
2348
+ /* @__PURE__ */ jsx22(Text17, { color: markerColor, bold: step.current, children: marker }),
2349
+ /* @__PURE__ */ jsx22(Text17, { color: afterLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 1) })
2350
+ ] }, step.name);
2351
+ }) })
2352
+ ] });
2353
+ }
2354
+
2355
+ // ink/configurationWizard/ConfigurationWizard.tsx
2356
+ import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
2357
+ function ConfigurationWizard({ onComplete }) {
2358
+ const [provider, setProvider] = useState15("OpenAI");
2359
+ const [ollamaModels, setOllamaModels] = useState15([]);
2360
+ useEffect12(() => {
2361
+ if (provider === "Ollama") {
2362
+ fetchOllamaModels().then((models2) => {
2363
+ if (models2.length > 0) {
2364
+ setOllamaModels(models2);
2365
+ }
2366
+ });
2367
+ }
2368
+ }, [provider]);
2369
+ useInput10((input, key) => {
2370
+ if (key.ctrl && input === "x") {
2371
+ emitToListeners("ExitUI", void 0);
2372
+ }
2373
+ });
2374
+ const models = provider === "Ollama" && ollamaModels.length > 0 ? [.../* @__PURE__ */ new Set([...ollamaModels, ...modelsByProvider[provider]])] : modelsByProvider[provider];
2375
+ const compactorModels = provider === "Ollama" && ollamaModels.length > 0 ? [.../* @__PURE__ */ new Set([...ollamaModels, ...compactorModelsByProvider[provider]])] : compactorModelsByProvider[provider];
2376
+ return /* @__PURE__ */ jsx23(Box18, { flexDirection: "column", padding: 1, minHeight: 10, children: /* @__PURE__ */ jsxs17(
2377
+ Stepper,
2378
+ {
2379
+ onComplete,
2380
+ onCancel: curryEmitToListeners("ExitUI", void 0),
2381
+ keyboardNav: true,
2382
+ renderProgress: StepperProgress,
2383
+ children: [
2384
+ /* @__PURE__ */ jsx23(Step, { name: "AI Provider", children: ({ goNext }) => /* @__PURE__ */ jsx23(
2385
+ ProviderStep,
2386
+ {
2387
+ onConfirm: (p) => {
2388
+ setProvider(p);
2389
+ goNext();
2390
+ }
2391
+ }
2392
+ ) }),
2393
+ /* @__PURE__ */ jsx23(Step, { name: provider !== "Ollama" ? "API Key" : "API", children: ({ goNext, goBack }) => provider !== "Ollama" ? /* @__PURE__ */ jsx23(
2394
+ ApiKeyStep,
2395
+ {
2396
+ provider,
2397
+ onConfirm: (key) => {
2398
+ updateEnvKeyForProvider(provider, key);
2399
+ goNext();
2400
+ },
2401
+ onBack: goBack
2402
+ }
2403
+ ) : /* @__PURE__ */ jsx23(
2404
+ ApiUrlStep,
2405
+ {
2406
+ provider,
2407
+ onConfirm: (key) => {
2408
+ updateEnvKeyForProvider(provider, key);
2409
+ goNext();
2410
+ },
2411
+ onBack: goBack
2412
+ }
2413
+ ) }),
2414
+ /* @__PURE__ */ jsx23(Step, { name: "Model", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx23(
2415
+ ModelSelectionStep,
2416
+ {
2417
+ title: "What model would you like to use?",
2418
+ models,
2419
+ onConfirm: (m) => {
2420
+ const finalModelName = provider === "Ollama" && !m.startsWith("ollama-") && !m.includes(":") ? `ollama-${m}` : m;
2421
+ updateEnv("HARPER_AGENT_MODEL", finalModelName);
2422
+ goNext();
2423
+ },
2424
+ onBack: goBack
2425
+ }
2426
+ ) }),
2427
+ /* @__PURE__ */ jsx23(Step, { name: "Compactor", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx23(
2428
+ ModelSelectionStep,
2429
+ {
2430
+ title: "What model should we use for memory compaction?",
2431
+ models: compactorModels,
2432
+ onConfirm: (m) => {
2433
+ const finalModelName = provider === "Ollama" && !m.startsWith("ollama-") && !m.includes(":") ? `ollama-${m}` : m;
2434
+ updateEnv("HARPER_AGENT_COMPACTION_MODEL", finalModelName);
2435
+ goNext();
2436
+ },
2437
+ onBack: goBack
2438
+ }
2439
+ ) }),
2440
+ /* @__PURE__ */ jsx23(Step, { name: "Settings", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx23(
2441
+ EnvironmentSettingsStep,
2442
+ {
2443
+ onConfirm: () => {
2444
+ goNext();
2445
+ },
2446
+ onBack: goBack
2447
+ }
2448
+ ) })
2449
+ ]
2450
+ }
2451
+ ) });
2452
+ }
2453
+
2454
+ // ink/main.tsx
2455
+ import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
2456
+ function bootstrapConfig() {
2457
+ return new Promise((resolve) => {
2458
+ render(/* @__PURE__ */ jsx24(MainConfig, { onComplete: resolve }));
2459
+ });
2460
+ }
2461
+ function MainConfig({ onComplete }) {
2462
+ const { exit } = useApp();
2463
+ useListener("ExitUI", () => exit(), [exit]);
2464
+ return /* @__PURE__ */ jsx24(ConfigurationWizard, { onComplete });
2465
+ }
2466
+ function bootstrapMain() {
2467
+ render(/* @__PURE__ */ jsx24(MainChat, {}));
2468
+ }
2469
+ function MainChat() {
2470
+ const { exit } = useApp();
2471
+ useListener("ExitUI", () => exit(), [exit]);
2472
+ return /* @__PURE__ */ jsx24(CostProvider, { children: /* @__PURE__ */ jsx24(PlanProvider, { children: /* @__PURE__ */ jsx24(ActionsProvider, { children: /* @__PURE__ */ jsx24(ApprovalProvider, { children: /* @__PURE__ */ jsx24(SettingsProvider, { children: /* @__PURE__ */ jsxs18(ChatProvider, { children: [
2473
+ /* @__PURE__ */ jsx24(ChatContent, {}),
2474
+ /* @__PURE__ */ jsx24(DiffApprovalView, {})
2475
+ ] }) }) }) }) }) });
2476
+ }
2477
+
2478
+ // utils/models/deprecations.ts
2479
+ import chalk2 from "chalk";
2480
+ var DEPRECATION_RULES = [
2481
+ // Redirect any gpt-4o variants (including dated and -mini) to gpt-5-nano
2482
+ {
2483
+ match: (name) => name.toLowerCase().startsWith("gpt-4o"),
2484
+ replacement: "gpt-5-nano",
2485
+ reason: "OpenAI gpt-4o family is deprecated in this agent"
2486
+ }
2487
+ ];
2488
+ function getDeprecatedReplacement(modelName) {
2489
+ if (!modelName) {
2490
+ return null;
2491
+ }
2492
+ for (const rule of DEPRECATION_RULES) {
2493
+ if (rule.match(modelName)) {
2494
+ return { replacement: rule.replacement, rule };
2495
+ }
2496
+ }
2497
+ return null;
2498
+ }
2499
+ function warnAndPersistRedirect(original, envKey, replacement, reason) {
2500
+ const reasonSuffix = reason ? ` Reason: ${reason}.` : "";
2501
+ console.warn(
2502
+ chalk2.yellow(
2503
+ `Warning: model "${original}" is deprecated and will be redirected to "${replacement}". Your environment setting (${envKey}) has been updated.${reasonSuffix}`
2504
+ )
2505
+ );
2506
+ updateEnv(envKey, replacement);
2507
+ }
2508
+
2509
+ // utils/shell/cli.ts
2510
+ import chalk3 from "chalk";
2511
+
2512
+ // utils/package/getOwnPackageJson.ts
2513
+ import { readFileSync } from "fs";
2514
+ import { join } from "path";
2515
+ import { fileURLToPath } from "url";
2516
+ var __dirname = fileURLToPath(new URL(".", import.meta.url));
2517
+ function getOwnPackageJson() {
2518
+ try {
2519
+ const packageContents = readFileSync(join(__dirname, "../package.json"), "utf8");
2520
+ return JSON.parse(packageContents);
2521
+ } catch {
2522
+ return { name: "@harperfast/agent", version: "0.0.0" };
2523
+ }
2524
+ }
2525
+
2526
+ // utils/shell/cli.ts
2527
+ function isHelpRequest(args) {
2528
+ const helpVariants = ["--help", "-h", "help"];
2529
+ return args.some((arg) => helpVariants.includes(arg.toLowerCase()));
2530
+ }
2531
+ function isVersionRequest(args) {
2532
+ const versionVariants = ["--version", "-v", "version"];
2533
+ return args.some((arg) => versionVariants.includes(arg.toLowerCase()));
2534
+ }
2535
+ function handleHelp() {
2536
+ console.log(`
2537
+ ${chalk3.bold("harper-agent")} - AI to help you with Harper app creation and modification
2538
+
2539
+ ${chalk3.bold("USAGE")}
2540
+ $ harper-agent [options]
2541
+ $ harper-agent [command]
2542
+
2543
+ ${chalk3.bold("OPTIONS")}
2544
+ -h, --help Show help information
2545
+ -v, --version Show version information
2546
+ -m, --model Specify the model to use (e.g., ${defaultOpenAIModel}, ${defaultAnthropicModel}, ${defaultOllamaModel})
2547
+ Can also be set via HARPER_AGENT_MODEL environment variable.
2548
+ For Ollama, use the ollama- prefix (e.g., ${defaultOllamaCompactionModel}).
2549
+ -c, --compaction-model Specify the compaction model to use (defaults to ${defaultOpenAICompactionModel}).
2550
+ Can also be set via HARPER_AGENT_COMPACTION_MODEL environment variable.
2551
+ -s, --session Specify a path to a SQLite database file to persist the chat session.
2552
+ Can also be set via HARPER_AGENT_SESSION environment variable.
2553
+ --max-turns Specify the maximum number of turns for the agent run.
2554
+ Can also be set via HARPER_AGENT_MAX_TURNS environment variable.
2555
+ --max-cost Specify the maximum cost (in USD) for the agent run.
2556
+ If exceeded, the agent will exit with a non-zero code.
2557
+ Can also be set via HARPER_AGENT_MAX_COST environment variable.
2558
+ --flex-tier Force the use of the flex service tier for lower costs but potentially
2559
+ more errors under high system load.
2560
+ Can also be set via HARPER_AGENT_FLEX_TIER=true environment variable.
2561
+ -p, --prompt Specify a prompt to be executed autonomously until completion.
2562
+
2563
+ ${chalk3.bold("COMMANDS")}
2564
+ --help Show help information
2565
+ --version Show version information
2566
+
2567
+ ${chalk3.bold("EXAMPLES")}
2568
+ $ harper-agent --help
2569
+ $ harper-agent --version
2570
+ $ harper-agent
2571
+ `);
2572
+ process.exit(0);
2573
+ }
2574
+ function handleVersion() {
2575
+ const pkg = getOwnPackageJson();
2576
+ console.log(pkg.version);
2577
+ process.exit(0);
2578
+ }
2579
+
2580
+ // lifecycle/parseArgs.ts
2581
+ function stripQuotes(str) {
2582
+ if (str.startsWith('"') && str.endsWith('"') || str.startsWith("'") && str.endsWith("'")) {
2583
+ return str.slice(1, -1);
2584
+ }
2585
+ return str;
2586
+ }
2587
+ function parseArgs() {
2588
+ const args = process.argv.slice(2);
2589
+ if (isHelpRequest(args)) {
2590
+ handleHelp();
2591
+ }
2592
+ if (isVersionRequest(args)) {
2593
+ handleVersion();
2594
+ }
2595
+ for (let i = 0; i < args.length; i++) {
2596
+ const arg = args[i];
2597
+ const flagPairs = [
2598
+ ["model", ["--model", "-m", "model"]],
2599
+ ["compactionModel", ["--compaction-model", "-c", "compaction-model"]],
2600
+ ["sessionPath", ["--session", "-s", "session"]],
2601
+ ["maxTurns", ["--max-turns"]],
2602
+ ["maxCost", ["--max-cost"]],
2603
+ ["rateLimitThreshold", ["--rate-limit-threshold"]],
2604
+ ["prompt", ["--prompt", "-p"]]
2605
+ ];
2606
+ let handled = false;
2607
+ for (const [key, prefixes] of flagPairs) {
2608
+ for (const prefix of prefixes) {
2609
+ if (arg === prefix) {
2610
+ if (args[i + 1]) {
2611
+ const val = stripQuotes(args[++i]);
2612
+ if (key === "maxTurns" || key === "maxCost" || key === "rateLimitThreshold") {
2613
+ trackedState[key] = parseFloat(val);
2614
+ } else {
2615
+ trackedState[key] = val;
2616
+ }
2617
+ }
2618
+ handled = true;
2619
+ break;
2620
+ } else if (arg.startsWith(`${prefix}=`)) {
2621
+ const val = stripQuotes(arg.slice(prefix.length + 1));
2622
+ if (key === "maxTurns" || key === "maxCost" || key === "rateLimitThreshold") {
2623
+ trackedState[key] = parseFloat(val);
2624
+ } else {
2625
+ trackedState[key] = val;
2626
+ }
2627
+ handled = true;
2628
+ break;
2629
+ }
2630
+ }
2631
+ if (handled) {
2632
+ break;
2633
+ }
2634
+ }
2635
+ if (handled) {
2636
+ continue;
2637
+ }
2638
+ if (arg === "--flex-tier") {
2639
+ trackedState.useFlexTier = true;
2640
+ } else if (arg === "--no-monitor-rate-limits") {
2641
+ trackedState.monitorRateLimits = false;
2642
+ } else if (arg === "--autonomous" || arg === "-a") {
2643
+ trackedState.autonomous = true;
2644
+ }
2645
+ }
2646
+ if (!trackedState.model && process.env.HARPER_AGENT_MODEL) {
2647
+ trackedState.model = process.env.HARPER_AGENT_MODEL;
2648
+ }
2649
+ if (!trackedState.compactionModel && process.env.HARPER_AGENT_COMPACTION_MODEL) {
2650
+ trackedState.compactionModel = process.env.HARPER_AGENT_COMPACTION_MODEL;
2651
+ }
2652
+ if (!trackedState.sessionPath && process.env.HARPER_AGENT_SESSION) {
2653
+ trackedState.sessionPath = process.env.HARPER_AGENT_SESSION;
2654
+ }
2655
+ if (process.env.HARPER_AGENT_MAX_TURNS) {
2656
+ trackedState.maxTurns = parseFloat(process.env.HARPER_AGENT_MAX_TURNS);
2657
+ }
2658
+ if (process.env.HARPER_AGENT_MAX_COST) {
2659
+ trackedState.maxCost = parseFloat(process.env.HARPER_AGENT_MAX_COST);
2660
+ }
2661
+ if (process.env.HARPER_AGENT_RATE_LIMIT_THRESHOLD) {
2662
+ trackedState.rateLimitThreshold = parseFloat(process.env.HARPER_AGENT_RATE_LIMIT_THRESHOLD);
2663
+ }
2664
+ if (process.env.HARPER_AGENT_MONITOR_RATE_LIMITS === "false") {
2665
+ trackedState.monitorRateLimits = false;
2666
+ }
2667
+ if (!trackedState.useFlexTier && isTrue(process.env.HARPER_AGENT_FLEX_TIER)) {
2668
+ trackedState.useFlexTier = true;
2669
+ }
2670
+ if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER)) {
2671
+ trackedState.autoApproveCodeInterpreter = true;
2672
+ }
2673
+ if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_PATCHES)) {
2674
+ trackedState.autoApprovePatches = true;
2675
+ }
2676
+ if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_SHELL)) {
2677
+ trackedState.autoApproveShell = true;
2678
+ }
2679
+ if (isTrue(process.env.HARPER_AGENT_AUTONOMOUS)) {
2680
+ trackedState.autonomous = true;
2681
+ }
2682
+ if (!trackedState.model || trackedState.model === defaultModelToken) {
2683
+ if (process.env.ANTHROPIC_API_KEY) {
2684
+ trackedState.model = defaultAnthropicModel;
2685
+ } else if (process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
2686
+ trackedState.model = defaultGoogleModel;
2687
+ } else if (process.env.OLLAMA_BASE_URL) {
2688
+ trackedState.model = defaultOllamaModel;
2689
+ } else {
2690
+ trackedState.model = defaultOpenAIModel;
2691
+ }
2692
+ }
2693
+ if (!trackedState.compactionModel || trackedState.compactionModel === defaultModelToken) {
2694
+ const m = trackedState.model;
2695
+ if (m.startsWith("claude-")) {
2696
+ trackedState.compactionModel = defaultAnthropicCompactionModel;
2697
+ } else if (m.startsWith("gemini-")) {
2698
+ trackedState.compactionModel = defaultGoogleCompactionModel;
2699
+ } else if (m.startsWith("ollama-")) {
2700
+ trackedState.compactionModel = defaultOllamaCompactionModel;
2701
+ } else {
2702
+ trackedState.compactionModel = defaultOpenAICompactionModel;
2703
+ }
2704
+ }
2705
+ if (!isOpenAIModel(trackedState.model)) {
2706
+ process.env.OPENAI_AGENTS_DISABLE_TRACING = process.env.OPENAI_AGENTS_DISABLE_TRACING || "1";
2707
+ }
2708
+ const maybeRedirect = (current, envKey) => {
2709
+ const hit = getDeprecatedReplacement(current);
2710
+ if (hit) {
2711
+ const { replacement, rule } = hit;
2712
+ warnAndPersistRedirect(current, envKey, replacement, rule.reason);
2713
+ return replacement;
2714
+ }
2715
+ return current;
2716
+ };
2717
+ trackedState.model = maybeRedirect(trackedState.model, "HARPER_AGENT_MODEL");
2718
+ trackedState.compactionModel = maybeRedirect(trackedState.compactionModel, "HARPER_AGENT_COMPACTION_MODEL");
2719
+ }
2720
+
2721
+ // utils/envLoader.ts
2722
+ import dotenv from "dotenv";
2723
+ import { existsSync } from "fs";
2724
+ import { homedir } from "os";
2725
+ import { join as join2 } from "path";
2726
+ function loadEnv() {
2727
+ const topLevelEnvPath = join2(homedir(), ".harper", "harper-agent-env");
2728
+ const localEnvPath = join2(process.cwd(), ".env");
2729
+ if (existsSync(topLevelEnvPath)) {
2730
+ dotenv.config({ path: topLevelEnvPath, quiet: true });
2731
+ }
2732
+ if (existsSync(localEnvPath)) {
2733
+ dotenv.config({ path: localEnvPath, override: true, quiet: true });
2734
+ }
2735
+ }
2736
+
2737
+ // utils/package/checkForUpdate.ts
2738
+ import { Select as Select3 } from "@inkjs/ui";
2739
+ import chalk4 from "chalk";
2740
+ import spawn from "cross-spawn";
2741
+ import { Box as Box19, render as render2, Text as Text18 } from "ink";
2742
+ import React20 from "react";
2743
+
2744
+ // utils/package/getLatestVersion.ts
2745
+ async function getLatestVersion(packageName) {
2746
+ try {
2747
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
2748
+ signal: AbortSignal.timeout(1e3)
2749
+ // 1 second timeout
2750
+ });
2751
+ if (!response.ok) {
2752
+ return null;
2753
+ }
2754
+ const data = await response.json();
2755
+ return data.version;
2756
+ } catch {
2757
+ return null;
2758
+ }
2759
+ }
2760
+
2761
+ // utils/package/isVersionNewer.ts
2762
+ function isVersionNewer(latest, current) {
2763
+ const l = latest.split(".").map((x) => parseInt(x, 10));
2764
+ const c = current.split(".").map((x) => parseInt(x, 10));
2765
+ for (let i = 0; i < 3; i++) {
2766
+ let latestNumber = l[i];
2767
+ let currentNumber = c[i];
2768
+ if (latestNumber === void 0 || currentNumber === void 0 || isNaN(latestNumber) || isNaN(currentNumber)) {
2769
+ break;
2770
+ }
2771
+ if (latestNumber > currentNumber) {
2772
+ return true;
2773
+ }
2774
+ if (latestNumber < currentNumber) {
2775
+ return false;
2776
+ }
2777
+ }
2778
+ return false;
2779
+ }
2780
+
2781
+ // utils/package/checkForUpdate.ts
2782
+ async function checkForUpdate() {
2783
+ const pkg = getOwnPackageJson();
2784
+ const packageName = pkg.name;
2785
+ const packageVersion = pkg.version;
2786
+ if (process.env.HARPER_AGENT_SKIP_UPDATE) {
2787
+ return packageVersion;
2788
+ }
2789
+ try {
2790
+ const latestVersion = await getLatestVersion(packageName);
2791
+ if (latestVersion && isVersionNewer(latestVersion, packageVersion)) {
2792
+ const choice = await promptForUpdateChoice(packageName, packageVersion, latestVersion);
2793
+ if (choice === "later") {
2794
+ return packageVersion;
2795
+ }
2796
+ if (choice === "never") {
2797
+ updateEnv("HARPER_AGENT_SKIP_UPDATE", "1");
2798
+ return packageVersion;
2799
+ }
2800
+ let isGlobal = false;
2801
+ try {
2802
+ const globalRootResult = spawn.sync("npm", ["root", "-g"], { encoding: "utf8" });
2803
+ const globalRoot = globalRootResult.stdout?.trim();
2804
+ if (globalRoot && process.argv[1] && process.argv[1].startsWith(globalRoot)) {
2805
+ isGlobal = true;
2806
+ }
2807
+ } catch {
2808
+ }
2809
+ if (isGlobal) {
2810
+ spawn.sync("npm", ["install", "-g", `${packageName}@latest`], { stdio: "inherit" });
2811
+ const result2 = spawn.sync("harper-agent", process.argv.slice(2), { stdio: "inherit" });
2812
+ process.exit(result2.status ?? 0);
2813
+ }
2814
+ const lsResult = spawn.sync("npm", ["cache", "npx", "ls", packageName], { encoding: "utf8" });
2815
+ if (lsResult.stdout) {
2816
+ const keys = lsResult.stdout.split("\n").map((line) => line.trim()).filter((line) => line.includes(":")).filter((line) => {
2817
+ const [, pkgPart] = line.split(":");
2818
+ return pkgPart && pkgPart.trim().startsWith(`${packageName}@`);
2819
+ }).map((line) => line.split(":")[0].trim());
2820
+ if (keys.length > 0) {
2821
+ spawn.sync("npm", ["cache", "npx", "rm", ...keys], { stdio: "inherit" });
2822
+ }
2823
+ }
2824
+ const result = spawn.sync("npx", ["-y", `${packageName}@latest`, ...process.argv.slice(2)], { stdio: "inherit" });
2825
+ process.exit(result.status ?? 0);
2826
+ }
2827
+ } catch {
2828
+ }
2829
+ return packageVersion;
2830
+ }
2831
+ function promptForUpdateChoice(pkgName, currentVersion, latestVersion) {
2832
+ return new Promise((resolve) => {
2833
+ const app = render2(
2834
+ React20.createElement(UpdatePrompt, {
2835
+ packageName: pkgName,
2836
+ currentVersion,
2837
+ latestVersion,
2838
+ onSelect: (c) => {
2839
+ resolve(c);
2840
+ app.unmount();
2841
+ }
2842
+ })
2843
+ );
2844
+ });
2845
+ }
2846
+ function UpdatePrompt({ packageName, currentVersion, latestVersion, onSelect }) {
2847
+ const options = [
2848
+ {
2849
+ label: `Update right now (will run: npx -y @harperfast/agent@latest)`,
2850
+ value: "now"
2851
+ },
2852
+ { label: "Update later", value: "later" },
2853
+ { label: "Don\u2019t ask again", value: "never" }
2854
+ ];
2855
+ return React20.createElement(
2856
+ Box19,
2857
+ { flexDirection: "column", padding: 1 },
2858
+ React20.createElement(
2859
+ Text18,
2860
+ null,
2861
+ `${chalk4.yellow("Update available:")} ${chalk4.bold(packageName)} ${chalk4.dim(`v${currentVersion}`)} \u2192 ${chalk4.green(`v${latestVersion}`)}`
2862
+ ),
2863
+ React20.createElement(
2864
+ Box19,
2865
+ { marginTop: 1 },
2866
+ React20.createElement(Select3, {
2867
+ options,
2868
+ onChange: (v) => onSelect(v)
2869
+ })
2870
+ )
2871
+ );
2872
+ }
2873
+
2874
+ // utils/shell/ensureApiKey.ts
2875
+ function ensureApiKey() {
2876
+ const models = [
2877
+ trackedState.model,
2878
+ trackedState.compactionModel
2879
+ ].filter(excludeFalsy);
2880
+ const requiredEnvVars = /* @__PURE__ */ new Set();
2881
+ for (const model of models) {
2882
+ if (model.startsWith("claude-")) {
2883
+ requiredEnvVars.add("ANTHROPIC_API_KEY");
2884
+ } else if (model.startsWith("gemini-")) {
2885
+ requiredEnvVars.add("GOOGLE_GENERATIVE_AI_API_KEY");
2886
+ } else if (model.startsWith("ollama-") || model.includes(":")) {
2887
+ } else {
2888
+ requiredEnvVars.add("OPENAI_API_KEY");
2889
+ }
2890
+ }
2891
+ for (const envVar of requiredEnvVars) {
2892
+ if (!process.env[envVar]) {
2893
+ return false;
2894
+ }
2895
+ }
2896
+ return true;
2897
+ }
2898
+
2899
+ // cli.ts
2900
+ (async function() {
2901
+ setupGlobalErrorHandlers();
2902
+ loadEnv();
2903
+ process.on("SIGINT", handleExit);
2904
+ process.on("SIGTERM", handleExit);
2905
+ await checkForUpdate();
2906
+ parseArgs();
2907
+ if (!ensureApiKey()) {
2908
+ resetTrackedState();
2909
+ await bootstrapConfig();
2910
+ emitToListeners("ExitUI", void 0);
2911
+ parseArgs();
2912
+ if (!ensureApiKey()) {
2913
+ console.log(chalk5.red("No key provided. Exiting."));
2914
+ process.exit(1);
2915
+ }
2916
+ }
2917
+ await agentManager.initialize();
2918
+ bootstrapMain();
2919
+ })();