UNPKG

pilot-agent-cli

Version:

GitHub Copilot automation tool with configuration-driven file management

429 lines (362 loc) • 15.2 kB
const { spawn } = require('child_process'); const readline = require('readline'); const { promisify } = require('util'); const exec = promisify(require('child_process').exec); const fs = require('fs'); const path = require('path'); // Suppress deprecation warnings for worker.terminate() process.removeAllListeners('warning'); process.on('warning', (warning) => { if (!warning.message.includes('DEP0132') && !warning.message.includes('Passing a callback to worker.terminate()')) { console.warn(warning.message); } }); // Import the new service following hexagonal architecture const CopilotServerDetector = require('./src/infrastructure/services/CopilotServerDetector'); const CopilotAgentInitializer = require('./src/infrastructure/services/CopilotAgentInitializer'); let copilotServer = null; let agentInitializer = null; let messageId = 1; let isPolling = false; let pollInterval = null; let authAttempts = 0; let maxAuthAttempts = 3; let isAuthenticating = false; let authenticationState = 'unknown'; // 'unknown', 'authenticated', 'pending', 'failed' // Function to detect and start Copilot server async function startCopilotServer() { const detector = new CopilotServerDetector(); const detection = await detector.detect(); if (!detection.found) { process.exit(1); } // Get spawn configuration from detector const spawnConfig = detector.getSpawnCommand(detection.method, detection.path); const spawnOptions = detector.getSpawnOptions(detection.method); // Start attempts with different methods const attempts = [ () => spawn(spawnConfig.command, spawnConfig.args, spawnOptions), () => spawn('npx', ['copilot-language-server', '--stdio'], { stdio: ['pipe', 'pipe', 'pipe'], shell: process.platform === 'win32' }) ]; // Add additional attempts based on detection if (detection.path) { attempts.push(() => spawn('node', [detection.path, '--stdio'], { stdio: ['pipe', 'pipe', 'pipe'] })); } let lastError = null; for (let i = 0; i < attempts.length; i++) { try { console.log(`šŸ”§ Attempt ${i + 1}/${attempts.length}...`); copilotServer = attempts[i](); // Wait to see if process starts await new Promise((resolve, reject) => { const timeout = setTimeout(() => resolve(), 3000); copilotServer.on('error', (error) => { clearTimeout(timeout); reject(error); }); copilotServer.on('spawn', () => { clearTimeout(timeout); resolve(); }); }); console.log('āœ… Server started successfully'); // Setup event handlers after successful spawn copilotServer.stderr.on('data', (data) => { console.error('Server error:', data.toString()); }); copilotServer.on('close', (code) => { console.log(`Server closed with code: ${code}`); process.exit(code); }); copilotServer.on('error', (error) => { console.error('āŒ Server error:', error.message); process.exit(1); }); break; } catch (error) { console.log(`āŒ Attempt ${i + 1} failed:`, error.message); lastError = error; if (copilotServer) { try { copilotServer.kill(); } catch (e) {} copilotServer = null; } } } if (!copilotServer) { console.error('āŒ Unable to start server'); console.log('šŸ” Debugging information:'); console.log(` Platform: ${process.platform}`); console.log(` Node version: ${process.version}`); if (detection.path) { console.log(` Server path: ${detection.path}`); } throw lastError || new Error('All attempts failed'); } return copilotServer; } // Interface to read user input const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // Function to send JSON-RPC message function sendMessage(method, params = {}) { const message = { jsonrpc: '2.0', id: messageId++, method: method, params: params }; const content = JSON.stringify(message); const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`; console.log(`→ Sending: ${method}`); copilotServer.stdin.write(header + content); } // Function to send JSON-RPC notification function sendNotification(method, params = {}) { const message = { jsonrpc: '2.0', method: method, params: params }; const content = JSON.stringify(message); const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`; console.log(`→ Notification: ${method}`); copilotServer.stdin.write(header + content); } // Clean shutdown handling process.on('SIGINT', () => { console.log('\nšŸ‘‹ Shutting down...'); if (copilotServer && !copilotServer.killed) { copilotServer.kill(); } rl.close(); process.exit(0); }); // Initialization sequence async function initialize() { console.log('šŸš€ Starting GitHub Copilot authentication...\n'); try { await startCopilotServer(); // Initialize the agent with proper LSP sequence agentInitializer = new CopilotAgentInitializer(console); // Setup event listeners for the initializer agentInitializer.on('statusChanged', (status) => { console.log(`šŸ”„ Agent status changed: ${status}`); }); agentInitializer.on('authenticationRequired', (authData) => { console.log('\nšŸ”— GITHUB COPILOT AUTHENTICATION REQUIRED šŸ”—'); console.log('═'.repeat(60)); console.log('🌐 Open this URL in your browser:'); console.log(` ${authData.verificationUri}`); console.log(''); console.log('šŸ”‘ Enter this code on the GitHub page:'); console.log(` ${authData.userCode}`); console.log(''); console.log(`ā±ļø Code valid for: ${Math.floor(authData.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(`šŸ’” OR use command: confirm ${authData.userCode}`); console.log('═'.repeat(60)); console.log('ā³ Waiting for your authentication...'); console.log(''); // Start automatic polling setTimeout(() => startPolling(authData.userCode), 5000); }); agentInitializer.on('error', (error) => { console.error('āŒ Agent initialization error:', error.message); if (error.message.includes('Agent service not initialized')) { console.log('\nšŸ’” SOLUTION STEPS:'); console.log('šŸ”¹ 1. Ensure GitHub Copilot subscription is active'); console.log('šŸ”¹ 2. Try: gh auth login --scopes copilot'); console.log('šŸ”¹ 3. Verify: gh auth status'); console.log('šŸ”¹ 4. Restart this script'); } }); // Perform proper agent initialization console.log('šŸ”§ Initializing agent service with LSP protocol...'); const initSuccess = await agentInitializer.initializeAgent(copilotServer); if (initSuccess) { const status = agentInitializer.getStatus(); console.log('\nāœ… AGENT INITIALIZATION SUCCESSFUL! āœ…'); console.log('═'.repeat(60)); console.log(`āœ… Agent Status: ${status.agentStatus}`); console.log(`āœ… LSP Initialized: ${status.isInitialized}`); if (status.agentStatus === 'authenticated') { console.log('āœ… Already authenticated - ready to use'); console.log('═'.repeat(60)); console.log('\nšŸ’” NEXT STEPS:'); console.log('šŸ”¹ GitHub Copilot is now authenticated and ready'); console.log('šŸ”¹ Authentication persists across sessions'); console.log('šŸ”¹ You can now use compatible Copilot clients'); console.log('šŸ”¹ Type "quit" to close this authentication script'); } else { console.log('šŸ” Authentication required - triggering sign-in...'); console.log('═'.repeat(60)); // Trigger authentication try { await agentInitializer.triggerAuthentication(copilotServer); } catch (authError) { if (authError.message.includes('No pending sign in')) { console.log('\nšŸŽ‰ AUTHENTICATION CONFIRMED! šŸŽ‰'); console.log('═'.repeat(60)); console.log('āœ… "No pending sign in" means you are already logged in'); console.log('āœ… GitHub Copilot is ready to use'); console.log('āœ… Authentication completed successfully'); console.log('═'.repeat(60)); } else { throw authError; } } } } else { throw new Error('Agent initialization failed'); } // Setup interactive interface setTimeout(() => { const currentStatus = agentInitializer.getStatus(); if (currentStatus.agentStatus !== 'authenticated') { console.log('\nšŸ“ Available commands:'); console.log('- "auth" : Start authentication'); console.log('- "status" : Check status'); console.log('- "confirm <code>" : Confirm with user code (after seeing the code)'); console.log('- "unauth" : Disconnect from GitHub Copilot'); console.log('- "stop" : Stop automatic polling'); console.log('- "quit" : Exit'); console.log(''); console.log('šŸ’” The "confirm" command is only used if you see an authentication code'); console.log('šŸ’” The "unauth" command revokes your GitHub Copilot authentication'); console.log('šŸ’” If you are already connected, just use "quit" to exit'); } rl.on('line', (input) => { const parts = input.trim().split(' '); const command = parts[0].toLowerCase(); switch (command) { case 'auth': console.log('šŸ” Starting authentication...'); agentInitializer.triggerAuthentication(copilotServer).catch(error => { if (error.message.includes('No pending sign in')) { console.log('āœ… Already authenticated'); } else { console.error('āŒ Authentication error:', error.message); } }); break; case 'status': console.log('šŸ“Š Checking status...'); const status = agentInitializer.getStatus(); console.log(`Agent Status: ${status.agentStatus}`); console.log(`LSP Initialized: ${status.isInitialized}`); console.log(`Capabilities: ${status.capabilities ? 'Available' : 'None'}`); break; case 'confirm': if (parts.length > 1) { const userCode = parts[1]; console.log(`šŸ”„ Confirming with code: ${userCode}`); sendMessage('signInConfirm', { userCode: userCode }); } else { console.log('āŒ Usage: confirm <user_code>'); console.log('šŸ’” Example: confirm ABCD-EFGH'); console.log('šŸ’” Use this command only if you see an authentication code'); } break; case 'unauth': case 'logout': case 'signout': console.log('šŸ”“ Disconnecting from GitHub Copilot...'); console.log('āš ļø This will revoke your GitHub Copilot authentication'); // Stop polling if active if (isPolling && pollInterval) { clearInterval(pollInterval); isPolling = false; console.log('šŸ›‘ Stopping polling before logout'); } console.log('šŸ”§ Logging out...'); sendMessage('signOut', { dummy: 'value' }); break; case 'stop': if (isPolling && pollInterval) { clearInterval(pollInterval); isPolling = false; console.log('šŸ›‘ Automatic polling stopped manually'); } else { console.log('ā„¹ļø No polling in progress'); } break; case 'quit': case 'exit': console.log('šŸ‘‹ Goodbye!'); console.log('āœ… GitHub Copilot remains authenticated for future use'); console.log('šŸ”§ Authentication will be available for all compatible Copilot clients'); // Clean up resources if (agentInitializer) { agentInitializer.cleanup(); } if (pollInterval) { clearInterval(pollInterval); isPolling = false; } rl.close(); if (copilotServer && !copilotServer.killed) { copilotServer.kill('SIGTERM'); setTimeout(() => { if (copilotServer && !copilotServer.killed) { copilotServer.kill('SIGKILL'); } process.exit(0); }, 2000); } else { process.exit(0); } break; default: console.log('āŒ Unknown command. Use: auth, status, confirm <code>, unauth, stop, or quit'); } }); }, 1000); } catch (error) { console.error('āŒ Error during initialization:', error.message); if (error.message.includes('copilot-language-server not found')) { console.log('\nšŸ“¦ INSTALLATION REQUIRED:'); console.log('npm install -g @github/copilot-language-server'); } else if (error.message.includes('Agent service not initialized')) { console.log('\nšŸ”§ TROUBLESHOOTING STEPS:'); console.log('1. Verify GitHub Copilot subscription is active'); console.log('2. Run: gh auth login --scopes copilot'); console.log('3. Check: gh auth status'); console.log('4. Restart this script'); } process.exit(1); } } // Function to start automatic polling function startPolling(userCode) { console.log('šŸ”„ Starting automatic polling...'); isPolling = true; pollInterval = setInterval(() => { console.log('šŸ”„ Checking authentication...'); sendMessage('signInConfirm', { userCode: userCode }); }, 10000); // Every 10 seconds // Stop polling after 15 minutes setTimeout(() => { if (isPolling) { console.log('ā° Polling timeout reached - stopping polling'); clearInterval(pollInterval); isPolling = false; } }, 15 * 60 * 1000); // 15 minutes } // Start initialization initialize();