codemini-cli 0.2.1 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
5
  "keywords": [
6
6
  "cli",
package/src/cli.js CHANGED
@@ -4,7 +4,7 @@ import { handleConfig } from './commands/config.js';
4
4
  import { handleDoctor } from './commands/doctor.js';
5
5
  import { handleSkill } from './commands/skill.js';
6
6
 
7
- const VERSION = '0.2.1';
7
+ const VERSION = '0.2.2';
8
8
 
9
9
  function printHelp() {
10
10
  console.log(`codemini ${VERSION}
@@ -63,6 +63,7 @@ const TUI_COPY = {
63
63
  waitingForInput: '等待输入',
64
64
  ready: '就绪',
65
65
  noMessagesYet: '还没有消息',
66
+ taskCompleted: '已完成任务',
66
67
  code: '代码',
67
68
  codeActivity: '代码活动',
68
69
  textNotes: '说明文本',
@@ -193,6 +194,7 @@ const TUI_COPY = {
193
194
  waitingForInput: 'waiting for input',
194
195
  ready: 'ready',
195
196
  noMessagesYet: 'No messages yet',
197
+ taskCompleted: 'Task completed',
196
198
  code: 'code',
197
199
  codeActivity: 'CODE ACTIVITY',
198
200
  textNotes: 'NOTES',
@@ -469,6 +471,15 @@ function getActivityDisplayParts(activity) {
469
471
  };
470
472
  }
471
473
 
474
+ export function isIndexSystemToolName(name) {
475
+ const parsed = parseToolDisplayName(name);
476
+ return parsed.base === 'project_index' || parsed.base === 'file_index';
477
+ }
478
+
479
+ export function shouldShowCompletionFooter(msg) {
480
+ return Boolean(msg && msg.label === 'coder' && !msg.loading && !(msg.phase || '').trim());
481
+ }
482
+
472
483
  function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
473
484
  const parsed = parseToolDisplayName(name);
474
485
  if (parsed.base === 'project_index') {
@@ -905,9 +916,16 @@ export function parsePlanProgressLine(text) {
905
916
  };
906
917
  }
907
918
 
908
- function getTailPreviewLines(text, maxLines = 3) {
919
+ function getTailPreviewWindow(text, maxLines = 3) {
909
920
  const source = String(text || '');
910
- if (!source.trim()) return [];
921
+ if (!source.trim()) return { lines: [], startLine: 1 };
922
+ const sliceTail = (lines) => {
923
+ const tail = lines.slice(-Math.max(1, maxLines));
924
+ return {
925
+ lines: tail,
926
+ startLine: Math.max(1, lines.length - tail.length + 1)
927
+ };
928
+ };
911
929
 
912
930
  const lines = source.split('\n').map((line) => line.replace(/\r$/, ''));
913
931
  let insideFence = false;
@@ -935,21 +953,21 @@ function getTailPreviewLines(text, maxLines = 3) {
935
953
  if (insideFence) {
936
954
  const codeLines = fenceLines.filter((line) => line.trim().length > 0);
937
955
  if (codeLines.length > 0) {
938
- return codeLines.slice(-Math.max(1, maxLines));
956
+ return sliceTail(codeLines);
939
957
  }
940
958
  }
941
959
 
942
960
  const closedFenceLines = latestClosedFenceLines.filter((line) => line.trim().length > 0);
943
961
  if (closedFenceLines.length > 0) {
944
- return closedFenceLines.slice(-Math.max(1, maxLines));
962
+ return sliceTail(closedFenceLines);
945
963
  }
946
964
 
947
965
  const tailLines = source
948
966
  .split('\n')
949
967
  .map((line) => line.replace(/\r$/, ''))
950
968
  .filter((line) => line.trim().length > 0);
951
- if (tailLines.length === 0) return [];
952
- return tailLines.slice(-Math.max(1, maxLines));
969
+ if (tailLines.length === 0) return { lines: [], startLine: 1 };
970
+ return sliceTail(tailLines);
953
971
  }
954
972
 
955
973
  function collectPreviewStrings(value, out = []) {
@@ -981,7 +999,6 @@ function collectPreviewStrings(value, out = []) {
981
999
  function extractPreviewTextFromRawArguments(raw) {
982
1000
  const source = String(raw || '');
983
1001
  if (!source.trim()) return '';
984
-
985
1002
  const contentMatch = source.match(/"(content|new_content|new_text|patch|code|body|script|source|value)"\s*:\s*"([\s\S]*)$/);
986
1003
  if (!contentMatch) return '';
987
1004
 
@@ -989,7 +1006,7 @@ function extractPreviewTextFromRawArguments(raw) {
989
1006
  .replace(/\\n/g, '\n')
990
1007
  .replace(/\\"/g, '"')
991
1008
  .replace(/\\\\/g, '\\')
992
- .replace(/",?\s*$/g, '')
1009
+ .replace(/"\s*[,}\]]*\s*$/g, '')
993
1010
  .trim();
994
1011
  }
995
1012
 
@@ -1000,40 +1017,76 @@ function compactPreviewLine(line, maxChars = 56) {
1000
1017
  return `${text.slice(0, Math.max(1, maxChars - 3))}...`;
1001
1018
  }
1002
1019
 
1020
+ function extractPreviewLinesFromTool(tool, maxLines = 3) {
1021
+ const previewSource =
1022
+ typeof tool?.arguments === 'string'
1023
+ ? (() => {
1024
+ const parsedArguments = safeJsonParse(tool.arguments);
1025
+ if (parsedArguments) {
1026
+ const parsedPreview = collectPreviewStrings(parsedArguments);
1027
+ if (parsedPreview.length > 0) return parsedPreview;
1028
+ }
1029
+ const rawArgumentPreview = extractPreviewTextFromRawArguments(tool.arguments);
1030
+ return rawArgumentPreview ? [rawArgumentPreview] : [];
1031
+ })()
1032
+ : collectPreviewStrings(tool?.arguments || tool?.content || tool?.summary || []);
1033
+
1034
+ if (previewSource.length === 0) return { lines: [], startLine: 1 };
1035
+ const combined = previewSource.join('\n');
1036
+ const previewWindow = getTailPreviewWindow(combined, maxLines);
1037
+ return {
1038
+ lines: previewWindow.lines.map((line) => compactPreviewLine(line)),
1039
+ startLine: previewWindow.startLine
1040
+ };
1041
+ }
1042
+
1003
1043
  function getLatestToolPreviewLines(msg, maxLines = 3) {
1004
- const toolCalls = [
1005
- ...(Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : []),
1006
- ...(Array.isArray(msg?.toolCalls) ? msg.toolCalls : [])
1007
- ];
1008
1044
  const codeTools = new Set(['edit', 'write', 'patch', 'generate_diff']);
1009
- for (let index = toolCalls.length - 1; index >= 0; index -= 1) {
1010
- const tool = toolCalls[index];
1011
- const parsed = parseToolDisplayName(tool?.name);
1012
- if (!codeTools.has(parsed.base)) continue;
1013
- const rawArgumentPreview =
1014
- typeof tool?.arguments === 'string' ? extractPreviewTextFromRawArguments(tool.arguments) : '';
1015
- const previewSource = rawArgumentPreview
1016
- ? [rawArgumentPreview]
1017
- : collectPreviewStrings(tool?.arguments || tool?.content || tool?.summary || []);
1018
- if (previewSource.length === 0) continue;
1019
- const combined = previewSource.join('\n');
1020
- const previewLines = getTailPreviewLines(combined, maxLines);
1021
- if (previewLines.length > 0) return previewLines.map((line) => compactPreviewLine(line));
1045
+ const extractFromCalls = (calls) => {
1046
+ for (let index = calls.length - 1; index >= 0; index -= 1) {
1047
+ const tool = calls[index];
1048
+ const parsed = parseToolDisplayName(tool?.name);
1049
+ if (!codeTools.has(parsed.base)) continue;
1050
+ const previewWindow = extractPreviewLinesFromTool(tool, maxLines);
1051
+ if (previewWindow.lines.length > 0) return previewWindow;
1052
+ }
1053
+ return { lines: [], startLine: 1 };
1054
+ };
1055
+
1056
+ const pendingCodeCalls = (Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : []).filter((tool) =>
1057
+ codeTools.has(parseToolDisplayName(tool?.name).base)
1058
+ );
1059
+ if (pendingCodeCalls.length > 0) {
1060
+ const latestPendingPreview = extractPreviewLinesFromTool(pendingCodeCalls.at(-1), maxLines);
1061
+ if (latestPendingPreview.lines.length > 0) return latestPendingPreview;
1062
+ return { lines: [], startLine: 1 };
1063
+ }
1064
+
1065
+ const toolCalls = (Array.isArray(msg?.toolCalls) ? msg.toolCalls : []).filter(
1066
+ (tool) => tool?.status === 'running' && codeTools.has(parseToolDisplayName(tool?.name).base)
1067
+ );
1068
+ if (toolCalls.length > 0) {
1069
+ return extractFromCalls(toolCalls);
1022
1070
  }
1023
- return [];
1071
+
1072
+ return { lines: [], startLine: 1 };
1024
1073
  }
1025
1074
 
1026
1075
  export function getGeneratingCodePlaceholderRows(msg, copy, contentWidth = 72) {
1027
1076
  const liveStatus = String(msg?.liveStatus || '').trim();
1028
1077
  if (!msg?.loading || (msg?.phase !== 'generating' && msg?.phase !== 'tooling')) return [];
1029
- if (liveStatus !== String(copy?.runtime?.generatingCode || '').trim()) return [];
1030
1078
 
1031
- const previewLines = getLatestToolPreviewLines(msg, 3);
1032
- if (previewLines.length === 0) return [];
1079
+ const previewWindow = getLatestToolPreviewLines(msg, 3);
1080
+ if (previewWindow.lines.length === 0) return [];
1081
+ const hasRunningCodeTool = (Array.isArray(msg?.toolCalls) ? msg.toolCalls : []).some(
1082
+ (tool) => tool?.status === 'running' && new Set(['edit', 'write', 'patch', 'generate_diff']).has(parseToolDisplayName(tool?.name).base)
1083
+ );
1084
+ const isCodeGenerationStatus = liveStatus === String(copy?.runtime?.generatingCode || '').trim();
1085
+ if (!isCodeGenerationStatus && !(msg?.phase === 'tooling' && hasRunningCodeTool)) return [];
1033
1086
 
1034
- return previewLines.map((line, idx) => ({
1087
+ return previewWindow.lines.map((line, idx) => ({
1035
1088
  kind: 'code-placeholder',
1036
- lineNo: idx + 1,
1089
+ lineNo: previewWindow.startLine + idx,
1037
1090
  text: line,
1038
1091
  color: 'gray'
1039
1092
  }));
@@ -1348,7 +1401,7 @@ export function insertRowsAfterLastCodeRow(rows, extraRows) {
1348
1401
 
1349
1402
  let insertIndex = -1;
1350
1403
  for (let index = source.length - 1; index >= 0; index -= 1) {
1351
- if (source[index]?.kind === 'code') {
1404
+ if (source[index]?.kind === 'code' || source[index]?.kind === 'code-placeholder') {
1352
1405
  insertIndex = index + 1;
1353
1406
  break;
1354
1407
  }
@@ -1526,11 +1579,17 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
1526
1579
 
1527
1580
  if (Array.isArray(msg?.segments) && msg.segments.length > 0) {
1528
1581
  const totalTools = msg.segments.filter(
1529
- (segment) => segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool'
1582
+ (segment) =>
1583
+ segment.type === 'tool' ||
1584
+ segment.type === 'skill' ||
1585
+ (segment.type === 'system_tool' && (showToolDetails || !isIndexSystemToolName(segment.name)))
1530
1586
  ).length;
1531
1587
  let toolIndex = 0;
1532
1588
  for (const segment of msg.segments) {
1533
1589
  if (segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool') {
1590
+ if (segment.type === 'system_tool' && !showToolDetails && isIndexSystemToolName(segment.name)) {
1591
+ continue;
1592
+ }
1534
1593
  pushActivityRows(segment, toolIndex, totalTools);
1535
1594
  toolIndex += 1;
1536
1595
  } else {
@@ -1816,7 +1875,14 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
1816
1875
  ? h(Text, { color: 'blueBright' }, autoSkillBadge)
1817
1876
  : h(Text, { color: theme.chrome }, ' ')
1818
1877
  ),
1819
- ...rendered
1878
+ ...rendered,
1879
+ shouldShowCompletionFooter(msg)
1880
+ ? h(
1881
+ Box,
1882
+ { marginTop: 1, marginLeft: 1, key: `row-completion-${msg.id}` },
1883
+ h(Text, { color: 'gray', dimColor: true }, copy.generic.taskCompleted)
1884
+ )
1885
+ : null
1820
1886
  )
1821
1887
  );
1822
1888
  }
@@ -2241,6 +2307,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2241
2307
  prev.map((m) => {
2242
2308
  if (m.id !== targetId) return m;
2243
2309
  const toolCalls = Array.isArray(m.toolCalls) ? [...m.toolCalls] : [];
2310
+ const pendingToolCalls = Array.isArray(m.pendingToolCalls) ? [...m.pendingToolCalls] : [];
2244
2311
  const activityType = toolEvent.type || 'tool';
2245
2312
  const idx = findActivityUpdateIndex(toolCalls, toolEvent);
2246
2313
  const startedAt = toolEvent.status === 'running' ? Date.now() : undefined;
@@ -2293,7 +2360,16 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2293
2360
  : {})
2294
2361
  };
2295
2362
  }
2296
- return { ...m, toolCalls, segments };
2363
+ const shouldClearPending =
2364
+ activityType === 'tool' && ['running', 'done', 'blocked', 'error'].includes(toolEvent.status);
2365
+ const nextPendingToolCalls = shouldClearPending
2366
+ ? pendingToolCalls.filter((entry) => {
2367
+ if (!entry) return false;
2368
+ if (toolEvent.id && entry.id) return entry.id !== toolEvent.id;
2369
+ return parseToolDisplayName(entry.name).base !== parseToolDisplayName(toolEvent.name).base;
2370
+ })
2371
+ : pendingToolCalls;
2372
+ return { ...m, toolCalls, segments, pendingToolCalls: nextPendingToolCalls };
2297
2373
  })
2298
2374
  );
2299
2375
  };