@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ww_nero/mini-cli",
3
- "version": "1.0.76",
3
+ "version": "1.0.77",
4
4
  "description": "极简的 AI 命令行助手",
5
5
  "bin": {
6
6
  "mini": "bin/mini.js"
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 = (context = {}) => {
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 {
@@ -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,
@@ -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
  };