import shutil import subprocess from typing import Any from duck_core.tools.base import ToolResult class OsUpdateCheckTool: name = "os_update_check" risk_level = "low" def __init__(self, workspace: str, timeout_seconds: int = 60, max_lines: int = 300): self.workspace = workspace self.timeout_seconds = timeout_seconds self.max_lines = max_lines async def run(self, args: dict[str, Any]) -> ToolResult: if shutil.which("apt"): return self._run_apt() return ToolResult( ok=False, error="No supported package manager found for update checks.", metadata={"supported_package_managers": ["apt"]}, ) def _run_apt(self) -> ToolResult: try: completed = subprocess.run( ["apt", "list", "--upgradable"], cwd=self.workspace, text=True, capture_output=True, timeout=self.timeout_seconds, check=False, ) except subprocess.SubprocessError as exc: return ToolResult(ok=False, error=str(exc), metadata={"package_manager": "apt"}) lines = completed.stdout.splitlines() package_lines = [line for line in lines if "/" in line and "upgradable from:" in line] output_lines = lines[: self.max_lines] truncated = len(lines) > self.max_lines if truncated: output_lines.append(f"... truncated after {self.max_lines} lines") return ToolResult( ok=completed.returncode == 0, output="\n".join(output_lines), error=completed.stderr or None, metadata={ "package_manager": "apt", "command": "apt list --upgradable", "returncode": completed.returncode, "upgradable_count": len(package_lines), "refreshed_cache": False, "truncated": truncated, }, )