ideacode 1.3.3 → 1.3.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/dist/repl.js CHANGED
@@ -52,6 +52,7 @@ const LOADING_TICK_MS = 80;
52
52
  const MAX_EMPTY_ASSISTANT_RETRIES = 3;
53
53
  const TYPING_LAYOUT_FREEZE_MS = 120;
54
54
  const INPUT_COMMIT_INTERVAL_MS = 45;
55
+ const SLASH_SUGGESTION_ROWS = Math.max(1, COMMANDS.length);
55
56
  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.)";
56
57
  function truncateToolResult(content) {
57
58
  if (content.length <= MAX_TOOL_RESULT_CHARS)
@@ -996,11 +997,11 @@ export function Repl({ apiKey, cwd, onQuit }) {
996
997
  }
997
998
  if (showSlashSuggestions && filteredSlashCommands.length > 0) {
998
999
  if (key.upArrow) {
999
- setSlashSuggestionIndex((i) => Math.max(0, i - 1));
1000
+ setSlashSuggestionIndex((i) => Math.min(filteredSlashCommands.length - 1, i + 1));
1000
1001
  return;
1001
1002
  }
1002
1003
  if (key.downArrow) {
1003
- setSlashSuggestionIndex((i) => Math.min(filteredSlashCommands.length - 1, i + 1));
1004
+ setSlashSuggestionIndex((i) => Math.max(0, i - 1));
1004
1005
  return;
1005
1006
  }
1006
1007
  if (key.tab) {
@@ -1239,22 +1240,8 @@ export function Repl({ apiKey, cwd, onQuit }) {
1239
1240
  handleQuit();
1240
1241
  }
1241
1242
  });
1242
- if (showModelSelector) {
1243
- const modelModalMaxHeight = 18;
1244
- const modelModalWidth = 108;
1245
- const modelModalHeight = Math.min(filteredModelList.length + 4, modelModalMaxHeight);
1246
- const topPad = Math.max(0, Math.floor((termRows - modelModalHeight) / 2));
1247
- const leftPad = Math.max(0, Math.floor((termColumns - modelModalWidth) / 2));
1248
- const visibleModelCount = Math.min(filteredModelList.length, modelModalHeight - 4);
1249
- const modelScrollOffset = Math.max(0, Math.min(modelIndex - Math.floor(visibleModelCount / 2), filteredModelList.length - visibleModelCount));
1250
- const visibleModels = filteredModelList.slice(modelScrollOffset, modelScrollOffset + visibleModelCount);
1251
- 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: modelModalWidth, minHeight: modelModalHeight, children: [_jsx(Text, { bold: true, children: " Select model " }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: inkColors.textSecondary, children: " Filter: " }), _jsx(Text, { children: modelSearchFilter || " " }), modelSearchFilter.length > 0 && (_jsxs(Text, { color: inkColors.textDisabled, children: [" ", "(", filteredModelList.length, " match", filteredModelList.length !== 1 ? "es" : "", ")"] }))] }), visibleModels.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match \u2014 type to search by id or name " })) : (visibleModels.map((m, i) => {
1252
- const actualIndex = modelScrollOffset + i;
1253
- return (_jsxs(Text, { color: actualIndex === modelIndex ? inkColors.primary : undefined, children: [actualIndex === modelIndex ? "› " : " ", m.name ? `${m.id} — ${m.name}` : m.id] }, m.id));
1254
- })), _jsx(Text, { color: inkColors.textSecondary, children: " \u2191/\u2193 select Enter confirm Esc cancel Type to filter " })] })] }), _jsx(Box, { flexGrow: 1 })] }));
1255
- }
1256
1243
  const slashSuggestionBoxLines = showSlashSuggestions
1257
- ? 3 + Math.max(1, filteredSlashCommands.length)
1244
+ ? 3 + SLASH_SUGGESTION_ROWS
1258
1245
  : 0;
1259
1246
  const atSuggestionBoxLines = cursorInAtSegment
1260
1247
  ? 4 + Math.max(1, filteredFilePaths.length)
