@ww_nero/mini-cli 1.0.96 → 1.0.98
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 +1 -1
- package/src/chat.js +53 -18
- package/src/tools/bash.js +5 -5
- package/src/tools/edit.js +9 -9
- package/src/tools/mcp.js +3 -3
- package/src/tools/read.js +3 -3
- package/src/tools/skills.js +1 -1
- package/src/tools/write.js +4 -4
package/package.json
CHANGED
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
|
|
130
|
+
return { isError: true, message: '写入失败' };
|
|
117
131
|
}
|
|
118
|
-
|
|
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
|
|
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) => {
|
|
@@ -169,6 +188,10 @@ const formatToolResultForDisplay = (text, maxLength = 100) => {
|
|
|
169
188
|
if (!normalized) {
|
|
170
189
|
return '';
|
|
171
190
|
}
|
|
191
|
+
if (normalized.startsWith('[Error]')) {
|
|
192
|
+
const errorMsg = normalized.replace('[Error] ', '');
|
|
193
|
+
return errorMsg.length > maxLength ? errorMsg.slice(0, maxLength) + '...' : errorMsg;
|
|
194
|
+
}
|
|
172
195
|
return normalized.length > maxLength ? 'Done' : normalized;
|
|
173
196
|
};
|
|
174
197
|
|
|
@@ -192,6 +215,15 @@ const indentToolResult = (text = '') => splitDisplayLines(text)
|
|
|
192
215
|
.map((line) => ` ${line}`)
|
|
193
216
|
.join('\n');
|
|
194
217
|
|
|
218
|
+
const formatTreeResult = (text = '') => {
|
|
219
|
+
const lines = splitDisplayLines(text);
|
|
220
|
+
if (lines.length === 0) return '';
|
|
221
|
+
return lines.map((line, index) => {
|
|
222
|
+
if (index === 0) return `└─ ${line}`;
|
|
223
|
+
return ` ${line}`;
|
|
224
|
+
}).join('\n');
|
|
225
|
+
};
|
|
226
|
+
|
|
195
227
|
const supportsCursorControl = () => Boolean(process.stdout && process.stdout.isTTY);
|
|
196
228
|
|
|
197
229
|
let loadingCursorActive = false;
|
|
@@ -1000,7 +1032,7 @@ const startChatSession = async ({
|
|
|
1000
1032
|
try {
|
|
1001
1033
|
toolResult = await handler(parsedArgs);
|
|
1002
1034
|
} catch (toolError) {
|
|
1003
|
-
toolResult =
|
|
1035
|
+
toolResult = `[Error] 工具执行异常: ${toolError.message}`;
|
|
1004
1036
|
} finally {
|
|
1005
1037
|
clearLoadingPrompt();
|
|
1006
1038
|
}
|
|
@@ -1011,30 +1043,33 @@ const startChatSession = async ({
|
|
|
1011
1043
|
|
|
1012
1044
|
// Display tool output
|
|
1013
1045
|
if (functionName === 'write') {
|
|
1014
|
-
// For write, display full content in white
|
|
1015
1046
|
const writeOutput = formatWriteOutput(toolResult);
|
|
1016
|
-
if (writeOutput) {
|
|
1017
|
-
console.log(writeOutput);
|
|
1047
|
+
if (writeOutput.isError) {
|
|
1048
|
+
console.log(chalk.red(formatTreeResult(writeOutput.message)));
|
|
1049
|
+
} else {
|
|
1050
|
+
console.log(chalk.gray(formatTreeResult(`已写入,共${writeOutput.lineCount}行`)));
|
|
1018
1051
|
}
|
|
1019
1052
|
} else if (functionName === 'edit') {
|
|
1020
|
-
// For edit, display diff-style output
|
|
1021
1053
|
const editOutput = formatEditOutput(toolResult);
|
|
1022
|
-
if (editOutput) {
|
|
1023
|
-
console.log(editOutput);
|
|
1054
|
+
if (editOutput.isError) {
|
|
1055
|
+
console.log(chalk.red(indentToolResult(editOutput.message)));
|
|
1056
|
+
} else if (editOutput.diff) {
|
|
1057
|
+
console.log(editOutput.diff);
|
|
1024
1058
|
}
|
|
1025
1059
|
} else if (functionName === 'read') {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (normalized && !normalized.startsWith('读取失败') && !normalized.startsWith('无效路径') && !normalized.startsWith('filePath 参数不能为空')) {
|
|
1030
|
-
console.log(chalk.gray(`已读取,共${lineCount}行`));
|
|
1060
|
+
const isError = rawToolContent.startsWith('[Error]');
|
|
1061
|
+
if (isError) {
|
|
1062
|
+
console.log(chalk.red(formatTreeResult(rawToolContent.replace('[Error] ', ''))));
|
|
1031
1063
|
} else {
|
|
1032
|
-
|
|
1064
|
+
const normalized = rawToolContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
|
|
1065
|
+
const lineCount = countLines(normalized);
|
|
1066
|
+
console.log(chalk.gray(formatTreeResult(`已读取,共${lineCount}行`)));
|
|
1033
1067
|
}
|
|
1034
1068
|
} else {
|
|
1069
|
+
const isError = rawToolContent.startsWith('[Error]');
|
|
1035
1070
|
const displayResult = formatToolResultForDisplay(rawToolContent, 100);
|
|
1036
1071
|
if (displayResult) {
|
|
1037
|
-
console.log(chalk.gray(
|
|
1072
|
+
console.log((isError ? chalk.red : chalk.gray)(formatTreeResult(displayResult.replace('[Error] ', ''))));
|
|
1038
1073
|
}
|
|
1039
1074
|
}
|
|
1040
1075
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
61
|
+
return `[Error] 在文件 ${filePath} 中未找到要搜索的内容`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const updated = normalizedOrigin.replaceAll(searchPattern, normalizedReplace);
|
|
65
65
|
if (isCodeIdentical(originCode, updated)) {
|
|
66
|
-
return
|
|
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
|
|
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
|
|
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
|
|
12
|
+
return `[Error] 无效路径: ${error.message}`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
16
|
return await readTextFile(absolutePath);
|
|
17
17
|
} catch (error) {
|
|
18
|
-
return
|
|
18
|
+
return `[Error] 读取失败 ${filePath}: ${error.message}`;
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
package/src/tools/skills.js
CHANGED
package/src/tools/write.js
CHANGED
|
@@ -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
|
|
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
|
|
36
|
+
return `[Error] 写入失败 ${filePath}: ${error.message}`;
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|