protoagent 0.0.1
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/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/agentic-loop.js +277 -0
- package/dist/config/client.js +166 -0
- package/dist/config/commands.js +208 -0
- package/dist/config/manager.js +117 -0
- package/dist/config/mcp-commands.js +266 -0
- package/dist/config/mcp-manager.js +240 -0
- package/dist/config/mcp-types.js +28 -0
- package/dist/config/providers.js +170 -0
- package/dist/config/setup.js +175 -0
- package/dist/config/system-prompt.js +301 -0
- package/dist/config/types.js +4 -0
- package/dist/index.js +156 -0
- package/dist/tools/create-directory.js +76 -0
- package/dist/tools/directory-operations.js +195 -0
- package/dist/tools/edit-file.js +144 -0
- package/dist/tools/file-operations.js +211 -0
- package/dist/tools/index.js +95 -0
- package/dist/tools/list-directory.js +84 -0
- package/dist/tools/read-file.js +111 -0
- package/dist/tools/run-shell-command.js +340 -0
- package/dist/tools/search-files.js +177 -0
- package/dist/tools/search-operations.js +179 -0
- package/dist/tools/shell-operations.js +342 -0
- package/dist/tools/todo.js +177 -0
- package/dist/tools/view-directory-tree.js +125 -0
- package/dist/tools/write-file.js +136 -0
- package/dist/tools.js +2 -0
- package/dist/utils/conversation-compactor.js +139 -0
- package/dist/utils/cost-tracker.js +106 -0
- package/dist/utils/file-operations-approval.js +163 -0
- package/dist/utils/logger.js +149 -0
- package/package.json +61 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Import all tool definitions
|
|
2
|
+
import { readFileTool } from '../tools/read-file.js';
|
|
3
|
+
import { writeFileTool } from '../tools/write-file.js';
|
|
4
|
+
import { editFileTool } from '../tools/edit-file.js';
|
|
5
|
+
import { listDirectoryTool } from '../tools/list-directory.js';
|
|
6
|
+
import { createDirectoryTool } from '../tools/create-directory.js';
|
|
7
|
+
import { viewDirectoryTreeTool } from '../tools/view-directory-tree.js';
|
|
8
|
+
import { searchFilesTool } from '../tools/search-files.js';
|
|
9
|
+
import { runShellCommandTool } from '../tools/run-shell-command.js';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
// Collect all tools
|
|
13
|
+
const allTools = [
|
|
14
|
+
readFileTool,
|
|
15
|
+
writeFileTool,
|
|
16
|
+
editFileTool,
|
|
17
|
+
listDirectoryTool,
|
|
18
|
+
createDirectoryTool,
|
|
19
|
+
viewDirectoryTreeTool,
|
|
20
|
+
searchFilesTool,
|
|
21
|
+
runShellCommandTool
|
|
22
|
+
];
|
|
23
|
+
// Generate tool descriptions dynamically
|
|
24
|
+
function generateToolDescriptions() {
|
|
25
|
+
return allTools.map((tool, index) => {
|
|
26
|
+
const { name, description, parameters } = tool.function;
|
|
27
|
+
// Extract required and optional parameters
|
|
28
|
+
const required = parameters.required || [];
|
|
29
|
+
const properties = parameters.properties || {};
|
|
30
|
+
const requiredParams = required.map(param => `${param} (required)`);
|
|
31
|
+
const optionalParams = Object.keys(properties)
|
|
32
|
+
.filter(param => !required.includes(param))
|
|
33
|
+
.map(param => `${param} (optional)`);
|
|
34
|
+
const allParams = [...requiredParams, ...optionalParams];
|
|
35
|
+
return `${index + 1}. **${name}** - ${description}
|
|
36
|
+
- Parameters: ${allParams.join(', ')}`;
|
|
37
|
+
}).join('\n\n');
|
|
38
|
+
}
|
|
39
|
+
// Get current working directory
|
|
40
|
+
function getCurrentWorkingDirectory() {
|
|
41
|
+
return process.cwd();
|
|
42
|
+
}
|
|
43
|
+
// Generate directory tree for context
|
|
44
|
+
async function generateDirectoryTree(dirPath = '.', depth = 0, maxDepth = 3) {
|
|
45
|
+
if (depth > maxDepth) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
let tree = '';
|
|
49
|
+
const indent = ' '.repeat(depth);
|
|
50
|
+
try {
|
|
51
|
+
const fullPath = path.resolve(dirPath);
|
|
52
|
+
const items = await fs.readdir(fullPath, { withFileTypes: true });
|
|
53
|
+
// Filter out common unnecessary directories/files
|
|
54
|
+
const filteredItems = items.filter(item => {
|
|
55
|
+
const name = item.name;
|
|
56
|
+
return !name.startsWith('.') &&
|
|
57
|
+
name !== 'node_modules' &&
|
|
58
|
+
name !== 'dist' &&
|
|
59
|
+
name !== 'build' &&
|
|
60
|
+
name !== 'coverage' &&
|
|
61
|
+
name !== '__pycache__' &&
|
|
62
|
+
!name.endsWith('.log');
|
|
63
|
+
});
|
|
64
|
+
for (const item of filteredItems.slice(0, 20)) { // Limit to 20 items per directory
|
|
65
|
+
if (item.isDirectory()) {
|
|
66
|
+
tree += `${indent}📁 ${item.name}/\n`;
|
|
67
|
+
const subTree = await generateDirectoryTree(path.join(dirPath, item.name), depth + 1, maxDepth);
|
|
68
|
+
tree += subTree;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
tree += `${indent}📄 ${item.name}\n`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (filteredItems.length > 20) {
|
|
75
|
+
tree += `${indent}... (${filteredItems.length - 20} more items)\n`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// If we can't read the directory, just skip it
|
|
80
|
+
}
|
|
81
|
+
return tree;
|
|
82
|
+
}
|
|
83
|
+
// Generate project context asynchronously
|
|
84
|
+
async function generateProjectContext() {
|
|
85
|
+
const workingDir = getCurrentWorkingDirectory();
|
|
86
|
+
const projectName = path.basename(workingDir);
|
|
87
|
+
try {
|
|
88
|
+
const tree = await generateDirectoryTree();
|
|
89
|
+
return `
|
|
90
|
+
## Current Project Context:
|
|
91
|
+
|
|
92
|
+
**Working Directory:** ${workingDir}
|
|
93
|
+
**Project Name:** ${projectName}
|
|
94
|
+
|
|
95
|
+
**Project Structure:**
|
|
96
|
+
${tree}`;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return `
|
|
100
|
+
## Current Project Context:
|
|
101
|
+
|
|
102
|
+
**Working Directory:** ${workingDir}
|
|
103
|
+
**Project Name:** ${projectName}
|
|
104
|
+
|
|
105
|
+
**Project Structure:** (Unable to read directory structure)`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Generate the complete system prompt with project context
|
|
109
|
+
export async function generateSystemPrompt() {
|
|
110
|
+
const projectContext = await generateProjectContext();
|
|
111
|
+
return `You are ProtoAgent, a helpful coding assistant with file system and shell command capabilities. Your objective is to help the user
|
|
112
|
+
complete their coding tasks, most likely related to the directory you were started in.
|
|
113
|
+
|
|
114
|
+
${projectContext}
|
|
115
|
+
|
|
116
|
+
You can:
|
|
117
|
+
|
|
118
|
+
1. Read and analyze code files to understand project structure
|
|
119
|
+
2. Create new files and directories for projects
|
|
120
|
+
3. Edit existing files with precision
|
|
121
|
+
4. Search through codebases to find specific patterns
|
|
122
|
+
5. List directory contents and show project structure
|
|
123
|
+
6. Execute shell commands for development tasks (npm, git, build tools, etc.)
|
|
124
|
+
|
|
125
|
+
When users ask you to work with files or run commands, you should:
|
|
126
|
+
- Use the available tools to complete tasks
|
|
127
|
+
- Always read files before editing them to understand the context
|
|
128
|
+
- Create directories before creating files in them
|
|
129
|
+
- Use shell commands for discovering the codebase, files, package management, git operations, building, testing, etc.
|
|
130
|
+
- Provide clear feedback about what you're doing
|
|
131
|
+
- Show the user the results of your operations
|
|
132
|
+
- Ask for confirmation before running any write, delete operations
|
|
133
|
+
|
|
134
|
+
## TODO TRACKING - MANDATORY REQUIREMENT:
|
|
135
|
+
|
|
136
|
+
**YOU MUST ALWAYS USE TODO TRACKING FOR ANY NON-TRIVIAL TASK:**
|
|
137
|
+
|
|
138
|
+
Before starting any complex task (more than a single file operation), you MUST:
|
|
139
|
+
1. Use the todo_write tool to create a detailed plan breaking down the work into steps
|
|
140
|
+
2. Update the TODO file as you complete each step using todo_write
|
|
141
|
+
3. Use todo_read to check your current progress and remaining tasks
|
|
142
|
+
4. Keep the TODO file updated throughout the entire process
|
|
143
|
+
|
|
144
|
+
**TODO File Format Requirements:**
|
|
145
|
+
- Use clear, actionable items with checkboxes: [ ] for incomplete, [x] for complete
|
|
146
|
+
- Include relevant file paths and specific changes needed
|
|
147
|
+
- Break down complex tasks into smaller, manageable steps
|
|
148
|
+
- Add context and rationale for decisions
|
|
149
|
+
- Update status as you progress through the work
|
|
150
|
+
|
|
151
|
+
**Examples of tasks that REQUIRE TODO tracking:**
|
|
152
|
+
- Implementing new features
|
|
153
|
+
- Refactoring code across multiple files
|
|
154
|
+
- Setting up project structures
|
|
155
|
+
- Debugging complex issues
|
|
156
|
+
- Any task involving more than 2 file operations
|
|
157
|
+
|
|
158
|
+
**Example TODO format:**
|
|
159
|
+
Use markdown format with checkboxes and clear structure like this:
|
|
160
|
+
# Task: Implement user authentication system
|
|
161
|
+
## Plan: [ ] Create user model, [ ] Set up middleware, [ ] Create endpoints
|
|
162
|
+
## Progress: [x] User model created, [ ] Working on middleware
|
|
163
|
+
## Notes: Using JWT tokens with 24h expiration
|
|
164
|
+
|
|
165
|
+
**FAILURE TO USE TODO TRACKING WILL RESULT IN POOR TASK COMPLETION.**
|
|
166
|
+
|
|
167
|
+
## File Operation Guidelines - CRITICAL:
|
|
168
|
+
|
|
169
|
+
**ALWAYS PREFER EDITING OVER WRITING FILES:**
|
|
170
|
+
|
|
171
|
+
**Use Edit File When (PREFERRED):**
|
|
172
|
+
- Modifying existing files (default choice for any existing file)
|
|
173
|
+
- Targeted changes - when you can identify specific text to replace
|
|
174
|
+
- Incremental updates - adding, removing, or modifying specific sections
|
|
175
|
+
- Preserving file structure - when most of the file should remain unchanged
|
|
176
|
+
- Any change to an existing file, no matter how extensive
|
|
177
|
+
|
|
178
|
+
**Use Write File ONLY When:**
|
|
179
|
+
- Creating completely new files that don't exist
|
|
180
|
+
- User explicitly requests file creation/replacement
|
|
181
|
+
- Complete replacement where the entire file content is fundamentally different
|
|
182
|
+
- Fundamental restructuring where edit operations would be impractical
|
|
183
|
+
|
|
184
|
+
**MANDATORY Process for File Operations:**
|
|
185
|
+
1. ALWAYS use read_file tool first to check if a file exists and examine its content
|
|
186
|
+
2. If file exists: Use edit_file tool with precise old_string/new_string replacements
|
|
187
|
+
3. If file doesn't exist: Only then use write_file tool
|
|
188
|
+
4. NEVER write over existing files unless explicitly requested by the user
|
|
189
|
+
|
|
190
|
+
**Before ANY file write operation, you MUST:**
|
|
191
|
+
- Confirm the file doesn't already exist by reading it first
|
|
192
|
+
- If it exists, explain why you're choosing edit over write
|
|
193
|
+
- If writing is necessary, explain why edit won't work
|
|
194
|
+
|
|
195
|
+
For finding and searching files, prefer efficient shell commands:
|
|
196
|
+
- Use 'find' with patterns: find . -name "*.js" -type f
|
|
197
|
+
- Use 'grep' for text search: grep -r "function" . --include="*.ts"
|
|
198
|
+
- Use 'ls' with glob patterns: ls src/**/*.ts (if supported)
|
|
199
|
+
- Use 'find' + 'grep' combinations for complex searches
|
|
200
|
+
- The search_files tool is available but shell commands are often faster
|
|
201
|
+
|
|
202
|
+
Note that you should definitely search for patterns and keywords based on what you think you can find.
|
|
203
|
+
|
|
204
|
+
Shell Command Security:
|
|
205
|
+
- Safe commands (ls, find, grep, git status, cat, etc.) execute automatically
|
|
206
|
+
- Other commands may require user confirmation
|
|
207
|
+
- Users can approve individual commands or entire sessions
|
|
208
|
+
- Running with --dangerously-accept-all skips all confirmations
|
|
209
|
+
|
|
210
|
+
Shell Command Non-Interactive Mode:
|
|
211
|
+
- Commands run in non-interactive mode and output is captured
|
|
212
|
+
- For tools that normally prompt (npm create, git init, etc.), provide all necessary flags
|
|
213
|
+
- Examples: npm create vite@latest my-app --template react (instead of letting it prompt)
|
|
214
|
+
- Use --yes, --no-interactive, --template, --default flags when available
|
|
215
|
+
- If a command times out or fails, check if it needs additional flags to avoid prompts
|
|
216
|
+
|
|
217
|
+
## Available Tools:
|
|
218
|
+
|
|
219
|
+
${generateToolDescriptions()}
|
|
220
|
+
|
|
221
|
+
Be helpful, accurate, and always explain what you're doing with the files and commands.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
## CONTEXT SECTION (Variable Content):`;
|
|
225
|
+
} // For backward compatibility, export a static version
|
|
226
|
+
export const SYSTEM_PROMPT = `You are ProtoAgent, a helpful coding assistant with file system and shell command capabilities. Your objective is to help the user
|
|
227
|
+
complete their coding tasks, most likely related to the directory you were started in.
|
|
228
|
+
|
|
229
|
+
You can:
|
|
230
|
+
|
|
231
|
+
1. Read and analyze code files to understand project structure
|
|
232
|
+
2. Create new files and directories for projects
|
|
233
|
+
3. Edit existing files with precision
|
|
234
|
+
4. Search through codebases to find specific patterns
|
|
235
|
+
5. List directory contents and show project structure
|
|
236
|
+
6. Execute shell commands for development tasks (npm, git, build tools, etc.)
|
|
237
|
+
|
|
238
|
+
When users ask you to work with files or run commands, you should:
|
|
239
|
+
- Use the available tools to complete tasks
|
|
240
|
+
- Always read files before editing them to understand the context
|
|
241
|
+
- Create directories before creating files in them
|
|
242
|
+
- Use shell commands for discovering the codebase, files, package management, git operations, building, testing, etc.
|
|
243
|
+
- Provide clear feedback about what you're doing
|
|
244
|
+
- Show the user the results of your operations
|
|
245
|
+
- Ask for confirmation before running any write, delete operations
|
|
246
|
+
|
|
247
|
+
## File Operation Guidelines - CRITICAL:
|
|
248
|
+
|
|
249
|
+
**ALWAYS PREFER EDITING OVER WRITING FILES:**
|
|
250
|
+
|
|
251
|
+
**Use Edit File When (PREFERRED):**
|
|
252
|
+
- Modifying existing files (default choice for any existing file)
|
|
253
|
+
- Targeted changes - when you can identify specific text to replace
|
|
254
|
+
- Incremental updates - adding, removing, or modifying specific sections
|
|
255
|
+
- Preserving file structure - when most of the file should remain unchanged
|
|
256
|
+
- Any change to an existing file, no matter how extensive
|
|
257
|
+
|
|
258
|
+
**Use Write File ONLY When:**
|
|
259
|
+
- Creating completely new files that don't exist
|
|
260
|
+
- User explicitly requests file creation/replacement
|
|
261
|
+
- Complete replacement where the entire file content is fundamentally different
|
|
262
|
+
- Fundamental restructuring where edit operations would be impractical
|
|
263
|
+
|
|
264
|
+
**MANDATORY Process for File Operations:**
|
|
265
|
+
1. ALWAYS use read_file tool first to check if a file exists and examine its content
|
|
266
|
+
2. If file exists: Use edit_file tool with precise old_string/new_string replacements
|
|
267
|
+
3. If file doesn't exist: Only then use write_file tool
|
|
268
|
+
4. NEVER write over existing files unless explicitly requested by the user
|
|
269
|
+
|
|
270
|
+
**Before ANY file write operation, you MUST:**
|
|
271
|
+
- Confirm the file doesn't already exist by reading it first
|
|
272
|
+
- If it exists, explain why you're choosing edit over write
|
|
273
|
+
- If writing is necessary, explain why edit won't work
|
|
274
|
+
|
|
275
|
+
For finding and searching files, prefer efficient shell commands:
|
|
276
|
+
- Use 'find' with patterns: find . -name "*.js" -type f
|
|
277
|
+
- Use 'grep' for text search: grep -r "function" . --include="*.ts"
|
|
278
|
+
- Use 'ls' with glob patterns: ls src/**/*.ts (if supported)
|
|
279
|
+
- Use 'find' + 'grep' combinations for complex searches
|
|
280
|
+
- The search_files tool is available but shell commands are often faster
|
|
281
|
+
|
|
282
|
+
Note that you should definitely search for patterns and keywords based on what you think you can find.
|
|
283
|
+
|
|
284
|
+
Shell Command Security:
|
|
285
|
+
- Safe commands (ls, find, grep, git status, cat, etc.) execute automatically
|
|
286
|
+
- Other commands may require user confirmation
|
|
287
|
+
- Users can approve individual commands or entire sessions
|
|
288
|
+
- Running with --dangerously-accept-all skips all confirmations
|
|
289
|
+
|
|
290
|
+
Shell Command Non-Interactive Mode:
|
|
291
|
+
- Commands run in non-interactive mode and output is captured
|
|
292
|
+
- For tools that normally prompt (npm create, git init, etc.), provide all necessary flags
|
|
293
|
+
- Examples: npm create vite@latest my-app --template react (instead of letting it prompt)
|
|
294
|
+
- Use --yes, --no-interactive, --template, --default flags when available
|
|
295
|
+
- If a command times out or fails, check if it needs additional flags to avoid prompts
|
|
296
|
+
|
|
297
|
+
## Available Tools:
|
|
298
|
+
|
|
299
|
+
${generateToolDescriptions()}
|
|
300
|
+
|
|
301
|
+
Be helpful, accurate, and always explain what you're doing with the files and commands.`;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import 'dotenv/config';
|
|
5
|
+
import { setToolsConfig, setToolsDangerouslyAcceptAll } from './tools/index.js';
|
|
6
|
+
import { hasConfig, validateConfig, loadConfig } from './config/manager.js';
|
|
7
|
+
import { setupConfig, promptReconfigure } from './config/setup.js';
|
|
8
|
+
import { createOpenAIClient } from './config/client.js';
|
|
9
|
+
import { showCurrentConfig, updateApiKey, updateModel, resetConfiguration } from './config/commands.js';
|
|
10
|
+
import { createAgenticLoop } from './agentic-loop.js';
|
|
11
|
+
import { logger, LogLevel } from './utils/logger.js';
|
|
12
|
+
const program = new Command();
|
|
13
|
+
// Global configuration and client
|
|
14
|
+
let config = null;
|
|
15
|
+
let openaiClient = null;
|
|
16
|
+
let agenticLoop = null;
|
|
17
|
+
program
|
|
18
|
+
.name('protoagent')
|
|
19
|
+
.description('Interactive AI coding agent CLI with file system capabilities')
|
|
20
|
+
.version('1.0.0')
|
|
21
|
+
.option('--dangerously-accept-all', 'Auto-approve all shell commands and file operations without confirmation (DANGEROUS)')
|
|
22
|
+
.option('--log-level <level>', 'Set logging level (ERROR, WARN, INFO, DEBUG, TRACE)', 'INFO');
|
|
23
|
+
// Configuration management command
|
|
24
|
+
program
|
|
25
|
+
.command('config')
|
|
26
|
+
.description('Manage ProtoAgent configuration')
|
|
27
|
+
.option('--show', 'Show current configuration')
|
|
28
|
+
.option('--update-key', 'Update OpenAI API key')
|
|
29
|
+
.option('--update-model', 'Update OpenAI model')
|
|
30
|
+
.option('--reset', 'Reset configuration (will prompt for new setup)')
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
if (options.show) {
|
|
33
|
+
await showCurrentConfig();
|
|
34
|
+
}
|
|
35
|
+
else if (options.updateKey) {
|
|
36
|
+
await updateApiKey();
|
|
37
|
+
}
|
|
38
|
+
else if (options.updateModel) {
|
|
39
|
+
await updateModel();
|
|
40
|
+
}
|
|
41
|
+
else if (options.reset) {
|
|
42
|
+
await resetConfiguration();
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log('Use --help to see available configuration options');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
async function respondToUser(userInput) {
|
|
49
|
+
if (agenticLoop) {
|
|
50
|
+
await agenticLoop.processUserInput(userInput);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.error('❌ Agentic loop not initialized');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function initializeConfig() {
|
|
57
|
+
// Check if configuration exists
|
|
58
|
+
if (await hasConfig()) {
|
|
59
|
+
try {
|
|
60
|
+
config = await loadConfig();
|
|
61
|
+
if (!validateConfig(config)) {
|
|
62
|
+
console.log('⚠️ Configuration is invalid or corrupted.');
|
|
63
|
+
if (await promptReconfigure()) {
|
|
64
|
+
config = await setupConfig();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log('Cannot proceed without valid configuration.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.log(`❌ Error loading configuration: ${error.message}`);
|
|
74
|
+
if (await promptReconfigure()) {
|
|
75
|
+
config = await setupConfig();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log('Cannot proceed without valid configuration.');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// No configuration exists, run setup
|
|
85
|
+
config = await setupConfig();
|
|
86
|
+
}
|
|
87
|
+
// Set config for tools
|
|
88
|
+
setToolsConfig(config);
|
|
89
|
+
// Initialize OpenAI client with the configuration
|
|
90
|
+
try {
|
|
91
|
+
openaiClient = createOpenAIClient(config);
|
|
92
|
+
// Initialize the agentic loop
|
|
93
|
+
agenticLoop = await createAgenticLoop(openaiClient, config, {
|
|
94
|
+
maxIterations: 100,
|
|
95
|
+
streamOutput: true
|
|
96
|
+
});
|
|
97
|
+
logger.debug(`✅ Successfully initialized ProtoAgent`);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
logger.error(`❌ Failed to initialize OpenAI client: ${error.message}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function startInteractiveMode() {
|
|
105
|
+
console.log('🤖 Welcome to ProtoAgent - Your AI Coding Assistant with File System & Shell Powers!');
|
|
106
|
+
console.log('💡 I can read, write, edit, search files and run shell commands in your project.');
|
|
107
|
+
console.log('🔧 Ask me to create files, analyze code, run npm commands, git operations, or execute any shell command.');
|
|
108
|
+
// Check for options
|
|
109
|
+
const options = program.opts();
|
|
110
|
+
// Set log level
|
|
111
|
+
if (options.logLevel) {
|
|
112
|
+
const logLevel = LogLevel[options.logLevel.toUpperCase()];
|
|
113
|
+
if (logLevel !== undefined) {
|
|
114
|
+
logger.setLevel(logLevel);
|
|
115
|
+
logger.debug(`🔍 Log level set to: ${options.logLevel.toUpperCase()}`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
logger.warn(`⚠️ Invalid log level: ${options.logLevel}. Using INFO.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Check for dangerous flag
|
|
122
|
+
if (options.dangerouslyAcceptAll) {
|
|
123
|
+
logger.warn('⚠️ DANGER MODE: All shell commands and file operations will be auto-approved without confirmation!');
|
|
124
|
+
setToolsDangerouslyAcceptAll(true);
|
|
125
|
+
}
|
|
126
|
+
console.log('🚪 Type "exit" to quit.\n');
|
|
127
|
+
// Initialize configuration
|
|
128
|
+
await initializeConfig();
|
|
129
|
+
logger.info(`🧠 Using ${config.provider} with model: ${config.model}`);
|
|
130
|
+
// Show current working directory
|
|
131
|
+
logger.info(`📁 Working directory: ${process.cwd()}`);
|
|
132
|
+
console.log(`📁 Working directory: ${process.cwd()}\n`);
|
|
133
|
+
while (true) {
|
|
134
|
+
const { input } = await inquirer.prompt([
|
|
135
|
+
{
|
|
136
|
+
type: 'input',
|
|
137
|
+
name: 'input',
|
|
138
|
+
message: 'protoagent>',
|
|
139
|
+
}
|
|
140
|
+
]);
|
|
141
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
142
|
+
console.log('👋 Goodbye! Happy coding!');
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
if (input.trim() === '') {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
await respondToUser(input);
|
|
149
|
+
console.log(); // Add extra newline for spacing
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
program
|
|
153
|
+
.action(() => {
|
|
154
|
+
startInteractiveMode();
|
|
155
|
+
});
|
|
156
|
+
program.parse();
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
export async function createDirectory(dirPath) {
|
|
47
|
+
try {
|
|
48
|
+
const validPath = await validatePath(dirPath);
|
|
49
|
+
await fs.mkdir(validPath, { recursive: true });
|
|
50
|
+
return `Successfully created directory ${dirPath}`;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof Error) {
|
|
54
|
+
throw new Error(`Failed to create directory: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
throw new Error('Failed to create directory: Unknown error');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Tool definition
|
|
60
|
+
export const createDirectoryTool = {
|
|
61
|
+
type: 'function',
|
|
62
|
+
function: {
|
|
63
|
+
name: 'create_directory',
|
|
64
|
+
description: 'Create a new directory or ensure a directory exists. Can create multiple nested directories in one operation. Use this when you need to set up directory structures for projects.',
|
|
65
|
+
parameters: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
directory_path: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'The path to the directory to create, relative to the current working directory. Examples: "src/components", "tests/unit", "docs"'
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
required: ['directory_path']
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|