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.
@@ -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.`;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Configuration type definitions for ProtoAgent
3
+ */
4
+ export {};
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
+ };