UNPKG

blue-beatle

Version:

šŸ¤– AI-Powered Development Assistant - Intelligent code analysis, problem solving, and terminal automation using Gemini API

563 lines (474 loc) • 21.3 kB
/** * ⚔ Terminal Executor - Smart command execution with AI assistance * Handles terminal commands with safety checks and AI suggestions */ const { spawn, exec, execSync } = require('child_process'); const chalk = require('chalk'); const ora = require('ora'); const inquirer = require('inquirer'); const fs = require('fs-extra'); const path = require('path'); const os = require('os'); class TerminalExecutor { constructor() { this.commandHistory = []; this.safeCommands = [ 'ls', 'dir', 'pwd', 'cd', 'cat', 'type', 'echo', 'which', 'where', 'node', 'npm', 'yarn', 'git', 'python', 'pip', 'java', 'javac', 'gcc', 'g++', 'make', 'cargo', 'go', 'rustc', 'dotnet' ]; this.dangerousCommands = [ 'rm', 'del', 'rmdir', 'rd', 'format', 'fdisk', 'dd', 'mkfs', 'shutdown', 'reboot', 'halt', 'poweroff', 'init', 'kill', 'killall', 'chmod 777', 'chown', 'sudo rm', 'sudo dd' ]; this.commandSuggestions = { 'install': { 'node': 'npm install', 'python': 'pip install', 'rust': 'cargo install', 'go': 'go install', 'java': 'mvn install' }, 'build': { 'node': 'npm run build', 'python': 'python setup.py build', 'rust': 'cargo build', 'go': 'go build', 'java': 'mvn compile' }, 'test': { 'node': 'npm test', 'python': 'pytest', 'rust': 'cargo test', 'go': 'go test', 'java': 'mvn test' }, 'run': { 'node': 'npm start', 'python': 'python main.py', 'rust': 'cargo run', 'go': 'go run .', 'java': 'java -jar target/*.jar' } }; this.logFile = path.join(os.tmpdir(), 'macadida-commands.log'); } async execute(command, options = {}) { console.log(chalk.cyan(`⚔ Terminal Executor: ${command}\n`)); // Log the command if (options.log) { await this.logCommand(command, 'requested'); } // Safety check if (options.safe && this.isDangerousCommand(command)) { const { proceed } = await inquirer.prompt([{ type: 'confirm', name: 'proceed', message: chalk.red(`āš ļø This command might be dangerous: "${command}". Continue?`), default: false }]); if (!proceed) { console.log(chalk.yellow('ā­ļø Command execution cancelled for safety.')); return; } } // Interactive mode with AI suggestions if (options.interactive) { await this.interactiveMode(command, options); return; } // Execute the command await this.executeCommand(command, options); } async interactiveMode(initialCommand, options) { console.log(chalk.cyan('šŸŽÆ Interactive Terminal Mode')); console.log(chalk.gray('Type "exit" to quit, "help" for commands\n')); let currentCommand = initialCommand; while (true) { try { // Show AI suggestions for the current command if (currentCommand) { await this.showAISuggestions(currentCommand); } const { action } = await inquirer.prompt([{ type: 'list', name: 'action', message: 'What would you like to do?', choices: [ { name: `Execute: ${currentCommand}`, value: 'execute' }, { name: 'Modify command', value: 'modify' }, { name: 'Get AI suggestions', value: 'suggest' }, { name: 'Show command help', value: 'help' }, { name: 'Enter new command', value: 'new' }, { name: 'Exit', value: 'exit' } ] }]); switch (action) { case 'execute': await this.executeCommand(currentCommand, options); break; case 'modify': const { modifiedCommand } = await inquirer.prompt([{ type: 'input', name: 'modifiedCommand', message: 'Enter modified command:', default: currentCommand }]); currentCommand = modifiedCommand; break; case 'suggest': await this.showDetailedSuggestions(currentCommand); break; case 'help': await this.showCommandHelp(currentCommand); break; case 'new': const { newCommand } = await inquirer.prompt([{ type: 'input', name: 'newCommand', message: 'Enter new command:' }]); currentCommand = newCommand; break; case 'exit': console.log(chalk.yellow('šŸ‘‹ Exiting interactive mode.')); return; } } catch (error) { if (error.isTtyError) { console.log(chalk.yellow('šŸ‘‹ Interactive mode ended.')); break; } else { console.error(chalk.red('āŒ Error:'), error.message); } } } } async executeCommand(command, options = {}) { const spinner = ora(`Executing: ${command}`).start(); try { const startTime = Date.now(); // Add to history this.commandHistory.push({ command, timestamp: new Date(), cwd: process.cwd() }); // Log if requested if (options.log) { await this.logCommand(command, 'executing'); } // Execute the command const result = await this.runCommand(command); const duration = Date.now() - startTime; spinner.stop(); // Display results console.log(chalk.green(`āœ… Command completed in ${duration}ms\n`)); if (result.stdout) { console.log(chalk.white('šŸ“¤ Output:')); console.log(result.stdout); } if (result.stderr) { console.log(chalk.yellow('āš ļø Warnings/Errors:')); console.log(result.stderr); } // Log success if (options.log) { await this.logCommand(command, 'completed', { duration, exitCode: result.exitCode }); } return result; } catch (error) { spinner.stop(); console.error(chalk.red(`āŒ Command failed: ${error.message}`)); // Log failure if (options.log) { await this.logCommand(command, 'failed', { error: error.message }); } // Suggest fixes await this.suggestFixes(command, error); throw error; } } async runCommand(command) { return new Promise((resolve, reject) => { const isWindows = process.platform === 'win32'; const shell = isWindows ? 'cmd.exe' : '/bin/bash'; const shellFlag = isWindows ? '/c' : '-c'; const child = spawn(shell, [shellFlag, command], { cwd: process.cwd(), stdio: ['pipe', 'pipe', 'pipe'], env: process.env }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { resolve({ exitCode: code, stdout: stdout.trim(), stderr: stderr.trim() }); }); child.on('error', (error) => { reject(error); }); // Handle timeout (optional) const timeout = setTimeout(() => { child.kill('SIGTERM'); reject(new Error('Command timed out')); }, 300000); // 5 minutes child.on('close', () => { clearTimeout(timeout); }); }); } isDangerousCommand(command) { const lowerCommand = command.toLowerCase(); return this.dangerousCommands.some(dangerous => lowerCommand.includes(dangerous.toLowerCase()) ); } async showAISuggestions(command) { console.log(chalk.blue('šŸ¤– AI Suggestions:\n')); // Parse command to understand intent const intent = this.parseCommandIntent(command); if (intent) { console.log(chalk.cyan(`Detected intent: ${intent.action} for ${intent.technology}`)); const suggestions = this.commandSuggestions[intent.action]; if (suggestions && suggestions[intent.technology]) { console.log(chalk.green(`šŸ’” Suggested command: ${suggestions[intent.technology]}`)); } } // Show safety warnings if (this.isDangerousCommand(command)) { console.log(chalk.red('āš ļø WARNING: This command could be dangerous!')); console.log(chalk.yellow('Consider using safer alternatives or double-check the command.')); } // Show similar commands from history const similarCommands = this.findSimilarCommands(command); if (similarCommands.length > 0) { console.log(chalk.gray('\nšŸ“š Similar commands from history:')); similarCommands.slice(0, 3).forEach(cmd => { console.log(chalk.gray(` - ${cmd.command}`)); }); } console.log(''); } parseCommandIntent(command) { const lowerCommand = command.toLowerCase(); // Detect technology let technology = 'unknown'; if (lowerCommand.includes('npm') || lowerCommand.includes('node')) technology = 'node'; else if (lowerCommand.includes('pip') || lowerCommand.includes('python')) technology = 'python'; else if (lowerCommand.includes('cargo') || lowerCommand.includes('rust')) technology = 'rust'; else if (lowerCommand.includes('go ')) technology = 'go'; else if (lowerCommand.includes('mvn') || lowerCommand.includes('java')) technology = 'java'; // Detect action let action = null; if (lowerCommand.includes('install')) action = 'install'; else if (lowerCommand.includes('build')) action = 'build'; else if (lowerCommand.includes('test')) action = 'test'; else if (lowerCommand.includes('run') || lowerCommand.includes('start')) action = 'run'; return action && technology !== 'unknown' ? { action, technology } : null; } findSimilarCommands(command) { return this.commandHistory.filter(historyItem => { const similarity = this.calculateSimilarity(command, historyItem.command); return similarity > 0.5; }).sort((a, b) => b.timestamp - a.timestamp); } calculateSimilarity(str1, str2) { const words1 = str1.toLowerCase().split(' '); const words2 = str2.toLowerCase().split(' '); const commonWords = words1.filter(word => words2.includes(word)); return commonWords.length / Math.max(words1.length, words2.length); } async showDetailedSuggestions(command) { console.log(chalk.cyan('\nšŸ” Detailed Command Analysis:\n')); // Command breakdown const parts = command.split(' '); console.log(chalk.blue('Command Structure:')); console.log(chalk.gray(` Base command: ${parts[0]}`)); if (parts.length > 1) { console.log(chalk.gray(` Arguments: ${parts.slice(1).join(' ')}`)); } // Check if command exists try { const which = process.platform === 'win32' ? 'where' : 'which'; execSync(`${which} ${parts[0]}`, { stdio: 'ignore' }); console.log(chalk.green(`āœ… Command "${parts[0]}" is available`)); } catch (error) { console.log(chalk.red(`āŒ Command "${parts[0]}" not found`)); await this.suggestInstallation(parts[0]); } // Show alternatives const alternatives = this.getCommandAlternatives(parts[0]); if (alternatives.length > 0) { console.log(chalk.yellow('\nšŸ”„ Alternative commands:')); alternatives.forEach(alt => { console.log(chalk.gray(` - ${alt}`)); }); } // Show flags and options const commonFlags = this.getCommonFlags(parts[0]); if (commonFlags.length > 0) { console.log(chalk.blue('\nšŸ Common flags:')); commonFlags.forEach(flag => { console.log(chalk.gray(` ${flag.flag}: ${flag.description}`)); }); } console.log(''); } async suggestInstallation(command) { const installSuggestions = { 'node': 'Install Node.js from https://nodejs.org/', 'npm': 'Install Node.js (includes npm) from https://nodejs.org/', 'python': 'Install Python from https://python.org/', 'pip': 'Install Python (includes pip) from https://python.org/', 'git': 'Install Git from https://git-scm.com/', 'docker': 'Install Docker from https://docker.com/', 'kubectl': 'Install kubectl from Kubernetes documentation', 'aws': 'Install AWS CLI from AWS documentation', 'az': 'Install Azure CLI from Microsoft documentation' }; const suggestion = installSuggestions[command]; if (suggestion) { console.log(chalk.yellow(`šŸ’” Installation suggestion: ${suggestion}`)); } } getCommandAlternatives(command) { const alternatives = { 'ls': ['dir', 'Get-ChildItem'], 'dir': ['ls', 'Get-ChildItem'], 'cat': ['type', 'Get-Content'], 'type': ['cat', 'Get-Content'], 'grep': ['findstr', 'Select-String'], 'findstr': ['grep', 'Select-String'], 'curl': ['wget', 'Invoke-WebRequest'], 'wget': ['curl', 'Invoke-WebRequest'] }; return alternatives[command] || []; } getCommonFlags(command) { const flags = { 'ls': [ { flag: '-l', description: 'Long format listing' }, { flag: '-a', description: 'Show hidden files' }, { flag: '-h', description: 'Human readable sizes' } ], 'git': [ { flag: '--help', description: 'Show help' }, { flag: '--version', description: 'Show version' }, { flag: '-v', description: 'Verbose output' } ], 'npm': [ { flag: '--save', description: 'Save to dependencies' }, { flag: '--save-dev', description: 'Save to dev dependencies' }, { flag: '--global', description: 'Install globally' } ], 'docker': [ { flag: '-d', description: 'Run in detached mode' }, { flag: '-p', description: 'Publish ports' }, { flag: '-v', description: 'Mount volumes' } ] }; return flags[command] || []; } async showCommandHelp(command) { console.log(chalk.cyan(`\nšŸ“– Help for: ${command}\n`)); try { // Try to get help from the command itself const helpCommands = [`${command} --help`, `${command} -h`, `man ${command}`]; for (const helpCmd of helpCommands) { try { const result = execSync(helpCmd, { encoding: 'utf8', timeout: 5000 }); console.log(result); return; } catch (error) { // Continue to next help command } } // Fallback to built-in help console.log(chalk.yellow('Built-in help not available. Here are some general tips:')); console.log(chalk.gray('- Use --help or -h flag for most commands')); console.log(chalk.gray('- Check the command documentation online')); console.log(chalk.gray('- Use "man command" on Unix systems')); } catch (error) { console.error(chalk.red('āŒ Could not retrieve help:'), error.message); } } async suggestFixes(command, error) { console.log(chalk.cyan('\nšŸ”§ Suggested Fixes:\n')); const errorMessage = error.message.toLowerCase(); if (errorMessage.includes('command not found') || errorMessage.includes('not recognized')) { console.log(chalk.yellow('šŸ’” Command not found suggestions:')); console.log(chalk.gray(' 1. Check if the command is installed')); console.log(chalk.gray(' 2. Verify the command spelling')); console.log(chalk.gray(' 3. Check if the command is in your PATH')); await this.suggestInstallation(command.split(' ')[0]); } if (errorMessage.includes('permission denied') || errorMessage.includes('access denied')) { console.log(chalk.yellow('šŸ’” Permission denied suggestions:')); console.log(chalk.gray(' 1. Run with administrator/sudo privileges')); console.log(chalk.gray(' 2. Check file/directory permissions')); console.log(chalk.gray(' 3. Ensure you have write access to the target')); } if (errorMessage.includes('no such file or directory')) { console.log(chalk.yellow('šŸ’” File not found suggestions:')); console.log(chalk.gray(' 1. Check the file path is correct')); console.log(chalk.gray(' 2. Verify the file exists')); console.log(chalk.gray(' 3. Check current working directory')); } if (errorMessage.includes('network') || errorMessage.includes('connection')) { console.log(chalk.yellow('šŸ’” Network error suggestions:')); console.log(chalk.gray(' 1. Check internet connection')); console.log(chalk.gray(' 2. Verify proxy settings')); console.log(chalk.gray(' 3. Check firewall settings')); } console.log(''); } async logCommand(command, status, metadata = {}) { const logEntry = { timestamp: new Date().toISOString(), command, status, cwd: process.cwd(), ...metadata }; try { const logLine = JSON.stringify(logEntry) + '\n'; await fs.appendFile(this.logFile, logLine); } catch (error) { console.error(chalk.red('āŒ Failed to log command:'), error.message); } } async getCommandHistory() { try { if (await fs.pathExists(this.logFile)) { const content = await fs.readFile(this.logFile, 'utf8'); return content.split('\n') .filter(line => line.trim()) .map(line => JSON.parse(line)); } } catch (error) { console.error(chalk.red('āŒ Failed to read command history:'), error.message); } return []; } async clearHistory() { try { await fs.remove(this.logFile); this.commandHistory = []; console.log(chalk.green('āœ… Command history cleared.')); } catch (error) { console.error(chalk.red('āŒ Failed to clear history:'), error.message); } } } module.exports = TerminalExecutor;