@ww_nero/mini-cli 1.0.81 → 1.0.82

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/src/tools/bash.js CHANGED
@@ -1,144 +1,144 @@
1
- const { spawn } = require('child_process');
2
- const { resolveWorkspacePath } = require('../utils/helpers');
3
-
4
- const executeCommand = async ({ command, workingDirectory = '.', isService = false } = {}, context = {}) => {
5
- if (!command || typeof command !== 'string' || !command.trim()) {
6
- return 'command 参数不能为空';
7
- }
8
-
9
- const normalizedCommand = command.replace(/\bpython3\b/g, 'python');
10
-
11
- const outputMaxLength = context.outputMaxLength || 12000;
12
- const executionTimeout = context.executionTimeout || 300000;
13
- const serviceBootWindow = context.serviceBootWindow || 5000;
14
-
15
- let cwd;
16
- try {
17
- cwd = resolveWorkspacePath(context.workspaceRoot, workingDirectory || '.');
18
- } catch (error) {
19
- return `工作目录无效: ${error.message}`;
20
- }
21
-
22
- return new Promise((resolve) => {
23
- const child = spawn('bash', ['-lc', normalizedCommand], {
24
- cwd,
25
- env: process.env,
26
- stdio: ['ignore', 'pipe', 'pipe']
27
- });
28
-
29
- let stdout = '';
30
- let stderr = '';
31
- let settled = false;
32
-
33
- const cleanup = () => {
34
- child.stdout.removeAllListeners();
35
- child.stderr.removeAllListeners();
36
- };
37
-
38
- child.stdout.on('data', (data) => {
39
- stdout += data.toString();
40
- if (stdout.length > outputMaxLength) {
41
- stdout = stdout.slice(0, outputMaxLength);
42
- }
43
- });
44
-
45
- child.stderr.on('data', (data) => {
46
- stderr += data.toString();
47
- if (stderr.length > outputMaxLength) {
48
- stderr = stderr.slice(0, outputMaxLength);
49
- }
50
- });
51
-
52
- const handleProcessError = (error) => {
53
- if (settled) return;
54
- settled = true;
55
- cleanup();
56
- resolve(`命令执行失败: ${error.message}`);
57
- };
58
-
59
- const handleProcessClose = (code) => {
60
- cleanup();
61
- if (settled) return;
62
- settled = true;
63
- if (code === 0) {
64
- const output = stdout.trim();
65
- resolve(output || '命令执行成功,无额外输出');
66
- } else {
67
- const errOutput = stderr.trim();
68
- const stdOutput = stdout.trim();
69
- resolve(`命令执行失败,退出码 ${code}${errOutput ? `\n错误: ${errOutput}` : ''}${stdOutput ? `\n输出: ${stdOutput}` : ''}`);
70
- }
71
- };
72
-
73
- if (isService) {
74
- const timer = setTimeout(() => {
75
- if (settled) return;
76
- settled = true;
77
- const stdOutput = stdout.trim();
78
- const errOutput = stderr.trim();
79
- const captured = stdOutput || errOutput ? `\n当前输出:\n${stdOutput}${errOutput ? `\n错误:\n${errOutput}` : ''}` : '\n暂无输出';
80
- resolve(`已等待 ${serviceBootWindow / 1000}s,命令已在后台持续运行(PID: ${child.pid})。${captured}`);
81
- }, serviceBootWindow);
82
-
83
- const serviceErrorHandler = (error) => {
84
- clearTimeout(timer);
85
- handleProcessError(error);
86
- };
87
-
88
- child.on('error', serviceErrorHandler);
89
- child.on('close', (code) => {
90
- clearTimeout(timer);
91
- handleProcessClose(code);
92
- });
93
- } else {
94
- child.on('error', handleProcessError);
95
- child.on('close', handleProcessClose);
96
-
97
- setTimeout(() => {
98
- if (settled) return;
99
- child.kill('SIGTERM');
100
- cleanup();
101
- resolve(`命令执行超时 (超过 ${executionTimeout / 1000}s)`);
102
- }, executionTimeout);
103
- }
104
- });
105
- };
106
-
107
- const createBashToolSchema = () => {
108
- const descriptionParts = ['在指定目录运行 bash 命令,支持使用 && / || 连接多个命令。'];
109
- descriptionParts.push('isService=true 时在后台运行服务,等待 5 秒返回初始输出,之后服务进程继续运行;默认等待命令执行完成,超时为 300 秒。');
110
-
111
- return {
112
- type: 'function',
113
- function: {
114
- name: 'bash',
115
- description: descriptionParts.join(' '),
116
- parameters: {
117
- type: 'object',
118
- properties: {
119
- command: {
120
- type: 'string',
121
- description: '要执行的 bash 命令'
122
- },
123
- workingDirectory: {
124
- type: 'string',
125
- description: '相对工作目录,默认为项目根目录',
126
- default: '.'
127
- },
128
- isService: {
129
- type: 'boolean',
130
- description: '是否为启动长时间运行服务的命令。为 true 时后台运行并在 5 秒后返回捕获的输出。',
131
- default: false
132
- }
133
- },
134
- required: ['command']
135
- }
136
- }
137
- };
138
- };
139
-
140
- module.exports = {
141
- name: 'bash',
142
- schema: createBashToolSchema,
143
- handler: executeCommand
144
- };
1
+ const { spawn } = require('child_process');
2
+ const { resolveWorkspacePath } = require('../utils/helpers');
3
+
4
+ const executeCommand = async ({ command, workingDirectory = '.', isService = false } = {}, context = {}) => {
5
+ if (!command || typeof command !== 'string' || !command.trim()) {
6
+ return 'command 参数不能为空';
7
+ }
8
+
9
+ const normalizedCommand = command.replace(/\bpython3\b/g, 'python');
10
+
11
+ const outputMaxLength = context.outputMaxLength || 12000;
12
+ const executionTimeout = context.executionTimeout || 300000;
13
+ const serviceBootWindow = context.serviceBootWindow || 5000;
14
+
15
+ let cwd;
16
+ try {
17
+ cwd = resolveWorkspacePath(context.workspaceRoot, workingDirectory || '.');
18
+ } catch (error) {
19
+ return `工作目录无效: ${error.message}`;
20
+ }
21
+
22
+ return new Promise((resolve) => {
23
+ const child = spawn('bash', ['-lc', normalizedCommand], {
24
+ cwd,
25
+ env: process.env,
26
+ stdio: ['ignore', 'pipe', 'pipe']
27
+ });
28
+
29
+ let stdout = '';
30
+ let stderr = '';
31
+ let settled = false;
32
+
33
+ const cleanup = () => {
34
+ child.stdout.removeAllListeners();
35
+ child.stderr.removeAllListeners();
36
+ };
37
+
38
+ child.stdout.on('data', (data) => {
39
+ stdout += data.toString();
40
+ if (stdout.length > outputMaxLength) {
41
+ stdout = stdout.slice(0, outputMaxLength);
42
+ }
43
+ });
44
+
45
+ child.stderr.on('data', (data) => {
46
+ stderr += data.toString();
47
+ if (stderr.length > outputMaxLength) {
48
+ stderr = stderr.slice(0, outputMaxLength);
49
+ }
50
+ });
51
+
52
+ const handleProcessError = (error) => {
53
+ if (settled) return;
54
+ settled = true;
55
+ cleanup();
56
+ resolve(`命令执行失败: ${error.message}`);
57
+ };
58
+
59
+ const handleProcessClose = (code) => {
60
+ cleanup();
61
+ if (settled) return;
62
+ settled = true;
63
+ if (code === 0) {
64
+ const output = stdout.trim();
65
+ resolve(output || '命令执行成功,无额外输出');
66
+ } else {
67
+ const errOutput = stderr.trim();
68
+ const stdOutput = stdout.trim();
69
+ resolve(`命令执行失败,退出码 ${code}${errOutput ? `\n错误: ${errOutput}` : ''}${stdOutput ? `\n输出: ${stdOutput}` : ''}`);
70
+ }
71
+ };
72
+
73
+ if (isService) {
74
+ const timer = setTimeout(() => {
75
+ if (settled) return;
76
+ settled = true;
77
+ const stdOutput = stdout.trim();
78
+ const errOutput = stderr.trim();
79
+ const captured = stdOutput || errOutput ? `\n当前输出:\n${stdOutput}${errOutput ? `\n错误:\n${errOutput}` : ''}` : '\n暂无输出';
80
+ resolve(`已等待 ${serviceBootWindow / 1000}s,命令已在后台持续运行(PID: ${child.pid})。${captured}`);
81
+ }, serviceBootWindow);
82
+
83
+ const serviceErrorHandler = (error) => {
84
+ clearTimeout(timer);
85
+ handleProcessError(error);
86
+ };
87
+
88
+ child.on('error', serviceErrorHandler);
89
+ child.on('close', (code) => {
90
+ clearTimeout(timer);
91
+ handleProcessClose(code);
92
+ });
93
+ } else {
94
+ child.on('error', handleProcessError);
95
+ child.on('close', handleProcessClose);
96
+
97
+ setTimeout(() => {
98
+ if (settled) return;
99
+ child.kill('SIGTERM');
100
+ cleanup();
101
+ resolve(`命令执行超时 (超过 ${executionTimeout / 1000}s)`);
102
+ }, executionTimeout);
103
+ }
104
+ });
105
+ };
106
+
107
+ const createBashToolSchema = () => {
108
+ const descriptionParts = ['在指定目录运行 bash 命令,支持使用 && / || 连接多个命令。'];
109
+ descriptionParts.push('isService=true 时在后台运行服务,等待 5 秒返回初始输出,之后服务进程继续运行;默认等待命令执行完成,超时为 300 秒。');
110
+
111
+ return {
112
+ type: 'function',
113
+ function: {
114
+ name: 'bash',
115
+ description: descriptionParts.join(' '),
116
+ parameters: {
117
+ type: 'object',
118
+ properties: {
119
+ command: {
120
+ type: 'string',
121
+ description: '要执行的 bash 命令'
122
+ },
123
+ workingDirectory: {
124
+ type: 'string',
125
+ description: '相对工作目录,默认为项目根目录',
126
+ default: '.'
127
+ },
128
+ isService: {
129
+ type: 'boolean',
130
+ description: '是否为启动长时间运行服务的命令。为 true 时后台运行并在 5 秒后返回捕获的输出。',
131
+ default: false
132
+ }
133
+ },
134
+ required: ['command']
135
+ }
136
+ }
137
+ };
138
+ };
139
+
140
+ module.exports = {
141
+ name: 'bash',
142
+ schema: createBashToolSchema,
143
+ handler: executeCommand
144
+ };
package/src/tools/edit.js CHANGED
@@ -1,137 +1,137 @@
1
- const { diffLines } = require('diff');
2
- const {
3
- resolveWorkspacePath,
4
- readTextFile,
5
- writeTextFile,
6
- normalizeLineBreaks,
7
- isCodeIdentical,
8
- processContent
9
- } = require('../utils/helpers');
10
-
11
- const countLines = (text = '') => {
12
- if (!text) return 0;
13
- const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14
- const lines = normalized.split('\n');
15
- if (lines[lines.length - 1] === '') {
16
- lines.pop();
17
- }
18
- return lines.length;
19
- };
20
-
21
- const computeDiffStats = (before = '', after = '') => {
22
- const parts = diffLines(before, after);
23
- return parts.reduce((acc, part) => {
24
- if (part.added) {
25
- acc.added += countLines(part.value);
26
- } else if (part.removed) {
27
- acc.removed += countLines(part.value);
28
- }
29
- return acc;
30
- }, { added: 0, removed: 0 });
31
- };
32
-
33
- const editFile = async ({ filePath, search, replace } = {}, context = {}) => {
34
- if (!filePath || typeof filePath !== 'string') {
35
- return 'filePath 参数不能为空';
36
- }
37
- if (typeof search !== 'string' || search.trim() === '') {
38
- return 'search 参数不能为空';
39
- }
40
- if (typeof replace !== 'string') {
41
- return 'replace 参数必须是字符串,可为空字符串表示删除';
42
- }
43
-
44
- let absolutePath;
45
- try {
46
- absolutePath = resolveWorkspacePath(context.workspaceRoot, filePath);
47
- } catch (error) {
48
- return `无效路径: ${error.message}`;
49
- }
50
-
51
- let originCode;
52
- try {
53
- originCode = await readTextFile(absolutePath);
54
- } catch (error) {
55
- return `读取文件失败 ${filePath}: ${error.message}`;
56
- }
57
-
58
- const normalizedOrigin = normalizeLineBreaks(originCode);
59
- const normalizedSearch = normalizeLineBreaks(search);
60
- let normalizedReplace = normalizeLineBreaks(replace);
61
-
62
- if (!normalizedSearch) {
63
- return 'search 参数规范化后为空';
64
- }
65
-
66
- if (normalizedReplace) {
67
- normalizedReplace = normalizedReplace.replace(/\n+$/, '');
68
- if (/\n$/.test(replace)) {
69
- normalizedReplace += '\n';
70
- }
71
- }
72
-
73
- let searchPattern = normalizedSearch;
74
- if (!normalizedReplace || normalizedReplace.trim() === '') {
75
- if (normalizedOrigin.includes(normalizedSearch + '\n')) {
76
- searchPattern = normalizedSearch + '\n';
77
- } else if (normalizedOrigin.includes('\n' + normalizedSearch)) {
78
- searchPattern = '\n' + normalizedSearch;
79
- }
80
- }
81
-
82
- if (!normalizedOrigin.includes(searchPattern)) {
83
- return `在文件 ${filePath} 中未找到要搜索的内容`;
84
- }
85
-
86
- const updated = normalizedOrigin.replaceAll(searchPattern, normalizedReplace);
87
- if (isCodeIdentical(originCode, updated)) {
88
- return `修改前后内容相同: ${filePath}`;
89
- }
90
-
91
- const processed = processContent(updated);
92
- try {
93
- await writeTextFile(absolutePath, processed);
94
- const diffStats = computeDiffStats(normalizedOrigin, normalizeLineBreaks(processed));
95
- return {
96
- success: true,
97
- message: `搜索替换成功: ${filePath}`,
98
- diffStats,
99
- search: normalizedSearch,
100
- replace: normalizedReplace
101
- };
102
- } catch (error) {
103
- return `搜索替换失败 ${filePath}: ${error.message}`;
104
- }
105
- };
106
-
107
- const schema = {
108
- type: 'function',
109
- function: {
110
- name: 'edit',
111
- description: '在文件中搜索并替换指定的代码片段(简单字符串匹配,非正则)',
112
- parameters: {
113
- type: 'object',
114
- properties: {
115
- filePath: {
116
- type: 'string',
117
- description: '文件的相对路径'
118
- },
119
- search: {
120
- type: 'string',
121
- description: '原始代码片段(完整拷贝,勿省略)'
122
- },
123
- replace: {
124
- type: 'string',
125
- description: '替换后的代码片段,可为空字符串表示删除'
126
- }
127
- },
128
- required: ['filePath', 'search', 'replace']
129
- }
130
- }
131
- };
132
-
133
- module.exports = {
134
- name: 'edit',
135
- schema,
136
- handler: editFile
137
- };
1
+ const { diffLines } = require('diff');
2
+ const {
3
+ resolveWorkspacePath,
4
+ readTextFile,
5
+ writeTextFile,
6
+ normalizeLineBreaks,
7
+ isCodeIdentical,
8
+ processContent
9
+ } = require('../utils/helpers');
10
+
11
+ const countLines = (text = '') => {
12
+ if (!text) return 0;
13
+ const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14
+ const lines = normalized.split('\n');
15
+ if (lines[lines.length - 1] === '') {
16
+ lines.pop();
17
+ }
18
+ return lines.length;
19
+ };
20
+
21
+ const computeDiffStats = (before = '', after = '') => {
22
+ const parts = diffLines(before, after);
23
+ return parts.reduce((acc, part) => {
24
+ if (part.added) {
25
+ acc.added += countLines(part.value);
26
+ } else if (part.removed) {
27
+ acc.removed += countLines(part.value);
28
+ }
29
+ return acc;
30
+ }, { added: 0, removed: 0 });
31
+ };
32
+
33
+ const editFile = async ({ filePath, search, replace } = {}, context = {}) => {
34
+ if (!filePath || typeof filePath !== 'string') {
35
+ return 'filePath 参数不能为空';
36
+ }
37
+ if (typeof search !== 'string' || search.trim() === '') {
38
+ return 'search 参数不能为空';
39
+ }
40
+ if (typeof replace !== 'string') {
41
+ return 'replace 参数必须是字符串,可为空字符串表示删除';
42
+ }
43
+
44
+ let absolutePath;
45
+ try {
46
+ absolutePath = resolveWorkspacePath(context.workspaceRoot, filePath);
47
+ } catch (error) {
48
+ return `无效路径: ${error.message}`;
49
+ }
50
+
51
+ let originCode;
52
+ try {
53
+ originCode = await readTextFile(absolutePath);
54
+ } catch (error) {
55
+ return `读取文件失败 ${filePath}: ${error.message}`;
56
+ }
57
+
58
+ const normalizedOrigin = normalizeLineBreaks(originCode);
59
+ const normalizedSearch = normalizeLineBreaks(search);
60
+ let normalizedReplace = normalizeLineBreaks(replace);
61
+
62
+ if (!normalizedSearch) {
63
+ return 'search 参数规范化后为空';
64
+ }
65
+
66
+ if (normalizedReplace) {
67
+ normalizedReplace = normalizedReplace.replace(/\n+$/, '');
68
+ if (/\n$/.test(replace)) {
69
+ normalizedReplace += '\n';
70
+ }
71
+ }
72
+
73
+ let searchPattern = normalizedSearch;
74
+ if (!normalizedReplace || normalizedReplace.trim() === '') {
75
+ if (normalizedOrigin.includes(normalizedSearch + '\n')) {
76
+ searchPattern = normalizedSearch + '\n';
77
+ } else if (normalizedOrigin.includes('\n' + normalizedSearch)) {
78
+ searchPattern = '\n' + normalizedSearch;
79
+ }
80
+ }
81
+
82
+ if (!normalizedOrigin.includes(searchPattern)) {
83
+ return `在文件 ${filePath} 中未找到要搜索的内容`;
84
+ }
85
+
86
+ const updated = normalizedOrigin.replaceAll(searchPattern, normalizedReplace);
87
+ if (isCodeIdentical(originCode, updated)) {
88
+ return `修改前后内容相同: ${filePath}`;
89
+ }
90
+
91
+ const processed = processContent(updated);
92
+ try {
93
+ await writeTextFile(absolutePath, processed);
94
+ const diffStats = computeDiffStats(normalizedOrigin, normalizeLineBreaks(processed));
95
+ return {
96
+ success: true,
97
+ message: `搜索替换成功: ${filePath}`,
98
+ diffStats,
99
+ search: normalizedSearch,
100
+ replace: normalizedReplace
101
+ };
102
+ } catch (error) {
103
+ return `搜索替换失败 ${filePath}: ${error.message}`;
104
+ }
105
+ };
106
+
107
+ const schema = {
108
+ type: 'function',
109
+ function: {
110
+ name: 'edit',
111
+ description: '在文件中搜索并替换指定的代码片段(简单字符串匹配,非正则)',
112
+ parameters: {
113
+ type: 'object',
114
+ properties: {
115
+ filePath: {
116
+ type: 'string',
117
+ description: '文件的相对路径'
118
+ },
119
+ search: {
120
+ type: 'string',
121
+ description: '原始代码片段(完整拷贝,勿省略)'
122
+ },
123
+ replace: {
124
+ type: 'string',
125
+ description: '替换后的代码片段,可为空字符串表示删除'
126
+ }
127
+ },
128
+ required: ['filePath', 'search', 'replace']
129
+ }
130
+ }
131
+ };
132
+
133
+ module.exports = {
134
+ name: 'edit',
135
+ schema,
136
+ handler: editFile
137
+ };