File size: 3,540 Bytes
0c85e62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
93
94
95
96
"""Watch the make-card working folder; auto-render a card whenever a JSON
is added or saved.

Usage:
    python scripts/watch_cards.py              # uses .claude/skills/make-card/work-folder.txt
    python scripts/watch_cards.py <folder>     # override
    python scripts/watch_cards.py --interval 1 # poll every 1 s (default 2)

Run it once in a terminal at the start of a session and forget about it.
Each time ComfyUI (or you) drops a new workflow JSON into the folder, a
matching `<stem>_card.png` is rendered next to it within a couple of
seconds. Skips JSONs whose card is already up to date.

Stop with Ctrl+C.
"""
from __future__ import annotations
import sys
import time
import subprocess
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
MAKE_CARD = ROOT / "scripts" / "make_card.py"
DEFAULT_CFG = ROOT / ".claude" / "skills" / "make-card" / "work-folder.txt"
PYTHON_EXE = sys.executable or "python"


def resolve_folder(argv: list[str]) -> Path:
    args = [a for a in argv if not a.startswith("-")]
    if args:
        return Path(args[0]).expanduser().resolve()
    if DEFAULT_CFG.exists():
        return Path(DEFAULT_CFG.read_text(encoding="utf-8").strip()).resolve()
    raise SystemExit(f"No folder argument and no config at {DEFAULT_CFG}")


def needs_render(json_path: Path) -> Path | None:
    """Return the target PNG path if it's missing or older than the JSON.
    Target lives in <working folder>/_AI/card.png — matches make_card.py."""
    ai_dir = json_path.parent / "_AI"
    png = ai_dir / "card.png"
    if not png.exists() or png.stat().st_mtime < json_path.stat().st_mtime:
        return png
    return None


def render(json_path: Path, png_path: Path) -> bool:
    print(f"[{time.strftime('%H:%M:%S')}] rendering {json_path.name} ...", flush=True)
    # Let make_card.py compute its own output path (writes to _AI/card.png).
    # Passing only the JSON keeps the watcher and the script in lockstep.
    r = subprocess.run(
        [PYTHON_EXE, str(MAKE_CARD), str(json_path)],
        capture_output=True, text=True,
    )
    if r.returncode == 0:
        print(f"  -> {png_path.relative_to(png_path.parent.parent)}", flush=True)
        return True
    print(f"  !! make_card.py failed (rc={r.returncode})\n{r.stderr}", flush=True)
    return False


def main():
    interval = 2.0
    if "--interval" in sys.argv:
        i = sys.argv.index("--interval")
        interval = float(sys.argv[i + 1])
    folder = resolve_folder(sys.argv[1:])
    if not folder.is_dir():
        raise SystemExit(f"Folder does not exist: {folder}")

    print(f"watching {folder}  (every {interval}s, Ctrl+C to stop)", flush=True)
    seen_mtimes: dict[Path, float] = {}
    # Skip post-loop outputs — the user's composited card+video saves a
    # JSON with "loop" in its name; treating it as input would feed the
    # script its own output.
    SKIP_MARKER = "loop"
    try:
        while True:
            for json_path in folder.glob("*.json"):
                if SKIP_MARKER in json_path.stem.lower():
                    continue
                mtime = json_path.stat().st_mtime
                if seen_mtimes.get(json_path) == mtime:
                    continue
                target = needs_render(json_path)
                if target is not None:
                    render(json_path, target)
                seen_mtimes[json_path] = mtime
            time.sleep(interval)
    except KeyboardInterrupt:
        print("\nstopped.", flush=True)


if __name__ == "__main__":
    main()