@devang0907/agent-dev 0.1.3 → 0.1.5
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/README.md +12 -2
- package/dist/agent/loop.d.ts +7 -0
- package/dist/agent/loop.js +96 -42
- package/dist/agent/platform.d.ts +10 -0
- package/dist/agent/platform.js +74 -0
- package/dist/agent/session.d.ts +8 -1
- package/dist/agent/session.js +19 -0
- package/dist/agent/tools/index.d.ts +1 -0
- package/dist/agent/tools/index.js +3 -0
- package/dist/agent/tools/read.js +11 -17
- package/dist/agent/tools/search.d.ts +5 -0
- package/dist/agent/tools/search.js +229 -0
- package/dist/agent/tools/shell.d.ts +1 -0
- package/dist/agent/tools/shell.js +131 -0
- package/dist/modes/print-mode.js +15 -0
- package/dist/providers/openai-compat.d.ts +2 -0
- package/dist/providers/openai-compat.js +98 -0
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.js +67 -15
- package/dist/ui/ChatView.d.ts +2 -1
- package/dist/ui/ChatView.js +15 -7
- package/dist/ui/CommandApprovalPrompt.d.ts +11 -0
- package/dist/ui/CommandApprovalPrompt.js +15 -0
- package/dist/ui/Editor.d.ts +2 -1
- package/dist/ui/Editor.js +6 -2
- package/dist/ui/format-tool.d.ts +2 -0
- package/dist/ui/format-tool.js +25 -0
- package/dist/ui/layout.d.ts +6 -0
- package/dist/ui/layout.js +22 -0
- package/dist/ui/scroll.d.ts +3 -0
- package/dist/ui/scroll.js +4 -0
- package/dist/ui/theme.js +1 -0
- package/package.json +1 -1
package/dist/ui/App.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
3
|
-
import { Box, useInput, useApp } from "ink";
|
|
3
|
+
import { Box, useInput, useApp, useStdout } from "ink";
|
|
4
4
|
import { ChatView } from "./ChatView.js";
|
|
5
5
|
import { Editor } from "./Editor.js";
|
|
6
6
|
import { Footer } from "./Footer.js";
|
|
@@ -9,8 +9,15 @@ import { ApiKeyPrompt } from "./ApiKeyPrompt.js";
|
|
|
9
9
|
import { SettingsView } from "./SettingsView.js";
|
|
10
10
|
import { hasProviderAuth, getDefaultModelForProvider } from "../providers/registry.js";
|
|
11
11
|
import { findModel } from "../config/models.js";
|
|
12
|
+
import { CommandApprovalPrompt } from "./CommandApprovalPrompt.js";
|
|
12
13
|
import { StartupBanner } from "./StartupBanner.js";
|
|
13
14
|
import { getTheme } from "./theme.js";
|
|
15
|
+
import { scrollViewportToBottom } from "./scroll.js";
|
|
16
|
+
import { formatToolForDisplay } from "./format-tool.js";
|
|
17
|
+
let nextMessageId = 0;
|
|
18
|
+
function toDisplayMessage(role, content, toolName) {
|
|
19
|
+
return { id: nextMessageId++, role, content, toolName };
|
|
20
|
+
}
|
|
14
21
|
function modelForProvider(provider, settings) {
|
|
15
22
|
const current = findModel(settings.defaultProvider, settings.defaultModel);
|
|
16
23
|
if (current?.provider === provider)
|
|
@@ -19,11 +26,7 @@ function modelForProvider(provider, settings) {
|
|
|
19
26
|
}
|
|
20
27
|
export function App({ session, workdir, onQuit }) {
|
|
21
28
|
const { exit } = useApp();
|
|
22
|
-
const [displayMessages, setDisplayMessages] = useState(() => session.getMessages().map((m) => (
|
|
23
|
-
role: m.role === "tool" ? "tool" : m.role === "user" ? "user" : "assistant",
|
|
24
|
-
content: m.content,
|
|
25
|
-
toolName: m.name,
|
|
26
|
-
})));
|
|
29
|
+
const [displayMessages, setDisplayMessages] = useState(() => session.getMessages().map((m) => toDisplayMessage(m.role === "tool" ? "tool" : m.role === "user" ? "user" : "assistant", m.content, m.name)));
|
|
27
30
|
const [streamingText, setStreamingText] = useState("");
|
|
28
31
|
const [overlay, setOverlay] = useState("none");
|
|
29
32
|
const [modelFilter, setModelFilter] = useState();
|
|
@@ -32,9 +35,13 @@ export function App({ session, workdir, onQuit }) {
|
|
|
32
35
|
const [settings, setSettings] = useState(session.getSettings());
|
|
33
36
|
const [model, setModel] = useState(session.getModel());
|
|
34
37
|
const [running, setRunning] = useState(false);
|
|
38
|
+
const [autoFollow, setAutoFollow] = useState(true);
|
|
39
|
+
const [pendingCommand, setPendingCommand] = useState(null);
|
|
35
40
|
const streamingRef = useRef("");
|
|
41
|
+
const autoFollowRef = useRef(true);
|
|
36
42
|
const startupChecked = useRef(false);
|
|
37
43
|
const theme = getTheme();
|
|
44
|
+
const { stdout } = useStdout();
|
|
38
45
|
const openApiKeyPrompt = useCallback((target, returnTo = "none") => {
|
|
39
46
|
setPendingModel(target);
|
|
40
47
|
setApiKeyReturnOverlay(returnTo);
|
|
@@ -65,18 +72,28 @@ export function App({ session, workdir, onQuit }) {
|
|
|
65
72
|
openApiKeyPrompt(current, "none");
|
|
66
73
|
}
|
|
67
74
|
}, [session, settings, openApiKeyPrompt]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (autoFollow && streamingText) {
|
|
77
|
+
scrollViewportToBottom(stdout);
|
|
78
|
+
}
|
|
79
|
+
}, [streamingText, autoFollow, stdout]);
|
|
68
80
|
useEffect(() => {
|
|
69
81
|
const handler = (event) => {
|
|
70
82
|
switch (event.type) {
|
|
71
83
|
case "user_message":
|
|
72
|
-
|
|
84
|
+
autoFollowRef.current = true;
|
|
85
|
+
setAutoFollow(true);
|
|
86
|
+
setDisplayMessages((prev) => [...prev, toDisplayMessage("user", event.content)]);
|
|
73
87
|
setRunning(true);
|
|
74
88
|
streamingRef.current = "";
|
|
75
89
|
setStreamingText("");
|
|
90
|
+
scrollViewportToBottom(stdout);
|
|
76
91
|
break;
|
|
77
92
|
case "message_start":
|
|
78
93
|
streamingRef.current = "";
|
|
79
94
|
setStreamingText("");
|
|
95
|
+
if (autoFollowRef.current)
|
|
96
|
+
scrollViewportToBottom(stdout);
|
|
80
97
|
break;
|
|
81
98
|
case "text_delta":
|
|
82
99
|
streamingRef.current += event.delta;
|
|
@@ -85,7 +102,7 @@ export function App({ session, workdir, onQuit }) {
|
|
|
85
102
|
case "tool_call":
|
|
86
103
|
const partial = streamingRef.current;
|
|
87
104
|
if (partial) {
|
|
88
|
-
setDisplayMessages((prev) => [...prev,
|
|
105
|
+
setDisplayMessages((prev) => [...prev, toDisplayMessage("assistant", partial)]);
|
|
89
106
|
streamingRef.current = "";
|
|
90
107
|
setStreamingText("");
|
|
91
108
|
}
|
|
@@ -93,13 +110,19 @@ export function App({ session, workdir, onQuit }) {
|
|
|
93
110
|
case "tool_result":
|
|
94
111
|
setDisplayMessages((prev) => [
|
|
95
112
|
...prev,
|
|
96
|
-
|
|
113
|
+
toDisplayMessage("tool", formatToolForDisplay(event.name, event.result), event.name),
|
|
97
114
|
]);
|
|
98
115
|
break;
|
|
99
116
|
case "turn_end":
|
|
100
117
|
const final = streamingRef.current;
|
|
101
118
|
if (final) {
|
|
102
|
-
setDisplayMessages((prev) =>
|
|
119
|
+
setDisplayMessages((prev) => {
|
|
120
|
+
const last = prev[prev.length - 1];
|
|
121
|
+
if (last?.role === "assistant" && last.content.trim() === final.trim()) {
|
|
122
|
+
return prev;
|
|
123
|
+
}
|
|
124
|
+
return [...prev, toDisplayMessage("assistant", final)];
|
|
125
|
+
});
|
|
103
126
|
}
|
|
104
127
|
streamingRef.current = "";
|
|
105
128
|
setStreamingText("");
|
|
@@ -108,15 +131,20 @@ export function App({ session, workdir, onQuit }) {
|
|
|
108
131
|
case "error":
|
|
109
132
|
setDisplayMessages((prev) => [
|
|
110
133
|
...prev,
|
|
111
|
-
|
|
134
|
+
toDisplayMessage("assistant", `Error: ${event.message}`),
|
|
112
135
|
]);
|
|
113
136
|
streamingRef.current = "";
|
|
114
137
|
setStreamingText("");
|
|
115
138
|
setRunning(false);
|
|
139
|
+
setPendingCommand(null);
|
|
116
140
|
if (/Missing .*API_KEY/i.test(event.message)) {
|
|
117
141
|
openApiKeyPrompt(model, "none");
|
|
118
142
|
}
|
|
119
143
|
break;
|
|
144
|
+
case "permission_request":
|
|
145
|
+
setPendingCommand(event.request);
|
|
146
|
+
setOverlay("commandApproval");
|
|
147
|
+
break;
|
|
120
148
|
case "model_changed":
|
|
121
149
|
setModel(event.model);
|
|
122
150
|
break;
|
|
@@ -126,12 +154,24 @@ export function App({ session, workdir, onQuit }) {
|
|
|
126
154
|
return () => {
|
|
127
155
|
session.off("event", handler);
|
|
128
156
|
};
|
|
129
|
-
}, [session, model, openApiKeyPrompt]);
|
|
130
|
-
|
|
157
|
+
}, [session, model, openApiKeyPrompt, stdout]);
|
|
158
|
+
autoFollowRef.current = autoFollow;
|
|
159
|
+
useInput((input, key) => {
|
|
131
160
|
if (overlay !== "none")
|
|
132
161
|
return;
|
|
133
162
|
if (key.escape && running) {
|
|
134
163
|
session.abort();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (key.pageUp || (key.upArrow && key.shift)) {
|
|
167
|
+
autoFollowRef.current = false;
|
|
168
|
+
setAutoFollow(false);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (input === "g" && key.ctrl) {
|
|
172
|
+
autoFollowRef.current = true;
|
|
173
|
+
setAutoFollow(true);
|
|
174
|
+
scrollViewportToBottom(stdout);
|
|
135
175
|
}
|
|
136
176
|
}, { isActive: overlay === "none" });
|
|
137
177
|
const handleSubmit = useCallback(async (value) => {
|
|
@@ -144,6 +184,7 @@ export function App({ session, workdir, onQuit }) {
|
|
|
144
184
|
session.newSession();
|
|
145
185
|
setDisplayMessages([]);
|
|
146
186
|
setStreamingText("");
|
|
187
|
+
setAutoFollow(true);
|
|
147
188
|
return;
|
|
148
189
|
}
|
|
149
190
|
if (value === "/settings") {
|
|
@@ -165,7 +206,10 @@ export function App({ session, workdir, onQuit }) {
|
|
|
165
206
|
await session.prompt(value);
|
|
166
207
|
}, [session, running, onQuit, exit, model, settings, openApiKeyPrompt]);
|
|
167
208
|
const hasChat = displayMessages.length > 0 || streamingText.length > 0;
|
|
168
|
-
return (_jsxs(Box, { flexDirection: "column",
|
|
209
|
+
return (_jsxs(Box, { flexDirection: "column", children: [!hasChat && (_jsx(Box, { paddingX: 2, marginBottom: 1, flexShrink: 0, children: _jsx(StartupBanner, { theme: theme }) })), _jsx(ChatView, { messages: displayMessages, theme: theme, model: model, streamingText: streamingText, running: running, autoFollow: autoFollow }), _jsx(Footer, { workdir: workdir, model: model, theme: theme }), overlay === "none" && (_jsx(Box, { flexShrink: 0, children: _jsx(Editor, { theme: theme, model: model, disabled: running, running: running, onSubmit: handleSubmit, onPauseFollow: () => {
|
|
210
|
+
autoFollowRef.current = false;
|
|
211
|
+
setAutoFollow(false);
|
|
212
|
+
} }) })), overlay === "model" && (_jsx(ModelSelector, { theme: theme, settings: settings, filter: modelFilter, onSelect: (m) => {
|
|
169
213
|
if (!hasProviderAuth(m.provider, settings)) {
|
|
170
214
|
openApiKeyPrompt(m, "model");
|
|
171
215
|
return;
|
|
@@ -186,5 +230,13 @@ export function App({ session, workdir, onQuit }) {
|
|
|
186
230
|
setSettings(s);
|
|
187
231
|
}, onSetApiKey: (provider) => {
|
|
188
232
|
openApiKeyPrompt(modelForProvider(provider, settings), "settings");
|
|
189
|
-
}, onClose: () => setOverlay("none") }))
|
|
233
|
+
}, onClose: () => setOverlay("none") })), overlay === "commandApproval" && pendingCommand && (_jsx(CommandApprovalPrompt, { theme: theme, request: pendingCommand, onApprove: () => {
|
|
234
|
+
session.respondToPermission(true);
|
|
235
|
+
setPendingCommand(null);
|
|
236
|
+
setOverlay("none");
|
|
237
|
+
}, onDeny: () => {
|
|
238
|
+
session.respondToPermission(false);
|
|
239
|
+
setPendingCommand(null);
|
|
240
|
+
setOverlay("none");
|
|
241
|
+
} }))] }));
|
|
190
242
|
}
|
package/dist/ui/ChatView.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ interface ChatViewProps {
|
|
|
8
8
|
model: Model;
|
|
9
9
|
streamingText?: string;
|
|
10
10
|
running?: boolean;
|
|
11
|
+
autoFollow?: boolean;
|
|
11
12
|
}
|
|
12
|
-
export declare function ChatView({ messages, theme, model, streamingText, running }: ChatViewProps): React.JSX.Element | null;
|
|
13
|
+
export declare function ChatView({ messages, theme, model, streamingText, running, autoFollow, }: ChatViewProps): React.JSX.Element | null;
|
|
13
14
|
export {};
|
package/dist/ui/ChatView.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { Box, Text } from "ink";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Box, Static, Text } from "ink";
|
|
4
4
|
import { SPINNER_FRAMES, TOOL_ICONS } from "./theme.js";
|
|
5
5
|
import { LeftBorder } from "./LeftBorder.js";
|
|
6
|
-
import { Panel } from "./Panel.js";
|
|
7
6
|
import { modelRef } from "../config/models.js";
|
|
8
|
-
function
|
|
9
|
-
|
|
7
|
+
function StaticMessage({ msg, theme, model, showModelTag, }) {
|
|
8
|
+
if (msg.role === "user") {
|
|
9
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(LeftBorder, { theme: theme, borderColor: theme.primary, marginBottom: 0, children: _jsx(Text, { color: theme.text, children: msg.content }) }) }));
|
|
10
|
+
}
|
|
11
|
+
if (msg.role === "assistant") {
|
|
12
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 1, children: [_jsx(Text, { color: theme.text, children: msg.content || "" }), showModelTag && (_jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: "\u25A3 " }), modelRef(model)] }))] }));
|
|
13
|
+
}
|
|
14
|
+
const icon = TOOL_ICONS[msg.toolName ?? ""] ?? "·";
|
|
15
|
+
return (_jsx(Box, { paddingLeft: 1, marginBottom: 0, children: _jsxs(Text, { color: theme.textMuted, children: [icon, " ", msg.content] }) }));
|
|
10
16
|
}
|
|
11
|
-
export function ChatView({ messages, theme, model, streamingText, running }) {
|
|
17
|
+
export function ChatView({ messages, theme, model, streamingText, running, autoFollow = true, }) {
|
|
12
18
|
const [spinIdx, setSpinIdx] = useState(0);
|
|
13
19
|
const hasContent = messages.length > 0 || (streamingText?.length ?? 0) > 0;
|
|
14
20
|
useEffect(() => {
|
|
@@ -20,5 +26,7 @@ export function ChatView({ messages, theme, model, streamingText, running }) {
|
|
|
20
26
|
if (!hasContent) {
|
|
21
27
|
return null;
|
|
22
28
|
}
|
|
23
|
-
|
|
29
|
+
const lastAssistantId = [...messages].reverse().find((m) => m.role === "assistant")?.id;
|
|
30
|
+
const showWorking = running && !streamingText && messages.length > 0;
|
|
31
|
+
return (_jsxs(Box, { flexDirection: "column", marginX: 2, marginBottom: 1, children: [_jsx(Static, { items: messages, children: (msg) => (_jsx(StaticMessage, { msg: msg, theme: theme, model: model, showModelTag: msg.role === "assistant" && msg.id === lastAssistantId && !running }, msg.id)) }), streamingText && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, children: [_jsx(Text, { color: theme.text, children: streamingText }), _jsxs(Text, { color: theme.textMuted, children: [_jsxs(Text, { color: theme.primary, children: [SPINNER_FRAMES[spinIdx], " "] }), !autoFollow && _jsx(Text, { color: theme.warning, children: "follow paused \u00B7 Ctrl+G " })] })] })), showWorking && (_jsx(Box, { paddingLeft: 1, marginTop: 1, children: _jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: SPINNER_FRAMES[spinIdx] }), " working\u2026"] }) }))] }));
|
|
24
32
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ThemeColors } from "./theme.js";
|
|
3
|
+
import type { PermissionRequest } from "../agent/loop.js";
|
|
4
|
+
interface CommandApprovalPromptProps {
|
|
5
|
+
theme: ThemeColors;
|
|
6
|
+
request: PermissionRequest;
|
|
7
|
+
onApprove: () => void;
|
|
8
|
+
onDeny: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function CommandApprovalPrompt({ theme, request, onApprove, onDeny, }: CommandApprovalPromptProps): React.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { LeftBorder } from "./LeftBorder.js";
|
|
4
|
+
export function CommandApprovalPrompt({ theme, request, onApprove, onDeny, }) {
|
|
5
|
+
useInput((input, key) => {
|
|
6
|
+
if (input === "y" || input === "Y") {
|
|
7
|
+
onApprove();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (input === "n" || input === "N" || key.escape) {
|
|
11
|
+
onDeny();
|
|
12
|
+
}
|
|
13
|
+
}, { isActive: true });
|
|
14
|
+
return (_jsx(Box, { flexDirection: "column", marginX: 2, marginTop: 1, marginBottom: 1, children: _jsxs(LeftBorder, { theme: theme, borderColor: theme.warning, children: [_jsx(Text, { color: theme.text, bold: true, children: "Run shell command?" }), _jsx(Text, { color: theme.textMuted, children: " y approve \u00B7 n or Esc deny" }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: theme.warning, paddingX: 1, paddingY: 0, children: _jsx(Text, { color: theme.text, children: request.command }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textMuted, children: "The agent wants to run this command in your project directory." }) })] }) }));
|
|
15
|
+
}
|
package/dist/ui/Editor.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ interface EditorProps {
|
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
running?: boolean;
|
|
9
9
|
onSubmit: (value: string) => void;
|
|
10
|
+
onPauseFollow?: () => void;
|
|
10
11
|
}
|
|
11
|
-
export declare function Editor({ theme, model, disabled, running, onSubmit }: EditorProps): React.JSX.Element;
|
|
12
|
+
export declare function Editor({ theme, model, disabled, running, onSubmit, onPauseFollow }: EditorProps): React.JSX.Element;
|
|
12
13
|
export {};
|
package/dist/ui/Editor.js
CHANGED
|
@@ -10,7 +10,7 @@ function BlinkingCursor({ theme, visible }) {
|
|
|
10
10
|
return null;
|
|
11
11
|
return _jsx(Text, { color: theme.primary, children: "\u258C" });
|
|
12
12
|
}
|
|
13
|
-
export function Editor({ theme, model, disabled, running, onSubmit }) {
|
|
13
|
+
export function Editor({ theme, model, disabled, running, onSubmit, onPauseFollow }) {
|
|
14
14
|
const [value, setValue] = useState("");
|
|
15
15
|
const [suggestions, setSuggestions] = useState([]);
|
|
16
16
|
const [spinIdx, setSpinIdx] = useState(0);
|
|
@@ -60,6 +60,10 @@ export function Editor({ theme, model, disabled, running, onSubmit }) {
|
|
|
60
60
|
}
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
+
if ((key.pageUp || key.upArrow) && !key.ctrl && !key.meta) {
|
|
64
|
+
onPauseFollow?.();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
63
67
|
if (key.backspace || key.delete) {
|
|
64
68
|
const newVal = value.slice(0, -1);
|
|
65
69
|
setValue(newVal);
|
|
@@ -74,5 +78,5 @@ export function Editor({ theme, model, disabled, running, onSubmit }) {
|
|
|
74
78
|
}, { isActive: !disabled });
|
|
75
79
|
const placeholder = "Ask anything…";
|
|
76
80
|
const showCursor = !disabled && cursorOn;
|
|
77
|
-
return (_jsxs(Box, { flexDirection: "column", marginX: 2, children: [suggestions.length > 0 && (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.border, paddingX: 1, marginBottom: 1, children: suggestions.map((s) => (_jsxs(Text, { children: [_jsx(Text, { color: theme.primary, children: s.cmd }), _jsxs(Text, { color: theme.textMuted, children: [" \u2014 ", s.desc] })] }, s.cmd))) })), _jsxs(Panel, { theme: theme, borderColor: disabled ? theme.border : theme.primary, marginBottom: 0, children: [_jsx(Box, { flexDirection: "row", children: value.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.text, children: value }), _jsx(BlinkingCursor, { theme: theme, visible: showCursor })] })) : showCursor ? (_jsx(BlinkingCursor, { theme: theme, visible: true })) : (_jsx(Text, { color: theme.textMuted, children: placeholder })) }), _jsxs(Text, { color: theme.textMuted, children: ["agent-dev \u00B7 ", _jsx(Text, { color: theme.text, children: modelRef(model) })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: running ? (_jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: SPINNER_FRAMES[spinIdx] }), " ", "esc interrupt"] })) : (_jsx(Text, { color: theme.textMuted, children: "Tab completes /commands" })) })] }));
|
|
81
|
+
return (_jsxs(Box, { flexDirection: "column", marginX: 2, children: [suggestions.length > 0 && (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.border, paddingX: 1, marginBottom: 1, children: suggestions.map((s) => (_jsxs(Text, { children: [_jsx(Text, { color: theme.primary, children: s.cmd }), _jsxs(Text, { color: theme.textMuted, children: [" \u2014 ", s.desc] })] }, s.cmd))) })), _jsxs(Panel, { theme: theme, borderColor: disabled ? theme.border : theme.primary, marginBottom: 0, children: [_jsx(Box, { flexDirection: "row", children: value.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.text, children: value }), _jsx(BlinkingCursor, { theme: theme, visible: showCursor })] })) : showCursor ? (_jsx(BlinkingCursor, { theme: theme, visible: true })) : (_jsx(Text, { color: theme.textMuted, children: placeholder })) }), _jsxs(Text, { color: theme.textMuted, children: ["agent-dev \u00B7 ", _jsx(Text, { color: theme.text, children: modelRef(model) })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: running ? (_jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: SPINNER_FRAMES[spinIdx] }), " ", "esc interrupt"] })) : (_jsx(Text, { color: theme.textMuted, children: "Tab completes /commands \u00B7 scroll freely \u00B7 Ctrl+G follow" })) })] }));
|
|
78
82
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Compact one-line labels for tool activity in the chat UI. */
|
|
2
|
+
export function formatToolForDisplay(toolName, result) {
|
|
3
|
+
if (toolName === "web_search") {
|
|
4
|
+
const query = result.match(/Headlines for:\s*(.+)/)?.[1]?.trim()
|
|
5
|
+
?? result.match(/Search results for:\s*(.+)/)?.[1]?.trim();
|
|
6
|
+
return query ? `news: "${query}"` : "searched the web";
|
|
7
|
+
}
|
|
8
|
+
if (toolName === "read") {
|
|
9
|
+
return result.startsWith("Error:") ? result : "read file";
|
|
10
|
+
}
|
|
11
|
+
if (toolName === "write" || toolName === "edit") {
|
|
12
|
+
return result.startsWith("Error:") ? result : result.split("\n")[0] ?? toolName;
|
|
13
|
+
}
|
|
14
|
+
if (toolName === "bash") {
|
|
15
|
+
if (result.includes("Dev server")) {
|
|
16
|
+
const first = result.split("\n").slice(0, 2).join(" · ");
|
|
17
|
+
return first.length > 100 ? first.slice(0, 100) + "…" : first;
|
|
18
|
+
}
|
|
19
|
+
if (result.startsWith("Error:"))
|
|
20
|
+
return result.split("\n")[0] ?? result;
|
|
21
|
+
const line = result.split("\n").find((l) => l.trim()) ?? "command finished";
|
|
22
|
+
return line.length > 80 ? line.slice(0, 80) + "…" : line;
|
|
23
|
+
}
|
|
24
|
+
return result.length > 100 ? result.slice(0, 100) + "…" : result;
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useStdout } from "ink";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
export function useTerminalSize() {
|
|
4
|
+
const { stdout } = useStdout();
|
|
5
|
+
const [size, setSize] = useState({
|
|
6
|
+
rows: stdout.rows,
|
|
7
|
+
cols: stdout.columns,
|
|
8
|
+
});
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const onResize = () => {
|
|
11
|
+
setSize({ rows: stdout.rows, cols: stdout.columns });
|
|
12
|
+
};
|
|
13
|
+
stdout.on("resize", onResize);
|
|
14
|
+
return () => {
|
|
15
|
+
stdout.off("resize", onResize);
|
|
16
|
+
};
|
|
17
|
+
}, [stdout]);
|
|
18
|
+
return size;
|
|
19
|
+
}
|
|
20
|
+
export function chatContentWidth(cols) {
|
|
21
|
+
return Math.max(20, cols - 12);
|
|
22
|
+
}
|
package/dist/ui/theme.js
CHANGED