@ww_nero/mini-cli 1.0.76 → 1.0.77
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/config.js +0 -12
- package/src/tools/bash.js +1 -98
- package/src/tools/index.js +0 -2
- package/src/utils/settings.js +3 -16
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -12,12 +12,6 @@ const FILE_NAMES = {
|
|
|
12
12
|
mini: 'MINI.md'
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const DEFAULT_ALLOWED_COMMANDS = [
|
|
16
|
-
'rm', 'rmdir', 'touch', 'mkdir', 'cd', 'cp', 'mv', 'node', 'npm', 'pkill', 'kill',
|
|
17
|
-
'curl', 'ls', 'pwd', 'grep', 'cat', 'echo', 'sed', 'head', 'tail', 'find', 'true',
|
|
18
|
-
'false', 'pip', 'python', 'ps', 'lsof', 'git', 'pandoc'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
15
|
const DEFAULT_TOOL_RESPONSE_MAX_TOKENS = 65536;
|
|
22
16
|
const DEFAULT_COMPACT_TOKEN_THRESHOLD = 65536;
|
|
23
17
|
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
@@ -91,7 +85,6 @@ const DEFAULT_SETTINGS = {
|
|
|
91
85
|
commands: [],
|
|
92
86
|
maxToolTokens: DEFAULT_TOOL_RESPONSE_MAX_TOKENS,
|
|
93
87
|
compactTokenThreshold: DEFAULT_COMPACT_TOKEN_THRESHOLD,
|
|
94
|
-
allowedCommands: [...DEFAULT_ALLOWED_COMMANDS],
|
|
95
88
|
mcpToolTimeout: DEFAULT_MCP_TOOL_TIMEOUT_MS,
|
|
96
89
|
outputMaxLength: DEFAULT_OUTPUT_MAX_LENGTH,
|
|
97
90
|
executionTimeout: DEFAULT_EXECUTION_TIMEOUT,
|
|
@@ -345,10 +338,6 @@ const loadSettings = ({ defaultTools = [] } = {}) => {
|
|
|
345
338
|
parsed.maxToolTokens,
|
|
346
339
|
DEFAULT_TOOL_RESPONSE_MAX_TOKENS
|
|
347
340
|
),
|
|
348
|
-
allowedCommands: (() => {
|
|
349
|
-
const list = ensureArrayOfStrings(parsed.allowedCommands);
|
|
350
|
-
return list.length ? Array.from(new Set(list)) : [...DEFAULT_ALLOWED_COMMANDS];
|
|
351
|
-
})(),
|
|
352
341
|
compactTokenThreshold: normalizePositiveInteger(
|
|
353
342
|
parsed.compactTokenThreshold,
|
|
354
343
|
DEFAULT_COMPACT_TOKEN_THRESHOLD
|
|
@@ -392,7 +381,6 @@ module.exports = {
|
|
|
392
381
|
loadSettings,
|
|
393
382
|
ensureConfigFiles,
|
|
394
383
|
getConfigPath,
|
|
395
|
-
DEFAULT_ALLOWED_COMMANDS,
|
|
396
384
|
DEFAULT_TOOL_RESPONSE_MAX_TOKENS,
|
|
397
385
|
DEFAULT_COMPACT_TOKEN_THRESHOLD,
|
|
398
386
|
COMPACT_SUMMARY_PROMPT
|
package/src/tools/bash.js
CHANGED
|
@@ -1,76 +1,5 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
const { resolveWorkspacePath } = require('../utils/helpers');
|
|
3
|
-
const { DEFAULT_ALLOWED_COMMANDS } = require('../config');
|
|
4
|
-
|
|
5
|
-
// Git 只读命令白名单
|
|
6
|
-
const GIT_READONLY_COMMANDS = ['show', 'diff', 'log', 'status', 'branch', 'tag', 'ls-files', 'ls-tree', 'rev-parse', 'reflog', 'blame', 'shortlog', 'describe', 'config --get', 'config --list', 'remote', 'ls-remote', 'fetch --dry-run', 'grep'];
|
|
7
|
-
|
|
8
|
-
// Git 禁止的命令(会修改状态的命令)
|
|
9
|
-
const GIT_FORBIDDEN_COMMANDS = ['add', 'commit', 'push', 'pull', 'merge', 'rebase', 'reset', 'revert', 'cherry-pick', 'apply', 'stash', 'clean', 'rm', 'mv', 'checkout', 'switch', 'restore', 'tag -a', 'tag -d', 'branch -d', 'branch -D', 'config --add', 'config --unset', 'submodule', 'clone', 'init'];
|
|
10
|
-
|
|
11
|
-
const splitShellCommands = (commandString = '') => {
|
|
12
|
-
const operators = ['&&', '||'];
|
|
13
|
-
const pattern = '(' + operators.map(op => op.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')';
|
|
14
|
-
return commandString
|
|
15
|
-
.split(new RegExp(pattern))
|
|
16
|
-
.map(part => part.trim())
|
|
17
|
-
.filter(part => part && !operators.includes(part));
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const validateSingleCommand = (commandString = '', allowedCommands = []) => {
|
|
21
|
-
const parts = commandString.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
|
|
22
|
-
if (parts.length === 0) {
|
|
23
|
-
return { isValid: false, reason: '命令为空' };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const command = parts[0];
|
|
27
|
-
if (!allowedCommands.includes(command)) {
|
|
28
|
-
return { isValid: false, reason: `命令 ${command} 不在允许名单内` };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if ((command === 'rm' || command === 'rmdir') && parts.slice(1).some(arg => !arg.startsWith('-') && arg.includes('.git'))) {
|
|
32
|
-
return { isValid: false, reason: '禁止对 .git 目录执行删除操作' };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Git 命令额外验证
|
|
36
|
-
if (command === 'git') {
|
|
37
|
-
if (parts.length < 2) {
|
|
38
|
-
return { isValid: false, reason: 'git 命令缺少子命令' };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const gitSubCommand = parts[1];
|
|
42
|
-
const gitArgs = parts.slice(2).join(' ');
|
|
43
|
-
const fullGitCommand = gitSubCommand + (gitArgs ? ' ' + gitArgs : '');
|
|
44
|
-
|
|
45
|
-
// 检查是否是禁止的命令
|
|
46
|
-
for (const forbidden of GIT_FORBIDDEN_COMMANDS) {
|
|
47
|
-
if (gitSubCommand === forbidden || fullGitCommand.startsWith(forbidden)) {
|
|
48
|
-
return {
|
|
49
|
-
isValid: false,
|
|
50
|
-
reason: `禁止执行会修改仓库状态的 git 命令: ${forbidden}。只允许执行只读命令如: ${GIT_READONLY_COMMANDS.join(', ')}`
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 检查是否是允许的只读命令
|
|
56
|
-
let isReadOnly = false;
|
|
57
|
-
for (const readonly of GIT_READONLY_COMMANDS) {
|
|
58
|
-
if (gitSubCommand === readonly || fullGitCommand.startsWith(readonly)) {
|
|
59
|
-
isReadOnly = true;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!isReadOnly) {
|
|
65
|
-
return {
|
|
66
|
-
isValid: false,
|
|
67
|
-
reason: `git 子命令 ${gitSubCommand} 不在只读命令白名单内。允许的命令: ${GIT_READONLY_COMMANDS.join(', ')}`
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return { isValid: true, reason: '' };
|
|
73
|
-
};
|
|
74
3
|
|
|
75
4
|
const executeCommand = async ({ command, workingDirectory = '.', isService = false } = {}, context = {}) => {
|
|
76
5
|
if (!command || typeof command !== 'string' || !command.trim()) {
|
|
@@ -79,30 +8,9 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
|
|
|
79
8
|
|
|
80
9
|
const normalizedCommand = command.replace(/\bpython3\b/g, 'python');
|
|
81
10
|
|
|
82
|
-
const allowedCommands = Array.isArray(context.allowedCommands) && context.allowedCommands.length > 0
|
|
83
|
-
? context.allowedCommands
|
|
84
|
-
: DEFAULT_ALLOWED_COMMANDS;
|
|
85
|
-
|
|
86
11
|
const outputMaxLength = context.outputMaxLength || 12000;
|
|
87
12
|
const executionTimeout = context.executionTimeout || 300000;
|
|
88
13
|
const serviceBootWindow = context.serviceBootWindow || 5000;
|
|
89
|
-
const commands = splitShellCommands(normalizedCommand);
|
|
90
|
-
if (commands.length === 0) {
|
|
91
|
-
return '未找到有效的命令';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < commands.length; i += 1) {
|
|
95
|
-
const validation = validateSingleCommand(commands[i], allowedCommands);
|
|
96
|
-
if (!validation.isValid) {
|
|
97
|
-
return [
|
|
98
|
-
'命令未通过安全校验',
|
|
99
|
-
`第 ${i + 1} 个指令: ${commands[i]}`,
|
|
100
|
-
`原因: ${validation.reason}`,
|
|
101
|
-
'允许的命令: ' + allowedCommands.join(', '),
|
|
102
|
-
'附加限制: rm/rmdir 禁止修改`.git`目录'
|
|
103
|
-
].join('\n');
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
14
|
|
|
107
15
|
let cwd;
|
|
108
16
|
try {
|
|
@@ -196,13 +104,8 @@ const executeCommand = async ({ command, workingDirectory = '.', isService = fal
|
|
|
196
104
|
});
|
|
197
105
|
};
|
|
198
106
|
|
|
199
|
-
const createBashToolSchema = (
|
|
200
|
-
const allowedCommands = Array.isArray(context.allowedCommands) && context.allowedCommands.length > 0
|
|
201
|
-
? context.allowedCommands
|
|
202
|
-
: DEFAULT_ALLOWED_COMMANDS;
|
|
107
|
+
const createBashToolSchema = () => {
|
|
203
108
|
const descriptionParts = ['在指定目录运行 bash 命令,支持使用 && / || 连接多个命令。'];
|
|
204
|
-
descriptionParts.push(`支持的命令: ${allowedCommands.join(', ')}。`);
|
|
205
|
-
descriptionParts.push('git 命令仅支持只读操作(show/diff/log/status 等),严禁执行 add/commit/push/reset 等会修改仓库状态的命令。');
|
|
206
109
|
descriptionParts.push('isService=true 时在后台运行服务,等待 5 秒返回初始输出,进程继续运行并持续捕获输出;默认等待命令执行完成,超时为 300 秒。');
|
|
207
110
|
|
|
208
111
|
return {
|
package/src/tools/index.js
CHANGED
|
@@ -13,11 +13,9 @@ const createToolRuntime = async (workspaceRoot, options = {}) => {
|
|
|
13
13
|
const { settings } = loadSettings({ defaultTools: defaultToolNames });
|
|
14
14
|
const enabledTools = new Set(settings.tools && settings.tools.length > 0 ? settings.tools : defaultToolNames);
|
|
15
15
|
const enabledMcps = Array.isArray(settings.mcps) ? settings.mcps : [];
|
|
16
|
-
const allowedCommands = settings.allowedCommands;
|
|
17
16
|
|
|
18
17
|
const context = {
|
|
19
18
|
workspaceRoot,
|
|
20
|
-
allowedCommands,
|
|
21
19
|
outputMaxLength: settings.outputMaxLength,
|
|
22
20
|
executionTimeout: settings.executionTimeout,
|
|
23
21
|
serviceBootWindow: settings.serviceBootWindow,
|
package/src/utils/settings.js
CHANGED
|
@@ -5,11 +5,6 @@ const path = require('path');
|
|
|
5
5
|
const MINI_DIR_NAME = '.mini';
|
|
6
6
|
const SETTINGS_FILE_NAME = 'settings.json';
|
|
7
7
|
const DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT = 32768;
|
|
8
|
-
const DEFAULT_ALLOWED_COMMANDS = [
|
|
9
|
-
'rm', 'rmdir', 'touch', 'mkdir', 'cd', 'cp', 'mv', 'node', 'npm', 'ls',
|
|
10
|
-
'grep', 'cat', 'echo', 'sed', 'head', 'tail', 'find', 'true', 'false',
|
|
11
|
-
'pkill', 'kill', 'curl', 'ps', 'lsof', 'git', 'pip', 'python', 'pandoc'
|
|
12
|
-
];
|
|
13
8
|
|
|
14
9
|
const ensureArrayOfStrings = (value, fallback = []) => {
|
|
15
10
|
if (!Array.isArray(value)) {
|
|
@@ -29,8 +24,7 @@ const loadSettings = ({ defaultTools = [] } = {}) => {
|
|
|
29
24
|
const defaultSettings = {
|
|
30
25
|
mcps: [],
|
|
31
26
|
tools: [...defaultTools],
|
|
32
|
-
toolOutputTokenLimit: DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT
|
|
33
|
-
allowedCommands: [...DEFAULT_ALLOWED_COMMANDS]
|
|
27
|
+
toolOutputTokenLimit: DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT
|
|
34
28
|
};
|
|
35
29
|
|
|
36
30
|
let parsed = null;
|
|
@@ -50,11 +44,6 @@ const loadSettings = ({ defaultTools = [] } = {}) => {
|
|
|
50
44
|
parsed.toolOutputTokenLimit = DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT;
|
|
51
45
|
needsUpdate = true;
|
|
52
46
|
}
|
|
53
|
-
|
|
54
|
-
if (!Array.isArray(parsed.allowedCommands)) {
|
|
55
|
-
parsed.allowedCommands = [...DEFAULT_ALLOWED_COMMANDS];
|
|
56
|
-
needsUpdate = true;
|
|
57
|
-
}
|
|
58
47
|
} catch (error) {
|
|
59
48
|
parsed = defaultSettings;
|
|
60
49
|
}
|
|
@@ -65,8 +54,7 @@ const loadSettings = ({ defaultTools = [] } = {}) => {
|
|
|
65
54
|
tools: ensureArrayOfStrings(parsed.tools, defaultSettings.tools),
|
|
66
55
|
toolOutputTokenLimit: typeof parsed.toolOutputTokenLimit === 'number' && parsed.toolOutputTokenLimit > 0
|
|
67
56
|
? parsed.toolOutputTokenLimit
|
|
68
|
-
: DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT
|
|
69
|
-
allowedCommands: ensureArrayOfStrings(parsed.allowedCommands, DEFAULT_ALLOWED_COMMANDS)
|
|
57
|
+
: DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT
|
|
70
58
|
};
|
|
71
59
|
|
|
72
60
|
if (needsUpdate) {
|
|
@@ -85,6 +73,5 @@ const loadSettings = ({ defaultTools = [] } = {}) => {
|
|
|
85
73
|
|
|
86
74
|
module.exports = {
|
|
87
75
|
loadSettings,
|
|
88
|
-
DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT
|
|
89
|
-
DEFAULT_ALLOWED_COMMANDS
|
|
76
|
+
DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT
|
|
90
77
|
};
|