from __future__ import annotations from typing import Any import subprocess import sys from pathlib import Path from core.tools.base import BaseTool, ToolContext from core.tools.registry import registry from core.safety import safety from core.events import bus class VenvTool(BaseTool): name = "venv" description = "Python virtual environment management (create, install, run, list packages)" # ========================================================= # EXECUTE ROUTER (ONLY ENTRYPOINT WITH ctx) # ========================================================= def execute(self, payload: dict[str, Any], ctx: ToolContext): action = str(payload.get("action", "")).strip() bus.log( "VENV", "venv_execute", "INFO", {"action": action} ) match action: case "create": return self.create_venv(payload) case "install": return self.install_package(payload) case "run": return self.run_python(payload) case "list": return self.list_packages(payload) case _: raise ValueError(f"Unknown venv action: {action}") # ========================================================= # PATH HELPERS # ========================================================= def _venv_path(self, path: str) -> Path: return safety.validate_path(path) def _python_bin(self, venv: Path) -> Path: """Cross-platform python binary resolution.""" if (venv / "bin").exists(): return venv / "bin" / "python" return venv / "Scripts" / "python.exe" def _pip_bin(self, venv: Path) -> Path: """Cross-platform pip binary resolution.""" if (venv / "bin").exists(): return venv / "bin" / "pip" return venv / "Scripts" / "pip.exe" # ========================================================= # CREATE VENV # ========================================================= def create_venv(self, payload: dict[str, Any]): path = payload.get("path") if not isinstance(path, str): raise ValueError("path must be string") venv_path = self._venv_path(path) if venv_path.exists(): return { "status": "exists", "path": str(venv_path) } subprocess.run( [sys.executable, "-m", "venv", str(venv_path)], check=True ) return { "status": "created", "path": str(venv_path) } # ========================================================= # INSTALL PACKAGE # ========================================================= def install_package(self, payload: dict[str, Any]): path = payload.get("path") package = payload.get("package") if not isinstance(path, str): raise ValueError("path must be string") if not isinstance(package, str): raise ValueError("package must be string") venv_path = self._venv_path(path) pip = self._pip_bin(venv_path) if not pip.exists(): raise ValueError("pip not found in virtual environment") result = subprocess.run( [str(pip), "install", package], capture_output=True, text=True ) return { "status": "ok" if result.returncode == 0 else "error", "stdout": result.stdout, "stderr": result.stderr } # ========================================================= # RUN PYTHON CODE # ========================================================= def run_python(self, payload: dict[str, Any]): path = payload.get("path") code = payload.get("code") if not isinstance(path, str): raise ValueError("path must be string") if not isinstance(code, str): raise ValueError("code must be string") venv_path = self._venv_path(path) python_bin = self._python_bin(venv_path) if not python_bin.exists(): raise ValueError("python executable not found in venv") result = subprocess.run( [str(python_bin), "-c", code], capture_output=True, text=True ) return { "status": "ok" if result.returncode == 0 else "error", "stdout": result.stdout, "stderr": result.stderr } # ========================================================= # LIST PACKAGES # ========================================================= def list_packages(self, payload: dict[str, Any]): path = payload.get("path") if not isinstance(path, str): raise ValueError("path must be string") venv_path = self._venv_path(path) pip = self._pip_bin(venv_path) result = subprocess.run( [str(pip), "list"], capture_output=True, text=True ) return { "status": "ok", "output": result.stdout } # ========================================================= # REGISTER TOOL # ========================================================= registry.register(VenvTool())