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.
Files changed (58) hide show
  1. package/README.md +99 -19
  2. package/dist/App.js +602 -0
  3. package/dist/agentic-loop.js +492 -525
  4. package/dist/cli.js +39 -0
  5. package/dist/components/CollapsibleBox.js +26 -0
  6. package/dist/components/ConfigDialog.js +40 -0
  7. package/dist/components/ConsolidatedToolMessage.js +41 -0
  8. package/dist/components/FormattedMessage.js +93 -0
  9. package/dist/components/Table.js +275 -0
  10. package/dist/config.js +171 -0
  11. package/dist/mcp.js +170 -0
  12. package/dist/providers.js +137 -0
  13. package/dist/sessions.js +161 -0
  14. package/dist/skills.js +229 -0
  15. package/dist/sub-agent.js +103 -0
  16. package/dist/system-prompt.js +131 -0
  17. package/dist/tools/bash.js +178 -0
  18. package/dist/tools/edit-file.js +65 -171
  19. package/dist/tools/index.js +79 -134
  20. package/dist/tools/list-directory.js +20 -73
  21. package/dist/tools/read-file.js +57 -101
  22. package/dist/tools/search-files.js +74 -162
  23. package/dist/tools/todo.js +57 -140
  24. package/dist/tools/webfetch.js +310 -0
  25. package/dist/tools/write-file.js +44 -135
  26. package/dist/utils/approval.js +69 -0
  27. package/dist/utils/compactor.js +87 -0
  28. package/dist/utils/cost-tracker.js +26 -81
  29. package/dist/utils/format-message.js +26 -0
  30. package/dist/utils/logger.js +101 -307
  31. package/dist/utils/path-validation.js +74 -0
  32. package/package.json +45 -51
  33. package/LICENSE +0 -21
  34. package/dist/config/client.js +0 -315
  35. package/dist/config/commands.js +0 -223
  36. package/dist/config/manager.js +0 -117
  37. package/dist/config/mcp-commands.js +0 -266
  38. package/dist/config/mcp-manager.js +0 -240
  39. package/dist/config/mcp-types.js +0 -28
  40. package/dist/config/providers.js +0 -229
  41. package/dist/config/setup.js +0 -209
  42. package/dist/config/system-prompt.js +0 -397
  43. package/dist/config/types.js +0 -4
  44. package/dist/index.js +0 -229
  45. package/dist/tools/create-directory.js +0 -76
  46. package/dist/tools/directory-operations.js +0 -195
  47. package/dist/tools/file-operations.js +0 -211
  48. package/dist/tools/run-shell-command.js +0 -746
  49. package/dist/tools/search-operations.js +0 -179
  50. package/dist/tools/shell-operations.js +0 -342
  51. package/dist/tools/task-complete.js +0 -26
  52. package/dist/tools/view-directory-tree.js +0 -125
  53. package/dist/tools.js +0 -2
  54. package/dist/utils/conversation-compactor.js +0 -140
  55. package/dist/utils/enhanced-prompt.js +0 -23
  56. package/dist/utils/file-operations-approval.js +0 -373
  57. package/dist/utils/interrupt-handler.js +0 -127
  58. package/dist/utils/user-cancellation.js +0 -34
