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 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(` ${model}`) + colors.dim(" · ") + colors.accentPale("OpenRouter") + colors.dim(` · ${cwd}`),
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
- setLastUserPrompt(userInput);
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
- onQuit();
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, onQuit, appendLog, openModelSelector, openBraveKeyModal, openHelpModal]);
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
- onQuit();
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
- onQuit();
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
- onQuit();
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 lastPromptLineCount = lastUserPrompt ? lastUserPrompt.split("\n").length : 0;
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: [lastUserPrompt ? (_jsx(Box, { flexDirection: "column", 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";
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ideacode",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "description": "CLI TUI for AI agents via OpenRouter — agentic loop, tools, markdown",
5
5
  "type": "module",
6
6
  "repository": {