pilot-agent-cli
Version:
GitHub Copilot automation tool with configuration-driven file management
717 lines (625 loc) โข 28.5 kB
JavaScript
/**
* 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.');
}
}