@@ -1,179 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- // Current working directory for file operations
4
- const workingDirectory = process.cwd();
5
- // Security utilities
6
- function normalizePath(p) {
7
- return path.normalize(p);
8
- }
9
- async function validatePath(requestedPath) {
10
- const absolute = path.isAbsolute(requestedPath)
11
- ? path.resolve(requestedPath)
12
- : path.resolve(workingDirectory, requestedPath);
13
- const normalizedRequested = normalizePath(absolute);
14
- // Check if path is within working directory
15
- if (!normalizedRequested.startsWith(workingDirectory)) {
16
- throw new Error(`Access denied - path outside working directory: ${absolute}`);
17
- }
18
- // Handle symlinks by checking their real path
19
- try {
20
- const realPath = await fs.realpath(absolute);
21
- const normalizedReal = normalizePath(realPath);
22
- if (!normalizedReal.startsWith(workingDirectory)) {
23
- throw new Error(`Access denied - symlink target outside working directory: ${realPath}`);
24
- }
25
- return realPath;
26
- }
27
- catch (error) {
28
- // For new files that don't exist yet, verify parent directory
29
- if (error.code === 'ENOENT') {
30
- const parentDir = path.dirname(absolute);
31
- try {
32
- const realParentPath = await fs.realpath(parentDir);
33
- const normalizedParent = normalizePath(realParentPath);
34
- if (!normalizedParent.startsWith(workingDirectory)) {
35
- throw new Error(`Access denied - parent directory outside working directory: ${realParentPath}`);
36
- }
37
- return absolute;
38
- }
39
- catch {
40
- throw new Error(`Parent directory does not exist: ${parentDir}`);
41
- }
42
- }
43
- throw error;
44
- }
45
- }
46
- // Search utility function
47
- async function searchInFile(filePath, searchTerm, caseSensitive = false) {
48
- try {
49
- const content = await fs.readFile(filePath, 'utf-8');
50
- const lines = content.split('\n');
51
- const matchingLines = [];
52
- let totalMatches = 0;
53
- const searchPattern = caseSensitive ? searchTerm : searchTerm.toLowerCase();
54
- lines.forEach((line, index) => {
55
- const searchLine = caseSensitive ? line : line.toLowerCase();
56
- if (searchLine.includes(searchPattern)) {
57
- totalMatches++;
58
- matchingLines.push(`${index + 1}: ${line}`);
59
- }
60
- });
61
- return { matches: totalMatches, lines: matchingLines };
62
- }
63
- catch (error) {
64
- return { matches: 0, lines: [] };
65
- }
66
- }
67
- async function searchInDirectory(dirPath, searchTerm, caseSensitive = false, fileExtensions = []) {
68
- const validPath = await validatePath(dirPath);
69
- const results = [];
70
- async function searchRecursive(currentPath, depth = 0) {
71
- if (depth > 10)
72
- return; // Prevent infinite recursion
73
- try {
74
- const entries = await fs.readdir(currentPath, { withFileTypes: true });
75
- for (const entry of entries) {
76
- const fullPath = path.join(currentPath, entry.name);
77
- // Skip common directories that should be ignored
78
- if (entry.isDirectory() && ['node_modules', '.git', '.DS_Store', 'dist', 'build'].includes(entry.name)) {
79
- continue;
80
- }
81
- if (entry.isDirectory()) {
82
- await searchRecursive(fullPath, depth + 1);
83
- }
84
- else if (entry.isFile()) {
85
- // Check file extension filter
86
- if (fileExtensions.length > 0) {
87
- const ext = path.extname(entry.name).toLowerCase();
88
- if (!fileExtensions.includes(ext)) {
89
- continue;
90
- }
91
- }
92
- try {
93
- const searchResult = await searchInFile(fullPath, searchTerm, caseSensitive);
94
- if (searchResult.matches > 0) {
95
- const relativePath = path.relative(workingDirectory, fullPath);
96
- results.push(`\nšŸ“ ${relativePath} (${searchResult.matches} matches):`);
97
- results.push(...searchResult.lines.map(line => ` ${line}`));
98
- }
99
- }
100
- catch (error) {
101
- // Skip files we can't read
102
- }
103
- }
104
- }
105
- }
106
- catch (error) {
107
- // Skip directories we can't read
108
- }
109
- }
110
- await searchRecursive(validPath);
111
- if (results.length === 0) {
112
- return `No matches found for "${searchTerm}" in ${dirPath}`;
113
- }
114
- return results.join('\n');
115
- }
116
- export async function searchFiles(searchTerm, dirPath = '.', caseSensitive = false, fileExtensions = []) {
117
- try {
118
- if (!searchTerm.trim()) {
119
- throw new Error('Search term cannot be empty');
120
- }
121
- const validPath = await validatePath(dirPath);
122
- // Check if path exists and is a directory
123
- const stats = await fs.stat(validPath);
124
- if (!stats.isDirectory()) {
125
- throw new Error('Search path must be a directory');
126
- }
127
- const results = await searchInDirectory(validPath, searchTerm, caseSensitive, fileExtensions);
128
- const searchInfo = [
129
- `šŸ” Searching for: "${searchTerm}"`,
130
- `šŸ“‚ In directory: ${path.relative(workingDirectory, validPath) || '.'}`,
131
- `šŸ“ Case sensitive: ${caseSensitive ? 'Yes' : 'No'}`,
132
- fileExtensions.length > 0 ? `šŸŽÆ File types: ${fileExtensions.join(', ')}` : 'šŸŽÆ All file types',
133
- '',
134
- ].join('\n');
135
- return searchInfo + results;
136
- }
137
- catch (error) {
138
- if (error instanceof Error) {
139
- throw new Error(`Failed to search files: ${error.message}`);
140
- }
141
- throw new Error('Failed to search files: Unknown error');
142
- }
143
- }
144
- // Tool definitions
145
- export const searchTools = [
146
- {
147
- type: 'function',
148
- function: {
149
- name: 'search_files',
150
- description: 'Search for a text string within files in a directory. Supports case sensitivity and file type filtering. Returns the list of files and matching lines. Use this to find specific code, configuration settings, or documentation within your project files.',
151
- parameters: {
152
- type: 'object',
153
- properties: {
154
- search_term: {
155
- type: 'string',
156
- description: 'The text string to search for within files.'
157
- },
158
- directory_path: {
159
- type: 'string',
160
- description: 'The directory to search within, relative to the current working directory. Use "." for the current directory.'
161
- },
162
- case_sensitive: {
163
- type: 'boolean',
164
- description: 'Whether the search should be case sensitive.'
165
- },
166
- file_extensions: {
167
- type: 'array',
168
- items: {
169
- type: 'string',
170
- description: 'File extension to include in the search (e.g., "js", "txt"). Leave empty to search all file types.'
171
- },
172
- description: 'List of file extensions to include in the search.'
173
- }
174
- },
175
- required: ['search_term', 'directory_path']
176
- }
177
- }
178
- }
179
- ];
@@ -1,342 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import inquirer from 'inquirer';
3
- // Current working directory for file operations
4
- const workingDirectory = process.cwd();
5
- // Global flags and session state
6
- let globalConfig = null;
7
- let dangerouslyAcceptAll = false;
8
- let approvedCommandsForSession = new Set();
9
- // Whitelisted safe commands that don't require confirmation
10
- const SAFE_COMMANDS = [
11
- 'ls', 'dir', 'pwd', 'whoami', 'date', 'echo', 'cat', 'head', 'tail',
12
- 'grep', 'find', 'wc', 'sort', 'uniq', 'cut', 'awk', 'sed',
13
- 'git status', 'git log', 'git diff', 'git branch', 'git show',
14
- 'npm list', 'npm ls', 'yarn list', 'node --version', 'npm --version',
15
- 'python --version', 'python3 --version', 'which', 'type', 'file'
16
- ];
17
- export function setShellConfig(config) {
18
- globalConfig = config;
19
- }
20
- export function setDangerouslyAcceptAll(accept) {
21
- dangerouslyAcceptAll = accept;
22
- }
23
- export async function runShellCommand(command, args = [], timeoutMs = 30000) {
24
- return await runShellCommandWithRetry(command, args, timeoutMs, 0);
25
- }
26
- async function runShellCommandWithRetry(command, args = [], timeoutMs = 30000, retryCount = 0) {
27
- const maxRetries = 2;
28
- try {
29
- // Security: Basic validation to prevent obviously dangerous commands
30
- const dangerousCommands = ['rm -rf', 'sudo', 'su', 'chmod 777', 'dd if=', 'mkfs', 'fdisk', 'format'];
31
- const fullCommand = `${command} ${args.join(' ')}`.toLowerCase();
32
- for (const dangerous of dangerousCommands) {
33
- if (fullCommand.includes(dangerous)) {
34
- throw new Error(`Command contains potentially dangerous operation: ${dangerous}`);
35
- }
36
- }
37
- const commandString = `${command} ${args.join(' ')}`;
38
- const baseCommand = command.toLowerCase();
39
- // Check if we should auto-approve (only show approval prompt on first attempt)
40
- if (retryCount === 0) {
41
- if (dangerouslyAcceptAll) {
42
- console.log(`šŸš€ Auto-executing (--dangerously-accept-all): ${commandString}`);
43
- }
44
- else if (approvedCommandsForSession.has(baseCommand)) {
45
- console.log(`šŸš€ Auto-executing (${baseCommand} approved for session): ${commandString}`);
46
- }
47
- else {
48
- // Check if it's a safe command
49
- const isSafeCommand = SAFE_COMMANDS.some(safe => {
50
- const normalizedSafe = safe.toLowerCase();
51
- const normalizedFull = fullCommand.toLowerCase();
52
- return normalizedFull === normalizedSafe ||
53
- (normalizedSafe.includes(' ') && normalizedFull.startsWith(normalizedSafe)) ||
54
- (!normalizedSafe.includes(' ') && normalizedFull.split(' ')[0] === normalizedSafe);
55
- });
56
- if (isSafeCommand) {
57
- console.log(`🟢 Executing safe command: ${commandString}`);
58
- }
59
- else {
60
- // Require user confirmation with enhanced options
61
- console.log(`\nšŸ” Shell Command Requested: ${commandString}`);
62
- console.log(`šŸ“ Working Directory: ${workingDirectory}`);
63
- const { choice } = await inquirer.prompt([
64
- {
65
- type: 'list',
66
- name: 'choice',
67
- message: 'Choose your action:',
68
- choices: [
69
- { name: '1. āœ… Execute this command now', value: 'execute' },
70
- { name: `2. āœ… Execute and approve all "${baseCommand}" commands for this session`, value: 'approve_session' },
71
- { name: '3. āŒ Cancel and suggest alternative', value: 'cancel' }
72
- ],
73
- default: 'execute'
74
- }
75
- ]);
76
- switch (choice) {
77
- case 'execute':
78
- console.log(`šŸš€ Executing: ${commandString}`);
79
- break;
80
- case 'approve_session':
81
- approvedCommandsForSession.add(baseCommand);
82
- console.log(`šŸ”“ "${baseCommand}" approved for session - all future ${baseCommand} commands will auto-execute`);
83
- console.log(`šŸš€ Executing: ${commandString}`);
84
- break;
85
- case 'cancel':
86
- return getSuggestion(commandString);
87
- default:
88
- return `Command execution cancelled by user: ${commandString}`;
89
- }
90
- }
91
- }
92
- }
93
- else {
94
- console.log(`šŸ”„ Retry attempt ${retryCount}/${maxRetries}: ${commandString}`);
95
- }
96
- return new Promise((resolve, reject) => {
97
- const child = spawn(command, args, {
98
- cwd: workingDirectory,
99
- stdio: ['pipe', 'pipe', 'pipe'], // Capture all input/output for AI processing
100
- shell: true,
101
- env: { ...process.env, PATH: process.env.PATH }
102
- });
103
- let stdout = '';
104
- let stderr = '';
105
- let completed = false;
106
- // Set up timeout
107
- const timeout = setTimeout(() => {
108
- if (!completed) {
109
- completed = true;
110
- child.kill('SIGTERM');
111
- reject(new Error(`TIMEOUT_ERROR:${timeoutMs}`));
112
- }
113
- }, timeoutMs);
114
- child.stdout?.on('data', (data) => {
115
- stdout += data.toString();
116
- });
117
- child.stderr?.on('data', (data) => {
118
- stderr += data.toString();
119
- });
120
- child.on('close', (code) => {
121
- if (!completed) {
122
- completed = true;
123
- clearTimeout(timeout);
124
- const output = stdout + (stderr ? `\nSTDERR:\n${stderr}` : '');
125
- if (code === 0) {
126
- resolve(`Command executed successfully (exit code: ${code})\n\nOutput:\n${output || '(no output)'}`);
127
- }
128
- else {
129
- // For non-zero exit codes, still return the output but indicate the failure
130
- resolve(`Command exited with code ${code}\n\nOutput:\n${output || '(no output)'}`);
131
- }
132
- }
133
- });
134
- child.on('error', (error) => {
135
- if (!completed) {
136
- completed = true;
137
- clearTimeout(timeout);
138
- reject(new Error(`Failed to execute command: ${error.message}`));
139
- }
140
- });
141
- });
142
- }
143
- catch (error) {
144
- if (error instanceof Error) {
145
- // Check if this is a timeout error and we haven't exceeded max retries
146
- if (error.message.startsWith('TIMEOUT_ERROR:') && retryCount < maxRetries) {
147
- const timeoutDuration = parseInt(error.message.split(':')[1]);
148
- return await analyzeTimeoutAndRetry(command, args, timeoutDuration, retryCount);
149
- }
150
- throw new Error(`Shell command error: ${error.message.replace('TIMEOUT_ERROR:', 'Command timed out after ').replace(/:\d+/, 'ms')}`);
151
- }
152
- throw new Error('Shell command error: Unknown error');
153
- }
154
- }
155
- async function analyzeTimeoutAndRetry(command, args = [], originalTimeout, retryCount) {
156
- const commandString = `${command} ${args.join(' ')}`;
157
- console.log(`ā±ļø Command timed out after ${originalTimeout}ms: ${commandString}`);
158
- console.log(`šŸ” Analyzing timeout cause and preparing retry...`);
159
- // Analyze the command to understand why it might have timed out
160
- const analysis = analyzeCommandForTimeout(command, args);
161
- console.log(`šŸ’” Timeout analysis: ${analysis.reason}`);
162
- if (analysis.suggestedArgs.length > 0) {
163
- console.log(`šŸ”§ Suggested fix: ${command} ${analysis.suggestedArgs.join(' ')}`);
164
- // Try with suggested arguments
165
- return await runShellCommandWithRetry(command, analysis.suggestedArgs, analysis.suggestedTimeout, retryCount + 1);
166
- }
167
- else if (analysis.suggestedTimeout > originalTimeout) {
168
- console.log(`ā° Increasing timeout to ${analysis.suggestedTimeout}ms for long-running operation`);
169
- // Try with longer timeout
170
- return await runShellCommandWithRetry(command, args, analysis.suggestedTimeout, retryCount + 1);
171
- }
172
- else {
173
- // No specific fix found, provide analysis
174
- return `Command timed out after ${originalTimeout}ms: ${commandString}\n\nTimeout Analysis: ${analysis.reason}\n\nSuggestion: ${analysis.suggestion}`;
175
- }
176
- }
177
- function analyzeCommandForTimeout(command, args) {
178
- const fullCommand = `${command} ${args.join(' ')}`.toLowerCase();
179
- const baseCommand = command.toLowerCase();
180
- // Interactive command detection
181
- if (baseCommand === 'npm' && args.length > 0) {
182
- const npmSubcommand = args[0].toLowerCase();
183
- if (npmSubcommand === 'create' || npmSubcommand === 'init') {
184
- // Check if template is specified for npm create
185
- if (npmSubcommand === 'create' && !args.some(arg => arg.includes('--template'))) {
186
- return {
187
- reason: "npm create command likely waiting for interactive template selection",
188
- suggestion: "Add --template flag to avoid interactive prompts",
189
- suggestedArgs: [...args, '--template', 'vanilla'],
190
- suggestedTimeout: 60000
191
- };
192
- }
193
- // Check for other interactive npm commands
194
- if (!args.some(arg => arg.includes('-y') || arg.includes('--yes'))) {
195
- return {
196
- reason: "npm command likely waiting for interactive confirmation",
197
- suggestion: "Add -y flag to auto-confirm prompts",
198
- suggestedArgs: [...args, '-y'],
199
- suggestedTimeout: 60000
200
- };
201
- }
202
- }
203
- if (npmSubcommand === 'install' || npmSubcommand === 'i') {
204
- return {
205
- reason: "npm install operations can take a long time",
206
- suggestion: "Increase timeout for package installation",
207
- suggestedArgs: args,
208
- suggestedTimeout: 120000 // 2 minutes
209
- };
210
- }
211
- }
212
- // Git operations
213
- if (baseCommand === 'git') {
214
- const gitSubcommand = args[0]?.toLowerCase();
215
- if (['clone', 'pull', 'push', 'fetch'].includes(gitSubcommand)) {
216
- return {
217
- reason: "Git network operations can be slow",
218
- suggestion: "Increase timeout for network operations",
219
- suggestedArgs: args,
220
- suggestedTimeout: 90000 // 1.5 minutes
221
- };
222
- }
223
- if (gitSubcommand === 'commit' && !args.some(arg => arg.includes('-m'))) {
224
- return {
225
- reason: "Git commit without -m flag opens interactive editor",
226
- suggestion: "Add commit message with -m flag",
227
- suggestedArgs: [...args, '-m', '"Auto-commit"'],
228
- suggestedTimeout: 30000
229
- };
230
- }
231
- }
232
- // Yarn operations
233
- if (baseCommand === 'yarn') {
234
- if (args.length === 0 || args[0] === 'install') {
235
- return {
236
- reason: "Yarn install operations can take a long time",
237
- suggestion: "Increase timeout for package installation",
238
- suggestedArgs: args,
239
- suggestedTimeout: 120000 // 2 minutes
240
- };
241
- }
242
- if (args[0] === 'create' && !args.some(arg => arg.includes('--template'))) {
243
- return {
244
- reason: "yarn create command likely waiting for interactive input",
245
- suggestion: "Add template specification to avoid prompts",
246
- suggestedArgs: [...args, '--template', 'default'],
247
- suggestedTimeout: 60000
248
- };
249
- }
250
- }
251
- // Build operations
252
- if (['build', 'compile', 'webpack', 'rollup', 'vite'].includes(baseCommand)) {
253
- return {
254
- reason: "Build operations often require more time",
255
- suggestion: "Increase timeout for build processes",
256
- suggestedArgs: args,
257
- suggestedTimeout: 120000 // 2 minutes
258
- };
259
- }
260
- // Testing operations
261
- if (['test', 'jest', 'mocha', 'karma'].includes(baseCommand) ||
262
- (baseCommand === 'npm' && args[0] === 'test')) {
263
- return {
264
- reason: "Test operations can take significant time",
265
- suggestion: "Increase timeout for test execution",
266
- suggestedArgs: args,
267
- suggestedTimeout: 90000 // 1.5 minutes
268
- };
269
- }
270
- // Python operations
271
- if (['python', 'python3', 'pip', 'pip3'].includes(baseCommand)) {
272
- if (args.some(arg => arg.includes('install'))) {
273
- return {
274
- reason: "Python package installation can be slow",
275
- suggestion: "Increase timeout for package installation",
276
- suggestedArgs: args,
277
- suggestedTimeout: 120000 // 2 minutes
278
- };
279
- }
280
- }
281
- // Default analysis for unknown timeouts
282
- return {
283
- reason: "Command exceeded default timeout, possibly due to network delays, large operations, or interactive prompts",
284
- 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",
285
- suggestedArgs: [],
286
- suggestedTimeout: 60000 // 1 minute default increase
287
- };
288
- }
289
- // Helper function to provide suggestions for cancelled commands
290
- function getSuggestion(commandString) {
291
- const suggestions = {
292
- 'rm': 'Consider using file tools like write_file or edit_file to manage files safely',
293
- 'sudo': 'ProtoAgent runs with your user permissions only for security',
294
- 'npm install': 'Try: Use the file tools to examine package.json first, then I can suggest safer alternatives',
295
- 'npm create': 'Add flags to avoid interactive prompts: npm create vite@latest my-app --template react',
296
- 'git push': 'Consider: First check git status, then review changes before pushing',
297
- 'chmod': 'Consider: Use file tools to check permissions first, specific chmod may not be needed'
298
- };
299
- const cmd = commandString.split(' ')[0].toLowerCase();
300
- const fullCmd = commandString.toLowerCase();
301
- // Check for interactive command patterns
302
- if (fullCmd.includes('npm create') && !fullCmd.includes('--template')) {
303
- 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`;
304
- }
305
- if (fullCmd.includes('git commit') && !fullCmd.includes('-m')) {
306
- 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"`;
307
- }
308
- const suggestion = suggestions[cmd] || suggestions[fullCmd.split(' ').slice(0, 2).join(' ')] ||
309
- 'Consider using the available file system tools (read_file, write_file, list_directory) for safer operations, or add flags to make commands non-interactive';
310
- 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`;
311
- }
312
- // Tool definitions
313
- export const shellTools = [
314
- {
315
- type: 'function',
316
- function: {
317
- name: 'run_shell_command',
318
- description: 'Execute a shell command in the current working directory. Commands run non-interactively and output is captured for analysis. 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',
319
- parameters: {
320
- type: 'object',
321
- properties: {
322
- command: {
323
- type: 'string',
324
- description: 'The command to execute. Examples: "find", "grep", "ls", "git", "npm", "python", "node", "yarn"'
325
- },
326
- args: {
327
- type: 'array',
328
- items: {
329
- type: 'string'
330
- },
331
- description: 'Arguments to pass to the command. Examples: [".", "-name", "*.js"] for find, ["-r", "TODO", "."] for grep, ["-la"] for ls'
332
- },
333
- timeout_ms: {
334
- type: 'integer',
335
- description: 'Timeout in milliseconds for the command execution. Default is 30000 (30 seconds). Use higher values for long-running operations.'
336
- }
337
- },
338
- required: ['command']
339
- }
340
- }
341
- }
342
- ];
@@ -1,26 +0,0 @@
1
- import { z } from 'zod';
2
- import { logger } from '../utils/logger.js';
3
- /**
4
- * Tool for the AI to explicitly signal that a task is complete
5
- * This gives the AI control over when to stop the agentic loop
6
- */
7
- const taskCompleteSchema = z.object({
8
- summary: z.string().describe("Brief summary of what was accomplished"),
9
- nextSteps: z.string().optional().describe("Optional suggestions for next steps or follow-up actions")
10
- });
11
- async function taskComplete(args) {
12
- logger.debug('šŸ AI signaled task completion', { component: 'TaskComplete', summary: args.summary });
13
- logger.consoleLog(`\nšŸ Task Complete: ${args.summary}`);
14
- if (args.nextSteps) {
15
- logger.consoleLog(`šŸ’” Next Steps: ${args.nextSteps}`);
16
- }
17
- // Return a special marker that the agentic loop can detect
18
- return "TASK_COMPLETE";
19
- }
20
- export const taskCompleteTool = {
21
- name: 'task_complete',
22
- description: 'Signal that the current task has been completed successfully. Use this when you have finished all requested work and want to return control to the user.',
23
- inputSchema: taskCompleteSchema,
24
- handler: taskComplete
25
- };
26
- export { taskComplete };
@@ -1,125 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- // Current working directory for file operations
4
- const workingDirectory = process.cwd();
5
- // Security utilities
6
- function normalizePath(p) {
7
- return path.normalize(p);
8
- }
9
- async function validatePath(requestedPath) {
10
- const absolute = path.isAbsolute(requestedPath)
11
- ? path.resolve(requestedPath)
12
- : path.resolve(workingDirectory, requestedPath);
13
- const normalizedRequested = normalizePath(absolute);
14
- // Check if path is within working directory
15
- if (!normalizedRequested.startsWith(workingDirectory)) {
16
- throw new Error(`Access denied - path outside working directory: ${absolute}`);
17
- }
18
- // Handle symlinks by checking their real path
19
- try {
20
- const realPath = await fs.realpath(absolute);
21
- const normalizedReal = normalizePath(realPath);
22
- if (!normalizedReal.startsWith(workingDirectory)) {
23
- throw new Error(`Access denied - symlink target outside working directory: ${realPath}`);
24
- }
25
- return realPath;
26
- }
27
- catch (error) {
28
- // For new files that don't exist yet, verify parent directory
29
- if (error.code === 'ENOENT') {
30
- const parentDir = path.dirname(absolute);
31
- try {
32
- const realParentPath = await fs.realpath(parentDir);
33
- const normalizedParent = normalizePath(realParentPath);
34
- if (!normalizedParent.startsWith(workingDirectory)) {
35
- throw new Error(`Access denied - parent directory outside working directory: ${realParentPath}`);
36
- }
37
- return absolute;
38
- }
39
- catch {
40
- throw new Error(`Parent directory does not exist: ${parentDir}`);
41
- }
42
- }
43
- throw error;
44
- }
45
- }
46
- // Directory tree utility function
47
- async function buildDirectoryTree(dirPath, prefix = '', isLast = true, maxDepth = 5, currentDepth = 0) {
48
- if (currentDepth >= maxDepth) {
49
- return '';
50
- }
51
- const validPath = await validatePath(dirPath);
52
- const stats = await fs.stat(validPath);
53
- if (!stats.isDirectory()) {
54
- return '';
55
- }
56
- let result = '';
57
- const entries = await fs.readdir(validPath, { withFileTypes: true });
58
- // Sort entries: directories first, then files, alphabetically
59
- entries.sort((a, b) => {
60
- if (a.isDirectory() && !b.isDirectory())
61
- return -1;
62
- if (!a.isDirectory() && b.isDirectory())
63
- return 1;
64
- return a.name.localeCompare(b.name);
65
- });
66
- for (let i = 0; i < entries.length; i++) {
67
- const entry = entries[i];
68
- const isLastEntry = i === entries.length - 1;
69
- const currentPrefix = isLastEntry ? '└── ' : 'ā”œā”€ā”€ ';
70
- const nextPrefix = prefix + (isLastEntry ? ' ' : '│ ');
71
- result += `${prefix}${currentPrefix}${entry.name}${entry.isDirectory() ? '/' : ''}\n`;
72
- if (entry.isDirectory()) {
73
- const subPath = path.join(dirPath, entry.name);
74
- try {
75
- result += await buildDirectoryTree(subPath, nextPrefix, isLastEntry, maxDepth, currentDepth + 1);
76
- }
77
- catch (error) {
78
- // Skip directories we can't read (permissions, etc.)
79
- result += `${nextPrefix}└── [Permission Denied]\n`;
80
- }
81
- }
82
- }
83
- return result;
84
- }
85
- export async function viewDirectoryTree(dirPath, maxDepth = 5) {
86
- try {
87
- const validPath = await validatePath(dirPath);
88
- // Check if path exists and is a directory
89
- const stats = await fs.stat(validPath);
90
- if (!stats.isDirectory()) {
91
- throw new Error('Path is not a directory');
92
- }
93
- const relativePath = path.relative(workingDirectory, validPath) || '.';
94
- const tree = await buildDirectoryTree(validPath, '', true, maxDepth);
95
- return `Directory tree for ${relativePath}:\n${relativePath}/\n${tree}`;
96
- }
97
- catch (error) {
98
- if (error instanceof Error) {
99
- throw new Error(`Failed to view directory tree: ${error.message}`);
100
- }
101
- throw new Error('Failed to view directory tree: Unknown error');
102
- }
103
- }
104
- // Tool definition
105
- export const viewDirectoryTreeTool = {
106
- type: 'function',
107
- function: {
108
- name: 'view_directory_tree',
109
- description: 'Display the directory tree of a specified directory up to a certain depth. This provides a visual overview of the directory structure, showing nested folders and files. Useful for understanding project layout or finding files.',
110
- parameters: {
111
- type: 'object',
112
- properties: {
113
- directory_path: {
114
- type: 'string',
115
- description: 'The path to the directory to display, relative to the current working directory. Examples: "src", "docs"'
116
- },
117
- max_depth: {
118
- type: 'integer',
119
- description: 'The maximum depth to display in the directory tree. Use a higher number to show more levels of the tree.'
120
- }
121
- },
122
- required: ['directory_path']
123
- }
124
- }
125
- };
package/dist/tools.js DELETED
@@ -1,2 +0,0 @@
1
- // Re-export everything from the tools directory
2
- export * from './tools/index.js';