"""Invoca `claude -p` via subprocess y devuelve la respuesta como string.""" import os import shutil import subprocess def _resolve_claude_bin() -> str | None: """Localiza claude CLI: PATH first, luego rutas convencionales.""" p = shutil.which("claude") if p: return p # Fallback paths comunes (WSL subsession sin .profile cargado, etc). home = os.path.expanduser("~") candidates = [ f"{home}/.local/bin/claude", "/usr/local/bin/claude", "/opt/homebrew/bin/claude", ] for c in candidates: if os.path.isfile(c) and os.access(c, os.X_OK): return c return None def claude_cli_prompt( prompt: str, timeout_s: int = 60, model: str | None = None, max_chars_response: int = 200_000, extra_args: list[str] | None = None, ) -> str: """Invoca `claude -p ""` via subprocess. Args: prompt: Texto del prompt a enviar a Claude. timeout_s: Timeout en segundos antes de raise TimeoutExpired. model: Modelo a usar (ej. "claude-opus-4-5"). None usa el default de `claude -p`. max_chars_response: Trunca stdout a este numero de caracteres. extra_args: Argumentos adicionales para el CLI (ej. ["--output-format", "json"]). Returns: Respuesta de Claude como texto (stdout), truncada a max_chars_response. Raises: FileNotFoundError: Si `claude` no esta en PATH ni rutas convencionales. RuntimeError: Si exit code != 0 (incluye primeros 500 chars de stderr). subprocess.TimeoutExpired: Si la llamada supera timeout_s segundos. """ claude_bin = _resolve_claude_bin() if claude_bin is None: raise FileNotFoundError( "'claude' CLI no encontrado en PATH ni rutas convencionales " "(~/.local/bin, /usr/local/bin, /opt/homebrew/bin). Instala Claude Code." ) cmd = [claude_bin, "-p", prompt] if model: cmd.extend(["--model", model]) if extra_args: cmd.extend(extra_args) result = subprocess.run( cmd, capture_output=True, text=True, timeout=timeout_s, ) if result.returncode != 0: stderr_snippet = result.stderr[:500] if result.stderr else "(sin stderr)" raise RuntimeError( f"claude -p failed (exit {result.returncode}): {stderr_snippet}" ) stdout = result.stdout if len(stdout) > max_chars_response: stdout = stdout[:max_chars_response] return stdout