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
JavaScript
/**
* ā” 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;