veil-pgd / src /veil_pgd /cli.py
Klaus Clawd
Initial public release: VEIL-PGD v0.1
c793f45
Raw
History Blame Contribute Delete
3.09 kB
"""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()