UNPKG

pilot-agent-cli

Version:

GitHub Copilot automation tool with configuration-driven file management

717 lines (625 loc) โ€ข 28.5 kB
#!/usr/bin/env node /** * Pilot Agent CLI - Enhanced version with integrated authentication * Following SOLID principles and hexagonal architecture */ const path = require('path'); const fs = require('fs'); const { spawn } = require('child_process'); const args = process.argv.slice(2); const command = args[0]; // Better detection of global vs local installation const isGlobalInstall = !fs.existsSync(path.join(__dirname, 'src')) || !fs.existsSync(path.join(__dirname, 'copilot-auth.js')); // Integrated authentication function using the shared service async function runIntegratedAuth() { console.log('๐Ÿ” GitHub Copilot Authentication'); console.log('================================='); console.log(''); // Try to import the appropriate detector service let CopilotServerDetector; try { if (isGlobalInstall) { // Use standalone detector for global installation console.log('๐Ÿ” Loading standalone detector for global installation...'); CopilotServerDetector = require('./standalone-copilot-detector'); console.log('โœ… Standalone detector loaded successfully'); } else { // Use full service for development environment console.log('๐Ÿ” Loading full detector for development environment...'); CopilotServerDetector = require('./src/infrastructure/services/CopilotServerDetector'); console.log('โœ… Full detector loaded successfully'); } } catch (error) { console.log(`โŒ Failed to load detector: ${error.message}`); console.log('โš ๏ธ Detector service not available'); console.log('๐Ÿ“ For full interactive authentication, run from development directory:'); console.log(' git clone https://github.com/benoitrolland/pilot-agent-cli.git'); console.log(' cd pilot-agent-cli'); console.log(' node copilot-auth.js'); console.log(''); console.log('๐Ÿ”— Alternative: Use GitHub CLI authentication:'); console.log(' gh auth login'); console.log(' gh auth status'); return; } const detector = new CopilotServerDetector(); const detection = await detector.detect(); if (!detection.found) { return; } console.log('๐Ÿš€ Starting authentication process...'); console.log(''); // Try to start full authentication with the detector try { console.log('๐Ÿ”„ Attempting interactive authentication...'); const spawnConfig = detector.getSpawnCommand(detection.method, detection.path); const spawnOptions = detector.getSpawnOptions(detection.method); console.log(`๐Ÿ”ง Spawn config: ${spawnConfig.command} ${spawnConfig.args.join(' ')}`); const authProcess = spawn(spawnConfig.command, spawnConfig.args, spawnOptions); let buffer = ''; let authenticationStarted = false; authProcess.stdout.on('data', (data) => { buffer += data.toString(); console.log('๐Ÿ“ฅ Raw output received:', data.toString().substring(0, 200) + '...'); // Look for JSON-RPC messages while (true) { const headerEnd = buffer.indexOf('\r\n\r\n'); if (headerEnd === -1) break; const header = buffer.substring(0, headerEnd); const contentLengthMatch = header.match(/Content-Length: (\d+)/); if (!contentLengthMatch) break; const contentLength = parseInt(contentLengthMatch[1]); const messageStart = headerEnd + 4; const messageEnd = messageStart + contentLength; if (buffer.length < messageEnd) break; const message = buffer.substring(messageStart, messageEnd); buffer = buffer.substring(messageEnd); try { const parsed = JSON.parse(message); console.log('๐Ÿ“จ Parsed JSON-RPC message:', JSON.stringify(parsed, null, 2)); // Handle authentication responses with device code if (parsed.result && parsed.result.verificationUri && parsed.result.userCode) { console.log('๐Ÿ”— GITHUB COPILOT AUTHENTICATION REQUIRED ๐Ÿ”—'); console.log('โ•'.repeat(60)); console.log('๐ŸŒ Open this URL in your browser:'); console.log(` ${parsed.result.verificationUri}`); console.log(''); console.log('๐Ÿ”‘ Enter this code on the GitHub page:'); console.log(` ${parsed.result.userCode}`); console.log(''); console.log(`โฑ๏ธ Code valid for: ${Math.floor(parsed.result.expiresIn / 60)} minutes`); console.log(''); console.log('๐Ÿ“‹ Steps to follow:'); console.log(' 1. Click the link above or copy it to your browser'); console.log(' 2. Log in to GitHub if necessary'); console.log(' 3. Enter the user code displayed above'); console.log(' 4. Authorize access to GitHub Copilot'); console.log(' 5. Return to this terminal'); console.log(''); console.log('โ•'.repeat(60)); authenticationStarted = true; // Clean up after showing the code setTimeout(() => { if (authProcess && !authProcess.killed) { authProcess.kill(); } }, 2000); } // Handle "already authenticated" case if (parsed.result && parsed.result.status === 'OK' && parsed.result.user) { console.log('๐ŸŽ‰ AUTHENTICATION ALREADY ACTIVE! ๐ŸŽ‰'); console.log('โ•'.repeat(60)); console.log(`โœ… Connected user: ${parsed.result.user}`); console.log('โœ… GitHub Copilot is ready to use'); console.log('โœ… No additional authentication required'); console.log('โ•'.repeat(60)); authenticationStarted = true; if (authProcess && !authProcess.killed) { authProcess.kill(); } } // Handle error messages (like "Agent service not initialized") if (parsed.error) { console.log(`โš ๏ธ Server error received: ${parsed.error.message}`); if (parsed.error.message.includes('Agent service not initialized')) { console.log(''); console.log('๐Ÿ“ This error means you need to authenticate first.'); console.log('๐Ÿ”ง Sending authentication initiation request...'); // Try to trigger authentication const authInitMessage = { jsonrpc: '2.0', id: 3, method: 'signInInitiate', params: {} }; const authContent = JSON.stringify(authInitMessage); const authHeader = `Content-Length: ${Buffer.byteLength(authContent)}\r\n\r\n`; authProcess.stdin.write(authHeader + authContent); } } } catch (parseError) { console.log('โš ๏ธ JSON parse error:', parseError.message); } } }); authProcess.stderr.on('data', (data) => { console.error('โŒ Server stderr:', data.toString()); }); authProcess.on('error', (error) => { console.error('โŒ Process error:', error.message); }); // Send initialization sequence console.log('๐Ÿ“ค Sending initialization message...'); const initMessage = { jsonrpc: '2.0', id: 1, method: 'initialize', params: { processId: process.pid, rootUri: `file://${process.cwd().replace(/\\/g, '/')}`, capabilities: { textDocument: { completion: { completionItem: { snippetSupport: true, commitCharactersSupport: true, documentationFormat: ['markdown', 'plaintext'] }, contextSupport: true, dynamicRegistration: true } }, workspace: { configuration: true, workspaceFolders: true } }, initializationOptions: { editorInfo: { name: "pilot-agent-cli", version: "1.0.0" }, editorPluginInfo: { name: "pilot-agent-plugin", version: "1.0.0" } } } }; const content = JSON.stringify(initMessage); const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`; authProcess.stdin.write(header + content); // Send initialized notification setTimeout(() => { console.log('๐Ÿ“ค Sending initialized notification...'); const initializedMessage = { jsonrpc: '2.0', method: 'initialized', params: {} }; const initContent = JSON.stringify(initializedMessage); const initHeader = `Content-Length: ${Buffer.byteLength(initContent)}\r\n\r\n`; authProcess.stdin.write(initHeader + initContent); }, 500); // Wait for response and then trigger auth setTimeout(() => { console.log('๐Ÿ“ค Sending authentication request...'); const authInitMessage = { jsonrpc: '2.0', id: 2, method: 'signInInitiate', params: {} }; const authContent = JSON.stringify(authInitMessage); const authHeader = `Content-Length: ${Buffer.byteLength(authContent)}\r\n\r\n`; authProcess.stdin.write(authHeader + authContent); }, 1500); // Timeout if no authentication code is provided setTimeout(() => { if (!authenticationStarted) { console.log('โฐ Authentication timeout - no response from server'); console.log(''); console.log('๐Ÿ”— Alternative: Use GitHub CLI authentication:'); console.log(' gh auth login'); console.log(' gh auth status'); } if (authProcess && !authProcess.killed) { authProcess.kill(); } }, 15000); } catch (error) { console.log(`โš ๏ธ Authentication failed: ${error.message}`); console.log('๐Ÿ”— Alternative: Use GitHub CLI authentication:'); console.log(' gh auth login'); console.log(' gh auth status'); } } // Handle auth command first if (command === 'auth') { if (isGlobalInstall) { // Use integrated authentication for global install runIntegratedAuth().catch(error => { console.error(`โŒ Authentication failed: ${error.message}`); }); } else { // Use full copilot-auth.js for development environment console.log('๐Ÿš€ Launching Copilot authentication from development directory...'); console.log(''); const authScript = path.join(__dirname, 'copilot-auth.js'); if (fs.existsSync(authScript)) { const authProcess = spawn('node', [authScript], { stdio: 'inherit' }); authProcess.on('close', (code) => { if (code === 0) { console.log('\nโœ… Authentication completed successfully'); } else { console.error(`\nโŒ Authentication failed with code: ${code}`); } }); authProcess.on('error', (error) => { console.error(`โŒ Failed to launch authentication: ${error.message}`); }); } else { console.error('โŒ copilot-auth.js not found in development directory'); } } return; } // Handle other commands with basic functionality for global install switch (command) { case 'init': initializeConfig(); break; case 'config': showConfig(); break; case 'run': runAgent(); break; case 'test': runTests(); break; case 'help': default: showHelp(); break; } // Command implementations function initializeConfig() { const configPath = path.join(process.cwd(), 'pilot-agent.config.json'); if (fs.existsSync(configPath)) { console.log('โš ๏ธ Configuration file already exists:'); console.log(` ${configPath}`); console.log(''); console.log('๐Ÿ’ก Use --force to overwrite:'); console.log(' pilot-agent-cli init --force'); return; } const defaultConfig = { "version": "1.2.6", "project": { "name": path.basename(process.cwd()), "description": "Project configured with Pilot Agent CLI", "rootDirectory": ".", "outputDirectory": "./output" }, "copilot": { "enabled": true, "model": "copilot-codex", "temperature": 0.2, "maxTokens": 1000 }, "processing": { "filePatterns": [ "**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx", "**/*.py", "**/*.java", "**/*.cs", "**/*.go", "**/*.rs", "**/*.php" ], "excludePatterns": [ "**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/coverage/**" ], "batchSize": 10, "parallel": true }, "features": { "autoDocumentation": true, "codeOptimization": true, "testGeneration": false, "errorHandling": true, "securityScanning": false }, "output": { "format": "enhanced", "preserveOriginal": true, "generateReport": true, "reportFormat": "json" } }; try { fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2)); console.log('โœ… Configuration file created successfully!'); console.log(`๐Ÿ“„ Location: ${configPath}`); console.log(''); console.log('๐Ÿ”ง Next steps:'); console.log('1. Edit the configuration file to match your project needs'); console.log('2. Run: pilot-agent-cli run'); console.log('3. Check output in the specified output directory'); console.log(''); console.log('๐Ÿ’ก Available commands:'); console.log(' pilot-agent-cli config # Show current configuration'); console.log(' pilot-agent-cli run # Execute the agent'); console.log(' pilot-agent-cli test # Run basic tests'); } catch (error) { console.error('โŒ Failed to create configuration file:', error.message); process.exit(1); } } function showConfig() { const configPath = path.join(process.cwd(), 'pilot-agent.config.json'); if (!fs.existsSync(configPath)) { console.log('โŒ Configuration file not found'); console.log(`๐Ÿ“ Expected location: ${configPath}`); console.log(''); console.log('๐Ÿ’ก Create configuration file:'); console.log(' pilot-agent-cli init'); return; } try { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); console.log('๐Ÿ“‹ Current Configuration'); console.log('========================'); console.log(`๐Ÿ“„ File: ${configPath}`); console.log(''); console.log('๐Ÿ”ง Project Settings:'); console.log(` Name: ${config.project?.name || 'Not set'}`); console.log(` Root: ${config.project?.rootDirectory || '.'}`); console.log(` Output: ${config.project?.outputDirectory || './output'}`); console.log(''); console.log('๐Ÿค– Copilot Settings:'); console.log(` Enabled: ${config.copilot?.enabled ? 'โœ…' : 'โŒ'}`); console.log(` Model: ${config.copilot?.model || 'Default'}`); console.log(` Temperature: ${config.copilot?.temperature || 0.2}`); console.log(''); console.log('โš™๏ธ Processing Settings:'); console.log(` Batch Size: ${config.processing?.batchSize || 10}`); console.log(` Parallel: ${config.processing?.parallel ? 'โœ…' : 'โŒ'}`); console.log(` File Patterns: ${config.processing?.filePatterns?.length || 0} patterns`); console.log(''); console.log('๐ŸŽฏ Features:'); Object.entries(config.features || {}).forEach(([feature, enabled]) => { console.log(` ${feature}: ${enabled ? 'โœ…' : 'โŒ'}`); }); } catch (error) { console.error('โŒ Failed to read configuration file:', error.message); process.exit(1); } } function runAgent() { const configPath = path.join(process.cwd(), 'pilot-agent.config.json'); if (!fs.existsSync(configPath)) { console.log('โŒ Configuration file not found'); console.log('๐Ÿ’ก Run: pilot-agent-cli init'); return; } console.log('๐Ÿš€ Starting Pilot Agent execution...'); console.log(''); try { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); // Basic validation if (!config.copilot?.enabled) { console.log('โš ๏ธ Copilot is disabled in configuration'); console.log('๐Ÿ’ก Enable it in pilot-agent.config.json'); return; } // Create output directory const outputDir = path.resolve(config.project?.outputDirectory || './output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); console.log(`๐Ÿ“ Created output directory: ${outputDir}`); } // Simulate processing (basic implementation for global install) console.log('๐Ÿ” Scanning project files...'); const patterns = config.processing?.filePatterns || ['**/*.js']; const excludes = config.processing?.excludePatterns || ['**/node_modules/**']; console.log(`๐Ÿ“‹ File patterns: ${patterns.join(', ')}`); console.log(`๐Ÿšซ Exclude patterns: ${excludes.join(', ')}`); // Basic file discovery const projectFiles = scanProjectFiles(config.project?.rootDirectory || '.', patterns, excludes); console.log(`๐Ÿ“„ Found ${projectFiles.length} files to process`); if (projectFiles.length === 0) { console.log('โš ๏ธ No files found matching the patterns'); console.log('๐Ÿ’ก Check your file patterns in the configuration'); return; } // Generate basic report const reportPath = path.join(outputDir, 'pilot-agent-report.json'); const report = { timestamp: new Date().toISOString(), project: config.project?.name || 'Unknown', filesScanned: projectFiles.length, configuration: config, files: projectFiles.map(file => ({ path: file, size: fs.statSync(file).size, modified: fs.statSync(file).mtime.toISOString() })), status: 'completed', note: 'Basic scan completed. For full Copilot integration, run from development directory.' }; fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log(''); console.log('โœ… Processing completed!'); console.log(`๐Ÿ“Š Report saved: ${reportPath}`); console.log(''); console.log('โš ๏ธ Note: This is a basic scan for global installation.'); console.log('๐Ÿ”ง For full Copilot integration and code generation:'); console.log(' 1. Clone the repository'); console.log(' 2. Run from development directory'); console.log(' 3. Use: node copilot-auth.js for authentication'); } catch (error) { console.error('โŒ Execution failed:', error.message); process.exit(1); } } function scanProjectFiles(rootDir, patterns, excludes) { const files = []; function scanDirectory(dir) { try { const entries = fs.readdirSync(dir); for (const entry of entries) { const fullPath = path.join(dir, entry); const relativePath = path.relative(process.cwd(), fullPath); // Check exclude patterns const isExcluded = excludes.some(pattern => { const normalizedPattern = pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'); return new RegExp(normalizedPattern).test(relativePath.replace(/\\/g, '/')); }); if (isExcluded) continue; const stat = fs.statSync(fullPath); if (stat.isDirectory()) { scanDirectory(fullPath); } else { // Check include patterns const isIncluded = patterns.some(pattern => { const normalizedPattern = pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'); return new RegExp(normalizedPattern).test(relativePath.replace(/\\/g, '/')); }); if (isIncluded) { files.push(fullPath); } } } } catch (error) { // Ignore permission errors and continue } } scanDirectory(rootDir); return files.slice(0, 100); // Limit for performance } function runTests() { console.log('๐Ÿงช Running basic tests...'); console.log(''); const tests = [ { name: 'Configuration File Access', test: () => { const configPath = path.join(process.cwd(), 'pilot-agent.config.json'); return fs.existsSync(configPath); } }, { name: 'Node.js Version', test: () => { const version = process.version; const majorVersion = parseInt(version.slice(1).split('.')[0]); return majorVersion >= 14; } }, { name: 'File System Permissions', test: () => { try { const testFile = path.join(process.cwd(), '.pilot-agent-test'); fs.writeFileSync(testFile, 'test'); fs.unlinkSync(testFile); return true; } catch (error) { return false; } } }, { name: 'Output Directory Creation', test: () => { try { const testDir = path.join(process.cwd(), 'test-output-dir'); fs.mkdirSync(testDir, { recursive: true }); fs.rmdirSync(testDir); return true; } catch (error) { return false; } } } ]; let passed = 0; let failed = 0; tests.forEach(test => { try { const result = test.test(); if (result) { console.log(`โœ… ${test.name}`); passed++; } else { console.log(`โŒ ${test.name}`); failed++; } } catch (error) { console.log(`โŒ ${test.name}: ${error.message}`); failed++; } }); console.log(''); console.log(`๐Ÿ“Š Test Results: ${passed} passed, ${failed} failed`); if (failed === 0) { console.log('๐ŸŽ‰ All tests passed!'); } else { console.log('โš ๏ธ Some tests failed. Check your environment setup.'); } } function showHelp() { console.log('๐Ÿค– Pilot Agent CLI - GitHub Copilot Automation Tool'); console.log('=================================================='); console.log(''); console.log('Usage: pilot-agent-cli <command> [options]'); console.log(''); console.log('Commands:'); console.log(' init Create default configuration file'); console.log(' run Execute Pilot Agent with current config'); console.log(' config Show current configuration'); console.log(' test Run basic tests'); console.log(' auth Run GitHub Copilot authentication'); console.log(' help Show this help message'); console.log(''); console.log('Options:'); console.log(' --config <path> Specify config file path (default: ./pilot-agent.config.json)'); console.log(' --verbose Enable verbose logging'); console.log(''); console.log('Examples:'); console.log(' pilot-agent-cli init'); console.log(' pilot-agent-cli auth # Authenticate with GitHub Copilot'); console.log(' pilot-agent-cli run --verbose'); console.log(' pilot-agent-cli run --config ./custom-config.json'); console.log(' pilot-agent-cli config'); console.log(' pilot-agent-cli test'); console.log(''); if (isGlobalInstall) { console.log('โš ๏ธ Global Installation Detected:'); console.log(' Full agent functionality requires running from development directory.'); console.log(' For complete features, clone the repository and run locally.'); console.log(' Repository: https://github.com/benoitrolland/pilot-agent-cli'); console.log(''); } console.log('Getting Started:'); console.log(' 1. pilot-agent-cli auth # Authenticate with GitHub Copilot'); console.log(' 2. pilot-agent-cli init # Create config file'); console.log(' 3. Edit pilot-agent.config.json # Customize settings'); console.log(' 4. pilot-agent-cli run # Execute automation'); console.log(''); console.log('Prerequisites:'); console.log(' - npm install -g @github/copilot-language-server'); console.log(' - GitHub Copilot subscription'); console.log(' - Authenticated GitHub CLI (gh auth login)'); if (command && command !== 'help') { console.log(''); console.log(`๐Ÿ’ก Command "${command}" received but limited functionality in global install.`); console.log('๐Ÿ’ก For full functionality, run from development directory.'); } }