@@ -1273,6 +1260,28 @@ export function Repl({ apiKey, cwd, onQuit }) {
1273
1260
  const sliceEnd = logStartIndex + logViewportHeight;
1274
1261
  const visibleLogLines = useMemo(() => effectiveLogLines.slice(logStartIndex, sliceEnd), [effectiveLogLines, logStartIndex, sliceEnd]);
1275
1262
  const useSimpleInputRenderer = inputLineCount > 1;
1263
+ const calculatedFooterLines = suggestionBoxLines + 1 + layoutInputLineCount;
1264
+ useEffect(() => {
1265
+ if (!isInputLayoutFrozen) {
1266
+ setFrozenFooterLines(calculatedFooterLines);
1267
+ }
1268
+ }, [isInputLayoutFrozen, calculatedFooterLines]);
1269
+ const footerLines = isInputLayoutFrozen ? frozenFooterLines : calculatedFooterLines;
1270
+ loadingFooterLinesRef.current = footerLines;
1271
+ if (showModelSelector) {
1272
+ const modelModalMaxHeight = 18;
1273
+ const modelModalWidth = 108;
1274
+ const modelModalHeight = Math.min(filteredModelList.length + 4, modelModalMaxHeight);
1275
+ const topPad = Math.max(0, Math.floor((termRows - modelModalHeight) / 2));
1276
+ const leftPad = Math.max(0, Math.floor((termColumns - modelModalWidth) / 2));
1277
+ const visibleModelCount = Math.min(filteredModelList.length, modelModalHeight - 4);
1278
+ const modelScrollOffset = Math.max(0, Math.min(modelIndex - Math.floor(visibleModelCount / 2), filteredModelList.length - visibleModelCount));
1279
+ const visibleModels = filteredModelList.slice(modelScrollOffset, modelScrollOffset + visibleModelCount);
1280
+ 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: modelModalWidth, minHeight: modelModalHeight, children: [_jsx(Text, { bold: true, children: " Select model " }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: inkColors.textSecondary, children: " Filter: " }), _jsx(Text, { children: modelSearchFilter || " " }), modelSearchFilter.length > 0 && (_jsxs(Text, { color: inkColors.textDisabled, children: [" ", "(", filteredModelList.length, " match", filteredModelList.length !== 1 ? "es" : "", ")"] }))] }), visibleModels.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match \u2014 type to search by id or name " })) : (visibleModels.map((m, i) => {
1281
+ const actualIndex = modelScrollOffset + i;
1282
+ return (_jsxs(Text, { color: actualIndex === modelIndex ? inkColors.primary : undefined, children: [actualIndex === modelIndex ? "› " : " ", m.name ? `${m.id} — ${m.name}` : m.id] }, m.id));
1283
+ })), _jsx(Text, { color: inkColors.textSecondary, children: " \u2191/\u2193 select Enter confirm Esc cancel Type to filter " })] })] }), _jsx(Box, { flexGrow: 1 })] }));
1284
+ }
1276
1285
  if (showHelpModal) {
1277
1286
  const helpModalWidth = Math.min(88, Math.max(80, termColumns - 4));
1278
1287
  const helpContentRows = 20;
@@ -1295,21 +1304,15 @@ export function Repl({ apiKey, cwd, onQuit }) {
1295
1304
  const leftPad = Math.max(0, Math.floor((termColumns - paletteModalWidth) / 2));
1296
1305
  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: inkColors.textSecondary, children: [" \u2014 ", c.desc] })] }, c.cmd))), _jsxs(Text, { color: paletteIndex === COMMANDS.length ? inkColors.primary : undefined, children: [paletteIndex === COMMANDS.length ? "› " : " ", "Cancel (Esc)"] }), _jsx(Text, { color: inkColors.textSecondary, children: " \u2191/\u2193 select, Enter confirm, Esc close " })] })] }), _jsx(Box, { flexGrow: 1 })] }));
1297
1306
  }
