"""Command-line interface.""" from __future__ import annotations import json import typer from rich.console import Console from veil_pgd.config import get_settings app = typer.Typer( add_completion=False, help="veil-pgd: image-side protection for vision-language training data", ) console = Console() @app.command() def protect( image: str = typer.Argument(..., help="Path to the input image"), truth: str = typer.Option(..., "--truth", "-t", help="True label of the image"), skip_blackbox: bool = typer.Option(False, help="Skip Tier B (no API calls)"), ): """Find and render the best protective overlay for IMAGE.""" from veil_pgd.pipeline import protect_image res = protect_image(image, truth, skip_blackbox=skip_blackbox) console.print_json(json.dumps(res.manifest)) @app.command() def evaluate( manifest: str = typer.Argument(..., help="CSV of image_path,truth per line"), out: str = typer.Option("artifacts/eval", help="Output dir"), ): """Run the eval harness over a labeled image set (single stealth level).""" from veil_pgd.eval.harness import run_eval from veil_pgd.eval.report import to_markdown report = run_eval(manifest, out) console.print(to_markdown(report)) @app.command() def sweep( manifest: str = typer.Argument(..., help="CSV of image_path,truth per line"), levels: str = typer.Option("strict,medium,loose", help="Comma-separated stealth levels"), out: str = typer.Option("artifacts/eval", help="Output dir"), skip_blackbox: bool = typer.Option(False, help="Skip Tier B (surrogate-only, no API calls)"), ): """Stealth sweep: map attack strength vs human-visibility across levels.""" from veil_pgd.eval.harness import run_sweep report = run_sweep( manifest, [x.strip() for x in levels.split(",")], out, skip_blackbox=skip_blackbox, ) console.print("[bold]Stealth frontier[/bold] (level: mean_dist | success@0.5 | stealth_pass)") for row in report["frontier"]: console.print( f" {row['level']:>7}: {row['mean_distance']:.3f} | " f"{row['success@0.5']:.1%} | {row['stealth_pass_rate']:.1%}" ) @app.command() def doctor(): """Check config + connectivity to klaus-3 services and OpenRouter.""" import httpx s = get_settings() console.print(f"[bold]OpenRouter key set:[/bold] {bool(s.openrouter_api_key)}") console.print(f"[bold]Targets:[/bold] {', '.join(s.blackbox_target_models)}") for name, url in [ ("qwen", s.klaus3_qwen_base_url), ("gemma4b", s.klaus3_gemma4b_base_url), ("vision-svc", s.klaus3_vision_service_url), ]: try: r = httpx.get(url.rstrip("/") + "/models", timeout=5.0) ok = r.status_code < 500 except Exception as e: # noqa: BLE001 ok = False console.print(f" {name}: [red]unreachable[/red] ({e})") continue console.print(f" {name}: {'[green]ok[/green]' if ok else '[yellow]?[/yellow]'} ({url})") if __name__ == "__main__": app()