"""Shell command execution tool""" import subprocess from typing import Dict, Any from luxx.tools.factory import tool from luxx.tools.core import ToolContext, CommandPermission from luxx.config import config from pathlib import Path def get_workspace_from_context(context: ToolContext) -> str: """Get workspace from context, auto-create if needed""" import hashlib from pathlib import Path if context.workspace: return context.workspace if context.user_id is not None: workspace_root = Path(config.workspace_root).resolve() user_hash = hashlib.sha256(str(context.user_id).encode()).hexdigest()[:16] user_workspace = workspace_root / user_hash if config.workspace_auto_create and not user_workspace.exists(): user_workspace.mkdir(parents=True, exist_ok=True) return str(user_workspace) return None @tool( name="shell_exec", description="Execute a shell command in the user's workspace directory", parameters={ "type": "object", "properties": { "command": { "type": "string", "description": "Shell command to execute" }, "timeout": { "type": "integer", "description": "Timeout in seconds (default 30)", "default": 30 }, "cwd": { "type": "string", "description": "Working directory relative to workspace (default: workspace root)", "default": "." } }, "required": ["command"] }, required_params=["command"], category="shell", required_permission=CommandPermission.EXECUTE ) def shell_exec(arguments: Dict[str, Any], context: ToolContext = None): """ Execute a shell command in the user's workspace. Args: command: Shell command to execute timeout: Timeout in seconds (default 30) cwd: Working directory relative to workspace (default: workspace root) Returns: {"stdout": str, "stderr": str, "returncode": int} """ workspace = get_workspace_from_context(context) if not workspace: return {"error": "Workspace not configured"} command = arguments.get("command", "") timeout = arguments.get("timeout", 30) cwd_rel = arguments.get("cwd", ".") # Build working directory workspace_path = Path(workspace).resolve() cwd_path = (workspace_path / cwd_rel).resolve() # Security check: ensure cwd is within workspace if not cwd_path.is_relative_to(workspace_path): return {"error": "Working directory is outside workspace"} try: result = subprocess.run( command, shell=True, cwd=str(cwd_path), capture_output=True, text=True, timeout=min(timeout, 120) # Max 2 minutes ) return { "stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode, "command": command, "cwd": str(cwd_path.relative_to(workspace_path)) } except subprocess.TimeoutExpired: return {"error": f"Command timed out after {timeout} seconds"} except Exception as e: return {"error": f"Execution error: {str(e)}"}