UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

206 lines 7.36 kB
import { exec, spawn } from 'child_process'; import { promisify } from 'util'; import { BaseTool } from '../base.js'; const execAsync = promisify(exec); export class BashTool extends BaseTool { name = 'bash'; displayName = '🖥️ Bash'; description = 'Execute bash commands. LAST RESORT - prefer specialized tools (search, git, file_*) which are faster and smarter.'; category = 'system'; icon = '🖥️'; parameters = [ { name: 'command', type: 'string', description: 'The bash command to execute', required: true }, { name: 'cwd', type: 'string', description: 'Working directory for the command (optional)', required: false }, { name: 'timeout', type: 'number', description: 'Command timeout in milliseconds (default: 30000)', required: false, default: 30000 }, { name: 'stream', type: 'boolean', description: 'Stream output in real-time (default: false)', required: false, default: false } ]; permissions = { shell: true }; ui = { showProgress: true, collapsible: true, dangerous: true }; async run(params, context) { const { command, cwd, timeout = 30000, stream = false } = params; if (!command || typeof command !== 'string') { throw new Error('Command must be a non-empty string'); } const workingDir = cwd || context.workingDirectory || process.cwd(); this.reportProgress(context, `Executing: ${command}`, 10); try { if (stream) { return await this.runStreaming(command, workingDir, timeout, context); } else { return await this.runNonStreaming(command, workingDir, timeout, context); } } catch (error) { if (error.killed && error.signal === 'SIGTERM') { throw new Error(`Command timed out after ${timeout}ms`); } throw error; } } async runNonStreaming(command, cwd, timeout, context) { const startTime = Date.now(); try { const { stdout, stderr } = await execAsync(command, { cwd, timeout, maxBuffer: 1024 * 1024 * 10, shell: '/bin/bash' }); const endTime = Date.now(); const duration = endTime - startTime; this.reportProgress(context, 'Command completed', 100); const maxOutputLength = 1000; let truncatedStdout = stdout.toString(); let truncatedStderr = stderr.toString(); let outputTruncated = false; if (truncatedStdout.length > maxOutputLength) { truncatedStdout = truncatedStdout.substring(0, maxOutputLength) + '\n... [output truncated]'; outputTruncated = true; } if (truncatedStderr.length > maxOutputLength) { truncatedStderr = truncatedStderr.substring(0, maxOutputLength) + '\n... [error output truncated]'; outputTruncated = true; } return { success: true, stdout: truncatedStdout, stderr: truncatedStderr, exitCode: 0, duration, command, cwd, outputTruncated }; } catch (error) { const endTime = Date.now(); const duration = endTime - startTime; const maxOutputLength = 1000; let truncatedStdout = error.stdout?.toString() || ''; let truncatedStderr = error.stderr?.toString() || error.message; let outputTruncated = false; if (truncatedStdout.length > maxOutputLength) { truncatedStdout = truncatedStdout.substring(0, maxOutputLength) + '\n... [output truncated]'; outputTruncated = true; } if (truncatedStderr.length > maxOutputLength) { truncatedStderr = truncatedStderr.substring(0, maxOutputLength) + '\n... [error output truncated]'; outputTruncated = true; } return { success: false, stdout: truncatedStdout, stderr: truncatedStderr, exitCode: error.code || 1, duration, command, cwd, error: error.message, outputTruncated }; } } async runStreaming(command, cwd, timeout, context) { return new Promise((resolve, reject) => { const startTime = Date.now(); const output = []; const errorOutput = []; const proc = spawn('/bin/bash', ['-c', command], { cwd, shell: false }); const timeoutHandle = setTimeout(() => { proc.kill('SIGTERM'); }, timeout); proc.stdout.on('data', (data) => { const text = data.toString(); output.push(text); const preview = text.split('\n')[0].substring(0, 50); this.reportProgress(context, `Output: ${preview}...`, 50); }); proc.stderr.on('data', (data) => { const text = data.toString(); errorOutput.push(text); }); proc.on('close', (code) => { clearTimeout(timeoutHandle); const endTime = Date.now(); const duration = endTime - startTime; this.reportProgress(context, 'Command completed', 100); resolve({ success: code === 0, stdout: output.join(''), stderr: errorOutput.join(''), exitCode: code || 0, duration, command, cwd }); }); proc.on('error', (error) => { clearTimeout(timeoutHandle); reject(error); }); }); } formatResult(result) { if (!result) return ''; let output = ''; output += `$ ${result.command}\n`; if (result.cwd && result.cwd !== process.cwd()) { output += `(in ${result.cwd})\n`; } output += '\n'; if (result.stdout) { output += result.stdout; if (!result.stdout.endsWith('\n')) { output += '\n'; } } if (result.stderr && !result.success) { output += '\n[ERROR]\n'; output += result.stderr; if (!result.stderr.endsWith('\n')) { output += '\n'; } } if (result.exitCode !== 0) { output += `\nExit code: ${result.exitCode}`; } if (result.duration) { output += `\n(Completed in ${result.duration}ms)`; } return output; } } //# sourceMappingURL=bash.js.map