#!/usr/bin/env python3 from __future__ import annotations import argparse import json import subprocess from pathlib import Path LIVE_TEMPLATES_DIR = Path.home() / ".nanobot" / "cards" / "templates" EXAMPLE_TEMPLATES_DIR = ( Path(__file__).resolve().parent.parent / "examples" / "cards" / "templates" ) LEGACY_MARKERS = ( "__nanobot", "document.currentScript", "mountLegacyTemplate", "legacy-template-module", ) def iter_template_dirs(root: Path) -> dict[str, Path]: templates: dict[str, Path] = {} if not root.exists(): return templates for child in sorted(root.iterdir()): if not child.is_dir(): continue if not (child / "manifest.json").exists(): continue templates[child.name] = child return templates def validate_template_dir(template_dir: Path, failures: list[str]) -> None: manifest_path = template_dir / "manifest.json" template_path = template_dir / "template.html" runtime_path = template_dir / "card.js" for required in (manifest_path, template_path, runtime_path): if not required.exists(): failures.append(f"{template_dir}: missing {required.name}") return try: manifest = json.loads(manifest_path.read_text(encoding="utf-8")) except Exception as exc: failures.append(f"{manifest_path}: invalid JSON ({exc})") return if not isinstance(manifest, dict): failures.append(f"{manifest_path}: manifest must be an object") template_html = template_path.read_text(encoding="utf-8") if " int: parser = argparse.ArgumentParser() parser.add_argument("--live-root", type=Path, default=LIVE_TEMPLATES_DIR) parser.add_argument("--example-root", type=Path, default=EXAMPLE_TEMPLATES_DIR) args = parser.parse_args() live_root = args.live_root.expanduser() example_root = args.example_root.expanduser() failures: list[str] = [] for template_dir in iter_template_dirs(live_root).values(): validate_template_dir(template_dir, failures) for template_dir in iter_template_dirs(example_root).values(): validate_template_dir(template_dir, failures) sync_check = subprocess.run( [ "python3", str(Path(__file__).resolve().parent / "sync_card_templates.py"), "--check", "--source", str(live_root), "--dest", str(example_root), ], capture_output=True, text=True, ) if sync_check.returncode != 0: detail = (sync_check.stdout + sync_check.stderr).strip() if detail: print(detail) failures.append("template mirror drift detected") if failures: for failure in failures: print(failure) return 1 print("card runtime ok") return 0 if __name__ == "__main__": raise SystemExit(main())