codemini-cli 0.1.12 → 0.1.13

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.
@@ -3,6 +3,7 @@ import { Box, Text, useApp, useInput } from 'ink';
3
3
  import { shouldCaptureEscapeSequence } from './input-escape.js';
4
4
 
5
5
  const h = React.createElement;
6
+ const SUGGESTION_PAGE_SIZE = 8;
6
7
  const BANNER = [
7
8
  ' ██████ ██████ ██████ ███████ ███ ███ ██ ███ ██ ██ ',
8
9
  '██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ',
@@ -122,13 +123,13 @@ const TUI_COPY = {
122
123
  },
123
124
  suggestion: {
124
125
  singleTab: 'Tab 补全当前命令',
125
- navFill: 'Tab 保持切换模式,↑↓选择,Enter 填入',
126
- navEnter: 'Tab 进入切换模式,再用 ↑↓ 选择',
126
+ navFill: 'Tab 保持切换模式,↑↓选择,←→翻页,Enter 填入',
127
+ navEnter: 'Tab 进入切换模式,再用 ↑↓ 选择,←→翻页',
127
128
  noSuggestions: '/ 查看命令,Tab 自动补全,↑↓ 历史,Ctrl+T 展开工具',
128
129
  oneNav: 'Tab 或 Enter 填入当前命令,↑↓ 历史',
129
130
  oneIdle: 'Tab 补全当前唯一候选,Enter 直接发送,↑↓ 历史',
130
- manyNav: (count) => `Tab 切换候选,↑↓选择,Enter 填入 (${count} 项)`,
131
- manyIdle: (count) => `Tab 进入候选切换 (${count} 项),↑↓ 历史`
131
+ manyNav: (count) => `Tab 切换候选,↑↓选择,←→翻页,Enter 填入 (${count} 项)`,
132
+ manyIdle: (count) => `Tab 进入候选切换 (${count} 项),↑↓ 历史,←→翻页`
132
133
  },
133
134
  runtime: {
134
135
  sendingToGateway: '正在发送到网关',
@@ -224,13 +225,13 @@ const TUI_COPY = {
224
225
  },
225
226
  suggestion: {
226
227
  singleTab: 'Tab completes the current command',
227
- navFill: 'Tab stays in pick mode, ↑↓ select, Enter applies',
228
- navEnter: 'Tab enters pick mode, then use ↑↓ to choose',
228
+ navFill: 'Tab stays in pick mode, ↑↓ select, ←→ page, Enter applies',
229
+ navEnter: 'Tab enters pick mode, then use ↑↓ to choose, ←→ page',
229
230
  noSuggestions: '/ shows commands, Tab autocompletes, ↑↓ history, Ctrl+T tools',
230
231
  oneNav: 'Tab or Enter applies the current command, ↑↓ history',
231
232
  oneIdle: 'Tab completes the only candidate, Enter sends, ↑↓ history',
232
- manyNav: (count) => `Tab cycles candidates, ↑↓ select, Enter applies (${count} items)`,
233
- manyIdle: (count) => `Tab enters candidate mode (${count} items), ↑↓ history`
233
+ manyNav: (count) => `Tab cycles candidates, ↑↓ select, ←→ page, Enter applies (${count} items)`,
234
+ manyIdle: (count) => `Tab enters candidate mode (${count} items), ↑↓ history, ←→ page`
234
235
  },
235
236
  runtime: {
236
237
  sendingToGateway: 'sending to gateway',
@@ -883,6 +884,66 @@ function getSuggestionDisplay(item) {
883
884
  return typeof item === 'string' ? item : String(item?.display || item?.value || '');
884
885
  }
885
886
 
887
+ function getSuggestionDescription(item) {
888
+ return typeof item === 'string' ? '' : String(item?.description || '');
889
+ }
890
+
891
+ export function formatSuggestionDescription(text, maxChars = 40) {
892
+ const value = String(text || '').replace(/\s+/g, ' ').trim();
893
+ if (!value) return '';
894
+ const limit = Math.max(4, Number(maxChars) || 40);
895
+ if (value.length <= limit) return value;
896
+ return `${value.slice(0, limit - 3)}...`;
897
+ }
898
+
899
+ export function getSuggestionPageState(commandSuggestions, menuIndex, pageSize = SUGGESTION_PAGE_SIZE) {
900
+ const items = Array.isArray(commandSuggestions) ? commandSuggestions : [];
901
+ const normalizedPageSize = Math.max(1, Number(pageSize) || SUGGESTION_PAGE_SIZE);
902
+ const safeIndex = items.length === 0 ? 0 : Math.max(0, Math.min(Number(menuIndex) || 0, items.length - 1));
903
+ const pageIndex = Math.floor(safeIndex / normalizedPageSize);
904
+ const pageCount = Math.max(1, Math.ceil(items.length / normalizedPageSize));
905
+ const pageStart = pageIndex * normalizedPageSize;
906
+ const pageEnd = Math.min(items.length, pageStart + normalizedPageSize);
907
+ return {
908
+ pageSize: normalizedPageSize,
909
+ safeIndex,
910
+ pageIndex,
911
+ pageCount,
912
+ pageStart,
913
+ pageEnd,
914
+ pageItems: items.slice(pageStart, pageEnd)
915
+ };
916
+ }
917
+
918
+ export function moveSuggestionSelection(currentIndex, itemCount, direction, pageSize = SUGGESTION_PAGE_SIZE) {
919
+ const total = Math.max(0, Number(itemCount) || 0);
920
+ if (total <= 0) return 0;
921
+ const normalizedPageSize = Math.max(1, Number(pageSize) || SUGGESTION_PAGE_SIZE);
922
+ const safeIndex = Math.max(0, Math.min(Number(currentIndex) || 0, total - 1));
923
+
924
+ if (direction === 'left') {
925
+ if (safeIndex < normalizedPageSize) return 0;
926
+ return Math.max(0, safeIndex - normalizedPageSize);
927
+ }
928
+
929
+ if (direction === 'right') {
930
+ const currentPageStart = Math.floor(safeIndex / normalizedPageSize) * normalizedPageSize;
931
+ const currentPageEnd = Math.min(total, currentPageStart + normalizedPageSize);
932
+ if (currentPageEnd >= total) return safeIndex;
933
+ return Math.min(total - 1, safeIndex + normalizedPageSize);
934
+ }
935
+
936
+ if (direction === 'up') {
937
+ return Math.max(0, safeIndex - 1);
938
+ }
939
+
940
+ if (direction === 'down') {
941
+ return Math.min(total - 1, safeIndex + 1);
942
+ }
943
+
944
+ return safeIndex;
945
+ }
946
+
886
947
  function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, contentWidth = 72, copy }) {
887
948
  if (msg?.planSummary || parseAutoPlanSummaryMessage(msg?.text)) {
888
949
  return h(PlanSummaryBubble, { msg, copy });
@@ -1028,7 +1089,8 @@ function MessageList({ messages, loaderTick, showToolDetails, contentWidth = 72,
1028
1089
 
1029
1090
  function SuggestionPanel({ commandSuggestions, suggestionNav, menuIndex, copy }) {
1030
1091
  if (commandSuggestions.length === 0) return null;
1031
- const grouped = groupCommandSuggestions(commandSuggestions);
1092
+ const pageState = getSuggestionPageState(commandSuggestions, menuIndex);
1093
+ const grouped = groupCommandSuggestions(pageState.pageItems);
1032
1094
  let flatIndex = -1;
1033
1095
  const panelHint =
1034
1096
  commandSuggestions.length === 1
@@ -1050,7 +1112,7 @@ function SuggestionPanel({ commandSuggestions, suggestionNav, menuIndex, copy })
1050
1112
  Box,
1051
1113
  { marginBottom: 1 },
1052
1114
  h(Text, { color: 'magentaBright' }, suggestionNav ? copy.generic.commandPaletteGroupedSelect : copy.generic.commandPaletteGroupedSuggestions),
1053
- h(Text, { color: 'gray' }, ` ${panelHint}`)
1115
+ h(Text, { color: 'gray' }, ` ${panelHint} · ${pageState.pageIndex + 1}/${pageState.pageCount}`)
1054
1116
  ),
1055
1117
  ...grouped.flatMap(([group, items]) => {
1056
1118
  const nodes = [
@@ -1063,13 +1125,17 @@ function SuggestionPanel({ commandSuggestions, suggestionNav, menuIndex, copy })
1063
1125
  ];
1064
1126
  items.forEach((c) => {
1065
1127
  flatIndex += 1;
1066
- const active = suggestionNav && menuIndex === flatIndex;
1128
+ const active = suggestionNav && menuIndex === pageState.pageStart + flatIndex;
1067
1129
  const label = getSuggestionDisplay(c);
1130
+ const description = formatSuggestionDescription(getSuggestionDescription(c), 42);
1068
1131
  nodes.push(
1069
1132
  h(
1070
1133
  Box,
1071
1134
  { key: `opt-${group}-${getSuggestionValue(c)}` },
1072
- h(Text, { color: active ? 'black' : 'magenta', backgroundColor: active ? 'magentaBright' : undefined }, `${active ? ' > ' : ' '}${label}`)
1135
+ h(Text, { color: active ? 'black' : 'magenta', backgroundColor: active ? 'magentaBright' : undefined }, `${active ? ' > ' : ' '}${label}`),
1136
+ description
1137
+ ? h(Text, { color: active ? 'black' : 'gray', backgroundColor: active ? 'magentaBright' : undefined }, ` ${description}`)
1138
+ : null
1073
1139
  )
1074
1140
  );
1075
1141
  });
@@ -1301,7 +1367,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1301
1367
 
1302
1368
  const commandSuggestions =
1303
1369
  inputValue.startsWith('/')
1304
- ? (runtime.getCompletionOptions(inputValue) || []).slice(0, 8)
1370
+ ? runtime.getCompletionOptions(inputValue) || []
1305
1371
  : [];
1306
1372
  const hasTransientPanels =
1307
1373
  commandSuggestions.length > 0 || pendingQueue.length > 0 || debugKeys || Boolean(planState?.total);
@@ -1853,7 +1919,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1853
1919
 
1854
1920
  if (key.upArrow) {
1855
1921
  if (suggestionNav && commandSuggestions.length > 0) {
1856
- setMenuIndex((prev) => Math.max(0, prev - 1));
1922
+ setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'up'));
1857
1923
  return;
1858
1924
  }
1859
1925
  if (history.length === 0) return;
@@ -1879,7 +1945,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1879
1945
 
1880
1946
  if (key.downArrow) {
1881
1947
  if (suggestionNav && commandSuggestions.length > 0) {
1882
- setMenuIndex((prev) => Math.min(commandSuggestions.length - 1, prev + 1));
1948
+ setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'down'));
1883
1949
  return;
1884
1950
  }
1885
1951
  if (history.length === 0 || historyIndex === null) return;
@@ -1900,6 +1966,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1900
1966
  return;
1901
1967
  }
1902
1968
  if (key.leftArrow) {
1969
+ if (suggestionNav && commandSuggestions.length > 0) {
1970
+ setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'left'));
1971
+ return;
1972
+ }
1903
1973
  setSuggestionNav(false);
1904
1974
  const next = Math.max(0, cursorIndexRef.current - 1);
1905
1975
  cursorIndexRef.current = next;
@@ -1907,6 +1977,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1907
1977
  return;
1908
1978
  }
1909
1979
  if (key.rightArrow) {
1980
+ if (suggestionNav && commandSuggestions.length > 0) {
1981
+ setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'right'));
1982
+ return;
1983
+ }
1910
1984
  setSuggestionNav(false);
1911
1985
  const next = Math.min(inputValue.length, cursorIndexRef.current + 1);
1912
1986
  cursorIndexRef.current = next;