@ww_nero/mini-cli 1.0.96 → 1.0.97

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.96",
3
+ "version": "1.0.97",
4
4
  "description": "极简的 AI 命令行助手",
5
5
  "bin": {
6
6
  "mini": "bin/mini.js"
package/src/chat.js CHANGED
@@ -111,16 +111,35 @@ const splitDisplayLines = (text = '') => {
111
111
  return lines;
112
112
  };
113
113
 
114
+ const isToolError = (result) => {
115
+ if (typeof result === 'string' && result.startsWith('[Error]')) {
116
+ return true;
117
+ }
118
+ if (result && typeof result === 'object' && result.success === false) {
119
+ return true;
120
+ }
121
+ return false;
122
+ };
123
+
114
124
  const formatWriteOutput = (result) => {
125
+ if (isToolError(result)) {
126
+ const errorMsg = typeof result === 'string' ? result : (result.message || '写入失败');
127
+ return { isError: true, message: errorMsg.replace('[Error] ', '') };
128
+ }
115
129
  if (!result || typeof result !== 'object' || !result.success) {
116
- return null;
130
+ return { isError: true, message: '写入失败' };
117
131
  }
118
- return typeof result.content === 'string' ? result.content : String(result.content || '');
132
+ const lineCount = result.lineCount || countLines(result.content);
133
+ return { isError: false, lineCount };
119
134
  };
120
135
 
121
136
  const formatEditOutput = (result) => {
137
+ if (isToolError(result)) {
138
+ const errorMsg = typeof result === 'string' ? result : (result.message || '编辑失败');
139
+ return { isError: true, message: errorMsg.replace('[Error] ', '') };
140
+ }
122
141
  if (!result || typeof result !== 'object' || !result.success) {
123
- return null;
142
+ return { isError: true, message: '编辑失败' };
124
143
  }
125
144
  const lines = [];
126
145
  const parts = diffLines(result.search || '', result.replace || '');
@@ -133,7 +152,7 @@ const formatEditOutput = (result) => {
133
152
  }
134
153
  }
135
154
 
136
- return lines.join('\n');
155
+ return { isError: false, diff: lines.join('\n') };
137
156
  };
138
157
 