1298
- const calculatedFooterLines = suggestionBoxLines + 1 + layoutInputLineCount;
1299
- useEffect(() => {
1300
- if (!isInputLayoutFrozen) {
1301
- setFrozenFooterLines(calculatedFooterLines);
1302
- }
1303
- }, [isInputLayoutFrozen, calculatedFooterLines]);
1304
- const footerLines = isInputLayoutFrozen ? frozenFooterLines : calculatedFooterLines;
1305
- loadingFooterLinesRef.current = footerLines;
1306
- return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", children: [_jsx(LogViewport, { lines: visibleLogLines, startIndex: logStartIndex, height: logViewportHeight }), _jsx(Box, { flexDirection: "row", marginTop: 1, marginBottom: 0, children: _jsx(Text, { color: inkColors.textSecondary, children: "\u00A0" }) })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: footerLines, children: [showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: inkColors.textDisabled, children: [filteredSlashCommands.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match " })) : ([...filteredSlashCommands].reverse().map((c, rev) => {
1307
- const i = filteredSlashCommands.length - 1 - rev;
1307
+ return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", children: [_jsx(LogViewport, { lines: visibleLogLines, startIndex: logStartIndex, height: logViewportHeight }), _jsx(Box, { flexDirection: "row", marginTop: 1, marginBottom: 0, children: _jsx(Text, { color: inkColors.textSecondary, children: "\u00A0" }) })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: footerLines, children: [showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: inkColors.textDisabled, height: slashSuggestionBoxLines, children: [filteredSlashCommands.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match " })) : ([...filteredSlashCommands.slice(0, SLASH_SUGGESTION_ROWS)].reverse().map((c, rev) => {
1308
+ const i = Math.min(filteredSlashCommands.length, SLASH_SUGGESTION_ROWS) - 1 - rev;
1308
1309
  return (_jsxs(Text, { color: i === clampedSlashIndex ? inkColors.primary : undefined, children: [i === clampedSlashIndex ? "› " : " ", c.cmd, _jsxs(Text, { color: inkColors.textSecondary, children: [" \u2014 ", c.desc] })] }, c.cmd));
1309
- })), _jsx(Text, { color: inkColors.textSecondary, children: " Commands (\u2191/\u2193 select, Enter run, Esc clear) " })] })), cursorInAtSegment && !showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: inkColors.textDisabled, children: [filteredFilePaths.length === 0 ? (_jsxs(Text, { color: inkColors.textSecondary, children: [" ", hasCharsAfterAt ? "No match" : "Type to search files", " "] })) : ([...filteredFilePaths].reverse().map((p, rev) => {
1310
+ })), Array.from({
1311
+ length: Math.max(0, SLASH_SUGGESTION_ROWS - Math.min(filteredSlashCommands.length, SLASH_SUGGESTION_ROWS)),
1312
+ }).map((_, idx) => (_jsx(Text, { children: "\u00A0" }, `slash-pad-${idx}`))), _jsx(Text, { color: inkColors.textSecondary, children: " Commands (\u2191/\u2193 select, Enter run, Esc clear) " })] })), cursorInAtSegment && !showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: inkColors.textDisabled, children: [filteredFilePaths.length === 0 ? (_jsxs(Text, { color: inkColors.textSecondary, children: [" ", hasCharsAfterAt ? "No match" : "Type to search files", " "] })) : ([...filteredFilePaths].reverse().map((p, rev) => {
1310
1313
  const i = filteredFilePaths.length - 1 - rev;
1311
1314
  return (_jsxs(Text, { color: i === clampedAtFileIndex ? inkColors.primary : undefined, children: [i === clampedAtFileIndex ? "› " : " ", p] }, p));
1312
- })), _jsx(Box, { flexDirection: "row", marginTop: 1, children: _jsx(Text, { color: inkColors.textSecondary, children: " Files (\u2191/\u2193 select, Enter/Tab complete, Esc clear) " }) })] })), _jsxs(Box, { flexDirection: "row", marginTop: 0, children: [_jsxs(Text, { color: inkColors.mutedDark, children: [" ", icons.tool, " ", tokenDisplay] }), _jsx(Text, { color: inkColors.mutedDark, children: ` · / ! @ trackpad/↑/↓ scroll Ctrl+J newline Tab queue Esc Esc edit` })] }), _jsx(Box, { flexDirection: "column", marginTop: 0, children: inputValue.length === 0 ? (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: inkColors.primary, children: [icons.prompt, " "] }), cursorBlinkOn ? (_jsx(Text, { inverse: true, color: inkColors.primary, children: " " })) : (_jsx(Text, { color: inkColors.primary, children: " " })), _jsx(Text, { color: inkColors.textSecondary, children: "Message or / for commands, @ for files, ! for shell, ? for help..." })] })) : ((() => {
1315
+ })), _jsx(Box, { flexDirection: "row", marginTop: 1, children: _jsx(Text, { color: inkColors.textSecondary, children: " Files (\u2191/\u2193 select, Enter/Tab complete, Esc clear) " }) })] })), _jsxs(Box, { flexDirection: "row", marginTop: 0, children: [_jsxs(Text, { color: inkColors.footerHint, children: [" ", icons.tool, " ", tokenDisplay] }), _jsx(Text, { color: inkColors.footerHint, children: ` · / ! @ trackpad/↑/↓ scroll Ctrl+J newline Tab queue Esc Esc edit` })] }), _jsx(Box, { flexDirection: "column", marginTop: 0, children: inputValue.length === 0 ? (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: inkColors.primary, children: [icons.prompt, " "] }), cursorBlinkOn ? (_jsx(Text, { inverse: true, color: inkColors.primary, children: " " })) : (_jsx(Text, { color: inkColors.primary, children: " " })), _jsx(Text, { color: inkColors.textSecondary, children: "Message or / for commands, @ for files, ! for shell, ? for help..." })] })) : ((() => {
1313
1316
  const lines = inputValue.split("\n");
1314
1317
  let lineStart = 0;
1315
1318
  return (_jsx(_Fragment, { children: lines.flatMap((lineText, lineIdx) => {
package/dist/ui/theme.js CHANGED
@@ -22,7 +22,7 @@ const THEME_DARK = {
22
22
  error: { main: "#f87171", dim: "#dc2626" },
23
23
  muted: { main: "#8a9a7a", dim: "#6a7a5a", dark: "#4a5a3a" },
24
24
  background: { dark: "#1a1a1a", darker: "#0f0f0f" },
25
- text: { primary: "#e2e8f0", secondary: "#98a08f", disabled: "#6f7867" },
25
+ text: { primary: "#e2e8f0", secondary: "#98a08f", disabled: "#6f7867", footerHint: "#635e4b" },
26
26
  },
27
27
  ui: {
28
28
  borderColor: "#7F9A65",
@@ -51,7 +51,7 @@ const THEME_LIGHT = {
51
51
  error: { main: "#dc2626", dim: "#b91c1c" },
52
52
  muted: { main: "#6b7a5a", dim: "#5a6a4a", dark: "#4a5a3a" },
53
53
  background: { dark: "#f1f5f9", darker: "#e2e8f0" },
54
- text: { primary: "#1e293b", secondary: "#5f6758", disabled: "#778070" },
54
+ text: { primary: "#1e293b", secondary: "#5f6758", disabled: "#778070", footerHint: "#635e4b" },
55
55
  },
56
56
  ui: {
57
57
  borderColor: "#5a7247",
@@ -83,6 +83,19 @@ function isDarkMode() {
83
83
  return true;
84
84
  return n <= 7;
85
85
  }
86
+ function hasTrueColor() {
87
+ const colorterm = (process.env.COLORTERM ?? "").toLowerCase();
88
+ if (colorterm.includes("truecolor") || colorterm.includes("24bit"))
89
+ return true;
90
+ const term = (process.env.TERM ?? "").toLowerCase();
91
+ return term.includes("direct");
92
+ }
93
+ function resolveFooterHintColor(base) {
94
+ const inTmux = Boolean(process.env.TMUX);
95
+ if (inTmux && !hasTrueColor())
96
+ return "#5a5a5a";
97
+ return base;
98
+ }
86
99
  const theme = isDarkMode() ? THEME_DARK : THEME_LIGHT;
87
100
  export { theme };
88
101
  export const colors = {
@@ -125,5 +138,6 @@ export const inkColors = {
125
138
  textPrimary: theme.colors.text.primary,
126
139
  textSecondary: theme.colors.text.secondary,
127
140
  textDisabled: theme.colors.text.disabled,
141
+ footerHint: resolveFooterHintColor(theme.colors.text.footerHint),
128
142
  };
129
143
  export const icons = theme.icons;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ideacode",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "CLI TUI for AI agents via OpenRouter — agentic loop, tools, markdown",
5
5
  "type": "module",
6
6
  "repository": {