protoagent 0.0.5 ā 0.1.0
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/README.md +99 -19
- package/dist/App.js +602 -0
- package/dist/agentic-loop.js +492 -525
- package/dist/cli.js +39 -0
- package/dist/components/CollapsibleBox.js +26 -0
- package/dist/components/ConfigDialog.js +40 -0
- package/dist/components/ConsolidatedToolMessage.js +41 -0
- package/dist/components/FormattedMessage.js +93 -0
- package/dist/components/Table.js +275 -0
- package/dist/config.js +171 -0
- package/dist/mcp.js +170 -0
- package/dist/providers.js +137 -0
- package/dist/sessions.js +161 -0
- package/dist/skills.js +229 -0
- package/dist/sub-agent.js +103 -0
- package/dist/system-prompt.js +131 -0
- package/dist/tools/bash.js +178 -0
- package/dist/tools/edit-file.js +65 -171
- package/dist/tools/index.js +79 -134
- package/dist/tools/list-directory.js +20 -73
- package/dist/tools/read-file.js +57 -101
- package/dist/tools/search-files.js +74 -162
- package/dist/tools/todo.js +57 -140
- package/dist/tools/webfetch.js +310 -0
- package/dist/tools/write-file.js +44 -135
- package/dist/utils/approval.js +69 -0
- package/dist/utils/compactor.js +87 -0
- package/dist/utils/cost-tracker.js +26 -81
- package/dist/utils/format-message.js +26 -0
- package/dist/utils/logger.js +101 -307
- package/dist/utils/path-validation.js +74 -0
- package/package.json +45 -51
- package/LICENSE +0 -21
- package/dist/config/client.js +0 -315
- package/dist/config/commands.js +0 -223
- package/dist/config/manager.js +0 -117
- package/dist/config/mcp-commands.js +0 -266
- package/dist/config/mcp-manager.js +0 -240
- package/dist/config/mcp-types.js +0 -28
- package/dist/config/providers.js +0 -229
- package/dist/config/setup.js +0 -209
- package/dist/config/system-prompt.js +0 -397
- package/dist/config/types.js +0 -4
- package/dist/index.js +0 -229
- package/dist/tools/create-directory.js +0 -76
- package/dist/tools/directory-operations.js +0 -195
- package/dist/tools/file-operations.js +0 -211
- package/dist/tools/run-shell-command.js +0 -746
- package/dist/tools/search-operations.js +0 -179
- package/dist/tools/shell-operations.js +0 -342
- package/dist/tools/task-complete.js +0 -26
- package/dist/tools/view-directory-tree.js +0 -125
- package/dist/tools.js +0 -2
- package/dist/utils/conversation-compactor.js +0 -140
- package/dist/utils/enhanced-prompt.js +0 -23
- package/dist/utils/file-operations-approval.js +0 -373
- package/dist/utils/interrupt-handler.js +0 -127
- package/dist/utils/user-cancellation.js +0 -34
|
@@ -1,746 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { logger } from '../utils/logger.js';
|
|
3
|
-
import { UserCancellationError } from '../utils/user-cancellation.js';
|
|
4
|
-
import { enhancedPrompt } from '../utils/enhanced-prompt.js';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
// Current working directory for file operations
|
|
8
|
-
const workingDirectory = process.cwd();
|
|
9
|
-
/**
|
|
10
|
-
* Parse compound shell commands and extract directory changes
|
|
11
|
-
* Handles patterns like: cd directory && command args
|
|
12
|
-
*/
|
|
13
|
-
function parseCompoundCommand(command, args) {
|
|
14
|
-
// Join command and args to analyze the full command string
|
|
15
|
-
const fullCommand = args.length > 0 ? `${command} ${args.join(' ')}` : command;
|
|
16
|
-
// Check for cd && pattern
|
|
17
|
-
const cdPattern = /^cd\s+([^&]+)\s*&&\s*(.+)$/;
|
|
18
|
-
const match = fullCommand.match(cdPattern);
|
|
19
|
-
if (match) {
|
|
20
|
-
const directory = match[1].trim();
|
|
21
|
-
const remainingCommand = match[2].trim();
|
|
22
|
-
// Parse the remaining command into command and args
|
|
23
|
-
const parts = remainingCommand.split(/\s+/);
|
|
24
|
-
const actualCommand = parts[0];
|
|
25
|
-
const actualArgs = parts.slice(1);
|
|
26
|
-
logger.debug('š Parsed compound command', {
|
|
27
|
-
component: 'ShellCommand',
|
|
28
|
-
original: fullCommand,
|
|
29
|
-
extractedDirectory: directory,
|
|
30
|
-
extractedCommand: actualCommand,
|
|
31
|
-
extractedArgs: actualArgs
|
|
32
|
-
});
|
|
33
|
-
return {
|
|
34
|
-
directory,
|
|
35
|
-
actualCommand,
|
|
36
|
-
actualArgs
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
// No compound command detected, return as-is
|
|
40
|
-
return {
|
|
41
|
-
actualCommand: command,
|
|
42
|
-
actualArgs: args
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Validate and resolve a subdirectory path safely
|
|
47
|
-
* Ensures the directory is within the working directory
|
|
48
|
-
*/
|
|
49
|
-
async function validateAndResolveDirectory(requestedDir) {
|
|
50
|
-
if (!requestedDir) {
|
|
51
|
-
return workingDirectory;
|
|
52
|
-
}
|
|
53
|
-
// Resolve the path relative to working directory
|
|
54
|
-
const resolvedPath = path.resolve(workingDirectory, requestedDir);
|
|
55
|
-
// Ensure the resolved path is within the working directory
|
|
56
|
-
if (!resolvedPath.startsWith(workingDirectory)) {
|
|
57
|
-
throw new Error(`Directory access denied - path outside working directory: ${requestedDir}`);
|
|
58
|
-
}
|
|
59
|
-
// Check if directory exists
|
|
60
|
-
try {
|
|
61
|
-
const stats = await fs.promises.stat(resolvedPath);
|
|
62
|
-
if (!stats.isDirectory()) {
|
|
63
|
-
throw new Error(`Path is not a directory: ${requestedDir}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
if (error.code === 'ENOENT') {
|
|
68
|
-
throw new Error(`Directory does not exist: ${requestedDir}`);
|
|
69
|
-
}
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
return resolvedPath;
|
|
73
|
-
}
|
|
74
|
-
// Global flags and session state
|
|
75
|
-
let globalConfig = null;
|
|
76
|
-
let dangerouslyAcceptAll = false;
|
|
77
|
-
let approvedCommandsForSession = new Set();
|
|
78
|
-
// Whitelisted safe commands that don't require confirmation
|
|
79
|
-
const SAFE_COMMANDS = [
|
|
80
|
-
'ls', 'dir', 'pwd', 'whoami', 'date', 'echo', 'cat', 'head', 'tail',
|
|
81
|
-
'grep', 'find', 'wc', 'sort', 'uniq', 'cut', 'awk', 'sed',
|
|
82
|
-
'git status', 'git log', 'git diff', 'git branch', 'git show',
|
|
83
|
-
'npm list', 'npm ls', 'yarn list', 'node --version', 'npm --version',
|
|
84
|
-
'python --version', 'python3 --version', 'which', 'type', 'file'
|
|
85
|
-
];
|
|
86
|
-
/**
|
|
87
|
-
* Detect if a command is likely to be interactive
|
|
88
|
-
*/
|
|
89
|
-
function isInteractiveCommand(commandString) {
|
|
90
|
-
const cmd = commandString.toLowerCase();
|
|
91
|
-
// Commands that are commonly interactive
|
|
92
|
-
const interactivePatterns = [
|
|
93
|
-
/npm create/,
|
|
94
|
-
/npm init(?!\s+\-y)/, // npm init without -y flag
|
|
95
|
-
/yarn create/,
|
|
96
|
-
/yarn init(?!\s+\-y)/,
|
|
97
|
-
/git commit(?!.*\-m)/, // git commit without -m flag
|
|
98
|
-
/git rebase\s+\-i/,
|
|
99
|
-
/git add\s+\-p/,
|
|
100
|
-
/npx create\-/,
|
|
101
|
-
/vue create/,
|
|
102
|
-
/ng new/,
|
|
103
|
-
/rails new/,
|
|
104
|
-
/django\-admin startproject/,
|
|
105
|
-
/composer create\-project/,
|
|
106
|
-
/cargo new/,
|
|
107
|
-
/dotnet new/
|
|
108
|
-
];
|
|
109
|
-
return interactivePatterns.some(pattern => pattern.test(cmd));
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Run an interactive command with direct terminal access
|
|
113
|
-
*/
|
|
114
|
-
async function runInteractiveCommand(command, args = [], executionDirectory) {
|
|
115
|
-
logger.consoleLog(`\nšÆ Running interactive command...`);
|
|
116
|
-
logger.consoleLog(`š Directory: ${executionDirectory}`);
|
|
117
|
-
logger.consoleLog(`š„ļø Command: ${command} ${args.join(' ')}`);
|
|
118
|
-
logger.consoleLog(`\nš” You can interact with this command directly. Press Ctrl+C if needed.\n`);
|
|
119
|
-
return new Promise((resolve, reject) => {
|
|
120
|
-
const child = spawn(command, args, {
|
|
121
|
-
cwd: executionDirectory,
|
|
122
|
-
stdio: 'inherit', // Allow direct interaction with terminal
|
|
123
|
-
shell: true,
|
|
124
|
-
env: { ...process.env, PATH: process.env.PATH }
|
|
125
|
-
});
|
|
126
|
-
child.on('close', (code) => {
|
|
127
|
-
if (code === 0) {
|
|
128
|
-
logger.consoleLog(`\nā
Interactive command completed successfully (exit code: ${code})`);
|
|
129
|
-
resolve(`Interactive command executed successfully (exit code: ${code})\n\nCommand: ${command} ${args.join(' ')}\nDirectory: ${executionDirectory}`);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
logger.consoleLog(`\nā ļø Interactive command exited with code ${code}`);
|
|
133
|
-
resolve(`Interactive command exited with code ${code}\n\nCommand: ${command} ${args.join(' ')}\nDirectory: ${executionDirectory}\n\nNote: Non-zero exit codes don't always indicate errors for interactive commands.`);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
child.on('error', (error) => {
|
|
137
|
-
logger.consoleLog(`\nā Interactive command failed: ${error.message}`);
|
|
138
|
-
reject(new Error(`Failed to execute interactive command: ${error.message}`));
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Show detailed preview of what a shell command will do
|
|
144
|
-
*/
|
|
145
|
-
async function showShellCommandPreview(commandString, directory) {
|
|
146
|
-
logger.consoleLog(`\nš Shell Command Preview`);
|
|
147
|
-
logger.consoleLog(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`);
|
|
148
|
-
logger.consoleLog(`š„ļø Command: ${commandString}`);
|
|
149
|
-
logger.consoleLog(`š Working Directory: ${directory}`);
|
|
150
|
-
// Show relative path for better readability
|
|
151
|
-
const relativePath = path.relative(workingDirectory, directory);
|
|
152
|
-
if (relativePath && relativePath !== '.') {
|
|
153
|
-
logger.consoleLog(`š Subdirectory: ${relativePath}`);
|
|
154
|
-
}
|
|
155
|
-
// Analyze the command and show what it will do
|
|
156
|
-
const analysis = analyzeShellCommand(commandString);
|
|
157
|
-
// Show risk assessment
|
|
158
|
-
const riskColor = analysis.riskLevel === 'LOW' ? 'š¢' : analysis.riskLevel === 'MEDIUM' ? 'š”' : 'š“';
|
|
159
|
-
logger.consoleLog(`${riskColor} Risk Level: ${analysis.riskLevel} - ${analysis.riskReason}`);
|
|
160
|
-
logger.consoleLog(`\nš Command Analysis:`);
|
|
161
|
-
logger.consoleLog(` ⢠Purpose: ${analysis.purpose}`);
|
|
162
|
-
logger.consoleLog(` ⢠Expected output: ${analysis.expectedOutput}`);
|
|
163
|
-
logger.consoleLog(` ⢠Side effects: ${analysis.sideEffects}`);
|
|
164
|
-
if (analysis.fileSystemChanges.length > 0) {
|
|
165
|
-
logger.consoleLog(`\nš Potential file system changes:`);
|
|
166
|
-
analysis.fileSystemChanges.forEach(change => {
|
|
167
|
-
logger.consoleLog(` ⢠${change}`);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
if (analysis.networkActivity) {
|
|
171
|
-
logger.consoleLog(`\nš Network activity: ${analysis.networkActivity}`);
|
|
172
|
-
}
|
|
173
|
-
if (analysis.warnings.length > 0) {
|
|
174
|
-
logger.consoleLog(`\nā ļø Warnings:`);
|
|
175
|
-
analysis.warnings.forEach(warning => {
|
|
176
|
-
logger.consoleLog(` ⢠${warning}`);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
if (analysis.suggestions.length > 0) {
|
|
180
|
-
logger.consoleLog(`\nš” Suggestions:`);
|
|
181
|
-
analysis.suggestions.forEach(suggestion => {
|
|
182
|
-
logger.consoleLog(` ⢠${suggestion}`);
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
logger.consoleLog(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`);
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Analyze a shell command to understand what it will do
|
|
189
|
-
*/
|
|
190
|
-
function analyzeShellCommand(commandString) {
|
|
191
|
-
const cmd = commandString.toLowerCase();
|
|
192
|
-
const parts = commandString.split(' ');
|
|
193
|
-
const baseCommand = parts[0].toLowerCase();
|
|
194
|
-
// Initialize analysis result
|
|
195
|
-
let analysis = {
|
|
196
|
-
riskLevel: 'LOW',
|
|
197
|
-
riskReason: 'Standard read-only operation',
|
|
198
|
-
purpose: 'Unknown command',
|
|
199
|
-
expectedOutput: 'Command output',
|
|
200
|
-
sideEffects: 'None',
|
|
201
|
-
fileSystemChanges: [],
|
|
202
|
-
networkActivity: undefined,
|
|
203
|
-
warnings: [],
|
|
204
|
-
suggestions: []
|
|
205
|
-
};
|
|
206
|
-
// Check if this is an interactive command
|
|
207
|
-
if (isInteractiveCommand(commandString)) {
|
|
208
|
-
analysis.warnings.push('This is an interactive command - you will be able to interact directly with it');
|
|
209
|
-
analysis.suggestions.push('The command will run with direct terminal access for interaction');
|
|
210
|
-
analysis.expectedOutput = 'Interactive session with prompts';
|
|
211
|
-
}
|
|
212
|
-
// Analyze based on command type
|
|
213
|
-
switch (baseCommand) {
|
|
214
|
-
case 'ls':
|
|
215
|
-
case 'dir':
|
|
216
|
-
analysis.purpose = 'List directory contents';
|
|
217
|
-
analysis.expectedOutput = 'File and folder names with details';
|
|
218
|
-
break;
|
|
219
|
-
case 'find':
|
|
220
|
-
analysis.purpose = 'Search for files and directories';
|
|
221
|
-
analysis.expectedOutput = 'Paths matching search criteria';
|
|
222
|
-
if (cmd.includes('-exec') || cmd.includes('-delete')) {
|
|
223
|
-
analysis.riskLevel = 'HIGH';
|
|
224
|
-
analysis.riskReason = 'Find command with execution or deletion flags';
|
|
225
|
-
analysis.sideEffects = 'May modify or delete files';
|
|
226
|
-
}
|
|
227
|
-
break;
|
|
228
|
-
case 'grep':
|
|
229
|
-
analysis.purpose = 'Search text within files';
|
|
230
|
-
analysis.expectedOutput = 'Matching lines with context';
|
|
231
|
-
break;
|
|
232
|
-
case 'cat':
|
|
233
|
-
case 'head':
|
|
234
|
-
case 'tail':
|
|
235
|
-
analysis.purpose = 'Display file contents';
|
|
236
|
-
analysis.expectedOutput = 'File content';
|
|
237
|
-
break;
|
|
238
|
-
case 'git':
|
|
239
|
-
const gitSubcommand = parts[1]?.toLowerCase();
|
|
240
|
-
switch (gitSubcommand) {
|
|
241
|
-
case 'status':
|
|
242
|
-
case 'log':
|
|
243
|
-
case 'diff':
|
|
244
|
-
case 'show':
|
|
245
|
-
case 'branch':
|
|
246
|
-
analysis.purpose = `Show git ${gitSubcommand} information`;
|
|
247
|
-
analysis.expectedOutput = `Git ${gitSubcommand} output`;
|
|
248
|
-
break;
|
|
249
|
-
case 'add':
|
|
250
|
-
analysis.purpose = 'Stage files for commit';
|
|
251
|
-
analysis.riskLevel = 'MEDIUM';
|
|
252
|
-
analysis.riskReason = 'Modifies git index';
|
|
253
|
-
analysis.sideEffects = 'Stages files for next commit';
|
|
254
|
-
break;
|
|
255
|
-
case 'commit':
|
|
256
|
-
analysis.purpose = 'Create a new commit';
|
|
257
|
-
analysis.riskLevel = 'MEDIUM';
|
|
258
|
-
analysis.riskReason = 'Creates permanent git history';
|
|
259
|
-
analysis.sideEffects = 'Adds commit to repository history';
|
|
260
|
-
if (!cmd.includes('-m')) {
|
|
261
|
-
analysis.warnings.push('Missing commit message - will open interactive editor');
|
|
262
|
-
analysis.suggestions.push('Add -m "message" to avoid interactive mode');
|
|
263
|
-
}
|
|
264
|
-
break;
|
|
265
|
-
case 'push':
|
|
266
|
-
case 'pull':
|
|
267
|
-
case 'fetch':
|
|
268
|
-
analysis.purpose = `Synchronize with remote repository (${gitSubcommand})`;
|
|
269
|
-
analysis.riskLevel = 'MEDIUM';
|
|
270
|
-
analysis.riskReason = 'Network operation affecting repository';
|
|
271
|
-
analysis.networkActivity = `Git ${gitSubcommand} to remote repository`;
|
|
272
|
-
analysis.sideEffects = 'May update local or remote repository';
|
|
273
|
-
break;
|
|
274
|
-
default:
|
|
275
|
-
analysis.purpose = `Execute git ${gitSubcommand} command`;
|
|
276
|
-
analysis.riskLevel = 'MEDIUM';
|
|
277
|
-
analysis.riskReason = 'Git operation with unknown effects';
|
|
278
|
-
}
|
|
279
|
-
break;
|
|
280
|
-
case 'npm':
|
|
281
|
-
case 'yarn':
|
|
282
|
-
const packageSubcommand = parts[1]?.toLowerCase();
|
|
283
|
-
switch (packageSubcommand) {
|
|
284
|
-
case 'install':
|
|
285
|
-
case 'i':
|
|
286
|
-
analysis.purpose = 'Install package dependencies';
|
|
287
|
-
analysis.riskLevel = 'MEDIUM';
|
|
288
|
-
analysis.riskReason = 'Downloads and installs packages from internet';
|
|
289
|
-
analysis.networkActivity = 'Downloads packages from npm registry';
|
|
290
|
-
analysis.fileSystemChanges = ['Creates/updates node_modules/', 'Updates package-lock.json'];
|
|
291
|
-
analysis.sideEffects = 'Installs dependencies and updates lock file';
|
|
292
|
-
break;
|
|
293
|
-
case 'create':
|
|
294
|
-
analysis.purpose = 'Create new project from template';
|
|
295
|
-
analysis.riskLevel = 'MEDIUM';
|
|
296
|
-
analysis.riskReason = 'Creates new project structure';
|
|
297
|
-
analysis.networkActivity = 'Downloads project template';
|
|
298
|
-
analysis.fileSystemChanges = ['Creates new project directory', 'Installs dependencies'];
|
|
299
|
-
analysis.expectedOutput = 'Interactive prompts for project configuration';
|
|
300
|
-
analysis.sideEffects = 'Creates project files and installs dependencies interactively';
|
|
301
|
-
if (!cmd.includes('--template')) {
|
|
302
|
-
analysis.warnings.push('Interactive command - you will be prompted to choose template and options');
|
|
303
|
-
analysis.suggestions.push('You can answer prompts directly in the terminal');
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
analysis.suggestions.push('Template specified - fewer interactive prompts expected');
|
|
307
|
-
}
|
|
308
|
-
break;
|
|
309
|
-
case 'run':
|
|
310
|
-
case 'start':
|
|
311
|
-
case 'build':
|
|
312
|
-
case 'test':
|
|
313
|
-
analysis.purpose = `Run ${packageSubcommand} script`;
|
|
314
|
-
analysis.riskLevel = 'MEDIUM';
|
|
315
|
-
analysis.riskReason = 'Executes custom scripts defined in package.json';
|
|
316
|
-
analysis.sideEffects = 'Depends on script contents - may build, test, or start server';
|
|
317
|
-
break;
|
|
318
|
-
default:
|
|
319
|
-
analysis.purpose = `Execute ${baseCommand} ${packageSubcommand} command`;
|
|
320
|
-
analysis.riskLevel = 'MEDIUM';
|
|
321
|
-
analysis.riskReason = 'Package manager operation';
|
|
322
|
-
}
|
|
323
|
-
break;
|
|
324
|
-
case 'mkdir':
|
|
325
|
-
analysis.purpose = 'Create directories';
|
|
326
|
-
analysis.riskLevel = 'LOW';
|
|
327
|
-
analysis.fileSystemChanges = [`Creates directory: ${parts.slice(1).join(', ')}`];
|
|
328
|
-
analysis.sideEffects = 'Creates new directories';
|
|
329
|
-
break;
|
|
330
|
-
case 'rm':
|
|
331
|
-
analysis.purpose = 'Remove files and directories';
|
|
332
|
-
analysis.riskLevel = 'HIGH';
|
|
333
|
-
analysis.riskReason = 'Permanently deletes files';
|
|
334
|
-
analysis.sideEffects = 'PERMANENTLY DELETES FILES';
|
|
335
|
-
analysis.fileSystemChanges = [`Deletes: ${parts.slice(1).join(', ')}`];
|
|
336
|
-
analysis.warnings.push('DESTRUCTIVE OPERATION - files will be permanently deleted');
|
|
337
|
-
if (cmd.includes('-rf')) {
|
|
338
|
-
analysis.warnings.push('Recursive force delete - EXTREMELY DANGEROUS');
|
|
339
|
-
}
|
|
340
|
-
break;
|
|
341
|
-
case 'cp':
|
|
342
|
-
case 'copy':
|
|
343
|
-
analysis.purpose = 'Copy files or directories';
|
|
344
|
-
analysis.riskLevel = 'LOW';
|
|
345
|
-
analysis.sideEffects = 'Creates file copies';
|
|
346
|
-
analysis.fileSystemChanges = ['Creates copies of specified files'];
|
|
347
|
-
break;
|
|
348
|
-
case 'mv':
|
|
349
|
-
case 'move':
|
|
350
|
-
analysis.purpose = 'Move or rename files';
|
|
351
|
-
analysis.riskLevel = 'MEDIUM';
|
|
352
|
-
analysis.riskReason = 'Modifies file locations';
|
|
353
|
-
analysis.sideEffects = 'Moves or renames files';
|
|
354
|
-
analysis.fileSystemChanges = ['Moves/renames specified files'];
|
|
355
|
-
break;
|
|
356
|
-
case 'chmod':
|
|
357
|
-
analysis.purpose = 'Change file permissions';
|
|
358
|
-
analysis.riskLevel = 'MEDIUM';
|
|
359
|
-
analysis.riskReason = 'Modifies file security permissions';
|
|
360
|
-
analysis.sideEffects = 'Changes file permissions';
|
|
361
|
-
if (cmd.includes('777')) {
|
|
362
|
-
analysis.riskLevel = 'HIGH';
|
|
363
|
-
analysis.riskReason = 'Setting permissions to 777 (world-writable) is dangerous';
|
|
364
|
-
analysis.warnings.push('chmod 777 makes files writable by everyone - security risk');
|
|
365
|
-
}
|
|
366
|
-
break;
|
|
367
|
-
case 'curl':
|
|
368
|
-
case 'wget':
|
|
369
|
-
analysis.purpose = 'Download content from internet';
|
|
370
|
-
analysis.riskLevel = 'MEDIUM';
|
|
371
|
-
analysis.riskReason = 'Network operation downloading external content';
|
|
372
|
-
analysis.networkActivity = 'Downloads content from specified URL';
|
|
373
|
-
if (cmd.includes('-o') || cmd.includes('--output')) {
|
|
374
|
-
analysis.fileSystemChanges = ['Creates downloaded file'];
|
|
375
|
-
}
|
|
376
|
-
break;
|
|
377
|
-
case 'python':
|
|
378
|
-
case 'python3':
|
|
379
|
-
case 'node':
|
|
380
|
-
analysis.purpose = 'Execute script';
|
|
381
|
-
analysis.riskLevel = 'MEDIUM';
|
|
382
|
-
analysis.riskReason = 'Executes code with unknown effects';
|
|
383
|
-
analysis.sideEffects = 'Depends on script contents';
|
|
384
|
-
break;
|
|
385
|
-
default:
|
|
386
|
-
analysis.purpose = 'Execute command';
|
|
387
|
-
analysis.riskLevel = 'MEDIUM';
|
|
388
|
-
analysis.riskReason = 'Unknown command with unpredictable effects';
|
|
389
|
-
analysis.warnings.push('Unknown command - effects cannot be predicted');
|
|
390
|
-
}
|
|
391
|
-
// Check for dangerous patterns
|
|
392
|
-
if (cmd.includes('sudo')) {
|
|
393
|
-
analysis.riskLevel = 'HIGH';
|
|
394
|
-
analysis.riskReason = 'Requires elevated privileges';
|
|
395
|
-
analysis.warnings.push('Sudo command requires administrator privileges');
|
|
396
|
-
}
|
|
397
|
-
if (cmd.includes('rm -rf')) {
|
|
398
|
-
analysis.riskLevel = 'HIGH';
|
|
399
|
-
analysis.riskReason = 'Recursive force deletion';
|
|
400
|
-
analysis.warnings.push('DANGER: rm -rf can delete entire directory trees');
|
|
401
|
-
}
|
|
402
|
-
if (cmd.includes('format') || cmd.includes('mkfs') || cmd.includes('fdisk')) {
|
|
403
|
-
analysis.riskLevel = 'HIGH';
|
|
404
|
-
analysis.riskReason = 'Disk formatting/partitioning commands';
|
|
405
|
-
analysis.warnings.push('DANGER: Disk formatting commands can destroy data');
|
|
406
|
-
}
|
|
407
|
-
return analysis;
|
|
408
|
-
}
|
|
409
|
-
export function setShellConfig(config) {
|
|
410
|
-
globalConfig = config;
|
|
411
|
-
}
|
|
412
|
-
export function setDangerouslyAcceptAll(accept) {
|
|
413
|
-
dangerouslyAcceptAll = accept;
|
|
414
|
-
}
|
|
415
|
-
export async function runShellCommand(command, args = [], timeoutMs = 30000, directory) {
|
|
416
|
-
// Parse compound commands like "cd directory && command"
|
|
417
|
-
const parsed = parseCompoundCommand(command, args);
|
|
418
|
-
const finalDirectory = parsed.directory || directory; // Parsed directory takes precedence
|
|
419
|
-
return await runShellCommandWithRetry(parsed.actualCommand, parsed.actualArgs, timeoutMs, 0, finalDirectory);
|
|
420
|
-
}
|
|
421
|
-
async function runShellCommandWithRetry(command, args = [], timeoutMs = 30000, retryCount = 0, directory) {
|
|
422
|
-
const maxRetries = 2;
|
|
423
|
-
try {
|
|
424
|
-
// Validate and resolve the directory parameter
|
|
425
|
-
const executionDirectory = await validateAndResolveDirectory(directory);
|
|
426
|
-
// Security: Basic validation to prevent obviously dangerous commands
|
|
427
|
-
const dangerousCommands = ['rm -rf', 'sudo', 'su', 'chmod 777', 'dd if=', 'mkfs', 'fdisk', 'format'];
|
|
428
|
-
const fullCommand = `${command} ${args.join(' ')}`.toLowerCase();
|
|
429
|
-
for (const dangerous of dangerousCommands) {
|
|
430
|
-
if (fullCommand.includes(dangerous)) {
|
|
431
|
-
throw new Error(`Command contains potentially dangerous operation: ${dangerous}`);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const commandString = `${command} ${args.join(' ')}`;
|
|
435
|
-
const baseCommand = command.toLowerCase();
|
|
436
|
-
// Check if we should auto-approve (only show approval prompt on first attempt)
|
|
437
|
-
if (retryCount === 0) {
|
|
438
|
-
if (dangerouslyAcceptAll) {
|
|
439
|
-
logger.consoleLog(`š Auto-executing (--dangerously-accept-all): ${commandString}`);
|
|
440
|
-
}
|
|
441
|
-
else if (approvedCommandsForSession.has(baseCommand)) {
|
|
442
|
-
logger.consoleLog(`š Auto-executing (${baseCommand} approved for session): ${commandString}`);
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
// Check if it's a safe command
|
|
446
|
-
const isSafeCommand = SAFE_COMMANDS.some(safe => {
|
|
447
|
-
const normalizedSafe = safe.toLowerCase();
|
|
448
|
-
const normalizedFull = fullCommand.toLowerCase();
|
|
449
|
-
return normalizedFull === normalizedSafe ||
|
|
450
|
-
(normalizedSafe.includes(' ') && normalizedFull.startsWith(normalizedSafe)) ||
|
|
451
|
-
(!normalizedSafe.includes(' ') && normalizedFull.split(' ')[0] === normalizedSafe);
|
|
452
|
-
});
|
|
453
|
-
if (isSafeCommand) {
|
|
454
|
-
logger.consoleLog(`š¢ Executing safe command: ${commandString}`);
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
// Require user confirmation with enhanced options
|
|
458
|
-
await showShellCommandPreview(commandString, executionDirectory);
|
|
459
|
-
const choice = await enhancedPrompt('Choose your action:', [
|
|
460
|
-
{ name: '1. ā
Execute this command now', value: 'execute' },
|
|
461
|
-
{ name: `2. ā
Execute and approve all "${baseCommand}" commands for this session`, value: 'approve_session' },
|
|
462
|
-
{ name: '3. ā Cancel and suggest alternative', value: 'cancel' }
|
|
463
|
-
], 'execute');
|
|
464
|
-
switch (choice) {
|
|
465
|
-
case 'execute':
|
|
466
|
-
logger.consoleLog(`š Executing: ${commandString}`);
|
|
467
|
-
break;
|
|
468
|
-
case 'approve_session':
|
|
469
|
-
approvedCommandsForSession.add(baseCommand);
|
|
470
|
-
logger.consoleLog(`š "${baseCommand}" approved for session - all future ${baseCommand} commands will auto-execute`);
|
|
471
|
-
logger.consoleLog(`š Executing: ${commandString}`);
|
|
472
|
-
break;
|
|
473
|
-
case 'cancel':
|
|
474
|
-
throw new UserCancellationError(`shell command: ${commandString}`, 'User chose to cancel and suggest alternative');
|
|
475
|
-
default:
|
|
476
|
-
throw new UserCancellationError(`shell command: ${commandString}`, 'User did not approve the command');
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
logger.consoleLog(`š Retry attempt ${retryCount}/${maxRetries}: ${commandString}`);
|
|
483
|
-
}
|
|
484
|
-
// Check if this is an interactive command and handle it differently
|
|
485
|
-
if (isInteractiveCommand(commandString)) {
|
|
486
|
-
logger.consoleLog(`šÆ Detected interactive command: ${commandString}`);
|
|
487
|
-
return await runInteractiveCommand(command, args, executionDirectory);
|
|
488
|
-
}
|
|
489
|
-
return new Promise((resolve, reject) => {
|
|
490
|
-
const child = spawn(command, args, {
|
|
491
|
-
cwd: executionDirectory,
|
|
492
|
-
stdio: ['pipe', 'pipe', 'pipe'], // Capture all input/output for AI processing
|
|
493
|
-
shell: true,
|
|
494
|
-
env: { ...process.env, PATH: process.env.PATH }
|
|
495
|
-
});
|
|
496
|
-
let stdout = '';
|
|
497
|
-
let stderr = '';
|
|
498
|
-
let completed = false;
|
|
499
|
-
// Set up timeout
|
|
500
|
-
const timeout = setTimeout(() => {
|
|
501
|
-
if (!completed) {
|
|
502
|
-
completed = true;
|
|
503
|
-
child.kill('SIGTERM');
|
|
504
|
-
reject(new Error(`TIMEOUT_ERROR:${timeoutMs}`));
|
|
505
|
-
}
|
|
506
|
-
}, timeoutMs);
|
|
507
|
-
child.stdout?.on('data', (data) => {
|
|
508
|
-
stdout += data.toString();
|
|
509
|
-
});
|
|
510
|
-
child.stderr?.on('data', (data) => {
|
|
511
|
-
stderr += data.toString();
|
|
512
|
-
});
|
|
513
|
-
child.on('close', (code) => {
|
|
514
|
-
if (!completed) {
|
|
515
|
-
completed = true;
|
|
516
|
-
clearTimeout(timeout);
|
|
517
|
-
const output = stdout + (stderr ? `\nSTDERR:\n${stderr}` : '');
|
|
518
|
-
if (code === 0) {
|
|
519
|
-
resolve(`Command executed successfully (exit code: ${code})\n\nOutput:\n${output || '(no output)'}`);
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
// For non-zero exit codes, still return the output but indicate the failure
|
|
523
|
-
resolve(`Command exited with code ${code}\n\nOutput:\n${output || '(no output)'}`);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
});
|
|
527
|
-
child.on('error', (error) => {
|
|
528
|
-
if (!completed) {
|
|
529
|
-
completed = true;
|
|
530
|
-
clearTimeout(timeout);
|
|
531
|
-
reject(new Error(`Failed to execute command: ${error.message}`));
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
catch (error) {
|
|
537
|
-
if (error instanceof Error) {
|
|
538
|
-
// Check if this is a timeout error and we haven't exceeded max retries
|
|
539
|
-
if (error.message.startsWith('TIMEOUT_ERROR:') && retryCount < maxRetries) {
|
|
540
|
-
const timeoutDuration = parseInt(error.message.split(':')[1]);
|
|
541
|
-
return await analyzeTimeoutAndRetry(command, args, timeoutDuration, retryCount);
|
|
542
|
-
}
|
|
543
|
-
throw new Error(`Shell command error: ${error.message.replace('TIMEOUT_ERROR:', 'Command timed out after ').replace(/:\d+/, 'ms')}`);
|
|
544
|
-
}
|
|
545
|
-
throw new Error('Shell command error: Unknown error');
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
async function analyzeTimeoutAndRetry(command, args = [], originalTimeout, retryCount) {
|
|
549
|
-
const commandString = `${command} ${args.join(' ')}`;
|
|
550
|
-
logger.consoleLog(`ā±ļø Command timed out after ${originalTimeout}ms: ${commandString}`);
|
|
551
|
-
logger.consoleLog(`š Analyzing timeout cause and preparing retry...`);
|
|
552
|
-
// Analyze the command to understand why it might have timed out
|
|
553
|
-
const analysis = analyzeCommandForTimeout(command, args);
|
|
554
|
-
logger.consoleLog(`š” Timeout analysis: ${analysis.reason}`);
|
|
555
|
-
if (analysis.suggestedArgs.length > 0) {
|
|
556
|
-
logger.consoleLog(`š§ Suggested fix: ${command} ${analysis.suggestedArgs.join(' ')}`);
|
|
557
|
-
// Try with suggested arguments
|
|
558
|
-
return await runShellCommandWithRetry(command, analysis.suggestedArgs, analysis.suggestedTimeout, retryCount + 1);
|
|
559
|
-
}
|
|
560
|
-
else if (analysis.suggestedTimeout > originalTimeout) {
|
|
561
|
-
logger.consoleLog(`ā° Increasing timeout to ${analysis.suggestedTimeout}ms for long-running operation`);
|
|
562
|
-
// Try with longer timeout
|
|
563
|
-
return await runShellCommandWithRetry(command, args, analysis.suggestedTimeout, retryCount + 1);
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
// No specific fix found, provide analysis
|
|
567
|
-
return `Command timed out after ${originalTimeout}ms: ${commandString}\n\nTimeout Analysis: ${analysis.reason}\n\nSuggestion: ${analysis.suggestion}`;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
function analyzeCommandForTimeout(command, args) {
|
|
571
|
-
const fullCommand = `${command} ${args.join(' ')}`.toLowerCase();
|
|
572
|
-
const baseCommand = command.toLowerCase();
|
|
573
|
-
// First check if this is an interactive command that should use direct terminal access
|
|
574
|
-
if (isInteractiveCommand(fullCommand)) {
|
|
575
|
-
return {
|
|
576
|
-
reason: "This is an interactive command that requires user input",
|
|
577
|
-
suggestion: "Interactive commands should use direct terminal access, not captured I/O",
|
|
578
|
-
suggestedArgs: args,
|
|
579
|
-
suggestedTimeout: 30000
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
// Interactive command detection
|
|
583
|
-
if (baseCommand === 'npm' && args.length > 0) {
|
|
584
|
-
const npmSubcommand = args[0].toLowerCase();
|
|
585
|
-
if (npmSubcommand === 'create' || npmSubcommand === 'init') {
|
|
586
|
-
// Check if template is specified for npm create
|
|
587
|
-
if (npmSubcommand === 'create' && !args.some(arg => arg.includes('--template'))) {
|
|
588
|
-
return {
|
|
589
|
-
reason: "npm create command likely waiting for interactive template selection",
|
|
590
|
-
suggestion: "Add --template flag to avoid interactive prompts, or use interactive mode",
|
|
591
|
-
suggestedArgs: [...args, '--template', 'vanilla'],
|
|
592
|
-
suggestedTimeout: 60000
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
// Check for other interactive npm commands
|
|
596
|
-
if (!args.some(arg => arg.includes('-y') || arg.includes('--yes'))) {
|
|
597
|
-
return {
|
|
598
|
-
reason: "npm command likely waiting for interactive confirmation",
|
|
599
|
-
suggestion: "Add -y flag to auto-confirm prompts, or use interactive mode",
|
|
600
|
-
suggestedArgs: [...args, '-y'],
|
|
601
|
-
suggestedTimeout: 60000
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
if (npmSubcommand === 'install' || npmSubcommand === 'i') {
|
|
606
|
-
return {
|
|
607
|
-
reason: "npm install operations can take a long time",
|
|
608
|
-
suggestion: "Increase timeout for package installation",
|
|
609
|
-
suggestedArgs: args,
|
|
610
|
-
suggestedTimeout: 120000 // 2 minutes
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
// Git operations
|
|
615
|
-
if (baseCommand === 'git') {
|
|
616
|
-
const gitSubcommand = args[0]?.toLowerCase();
|
|
617
|
-
if (['clone', 'pull', 'push', 'fetch'].includes(gitSubcommand)) {
|
|
618
|
-
return {
|
|
619
|
-
reason: "Git network operations can be slow",
|
|
620
|
-
suggestion: "Increase timeout for network operations",
|
|
621
|
-
suggestedArgs: args,
|
|
622
|
-
suggestedTimeout: 90000 // 1.5 minutes
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
if (gitSubcommand === 'commit' && !args.some(arg => arg.includes('-m'))) {
|
|
626
|
-
return {
|
|
627
|
-
reason: "Git commit without -m flag opens interactive editor",
|
|
628
|
-
suggestion: "Add commit message with -m flag",
|
|
629
|
-
suggestedArgs: [...args, '-m', '"Auto-commit"'],
|
|
630
|
-
suggestedTimeout: 30000
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
// Yarn operations
|
|
635
|
-
if (baseCommand === 'yarn') {
|
|
636
|
-
if (args.length === 0 || args[0] === 'install') {
|
|
637
|
-
return {
|
|
638
|
-
reason: "Yarn install operations can take a long time",
|
|
639
|
-
suggestion: "Increase timeout for package installation",
|
|
640
|
-
suggestedArgs: args,
|
|
641
|
-
suggestedTimeout: 120000 // 2 minutes
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
if (args[0] === 'create' && !args.some(arg => arg.includes('--template'))) {
|
|
645
|
-
return {
|
|
646
|
-
reason: "yarn create command likely waiting for interactive input",
|
|
647
|
-
suggestion: "Add template specification to avoid prompts",
|
|
648
|
-
suggestedArgs: [...args, '--template', 'default'],
|
|
649
|
-
suggestedTimeout: 60000
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
// Build operations
|
|
654
|
-
if (['build', 'compile', 'webpack', 'rollup', 'vite'].includes(baseCommand)) {
|
|
655
|
-
return {
|
|
656
|
-
reason: "Build operations often require more time",
|
|
657
|
-
suggestion: "Increase timeout for build processes",
|
|
658
|
-
suggestedArgs: args,
|
|
659
|
-
suggestedTimeout: 120000 // 2 minutes
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
// Testing operations
|
|
663
|
-
if (['test', 'jest', 'mocha', 'karma'].includes(baseCommand) ||
|
|
664
|
-
(baseCommand === 'npm' && args[0] === 'test')) {
|
|
665
|
-
return {
|
|
666
|
-
reason: "Test operations can take significant time",
|
|
667
|
-
suggestion: "Increase timeout for test execution",
|
|
668
|
-
suggestedArgs: args,
|
|
669
|
-
suggestedTimeout: 90000 // 1.5 minutes
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
// Python operations
|
|
673
|
-
if (['python', 'python3', 'pip', 'pip3'].includes(baseCommand)) {
|
|
674
|
-
if (args.some(arg => arg.includes('install'))) {
|
|
675
|
-
return {
|
|
676
|
-
reason: "Python package installation can be slow",
|
|
677
|
-
suggestion: "Increase timeout for package installation",
|
|
678
|
-
suggestedArgs: args,
|
|
679
|
-
suggestedTimeout: 120000 // 2 minutes
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
// Default analysis for unknown timeouts
|
|
684
|
-
return {
|
|
685
|
-
reason: "Command exceeded default timeout, possibly due to network delays, large operations, or interactive prompts",
|
|
686
|
-
suggestion: "Try adding flags to make the command non-interactive (like -y, --yes, --no-interactive) or check if the command is waiting for user input",
|
|
687
|
-
suggestedArgs: [],
|
|
688
|
-
suggestedTimeout: 60000 // 1 minute default increase
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
// Helper function to provide suggestions for cancelled commands
|
|
692
|
-
function getSuggestion(commandString) {
|
|
693
|
-
const suggestions = {
|
|
694
|
-
'rm': 'Consider using file tools like write_file or edit_file to manage files safely',
|
|
695
|
-
'sudo': 'ProtoAgent runs with your user permissions only for security',
|
|
696
|
-
'npm install': 'Try: Use the file tools to examine package.json first, then I can suggest safer alternatives',
|
|
697
|
-
'npm create': 'Add flags to avoid interactive prompts: npm create vite@latest my-app --template react',
|
|
698
|
-
'git push': 'Consider: First check git status, then review changes before pushing',
|
|
699
|
-
'chmod': 'Consider: Use file tools to check permissions first, specific chmod may not be needed'
|
|
700
|
-
};
|
|
701
|
-
const cmd = commandString.split(' ')[0].toLowerCase();
|
|
702
|
-
const fullCmd = commandString.toLowerCase();
|
|
703
|
-
// Check for interactive command patterns
|
|
704
|
-
if (fullCmd.includes('npm create') && !fullCmd.includes('--template')) {
|
|
705
|
-
return `Command cancelled: ${commandString}\n\nš” Suggestion: npm create commands require template specification to avoid interactive prompts. Try: npm create vite@latest my-app --template react\n\nAvailable templates: vanilla, vue, react, preact, lit, svelte, solid, qwik, angular`;
|
|
706
|
-
}
|
|
707
|
-
if (fullCmd.includes('git commit') && !fullCmd.includes('-m')) {
|
|
708
|
-
return `Command cancelled: ${commandString}\n\nš” Suggestion: git commit requires a message flag to avoid opening an interactive editor. Try: git commit -m "Your commit message"`;
|
|
709
|
-
}
|
|
710
|
-
const suggestion = suggestions[cmd] || suggestions[fullCmd.split(' ').slice(0, 2).join(' ')] ||
|
|
711
|
-
'Consider using the available file system tools (read_file, write_file, list_directory) for safer operations, or add flags to make commands non-interactive';
|
|
712
|
-
return `Command cancelled: ${commandString}\n\nš” Suggestion: ${suggestion}\n\nYou can:\n- Use 'protoagent --dangerously-accept-all' to auto-approve all commands\n- Choose option 2 next time to approve commands for the session\n- Add flags to make commands non-interactive (e.g., --template, --yes, --no-interactive, -m for git)\n- ProtoAgent can automatically retry timed-out commands with better parameters\n- Ask me to break down the task into safer operations`;
|
|
713
|
-
}
|
|
714
|
-
// Tool definition
|
|
715
|
-
export const runShellCommandTool = {
|
|
716
|
-
type: 'function',
|
|
717
|
-
function: {
|
|
718
|
-
name: 'run_shell_command',
|
|
719
|
-
description: 'Execute a shell command in the current working directory or a specific subdirectory. Commands run non-interactively and output is captured for analysis. Supports compound commands like "cd subdirectory && npm install" which will automatically change to the subdirectory and run the command there. For tools that normally prompt for input (like npm create), provide all necessary flags to avoid interactive prompts. Safe commands (ls, find, grep, git status, etc.) run automatically. Other commands may require user confirmation unless running with --dangerously-accept-all flag. Examples: find . -name "*.js", grep -r "TODO" ., npm create vite@latest my-app --template react --no-interactive, cd frontend && npm install',
|
|
720
|
-
parameters: {
|
|
721
|
-
type: 'object',
|
|
722
|
-
properties: {
|
|
723
|
-
command: {
|
|
724
|
-
type: 'string',
|
|
725
|
-
description: 'The command to execute. Can be a simple command like "find" or a compound command like "cd directory && npm install". Examples: "find", "grep", "ls", "git", "npm", "python", "node", "yarn", "cd frontend && npm install"'
|
|
726
|
-
},
|
|
727
|
-
args: {
|
|
728
|
-
type: 'array',
|
|
729
|
-
items: {
|
|
730
|
-
type: 'string'
|
|
731
|
-
},
|
|
732
|
-
description: 'Arguments to pass to the command. Examples: [".", "-name", "*.js"] for find, ["-r", "TODO", "."] for grep, ["-la"] for ls'
|
|
733
|
-
},
|
|
734
|
-
timeout_ms: {
|
|
735
|
-
type: 'integer',
|
|
736
|
-
description: 'Timeout in milliseconds for the command execution. Default is 30000 (30 seconds). Use higher values for long-running operations.'
|
|
737
|
-
},
|
|
738
|
-
directory: {
|
|
739
|
-
type: 'string',
|
|
740
|
-
description: 'Optional: Subdirectory path relative to the working directory where the command should be executed. Must be within the current working directory for security. Examples: "src", "src/components", "tests". Leave empty to execute in the current directory.'
|
|
741
|
-
}
|
|
742
|
-
},
|
|
743
|
-
required: ['command']
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
};
|