File size: 3,085 Bytes
c793f45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""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()