"""Wrap CLI commands to run through Headroom proxy. Usage: headroom wrap claude # Start proxy + rtk + claude headroom wrap codex # Start proxy + OpenAI Codex CLI headroom wrap aider # Start proxy + aider headroom wrap cursor # Start proxy + print Cursor config instructions headroom wrap claude --no-rtk # Without rtk hooks headroom wrap claude --port 9999 # Custom proxy port headroom wrap claude -- --model opus # Pass args to claude """ from __future__ import annotations import os import shutil import signal import socket import subprocess import sys import time from pathlib import Path from typing import Any import click from .main import main # Proxy health check (reused from evals/suite_runner.py pattern) def _check_proxy(port: int) -> bool: """Check if Headroom proxy is running on given port.""" try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(1) s.connect(("127.0.0.1", port)) return True except (TimeoutError, ConnectionRefusedError, OSError): return False def _get_log_path() -> Path: """Get path for proxy log file.""" log_dir = Path.home() / ".headroom" / "logs" log_dir.mkdir(parents=True, exist_ok=True) return log_dir / "proxy.log" def _start_proxy(port: int) -> subprocess.Popen: """Start Headroom proxy as a background subprocess. Logs are written to ~/.headroom/logs/proxy.log to avoid pipe buffer deadlocks (macOS pipe buffer is ~64KB — a busy proxy fills it quickly, blocking the process). """ cmd = [sys.executable, "-m", "headroom.cli", "proxy", "--port", str(port)] log_path = _get_log_path() log_file = open(log_path, "a") # noqa: SIM115 proc = subprocess.Popen( cmd, stdout=log_file, stderr=log_file, ) # Wait for proxy to be ready (up to 15 seconds) for _i in range(15): time.sleep(1) if _check_proxy(port): click.echo(f" Logs: {log_path}") return proc # Check if process died if proc.poll() is not None: log_file.close() # Read last few lines of log for error context try: tail = log_path.read_text()[-500:] except Exception: tail = "(no log output)" raise RuntimeError(f"Proxy exited with code {proc.returncode}: {tail}") proc.kill() log_file.close() raise RuntimeError(f"Proxy failed to start on port {port} within 15 seconds") def _setup_rtk(verbose: bool = False) -> Path | None: """Ensure rtk is installed and hooks are registered.""" from headroom.rtk import get_rtk_path from headroom.rtk.installer import ensure_rtk, register_claude_hooks rtk_path = get_rtk_path() if rtk_path: if verbose: click.echo(f" rtk found at {rtk_path}") else: click.echo(" Downloading rtk (Rust Token Killer)...") rtk_path = ensure_rtk() if rtk_path: click.echo(f" rtk installed at {rtk_path}") else: click.echo(" rtk download failed — continuing without it") return None # Register hooks (idempotent) if register_claude_hooks(rtk_path): if verbose: click.echo(" rtk hooks registered in Claude Code") else: click.echo(" rtk hook registration failed — continuing without it") return rtk_path # rtk instructions for tools without hook support (Codex, Cursor, Aider). # These get injected into AGENTS.md / .cursorrules so the LLM voluntarily # uses rtk-prefixed commands. Kept concise to minimize instruction overhead. RTK_INSTRUCTIONS_BLOCK = """\ # RTK (Rust Token Killer) - Token-Optimized Commands When running shell commands, **always prefix with `rtk`**. This reduces context usage by 60-90% with zero behavior change. If rtk has no filter for a command, it passes through unchanged — so it is always safe to use. ## Key Commands ```bash # Git (59-80% savings) rtk git status rtk git diff rtk git log # Files & Search (60-75% savings) rtk ls rtk read rtk grep rtk find rtk diff # Test (90-99% savings) — shows failures only rtk pytest tests/ rtk cargo test rtk test # Build & Lint (80-90% savings) — shows errors only rtk tsc rtk lint rtk cargo build rtk prettier --check rtk mypy rtk ruff check # Analysis (70-90% savings) rtk err rtk log rtk json rtk summary rtk deps rtk env # GitHub (26-87% savings) rtk gh pr view rtk gh run list rtk gh issue list # Infrastructure (85% savings) rtk docker ps rtk kubectl get rtk docker logs # Package managers (70-90% savings) rtk pip list rtk pnpm install rtk npm run