""" Bash execution tool """ import subprocess import asyncio import os from typing import Dict, Any from .registry import Tool class BashTool(Tool): """Tool for executing shell commands""" def __init__(self): super().__init__( name="bash", description="Execute shell commands with timeout. Use for running system commands, scripts, and terminal operations.", parameters={ "type": "object", "properties": { "command": { "type": "string", "description": "Shell command to execute" }, "timeout": { "type": "number", "description": "Timeout in seconds (default: 30)", "default": 30 }, "cwd": { "type": "string", "description": "Working directory to execute command in (optional)" } }, "required": ["command"] } ) async def execute(self, args: Dict[str, Any]) -> str: """Execute the bash tool""" command = args.get("command") timeout = args.get("timeout", 30) cwd = args.get("cwd") if not command: return "Error: command is required" # Validate timeout if timeout <= 0: timeout = 30 # Validate and resolve cwd if cwd: if not os.path.isabs(cwd): cwd = os.path.abspath(cwd) if not os.path.exists(cwd): return f"Error: Working directory '{cwd}' does not exist" if not os.path.isdir(cwd): return f"Error: Working directory '{cwd}' is not a directory" try: # Execute command asynchronously process = await asyncio.create_subprocess_shell( command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=cwd, env=os.environ.copy() ) try: stdout, stderr = await asyncio.wait_for( process.communicate(), timeout=timeout ) except asyncio.TimeoutError: # Kill the process if it times out process.kill() await process.wait() return f"Error: Command timed out after {timeout} seconds" # Decode output stdout_text = stdout.decode('utf-8', errors='replace').strip() stderr_text = stderr.decode('utf-8', errors='replace').strip() # Format result result_parts = [f"Command: {command}"] if cwd: result_parts.append(f"Working directory: {cwd}") result_parts.append(f"Exit code: {process.returncode}") if stdout_text: result_parts.append(f"STDOUT:\n{stdout_text}") if stderr_text: result_parts.append(f"STDERR:\n{stderr_text}") if not stdout_text and not stderr_text: result_parts.append("(No output)") return "\n\n".join(result_parts) except Exception as e: return f"Error: Could not execute command '{command}': {e}"