ideacode 1.0.4 → 1.1.1
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/index.js +6 -0
- package/dist/repl.js +101 -21
- package/dist/ui/format.js +35 -0
- package/dist/ui/index.js +1 -1
- package/dist/version.js +83 -0
- 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/index.js
CHANGED
|
@@ -3,9 +3,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import "dotenv/config";
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
import { getApiKey } from "./config.js";
|
|
6
|
+
import { getVersion } from "./version.js";
|
|
6
7
|
import { runOnboarding } from "./onboarding.js";
|
|
7
8
|
import { Repl } from "./repl.js";
|
|
8
9
|
async function main() {
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
12
|
+
console.log(getVersion());
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
9
15
|
let apiKey = getApiKey();
|
|
10
16
|
if (!apiKey) {
|
|
11
17
|
await runOnboarding();
|
package/dist/repl.js
CHANGED
|
@@ -10,11 +10,13 @@ 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";
|
|
15
|
+
import { getVersion, checkForUpdate } from "./version.js";
|
|
14
16
|
import { estimateTokens, ensureUnderBudget } from "./context.js";
|
|
15
17
|
import { runTool } from "./tools/index.js";
|
|
16
18
|
import { COMMANDS, matchCommand, resolveCommand } from "./commands.js";
|
|
17
|
-
import { colors, icons, separator, agentMessage, toolCallBox, toolResultLine, inkColors, } from "./ui/index.js";
|
|
19
|
+
import { colors, icons, separator, agentMessage, toolCallBox, toolResultLine, userPromptBox, inkColors, } from "./ui/index.js";
|
|
18
20
|
function wordStartBackward(value, cursor) {
|
|
19
21
|
let i = cursor - 1;
|
|
20
22
|
while (i >= 0 && /\s/.test(value[i]))
|
|
@@ -83,6 +85,46 @@ function wrapLine(line, width) {
|
|
|
83
85
|
}
|
|
84
86
|
return out.length > 0 ? out : [""];
|
|
85
87
|
}
|
|
88
|
+
function replayMessagesToLogLines(messages) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
for (let i = 0; i < messages.length; i++) {
|
|
91
|
+
const msg = messages[i];
|
|
92
|
+
if (msg.role === "user") {
|
|
93
|
+
if (typeof msg.content === "string") {
|
|
94
|
+
lines.push(...userPromptBox(msg.content).split("\n"), "");
|
|
95
|
+
}
|
|
96
|
+
else if (Array.isArray(msg.content)) {
|
|
97
|
+
const prev = messages[i - 1];
|
|
98
|
+
const toolResults = msg.content;
|
|
99
|
+
if (prev?.role === "assistant" && Array.isArray(prev.content)) {
|
|
100
|
+
const blocks = prev.content;
|
|
101
|
+
const toolUses = blocks.filter((b) => b.type === "tool_use");
|
|
102
|
+
for (const tr of toolResults) {
|
|
103
|
+
const block = toolUses.find((b) => b.id === tr.tool_use_id);
|
|
104
|
+
if (block?.name) {
|
|
105
|
+
const firstVal = block.input && typeof block.input === "object" ? Object.values(block.input)[0] : undefined;
|
|
106
|
+
const argPreview = String(firstVal ?? "").slice(0, 50);
|
|
107
|
+
const ok = !(tr.content ?? "").startsWith("error:");
|
|
108
|
+
lines.push(toolCallBox(block.name, argPreview, ok));
|
|
109
|
+
const preview = (tr.content ?? "").split("\n")[0]?.slice(0, 60) ?? "";
|
|
110
|
+
lines.push(toolResultLine(preview, ok));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
117
|
+
const blocks = msg.content;
|
|
118
|
+
for (const block of blocks) {
|
|
119
|
+
if (block.type === "text" && block.text) {
|
|
120
|
+
lines.push("");
|
|
121
|
+
lines.push(...agentMessage(block.text).trimEnd().split("\n"));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return lines;
|
|
127
|
+
}
|
|
86
128
|
function useTerminalSize() {
|
|
87
129
|
const { stdout } = useStdout();
|
|
88
130
|
const [size, setSize] = useState(() => ({
|
|
@@ -114,17 +156,57 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
114
156
|
`;
|
|
115
157
|
const [logLines, setLogLines] = useState(() => {
|
|
116
158
|
const model = getModel();
|
|
159
|
+
const version = getVersion();
|
|
117
160
|
return [
|
|
118
161
|
"",
|
|
119
162
|
matchaGradient(bigLogo),
|
|
120
|
-
colors.accent(` ${
|
|
163
|
+
colors.accent(` ideacode v${version}`) + colors.dim(" · ") + colors.accentPale(model) + colors.dim(" · ") + colors.bold("OpenRouter") + colors.dim(` · ${cwd}`),
|
|
121
164
|
colors.mutedDark(" / commands ! shell @ files · Ctrl+P palette · Ctrl+C or /q to quit"),
|
|
122
165
|
"",
|
|
123
166
|
];
|
|
124
167
|
});
|
|
125
168
|
const [inputValue, setInputValue] = useState("");
|
|
126
169
|
const [currentModel, setCurrentModel] = useState(getModel);
|
|
127
|
-
const [messages, setMessages] = useState(
|
|
170
|
+
const [messages, setMessages] = useState(() => loadConversation(cwd));
|
|
171
|
+
const messagesRef = useRef(messages);
|
|
172
|
+
const hasRestoredLogRef = useRef(false);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
messagesRef.current = messages;
|
|
175
|
+
}, [messages]);
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (messages.length > 0 && !hasRestoredLogRef.current) {
|
|
178
|
+
hasRestoredLogRef.current = true;
|
|
179
|
+
const model = getModel();
|
|
180
|
+
const version = getVersion();
|
|
181
|
+
const banner = [
|
|
182
|
+
"",
|
|
183
|
+
matchaGradient(bigLogo),
|
|
184
|
+
colors.accent(` ideacode v${version}`) + colors.dim(" · ") + colors.accent(model) + colors.dim(" · ") + colors.accentPale("OpenRouter") + colors.dim(` · ${cwd}`),
|
|
185
|
+
colors.mutedDark(" / commands ! shell @ files · Ctrl+P palette · Ctrl+C or /q to quit"),
|
|
186
|
+
"",
|
|
187
|
+
];
|
|
188
|
+
setLogLines([...banner, ...replayMessagesToLogLines(messages)]);
|
|
189
|
+
}
|
|
190
|
+
}, [messages, cwd]);
|
|
191
|
+
const saveDebounceRef = useRef(null);
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (saveDebounceRef.current)
|
|
194
|
+
clearTimeout(saveDebounceRef.current);
|
|
195
|
+
saveDebounceRef.current = setTimeout(() => {
|
|
196
|
+
saveDebounceRef.current = null;
|
|
197
|
+
saveConversation(cwd, messages);
|
|
198
|
+
}, 500);
|
|
199
|
+
return () => {
|
|
200
|
+
if (saveDebounceRef.current)
|
|
201
|
+
clearTimeout(saveDebounceRef.current);
|
|
202
|
+
};
|
|
203
|
+
}, [cwd, messages]);
|
|
204
|
+
const handleQuit = useCallback(() => {
|
|
205
|
+
if (saveDebounceRef.current)
|
|
206
|
+
clearTimeout(saveDebounceRef.current);
|
|
207
|
+
saveConversation(cwd, messagesRef.current);
|
|
208
|
+
onQuit();
|
|
209
|
+
}, [cwd, onQuit]);
|
|
128
210
|
const [loading, setLoading] = useState(false);
|
|
129
211
|
const [showPalette, setShowPalette] = useState(false);
|
|
130
212
|
const [paletteIndex, setPaletteIndex] = useState(0);
|
|
@@ -140,7 +222,6 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
140
222
|
const skipNextSubmitRef = useRef(false);
|
|
141
223
|
const queuedMessageRef = useRef(null);
|
|
142
224
|
const lastUserMessageRef = useRef("");
|
|
143
|
-
const [lastUserPrompt, setLastUserPrompt] = useState("");
|
|
144
225
|
const [logScrollOffset, setLogScrollOffset] = useState(0);
|
|
145
226
|
const prevEscRef = useRef(false);
|
|
146
227
|
const [spinnerTick, setSpinnerTick] = useState(0);
|
|
@@ -221,6 +302,13 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
221
302
|
lines.shift();
|
|
222
303
|
setLogLines((prev) => [...prev, ...lines]);
|
|
223
304
|
}, []);
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
const version = getVersion();
|
|
307
|
+
checkForUpdate(version, (latest) => {
|
|
308
|
+
appendLog(colors.warn(` Update available: ideacode ${latest} (you have ${version}). Run: npm i -g ideacode`));
|
|
309
|
+
appendLog("");
|
|
310
|
+
});
|
|
311
|
+
}, [appendLog]);
|
|
224
312
|
const braveKeyHadExistingRef = useRef(false);
|
|
225
313
|
const BRAVE_KEY_PLACEHOLDER = "••••••••";
|
|
226
314
|
const openBraveKeyModal = useCallback(() => {
|
|
@@ -303,7 +391,8 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
303
391
|
setLogScrollOffset(0);
|
|
304
392
|
}
|
|
305
393
|
lastUserMessageRef.current = userInput;
|
|
306
|
-
|
|
394
|
+
appendLog(userPromptBox(userInput));
|
|
395
|
+
appendLog("");
|
|
307
396
|
let state = [...messages, { role: "user", content: userInput }];
|
|
308
397
|
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.`;
|
|
309
398
|
const modelContext = modelList.find((m) => m.id === currentModel)?.context_length;
|
|
@@ -388,7 +477,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
388
477
|
try {
|
|
389
478
|
const cont = await processInput(value);
|
|
390
479
|
if (!cont) {
|
|
391
|
-
|
|
480
|
+
handleQuit();
|
|
392
481
|
return;
|
|
393
482
|
}
|
|
394
483
|
const queued = queuedMessageRef.current;
|
|
@@ -401,7 +490,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
401
490
|
appendLog(colors.error(`${icons.error} ${err instanceof Error ? err.message : String(err)}`));
|
|
402
491
|
appendLog("");
|
|
403
492
|
}
|
|
404
|
-
}, [processInput,
|
|
493
|
+
}, [processInput, handleQuit, appendLog, openModelSelector, openBraveKeyModal, openHelpModal]);
|
|
405
494
|
useInput((input, key) => {
|
|
406
495
|
if (showHelpModal) {
|
|
407
496
|
setShowHelpModal(false);
|
|
@@ -471,7 +560,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
471
560
|
setShowPalette(false);
|
|
472
561
|
processInput(selected.cmd).then((cont) => {
|
|
473
562
|
if (!cont)
|
|
474
|
-
|
|
563
|
+
handleQuit();
|
|
475
564
|
}).catch((err) => {
|
|
476
565
|
appendLog(colors.error(`${icons.error} ${err instanceof Error ? err.message : String(err)}`));
|
|
477
566
|
appendLog("");
|
|
@@ -522,7 +611,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
522
611
|
}
|
|
523
612
|
processInput(selected.cmd).then((cont) => {
|
|
524
613
|
if (!cont)
|
|
525
|
-
|
|
614
|
+
handleQuit();
|
|
526
615
|
}).catch((err) => {
|
|
527
616
|
appendLog(colors.error(`${icons.error} ${err instanceof Error ? err.message : String(err)}`));
|
|
528
617
|
appendLog("");
|
|
@@ -718,7 +807,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
718
807
|
setShowPalette(true);
|
|
719
808
|
}
|
|
720
809
|
if (key.ctrl && input === "c") {
|
|
721
|
-
|
|
810
|
+
handleQuit();
|
|
722
811
|
}
|
|
723
812
|
});
|
|
724
813
|
if (showModelSelector) {
|
|
@@ -747,11 +836,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
747
836
|
const lines = inputValue.split("\n");
|
|
748
837
|
return lines.reduce((sum, line) => sum + Math.max(1, Math.ceil(line.length / wrapWidth)), 0);
|
|
749
838
|
})();
|
|
750
|
-
const
|
|
751
|
-
const lastPromptLines = lastUserPrompt
|
|
752
|
-
? (lastPromptLineCount > 3 ? 4 : lastPromptLineCount)
|
|
753
|
-
: 0;
|
|
754
|
-
const reservedLines = 1 + lastPromptLines + inputLineCount + (loading ? 2 : 1);
|
|
839
|
+
const reservedLines = 1 + inputLineCount + (loading ? 2 : 1);
|
|
755
840
|
const logViewportHeight = Math.max(1, termRows - reservedLines - suggestionBoxLines);
|
|
756
841
|
const maxLogScrollOffset = Math.max(0, logLines.length - logViewportHeight);
|
|
757
842
|
const logStartIndex = Math.max(0, logLines.length - logViewportHeight - Math.min(logScrollOffset, maxLogScrollOffset));
|
|
@@ -776,12 +861,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
776
861
|
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 })] }));
|
|
777
862
|
}
|
|
778
863
|
const footerLines = suggestionBoxLines + 1 + inputLineCount;
|
|
779
|
-
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", children: [
|
|
780
|
-
const lines = lastUserPrompt.split("\n");
|
|
781
|
-
const showLines = lines.slice(0, 3);
|
|
782
|
-
const hasMore = lines.length > 3;
|
|
783
|
-
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" }) }))] }));
|
|
784
|
-
})() })) : 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) => {
|
|
864
|
+
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) => {
|
|
785
865
|
const i = filteredSlashCommands.length - 1 - rev;
|
|
786
866
|
return (_jsxs(Text, { color: i === clampedSlashIndex ? inkColors.primary : undefined, children: [i === clampedSlashIndex ? "› " : " ", c.cmd, _jsxs(Text, { color: "gray", children: [" \u2014 ", c.desc] })] }, c.cmd));
|
|
787
867
|
})), _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/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";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { getConfigDir } from "./config.js";
|
|
5
|
+
const OWN_PACKAGE_JSON = (() => {
|
|
6
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
return path.join(dir, "..", "package.json");
|
|
8
|
+
})();
|
|
9
|
+
export function getVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const raw = fs.readFileSync(OWN_PACKAGE_JSON, "utf-8");
|
|
12
|
+
const pkg = JSON.parse(raw);
|
|
13
|
+
return pkg.version ?? "0.0.0";
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return "0.0.0";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function parseSemver(s) {
|
|
20
|
+
const parts = s.replace(/^v/, "").split(".").map((p) => parseInt(p, 10) || 0);
|
|
21
|
+
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
22
|
+
}
|
|
23
|
+
function isNewer(latest, current) {
|
|
24
|
+
const a = parseSemver(latest);
|
|
25
|
+
const b = parseSemver(current);
|
|
26
|
+
for (let i = 0; i < 3; i++) {
|
|
27
|
+
if (a[i] > b[i])
|
|
28
|
+
return true;
|
|
29
|
+
if (a[i] < b[i])
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const UPDATE_CHECK_FILE = "last-update-check.json";
|
|
35
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
function shouldSkipCheck() {
|
|
37
|
+
try {
|
|
38
|
+
const file = path.join(getConfigDir(), UPDATE_CHECK_FILE);
|
|
39
|
+
const raw = fs.readFileSync(file, "utf-8");
|
|
40
|
+
const data = JSON.parse(raw);
|
|
41
|
+
const last = data.lastCheck ?? 0;
|
|
42
|
+
return Date.now() - last < CHECK_INTERVAL_MS;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function markCheckDone() {
|
|
49
|
+
try {
|
|
50
|
+
const dir = getConfigDir();
|
|
51
|
+
const file = path.join(dir, UPDATE_CHECK_FILE);
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
fs.writeFileSync(file, JSON.stringify({ lastCheck: Date.now() }), "utf-8");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* ignore */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function checkForUpdate(currentVersion, onNewVersion) {
|
|
60
|
+
if (process.env.IDEACODE_SHOW_UPDATE_NOTICE) {
|
|
61
|
+
onNewVersion(process.env.IDEACODE_SHOW_UPDATE_NOTICE || "99.0.0");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (shouldSkipCheck())
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch("https://registry.npmjs.org/ideacode/latest", {
|
|
68
|
+
headers: { Accept: "application/json" },
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok)
|
|
71
|
+
return;
|
|
72
|
+
const data = (await res.json());
|
|
73
|
+
const latest = data.version;
|
|
74
|
+
if (!latest || typeof latest !== "string")
|
|
75
|
+
return;
|
|
76
|
+
markCheckDone();
|
|
77
|
+
if (isNewer(latest, currentVersion))
|
|
78
|
+
onNewVersion(latest);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* ignore */
|
|
82
|
+
}
|
|
83
|
+
}
|