ideacode 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +3 -0
- package/dist/conversation.js +30 -0
- package/dist/repl.js +100 -22
- package/dist/tools/index.js +7 -3
- package/dist/ui/format.js +35 -0
- package/dist/ui/index.js +1 -1
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -7,6 +7,9 @@ const CONFIG_DIR = process.env.XDG_CONFIG_HOME
|
|
|
7
7
|
? path.join(process.env.LOCALAPPDATA ?? os.homedir(), "ideacode")
|
|
8
8
|
: path.join(os.homedir(), ".config", "ideacode");
|
|
9
9
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
10
|
+
export function getConfigDir() {
|
|
11
|
+
return CONFIG_DIR;
|
|
12
|
+
}
|
|
10
13
|
function loadConfigFile() {
|
|
11
14
|
try {
|
|
12
15
|
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { getConfigDir } from "./config.js";
|
|
5
|
+
function hashCwd(cwd) {
|
|
6
|
+
return crypto.createHash("sha256").update(cwd, "utf8").digest("hex").slice(0, 16);
|
|
7
|
+
}
|
|
8
|
+
export function getConversationPath(cwd) {
|
|
9
|
+
const dir = path.join(getConfigDir(), "conversations");
|
|
10
|
+
return path.join(dir, `${hashCwd(cwd)}.json`);
|
|
11
|
+
}
|
|
12
|
+
export function loadConversation(cwd) {
|
|
13
|
+
const filePath = getConversationPath(cwd);
|
|
14
|
+
try {
|
|
15
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
16
|
+
const data = JSON.parse(raw);
|
|
17
|
+
if (!Array.isArray(data))
|
|
18
|
+
return [];
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function saveConversation(cwd, messages) {
|
|
26
|
+
const filePath = getConversationPath(cwd);
|
|
27
|
+
const dir = path.dirname(filePath);
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
fs.writeFileSync(filePath, JSON.stringify(messages), "utf-8");
|
|
30
|
+
}
|
package/dist/repl.js
CHANGED
|
@@ -10,11 +10,12 @@ import gradient from "gradient-string";
|
|
|
10
10
|
// Custom matcha-themed gradient: matcha green → dark sepia
|
|
11
11
|
const matchaGradient = gradient(["#7F9A65", "#5C4033"]);
|
|
12
12
|
import { getModel, saveModel, saveBraveSearchApiKey, getBraveSearchApiKey } from "./config.js";
|
|
13
|
+
import { loadConversation, saveConversation } from "./conversation.js";
|
|
13
14
|
import { callApi, fetchModels } from "./api.js";
|
|
14
15
|
import { estimateTokens, ensureUnderBudget } from "./context.js";
|
|
15
16
|
import { runTool } from "./tools/index.js";
|
|
16
17
|
import { COMMANDS, matchCommand, resolveCommand } from "./commands.js";
|
|
17
|
-
import { colors, icons, separator, agentMessage, toolCallBox, toolResultLine, inkColors, } from "./ui/index.js";
|
|
18
|
+
import { colors, icons, separator, agentMessage, toolCallBox, toolResultLine, userPromptBox, inkColors, } from "./ui/index.js";
|
|
18
19
|
function wordStartBackward(value, cursor) {
|
|
19
20
|
let i = cursor - 1;
|
|
20
21
|
while (i >= 0 && /\s/.test(value[i]))
|
|
@@ -30,8 +31,15 @@ function wordEndForward(value, cursor) {
|
|
|
30
31
|
return i;
|
|
31
32
|
}
|
|
32
33
|
const CONTEXT_WINDOW_K = 128;
|
|
34
|
+
const MAX_TOOL_RESULT_CHARS = 3500;
|
|
33
35
|
const MAX_AT_SUGGESTIONS = 12;
|
|
34
36
|
const INITIAL_BANNER_LINES = 12;
|
|
37
|
+
const TRUNCATE_NOTE = "\n\n(Output truncated to save context. Use read with offset/limit, grep with a specific pattern, or tail with fewer lines to get more.)";
|
|
38
|
+
function truncateToolResult(content) {
|
|
39
|
+
if (content.length <= MAX_TOOL_RESULT_CHARS)
|
|
40
|
+
return content;
|
|
41
|
+
return content.slice(0, MAX_TOOL_RESULT_CHARS) + TRUNCATE_NOTE;
|
|
42
|
+
}
|
|
35
43
|
const isMac = process.platform === "darwin";
|
|
36
44
|
const pasteShortcut = isMac ? "Cmd+V" : "Ctrl+V";
|
|
37
45
|
function listFilesWithFilter(cwd, filter) {
|
|
@@ -76,6 +84,46 @@ function wrapLine(line, width) {
|
|
|
76
84
|
}
|
|
77
85
|
return out.length > 0 ? out : [""];
|
|
78
86
|
}
|
|
87
|
+
function replayMessagesToLogLines(messages) {
|
|
88
|
+
const lines = [];
|
|
89
|
+
for (let i = 0; i < messages.length; i++) {
|
|
90
|
+
const msg = messages[i];
|
|
91
|
+
if (msg.role === "user") {
|
|
92
|
+
if (typeof msg.content === "string") {
|
|
93
|
+
lines.push(...userPromptBox(msg.content).split("\n"), "");
|
|
94
|
+
}
|
|
95
|
+
else if (Array.isArray(msg.content)) {
|
|
96
|
+
const prev = messages[i - 1];
|
|
97
|
+
const toolResults = msg.content;
|
|
98
|
+
if (prev?.role === "assistant" && Array.isArray(prev.content)) {
|
|
99
|
+
const blocks = prev.content;
|
|
100
|
+
const toolUses = blocks.filter((b) => b.type === "tool_use");
|
|
101
|
+
for (const tr of toolResults) {
|
|
102
|
+
const block = toolUses.find((b) => b.id === tr.tool_use_id);
|
|
103
|
+
if (block?.name) {
|
|
104
|
+
const firstVal = block.input && typeof block.input === "object" ? Object.values(block.input)[0] : undefined;
|
|
105
|
+
const argPreview = String(firstVal ?? "").slice(0, 50);
|
|
106
|
+
const ok = !(tr.content ?? "").startsWith("error:");
|
|
107
|
+
lines.push(toolCallBox(block.name, argPreview, ok));
|
|
108
|
+
const preview = (tr.content ?? "").split("\n")[0]?.slice(0, 60) ?? "";
|
|
109
|
+
lines.push(toolResultLine(preview, ok));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
116
|
+
const blocks = msg.content;
|
|
117
|
+
for (const block of blocks) {
|
|
118
|
+
if (block.type === "text" && block.text) {
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push(...agentMessage(block.text).trimEnd().split("\n"));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return lines;
|
|
126
|
+
}
|
|
79
127
|
function useTerminalSize() {
|
|
80
128
|
const { stdout } = useStdout();
|
|
81
129
|
const [size, setSize] = useState(() => ({
|
|
@@ -117,7 +165,45 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
117
165
|
});
|
|
118
166
|
const [inputValue, setInputValue] = useState("");
|
|
119
167
|
const [currentModel, setCurrentModel] = useState(getModel);
|
|
120
|
-
const [messages, setMessages] = useState(
|
|
168
|
+
const [messages, setMessages] = useState(() => loadConversation(cwd));
|
|
169
|
+
const messagesRef = useRef(messages);
|
|
170
|
+
const hasRestoredLogRef = useRef(false);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
messagesRef.current = messages;
|
|
173
|
+
}, [messages]);
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (messages.length > 0 && !hasRestoredLogRef.current) {
|
|
176
|
+
hasRestoredLogRef.current = true;
|
|
177
|
+
const model = getModel();
|
|
178
|
+
const banner = [
|
|
179
|
+
"",
|
|
180
|
+
matchaGradient(bigLogo),
|
|
181
|
+
colors.accent(` ${model}`) + colors.dim(" · ") + colors.accentPale("OpenRouter") + colors.dim(` · ${cwd}`),
|
|
182
|
+
colors.mutedDark(" / commands ! shell @ files · Ctrl+P palette · Ctrl+C or /q to quit"),
|
|
183
|
+
"",
|
|
184
|
+
];
|
|
185
|
+
setLogLines([...banner, ...replayMessagesToLogLines(messages)]);
|
|
186
|
+
}
|
|
187
|
+
}, [messages, cwd]);
|
|
188
|
+
const saveDebounceRef = useRef(null);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (saveDebounceRef.current)
|
|
191
|
+
clearTimeout(saveDebounceRef.current);
|
|
192
|
+
saveDebounceRef.current = setTimeout(() => {
|
|
193
|
+
saveDebounceRef.current = null;
|
|
194
|
+
saveConversation(cwd, messages);
|
|
195
|
+
}, 500);
|
|
196
|
+
return () => {
|
|
197
|
+
if (saveDebounceRef.current)
|
|
198
|
+
clearTimeout(saveDebounceRef.current);
|
|
199
|
+
};
|
|
200
|
+
}, [cwd, messages]);
|
|
201
|
+
const handleQuit = useCallback(() => {
|
|
202
|
+
if (saveDebounceRef.current)
|
|
203
|
+
clearTimeout(saveDebounceRef.current);
|
|
204
|
+
saveConversation(cwd, messagesRef.current);
|
|
205
|
+
onQuit();
|
|
206
|
+
}, [cwd, onQuit]);
|
|
121
207
|
const [loading, setLoading] = useState(false);
|
|
122
208
|
const [showPalette, setShowPalette] = useState(false);
|
|
123
209
|
const [paletteIndex, setPaletteIndex] = useState(0);
|
|
@@ -133,7 +219,6 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
133
219
|
const skipNextSubmitRef = useRef(false);
|
|
134
220
|
const queuedMessageRef = useRef(null);
|
|
135
221
|
const lastUserMessageRef = useRef("");
|
|
136
|
-
const [lastUserPrompt, setLastUserPrompt] = useState("");
|
|
137
222
|
const [logScrollOffset, setLogScrollOffset] = useState(0);
|
|
138
223
|
const prevEscRef = useRef(false);
|
|
139
224
|
const [spinnerTick, setSpinnerTick] = useState(0);
|
|
@@ -296,9 +381,10 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
296
381
|
setLogScrollOffset(0);
|
|
297
382
|
}
|
|
298
383
|
lastUserMessageRef.current = userInput;
|
|
299
|
-
|
|
384
|
+
appendLog(userPromptBox(userInput));
|
|
385
|
+
appendLog("");
|
|
300
386
|
let state = [...messages, { role: "user", content: userInput }];
|
|
301
|
-
const systemPrompt = `Concise coding assistant. cwd: ${cwd}. Use focused greps (specific patterns, narrow paths) and read in chunks when files are large; avoid one huge grep or read that floods context. When exploring a dependency, set path to that package (e.g. node_modules/<pkg>) and list/read only what you need.`;
|
|
387
|
+
const systemPrompt = `Concise coding assistant. cwd: ${cwd}. Use focused greps (specific patterns, narrow paths) and read in chunks when files are large; avoid one huge grep or read that floods context. When exploring a dependency, set path to that package (e.g. node_modules/<pkg>) and list/read only what you need. Prefer grep or keyword search for the most recent or specific occurrence; avoid tail/read of thousands of lines. If a tool result says it was truncated, call the tool again with offset, limit, or a narrower pattern to get what you need.`;
|
|
302
388
|
const modelContext = modelList.find((m) => m.id === currentModel)?.context_length;
|
|
303
389
|
const maxContextTokens = Math.floor((modelContext ?? CONTEXT_WINDOW_K * 1024) * 0.85);
|
|
304
390
|
const stateBeforeCompress = state;
|
|
@@ -337,7 +423,8 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
337
423
|
preview += "...";
|
|
338
424
|
appendLog(toolResultLine(preview, ok));
|
|
339
425
|
if (block.id) {
|
|
340
|
-
|
|
426
|
+
const contentForApi = truncateToolResult(result);
|
|
427
|
+
toolResults.push({ type: "tool_result", tool_use_id: block.id, content: contentForApi });
|
|
341
428
|
}
|
|
342
429
|
}
|
|
343
430
|
}
|
|
@@ -380,7 +467,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
380
467
|
try {
|
|
381
468
|
const cont = await processInput(value);
|
|
382
469
|
if (!cont) {
|
|
383
|
-
|
|
470
|
+
handleQuit();
|
|
384
471
|
return;
|
|
385
472
|
}
|
|
386
473
|
const queued = queuedMessageRef.current;
|
|
@@ -393,7 +480,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
393
480
|
appendLog(colors.error(`${icons.error} ${err instanceof Error ? err.message : String(err)}`));
|
|
394
481
|
appendLog("");
|
|
395
482
|
}
|
|
396
|
-
}, [processInput,
|
|
483
|
+
}, [processInput, handleQuit, appendLog, openModelSelector, openBraveKeyModal, openHelpModal]);
|
|
397
484
|
useInput((input, key) => {
|
|
398
485
|
if (showHelpModal) {
|
|
399
486
|
setShowHelpModal(false);
|
|
@@ -463,7 +550,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
463
550
|
setShowPalette(false);
|
|
464
551
|
processInput(selected.cmd).then((cont) => {
|
|
465
552
|
if (!cont)
|
|
466
|
-
|
|
553
|
+
handleQuit();
|
|
467
554
|
}).catch((err) => {
|
|
468
555
|
appendLog(colors.error(`${icons.error} ${err instanceof Error ? err.message : String(err)}`));
|
|
469
556
|
appendLog("");
|
|
@@ -514,7 +601,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
514
601
|
}
|
|
515
602
|
processInput(selected.cmd).then((cont) => {
|
|
516
603
|
if (!cont)
|
|
517
|
-
|
|
604
|
+
handleQuit();
|
|
518
605
|
}).catch((err) => {
|
|
519
606
|
appendLog(colors.error(`${icons.error} ${err instanceof Error ? err.message : String(err)}`));
|
|
520
607
|
appendLog("");
|
|
@@ -710,7 +797,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
710
797
|
setShowPalette(true);
|
|
711
798
|
}
|
|
712
799
|
if (key.ctrl && input === "c") {
|
|
713
|
-
|
|
800
|
+
handleQuit();
|
|
714
801
|
}
|
|
715
802
|
});
|
|
716
803
|
if (showModelSelector) {
|
|
@@ -739,11 +826,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
739
826
|
const lines = inputValue.split("\n");
|
|
740
827
|
return lines.reduce((sum, line) => sum + Math.max(1, Math.ceil(line.length / wrapWidth)), 0);
|
|
741
828
|
})();
|
|
742
|
-
const
|
|
743
|
-
const lastPromptLines = lastUserPrompt
|
|
744
|
-
? (lastPromptLineCount > 3 ? 4 : lastPromptLineCount)
|
|
745
|
-
: 0;
|
|
746
|
-
const reservedLines = 1 + lastPromptLines + inputLineCount + (loading ? 2 : 1);
|
|
829
|
+
const reservedLines = 1 + inputLineCount + (loading ? 2 : 1);
|
|
747
830
|
const logViewportHeight = Math.max(1, termRows - reservedLines - suggestionBoxLines);
|
|
748
831
|
const maxLogScrollOffset = Math.max(0, logLines.length - logViewportHeight);
|
|
749
832
|
const logStartIndex = Math.max(0, logLines.length - logViewportHeight - Math.min(logScrollOffset, maxLogScrollOffset));
|
|
@@ -768,12 +851,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
768
851
|
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsx(Box, { height: topPad }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: leftPad }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: inkColors.primary, paddingX: 2, paddingY: 1, width: paletteModalWidth, minHeight: paletteModalHeight, children: [_jsx(Text, { bold: true, children: " Command palette " }), COMMANDS.map((c, i) => (_jsxs(Text, { color: i === paletteIndex ? inkColors.primary : undefined, children: [i === paletteIndex ? "› " : " ", c.cmd, _jsxs(Text, { color: "gray", children: [" \u2014 ", c.desc] })] }, c.cmd))), _jsxs(Text, { color: paletteIndex === COMMANDS.length ? inkColors.primary : undefined, children: [paletteIndex === COMMANDS.length ? "› " : " ", "Cancel (Esc)"] }), _jsx(Text, { color: "gray", children: " \u2191/\u2193 select, Enter confirm, Esc close " })] })] }), _jsx(Box, { flexGrow: 1 })] }));
|
|
769
852
|
}
|
|
770
853
|
const footerLines = suggestionBoxLines + 1 + inputLineCount;
|
|
771
|
-
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", children: [
|
|
772
|
-
const lines = lastUserPrompt.split("\n");
|
|
773
|
-
const showLines = lines.slice(0, 3);
|
|
774
|
-
const hasMore = lines.length > 3;
|
|
775
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: inkColors.primary, dimColor: true, children: [icons.prompt, " Last:", " "] }), _jsx(Text, { dimColor: true, color: "gray", children: showLines[0] ?? "" })] }), showLines.slice(1).map((ln, i) => (_jsx(Box, { flexDirection: "row", paddingLeft: 8, children: _jsx(Text, { dimColor: true, color: "gray", children: ln }) }, i))), hasMore && (_jsx(Box, { flexDirection: "row", paddingLeft: 8, children: _jsx(Text, { dimColor: true, color: "gray", children: "\u2026" }) }))] }));
|
|
776
|
-
})() })) : null, _jsx(Box, { flexDirection: "column", height: logViewportHeight, overflow: "hidden", children: visibleLogLines.map((line, i) => (_jsx(Text, { children: line === "" ? "\u00A0" : line }, logLines.length - visibleLogLines.length + i))) }), loading && (_jsx(Box, { flexDirection: "row", marginTop: 1, marginBottom: 0, children: _jsxs(Text, { color: "gray", children: [" ", SPINNER[spinnerTick % SPINNER.length], " Thinking\u2026"] }) }))] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: footerLines, children: [showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: "gray", children: [filteredSlashCommands.length === 0 ? (_jsx(Text, { color: "gray", children: " No match " })) : ([...filteredSlashCommands].reverse().map((c, rev) => {
|
|
854
|
+
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", children: [_jsx(Box, { flexDirection: "column", height: logViewportHeight, overflow: "hidden", children: visibleLogLines.map((line, i) => (_jsx(Text, { children: line === "" ? "\u00A0" : line }, logLines.length - visibleLogLines.length + i))) }), loading && (_jsx(Box, { flexDirection: "row", marginTop: 1, marginBottom: 0, children: _jsxs(Text, { color: "gray", children: [" ", SPINNER[spinnerTick % SPINNER.length], " Thinking\u2026"] }) }))] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: footerLines, children: [showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: "gray", children: [filteredSlashCommands.length === 0 ? (_jsx(Text, { color: "gray", children: " No match " })) : ([...filteredSlashCommands].reverse().map((c, rev) => {
|
|
777
855
|
const i = filteredSlashCommands.length - 1 - rev;
|
|
778
856
|
return (_jsxs(Text, { color: i === clampedSlashIndex ? inkColors.primary : undefined, children: [i === clampedSlashIndex ? "› " : " ", c.cmd, _jsxs(Text, { color: "gray", children: [" \u2014 ", c.desc] })] }, c.cmd));
|
|
779
857
|
})), _jsx(Text, { color: "gray", children: " Commands (\u2191/\u2193 select, Enter run, Esc clear) " })] })), cursorInAtSegment && !showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: "gray", children: [filteredFilePaths.length === 0 ? (_jsxs(Text, { color: "gray", children: [" ", hasCharsAfterAt ? "No match" : "Type to search files", " "] })) : ([...filteredFilePaths].reverse().map((p, rev) => {
|
package/dist/tools/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { runBash } from "./bash.js";
|
|
|
5
5
|
import { webFetch, webSearch } from "./web.js";
|
|
6
6
|
export const TOOLS = {
|
|
7
7
|
read: [
|
|
8
|
-
"Read file with line numbers (file path, not directory). Use limit to read a portion; avoid reading huge files in one go.",
|
|
8
|
+
"Read file with line numbers (file path, not directory). Use limit and offset to read a portion; avoid reading huge files in one go. Long output is truncated; use offset/limit to get more.",
|
|
9
9
|
{ path: "string", offset: "number?", limit: "number?" },
|
|
10
10
|
readFile,
|
|
11
11
|
],
|
|
@@ -21,11 +21,15 @@ export const TOOLS = {
|
|
|
21
21
|
globFiles,
|
|
22
22
|
],
|
|
23
23
|
grep: [
|
|
24
|
-
"Search files for regex. With path '.' (default), .gitignore entries are excluded; use path node_modules/<pkg> to search one package.
|
|
24
|
+
"Search files for regex. Prefer specific patterns and narrow path; search for the most recent or relevant occurrence by keyword. With path '.' (default), .gitignore entries are excluded; use path node_modules/<pkg> to search one package. Returns at most limit matches (default 50, max 100). Long output is truncated.",
|
|
25
25
|
{ pat: "string", path: "string?", limit: "number?" },
|
|
26
26
|
grepFiles,
|
|
27
27
|
],
|
|
28
|
-
bash: [
|
|
28
|
+
bash: [
|
|
29
|
+
"Run shell command. Prefer targeted commands (e.g. grep, head, tail with small line count); avoid tail -1000 or dumping huge output.",
|
|
30
|
+
{ cmd: "string" },
|
|
31
|
+
runBash,
|
|
32
|
+
],
|
|
29
33
|
web_fetch: [
|
|
30
34
|
"Fetch a URL and return the main text content (handles JS-rendered pages). Use for docs, raw GitHub, any web page.",
|
|
31
35
|
{ url: "string" },
|
package/dist/ui/format.js
CHANGED
|
@@ -40,6 +40,41 @@ export function header(title, subtitle) {
|
|
|
40
40
|
borderStyle: "round",
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
|
+
function wrapToWidth(text, width) {
|
|
44
|
+
const lines = [];
|
|
45
|
+
for (const line of text.split("\n")) {
|
|
46
|
+
if (line.length <= width) {
|
|
47
|
+
lines.push(line);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
let rest = line;
|
|
51
|
+
while (rest.length > 0) {
|
|
52
|
+
if (rest.length <= width) {
|
|
53
|
+
lines.push(rest);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
const chunk = rest.slice(0, width);
|
|
57
|
+
const lastSpace = chunk.lastIndexOf(" ");
|
|
58
|
+
const breakAt = lastSpace > width >> 1 ? lastSpace : width;
|
|
59
|
+
lines.push(rest.slice(0, breakAt).trimEnd());
|
|
60
|
+
rest = rest.slice(breakAt).trimStart();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
export function userPromptBox(prompt) {
|
|
66
|
+
const cols = process.stdout.columns ?? 80;
|
|
67
|
+
const boxWidth = Math.max(20, cols - 4);
|
|
68
|
+
const innerWidth = boxWidth - 2 - 2;
|
|
69
|
+
const text = wrapToWidth((prompt.trim() || "\u00A0"), innerWidth);
|
|
70
|
+
return boxen(text, {
|
|
71
|
+
width: boxWidth,
|
|
72
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
73
|
+
margin: { bottom: 0 },
|
|
74
|
+
borderColor: inkColors.primary,
|
|
75
|
+
borderStyle: "round",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
43
78
|
const TOOL_INDENT = " ";
|
|
44
79
|
const toolSubdued = chalk.hex("#3d3d3d");
|
|
45
80
|
export function toolCallBox(toolName, argPreview, success = true) {
|
package/dist/ui/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { colors, icons, theme, inkColors, separator, agentMessage, toolCallBox, toolResultLine, bashOutputLine, renderMarkdown, header, } from "./format.js";
|
|
1
|
+
export { colors, icons, theme, inkColors, separator, agentMessage, toolCallBox, toolResultLine, userPromptBox, bashOutputLine, renderMarkdown, header, } from "./format.js";
|