139
158
  const stringifyToolResult = (value) => {
@@ -1000,7 +1019,7 @@ const startChatSession = async ({
1000
1019
  try {
1001
1020
  toolResult = await handler(parsedArgs);
1002
1021
  } catch (toolError) {
1003
- toolResult = `工具执行异常: ${toolError.message}`;
1022
+ toolResult = `[Error] 工具执行异常: ${toolError.message}`;
1004
1023
  } finally {
1005
1024
  clearLoadingPrompt();
1006
1025
  }
@@ -1011,30 +1030,33 @@ const startChatSession = async ({
1011
1030
 
1012
1031
  // Display tool output
1013
1032
  if (functionName === 'write') {
1014
- // For write, display full content in white
1015
1033
  const writeOutput = formatWriteOutput(toolResult);
1016
- if (writeOutput) {
1017
- console.log(writeOutput);
1034
+ if (writeOutput.isError) {
1035
+ console.log(chalk.red(indentToolResult(writeOutput.message)));
1036
+ } else {
1037
+ console.log(chalk.gray(`已写入,共${writeOutput.lineCount}行`));
1018
1038
  }
1019
1039
  } else if (functionName === 'edit') {
1020
- // For edit, display diff-style output
1021
1040
  const editOutput = formatEditOutput(toolResult);
1022
- if (editOutput) {
1023
- console.log(editOutput);
1041
+ if (editOutput.isError) {
1042
+ console.log(chalk.red(indentToolResult(editOutput.message)));
1043
+ } else if (editOutput.diff) {
1044
+ console.log(editOutput.diff);
1024
1045
  }
1025
1046
  } else if (functionName === 'read') {
1026
- // read 工具特殊处理:成功时显示行数,失败时显示错误信息
1027
- const normalized = rawToolContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
1028
- const lineCount = countLines(normalized);
1029
- if (normalized && !normalized.startsWith('读取失败') && !normalized.startsWith('无效路径') && !normalized.startsWith('filePath 参数不能为空')) {
1030
- console.log(chalk.gray(`已读取,共${lineCount}行`));
1047
+ const isError = rawToolContent.startsWith('[Error]');
1048
+ if (isError) {
1049
+ console.log(chalk.red(indentToolResult(rawToolContent.replace('[Error] ', ''))));
1031
1050
  } else {
1032
- console.log(chalk.gray(indentToolResult(rawToolContent)));
1051
+ const normalized = rawToolContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
1052
+ const lineCount = countLines(normalized);
1053
+ console.log(chalk.gray(`已读取,共${lineCount}行`));
1033
1054
  }
1034
1055
  } else {
1056
+ const isError = rawToolContent.startsWith('[Error]');
1035
1057
  const displayResult = formatToolResultForDisplay(rawToolContent, 100);
1036
1058
  if (displayResult) {
1037
- console.log(chalk.gray(indentToolResult(displayResult)));
1059
+ console.log((isError ? chalk.red : chalk.gray)(indentToolResult(displayResult.replace('[Error] ', ''))));
1038
1060
  }
1039
1061
  }
1040
1062
 
package/src/tools/bash.js CHANGED
@@ -3,7 +3,7 @@ const { resolveWorkspacePath } = require('../utils/helpers');
3
3
 
4
4
  const executeCommand = async ({ command, workingDirectory = '.', isService = false } = {}, context = {}) => {
5
5
  if (!command || typeof command !== 'string' || !command.trim()) {
6
- return 'command 参数不能为空';
6
+ return '[Error] command 参数不能为空';
7
7
  }
8
8
 
9
9
  const normalizedCommand = command.replace(/\bpython3\b/g, 'python');
@@ -16,7 +16,7 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
16
16
  try {
17
17
  cwd = resolveWorkspacePath(context.workspaceRoot, workingDirectory || '.');
18
18
  } catch (error) {
19
- return `工作目录无效: ${error.message}`;
19
+ return `[Error] 工作目录无效: ${error.message}`;
20
20
  }
21
21
 
22
22
  return new Promise((resolve) => {
@@ -53,7 +53,7 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
53
53
  if (settled) return;
54
54
  settled = true;
55
55
  cleanup();
56
- resolve(`命令执行失败: ${error.message}`);
56
+ resolve(`[Error] 命令执行失败: ${error.message}`);
57
57
  };
58
58
 
59
59
  const handleProcessClose = (code) => {
@@ -66,7 +66,7 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
66
66
  } else {
67
67
  const errOutput = stderr.trim();
68
68
  const stdOutput = stdout.trim();
69
- resolve(`命令执行失败,退出码 ${code}${errOutput ? `\n错误: ${errOutput}` : ''}${stdOutput ? `\n输出: ${stdOutput}` : ''}`);
69
+ resolve(`[Error] 命令执行失败,退出码 ${code}${errOutput ? `\n错误: ${errOutput}` : ''}${stdOutput ? `\n输出: ${stdOutput}` : ''}`);
70
70
  }
71
71
  };
72
72
 
@@ -98,7 +98,7 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
98
98
  if (settled) return;
99
99
  child.kill('SIGTERM');
100
100
  cleanup();
101
- resolve(`命令执行超时 (超过 ${executionTimeout / 1000}s)`);
101
+ resolve(`[Error] 命令执行超时 (超过 ${executionTimeout / 1000}s)`);
102
102
  }, executionTimeout);
103
103
  }
104
104
  });
package/src/tools/edit.js CHANGED
@@ -10,27 +10,27 @@ const {
10
10
 
11
11
  const editFile = async ({ filePath, search, replace } = {}, context = {}) => {
12
12
  if (!filePath || typeof filePath !== 'string') {
13
- return 'filePath 参数不能为空';
13
+ return '[Error] filePath 参数不能为空';
14
14
  }
15
15
  if (typeof search !== 'string' || search.trim() === '') {
16
- return 'search 参数不能为空';
16
+ return '[Error] search 参数不能为空';
17
17
  }
18
18
  if (typeof replace !== 'string') {
19
- return 'replace 参数必须是字符串,可为空字符串表示删除';
19
+ return '[Error] replace 参数必须是字符串,可为空字符串表示删除';
20
20
  }
21
21
 
22
22
  let absolutePath;
23
23
  try {
24
24
  absolutePath = resolveWorkspacePath(context.workspaceRoot, filePath);
25
25
  } catch (error) {
26
- return `无效路径: ${error.message}`;
26
+ return `[Error] 无效路径: ${error.message}`;
27
27
  }
28
28
 
29
29
  let originCode;
30
30
  try {
31
31
  originCode = await readTextFile(absolutePath);
32
32
  } catch (error) {
33
- return `读取文件失败 ${filePath}: ${error.message}`;
33
+ return `[Error] 读取文件失败 ${filePath}: ${error.message}`;
34
34
  }
35
35
 
36
36
  const normalizedOrigin = normalizeLineBreaks(originCode);
@@ -38,7 +38,7 @@ const editFile = async ({ filePath, search, replace } = {}, context = {}) => {
38
38
  let normalizedReplace = normalizeLineBreaks(replace);
39
39
 
40
40
  if (!normalizedSearch) {
41
- return 'search 参数规范化后为空';
41
+ return '[Error] search 参数规范化后为空';
42
42
  }
43
43
 
44
44
  if (normalizedReplace) {
@@ -58,12 +58,12 @@ const editFile = async ({ filePath, search, replace } = {}, context = {}) => {
58
58
  }
59
59
 
60
60
  if (!normalizedOrigin.includes(searchPattern)) {
61
- return `在文件 ${filePath} 中未找到要搜索的内容`;
61
+ return `[Error] 在文件 ${filePath} 中未找到要搜索的内容`;
62
62
  }
63
63
 
64
64
  const updated = normalizedOrigin.replaceAll(searchPattern, normalizedReplace);
65
65
  if (isCodeIdentical(originCode, updated)) {
66
- return `修改前后内容相同: ${filePath}`;
66
+ return `[Error] 修改前后内容相同: ${filePath}`;
67
67
  }
68
68
 
69
69
  const processed = processContent(updated);
@@ -78,7 +78,7 @@ const editFile = async ({ filePath, search, replace } = {}, context = {}) => {
78
78
  replace: normalizedReplace
79
79
  };
80
80
  } catch (error) {
81
- return `搜索替换失败 ${filePath}: ${error.message}`;
81
+ return `[Error] 搜索替换失败 ${filePath}: ${error.message}`;
82
82
  }
83
83
  };
84
84
 
package/src/tools/mcp.js CHANGED
@@ -319,7 +319,7 @@ const resolveImageSource = (workspaceRoot, args = {}) => {
319
319
  const resolved = path.resolve(root, imageSource);
320
320
  const relative = path.relative(root, resolved);
321
321
  if (relative.startsWith('..') || path.isAbsolute(relative) || !fs.existsSync(resolved)) {
322
- return { error: '找不到图片' };
322
+ return { error: '[Error] 找不到图片' };
323
323
  }
324
324
  return { args: { ...args, image_source: resolved } };
325
325
  };
@@ -438,11 +438,11 @@ class McpManager {
438
438
  const isError = Boolean(result && result.isError);
439
439
  const output = formatMcpContent(result && result.content);
440
440
  if (isError) {
441
- return output ? `MCP 工具返回错误:${output}` : 'MCP 工具返回错误';
441
+ return output ? `[Error] MCP 工具返回错误:${output}` : '[Error] MCP 工具返回错误';
442
442
  }
443
443
  return output || 'MCP 工具无输出';
444
444
  } catch (error) {
445
- return `调用 MCP 工具失败:${error.message}`;
445
+ return `[Error] 调用 MCP 工具失败:${error.message}`;
446
446
  }
447
447
  }
448
448
  };
package/src/tools/read.js CHANGED
@@ -2,20 +2,20 @@ const { resolveWorkspacePath, readTextFile } = require('../utils/helpers');
2
2
 
3
3
  const readFile = async ({ filePath } = {}, context = {}) => {
4
4
  if (!filePath || typeof filePath !== 'string') {
5
- return 'filePath 参数不能为空';
5
+ return '[Error] filePath 参数不能为空';
6
6
  }
7
7
 
8
8
  let absolutePath;
9
9
  try {
10
10
  absolutePath = resolveWorkspacePath(context.workspaceRoot, filePath);
11
11
  } catch (error) {
12
- return `无效路径: ${error.message}`;
12
+ return `[Error] 无效路径: ${error.message}`;
13
13
  }
14
14
 
15
15
  try {
16
16
  return await readTextFile(absolutePath);
17
17
  } catch (error) {
18
- return `读取失败 ${filePath}: ${error.message}`;
18
+ return `[Error] 读取失败 ${filePath}: ${error.message}`;
19
19
  }
20
20
  };
21
21
 
@@ -16,7 +16,7 @@ const handler = async ({ skillName, filePath } = {}) => {
16
16
  content: result.content
17
17
  };
18
18
  } catch (error) {
19
- return `读取 skill 失败:${error.message}`;
19
+ return `[Error] 读取 skill 失败:${error.message}`;
20
20
  }
21
21
  };
22
22
 
@@ -11,17 +11,17 @@ const countLines = (text = '') => {
11
11
 
12
12
  const writeFile = async ({ filePath, content } = {}, context = {}) => {
13
13
  if (!filePath || typeof filePath !== 'string') {
14
- return 'filePath 参数不能为空';
14
+ return '[Error] filePath 参数不能为空';
15
15
  }
16
16
  if (typeof content !== 'string') {
17
- return 'content 必须是字符串';
17
+ return '[Error] content 必须是字符串';
18
18
  }
19
19
 
20
20
  let absolutePath;
21
21
  try {
22
22
  absolutePath = resolveWorkspacePath(context.workspaceRoot, filePath);
23
23
  } catch (error) {
24
- return `无效路径: ${error.message}`;
24
+ return `[Error] 无效路径: ${error.message}`;
25
25
  }
26
26
 
27
27
  try {
@@ -33,7 +33,7 @@ const writeFile = async ({ filePath, content } = {}, context = {}) => {
33
33
  lineCount: countLines(content)
34
34
  };
35
35
  } catch (error) {
36
- return `写入失败 ${filePath}: ${error.message}`;
36
+ return `[Error] 写入失败 ${filePath}: ${error.message}`;
37
37
  }
38
38
  };
39
39