@ww_nero/mini-cli 1.0.68 → 1.0.70

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": "@ww_nero/mini-cli",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
4
4
  "description": "极简的 AI 命令行助手",
5
5
  "bin": {
6
6
  "mini": "bin/mini.js"
package/src/chat.js CHANGED
@@ -66,12 +66,12 @@ const appendMiniInstructions = (baseContent, workspaceRoot) => {
66
66
 
67
67
  const globalContent = readMiniFileContent(globalMiniPath);
68
68
  if (globalContent) {
69
- sections.push(`<system_instruction>\n${globalContent}\n</system_instruction>`);
69
+ sections.push(`<system>\n${globalContent}\n</system>`);
70
70
  }
71
71
 
72
72
  const workspaceContent = readMiniFileContent(workspaceMiniPath);
73
73
  if (workspaceContent) {
74
- sections.push(`<project_instruction>\n${workspaceContent}\n</project_instruction>`);
74
+ sections.push(`<project>\n${workspaceContent}\n</project>`);
75
75
  }
76
76
 
77
77
  if (sections.length === 0) {
@@ -81,6 +81,40 @@ const appendMiniInstructions = (baseContent, workspaceRoot) => {
81
81
  return `${baseContent}\n\n${sections.join('\n\n')}`;
82
82
  };
83
83
 
84
+ const formatWriteOutput = (result) => {
85
+ if (!result || typeof result !== 'object' || !result.success) {
86
+ return null;
87
+ }
88
+ const content = result.content || '';
89
+ const lines = content.split('\n');
90
+ if (lines.length <= 100) {
91
+ return content;
92
+ }
93
+ return lines.slice(0, 100).join('\n') + '\n...(更多内容)';
94
+ };
95
+
96
+ const formatReplaceOutput = (result) => {
97
+ if (!result || typeof result !== 'object' || !result.success) {
98
+ return null;
99
+ }
100
+ const searchContent = result.search || '';
101
+ const replaceContent = result.replace || '';
102
+
103
+ const lines = [];
104
+
105
+ const searchLines = searchContent.split('\n');
106
+ searchLines.forEach((line) => {
107
+ lines.push(chalk.red(`-${line}`));
108
+ });
109
+
110
+ const replaceLines = replaceContent.split('\n');
111
+ replaceLines.forEach((line) => {
112
+ lines.push(chalk.green(`+${line}`));
113
+ });
114
+
115
+ return lines.join('\n');
116
+ };
117
+
84
118
  const stringifyToolResult = (value) => {
85
119
  if (typeof value === 'string') {
86
120
  return value;
@@ -919,7 +953,19 @@ const startChatSession = async ({
919
953
  const toolContent = resultInfo.content;
920
954
 
921
955
  // Display tool output
922
- if (functionName === 'todos') {
956
+ if (functionName === 'write') {
957
+ // For write, display full content in white
958
+ const writeOutput = formatWriteOutput(toolResult);
959
+ if (writeOutput) {
960
+ console.log(writeOutput);
961
+ }
962
+ } else if (functionName === 'replace') {
963
+ // For replace, display diff-style output
964
+ const replaceOutput = formatReplaceOutput(toolResult);
965
+ if (replaceOutput) {
966
+ console.log(replaceOutput);
967
+ }
968
+ } else if (functionName === 'todos') {
923
969
  // For todos, display full formatted output without truncation
924
970
  if (toolContent) {
925
971
  console.log(chalk.gray(` ${toolContent}`));
@@ -931,11 +977,9 @@ const startChatSession = async ({
931
977
  console.log(chalk.gray(` ${preview}`));
932
978
  }
933
979
 
934
- // If truncated, show warning with token count
980
+ // If truncated, show warning
935
981
  if (resultInfo.truncated) {
936
982
  console.log(chalk.yellow(` ⚠ 输出已截断 (${resultInfo.originalTokenCount} tokens > ${resultInfo.limit} limit)`));
937
- } else if (resultInfo.tokenCount != null) {
938
- console.log(chalk.gray(` (${resultInfo.tokenCount} tokens)`));
939
983
  }
940
984
  }
941
985
 
@@ -95,7 +95,9 @@ const replaceInFile = async ({ filePath, search, replace } = {}, context = {}) =
95
95
  return {
96
96
  success: true,
97
97
  message: `搜索替换成功: ${filePath}`,
98
- diffStats
98
+ diffStats,
99
+ search: normalizedSearch,
100
+ replace: normalizedReplace
99
101
  };
100
102
  } catch (error) {
101
103
  return `搜索替换失败 ${filePath}: ${error.message}`;
@@ -1,5 +1,14 @@
1
1
  const { resolveWorkspacePath, writeTextFile } = require('../utils/helpers');
2
2
 
3
+ const countLines = (text = '') => {
4
+ if (!text) return 0;
5
+ const normalized = text.replace(/\r/g, '');
6
+ const newlineMatches = normalized.match(/\n/g);
7
+ const newlineCount = Array.isArray(newlineMatches) ? newlineMatches.length : 0;
8
+ const endsWithNewline = normalized.endsWith('\n');
9
+ return endsWithNewline ? newlineCount : newlineCount + 1;
10
+ };
11
+
3
12
  const writeFile = async ({ filePath, content } = {}, context = {}) => {
4
13
  if (!filePath || typeof filePath !== 'string') {
5
14
  return 'filePath 参数不能为空';
@@ -17,7 +26,12 @@ const writeFile = async ({ filePath, content } = {}, context = {}) => {
17
26
 
18
27
  try {
19
28
  await writeTextFile(absolutePath, content);
20
- return `写入成功: ${filePath}`;
29
+ return {
30
+ success: true,
31
+ message: `写入成功: ${filePath}`,
32
+ content,
33
+ lineCount: countLines(content)
34
+ };
21
35
  } catch (error) {
22
36
  return `写入失败 ${filePath}: ${error.message}`;
23
37
  }
@@ -62,8 +62,7 @@ const printUsageTokens = (usage) => {
62
62
  if (!formatted) {
63
63
  return;
64
64
  }
65
- const colorFn = typeof chalk.hex === 'function' ? chalk.hex('#9A32CD') : chalk.magenta;
66
- console.log(colorFn(`已使用tokens: ${formatted}`));
65
+ console.log(chalk.gray(`已使用tokens: ${formatted}`));
67
66
  };
68
67
 
69
68
  module.exports = {
@@ -1,9 +1,5 @@
1
1
  const chalk = require('chalk');
2
2
 
3
- const ORANGE = typeof chalk.hex === 'function'
4
- ? chalk.hex('#FFA500')
5
- : chalk.keyword('orange');
6
-
7
3
  const truncateText = (text, maxLength = 200) => {
8
4
  if (!text || typeof text !== 'string') {
9
5
  return text;
@@ -14,28 +10,42 @@ const truncateText = (text, maxLength = 200) => {
14
10
  return text.substring(0, maxLength) + '...';
15
11
  };
16
12
 
17
- const formatWriteLineCountTag = (args = {}) => {
18
- if (!args || typeof args.content !== 'string') {
19
- return ORANGE('(0)');
20
- }
21
- const normalized = args.content.replace(/\r/g, '');
22
- if (!normalized) {
23
- return ORANGE('(0)');
24
- }
13
+ const countLines = (text = '') => {
14
+ if (!text) return 0;
15
+ const normalized = text.replace(/\r/g, '');
25
16
  const newlineMatches = normalized.match(/\n/g);
26
17
  const newlineCount = Array.isArray(newlineMatches) ? newlineMatches.length : 0;
27
18
  const endsWithNewline = normalized.endsWith('\n');
28
- const lineCount = endsWithNewline ? newlineCount : newlineCount + 1;
29
- return ORANGE(`(${lineCount})`);
19
+ return endsWithNewline ? newlineCount : newlineCount + 1;
20
+ };
21
+
22
+ const formatWriteHeader = (args = {}) => {
23
+ const filePath = args.filePath || '';
24
+ const lineCount = countLines(args.content);
25
+ return ` (${filePath}, +${lineCount})`;
26
+ };
27
+
28
+ const formatReplaceHeader = (args = {}) => {
29
+ const filePath = args.filePath || '';
30
+ const searchLines = countLines(args.search);
31
+ const replaceLines = countLines(args.replace);
32
+ return ` (${filePath}, ${chalk.green(`+${replaceLines}`)} ${chalk.red(`-${searchLines}`)})`;
30
33
  };
31
34
 
32
35
  const formatHeader = (name, args, options = {}) => {
33
36
  const statusTag = options.statusTag || '';
34
37
  const extraMeta = Array.isArray(options.extraMeta) ? options.extraMeta : [];
35
38
  const labelBase = `${chalk.yellow('🔧')} ${chalk.yellow(name)}`;
36
- const writeTag = name === 'write' ? formatWriteLineCountTag(args) : '';
37
- const labelWithWriteInfo = `${labelBase}${writeTag}`;
38
- const label = statusTag ? `${labelWithWriteInfo}${statusTag}` : labelWithWriteInfo;
39
+
40
+ let specialHeader = '';
41
+ if (name === 'write') {
42
+ specialHeader = formatWriteHeader(args);
43
+ } else if (name === 'replace') {
44
+ specialHeader = formatReplaceHeader(args);
45
+ }
46
+
47
+ const labelWithInfo = `${labelBase}${specialHeader}`;
48
+ const label = statusTag ? `${labelWithInfo}${statusTag}` : labelWithInfo;
39
49
  const metaParts = [...extraMeta];
40
50
 
41
51
  if (name === 'bash') {
@@ -43,7 +53,7 @@ const formatHeader = (name, args, options = {}) => {
43
53
  if (args.workingDirectory && args.workingDirectory !== '.') {
44
54
  metaParts.push(`dir=${args.workingDirectory}`);
45
55
  }
46
- } else if (['read', 'write', 'replace'].includes(name)) {
56
+ } else if (name === 'read') {
47
57
  if (args.filePath) metaParts.push(args.filePath);
48
58
  } else if (name === 'todos') {
49
59
  const count = Array.isArray(args.todos) ? args.todos.length : 0;