scripts/auto_fix_deps.py
scripts/auto_fix_deps.pyBrowse 33 files
1,988 tokens
8,351 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""3auto_fix_deps.py — Run check_deps.py, then attempt to install whatever is missing.4 5For local servers:6 - Missing custom nodes → `comfy node install <package>`7 - Missing models → `comfy model download` (only if a URL is supplied via8 --model-source-file or detected via well-known names)9 10For cloud: prints what would be needed but cannot install (cloud preinstalls11custom nodes and most models server-side; if something genuinely isn't there,12ask Comfy support).13 14This is conservative: it never installs without an explicit URL for models15(downloading the wrong model is hard to undo). Custom nodes from the registry16are auto-installed by name.17 18Usage:19 python3 auto_fix_deps.py workflow_api.json20 python3 auto_fix_deps.py workflow_api.json --models-from-file urls.json21 python3 auto_fix_deps.py workflow_api.json --dry-run22"""23 24from __future__ import annotations25 26import argparse27import json28import shutil29import subprocess30import sys31from pathlib import Path32 33sys.path.insert(0, str(Path(__file__).resolve().parent))34from _common import ( # noqa: E40235 DEFAULT_LOCAL_HOST, ENV_API_KEY, emit_json, log, resolve_api_key,36)37from check_deps import check_deps # noqa: E40238from _common import unwrap_workflow # noqa: E40239 40 41def comfy_cli_available() -> str | None:42 """Return command prefix for comfy-cli, or None."""43 if shutil.which("comfy"):44 return "comfy"45 if shutil.which("uvx"):46 return "uvx --from comfy-cli comfy"47 return None48 49 50def run_cmd(cmd: list[str], *, dry_run: bool = False) -> tuple[int, str]:51 if dry_run:52 return 0, "[dry-run]"53 log(f"$ {' '.join(cmd)}")54 proc = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='replace', check=False)55 out = (proc.stdout or "") + (proc.stderr or "")56 return proc.returncode, out57 58 59def install_node(package: str, *, dry_run: bool = False, comfy_cmd: str = "comfy") -> bool:60 cmd = comfy_cmd.split() + ["--skip-prompt", "node", "install", package]61 code, _ = run_cmd(cmd, dry_run=dry_run)62 return code == 063 64 65def install_model(url: str, folder: str, filename: str | None = None,66 *, dry_run: bool = False, comfy_cmd: str = "comfy",67 hf_token: str | None = None, civitai_token: str | None = None) -> bool:68 cmd = comfy_cmd.split() + [69 "--skip-prompt", "model", "download",70 "--url", url,71 "--relative-path", f"models/{folder}",72 ]73 if filename:74 cmd.extend(["--filename", filename])75 if hf_token:76 cmd.extend(["--set-hf-api-token", hf_token])77 if civitai_token:78 cmd.extend(["--set-civitai-api-token", civitai_token])79 code, _ = run_cmd(cmd, dry_run=dry_run)80 return code == 081 82 83def main(argv: list[str] | None = None) -> int:84 p = argparse.ArgumentParser(description="Run check_deps and install whatever is missing")85 p.add_argument("workflow")86 p.add_argument("--host", default=DEFAULT_LOCAL_HOST)87 p.add_argument("--api-key", help=f"or set ${ENV_API_KEY}")88 p.add_argument("--models-from-file",89 help="JSON file mapping {model_filename: download_url} for models that need install")90 p.add_argument("--hf-token", help="HuggingFace token for downloads")91 p.add_argument("--civitai-token", help="CivitAI token for downloads")92 p.add_argument("--dry-run", action="store_true",93 help="Show what would be installed without doing it")94 p.add_argument("--no-restart", action="store_true",95 help="Don't suggest restarting the server after node install")96 args = p.parse_args(argv)97 98 api_key = resolve_api_key(args.api_key)99 100 wf_path = Path(args.workflow).expanduser()101 if not wf_path.exists():102 emit_json({"error": f"Workflow not found: {args.workflow}"})103 return 1104 try:105 with wf_path.open(encoding="utf-8-sig") as f:106 workflow = unwrap_workflow(json.load(f))107 except (ValueError, json.JSONDecodeError) as e:108 emit_json({"error": str(e)})109 return 1110 111 report = check_deps(workflow, host=args.host, api_key=api_key)112 113 if report["is_ready"]:114 emit_json({"status": "ready", "report": report})115 return 0116 117 if report["is_cloud"]:118 emit_json({119 "status": "cannot_fix_cloud",120 "reason": "Comfy Cloud preinstalls nodes; if something is genuinely missing, contact support.",121 "report": report,122 })123 return 1124 125 comfy_cmd = comfy_cli_available()126 if not comfy_cmd:127 emit_json({128 "status": "cannot_fix",129 "reason": "comfy-cli not on PATH; install with `pip install comfy-cli` or `pipx install comfy-cli`",130 "report": report,131 })132 return 1133 134 actions: list[dict] = []135 failures: list[dict] = []136 137 # ---- Install missing custom nodes ----138 seen_packages: set[str] = set()139 for entry in report["missing_nodes"]:140 cmd = entry.get("fix_command", "")141 if cmd.startswith("comfy node install "):142 package = cmd.split(" ")[-1]143 if package in seen_packages:144 continue145 seen_packages.add(package)146 ok = install_node(package, dry_run=args.dry_run, comfy_cmd=comfy_cmd)147 (actions if ok else failures).append({148 "kind": "node", "package": package, "node_class": entry["class_type"],149 "ok": ok,150 })151 else:152 failures.append({153 "kind": "node", "node_class": entry["class_type"],154 "ok": False, "reason": "No registry mapping known. " + entry.get("fix_hint", ""),155 })156 157 # ---- Install missing models (only when URL provided) ----158 sources: dict[str, str] = {}159 if args.models_from_file:160 try:161 sources = json.loads(Path(args.models_from_file).read_text(encoding="utf-8"))162 except (OSError, json.JSONDecodeError) as e:163 log(f"Could not read --models-from-file: {e}")164 165 for entry in report["missing_models"]:166 filename = entry["value"]167 url = sources.get(filename)168 if not url:169 failures.append({170 "kind": "model", "filename": filename, "folder": entry["folder"],171 "ok": False, "reason": "No URL provided in --models-from-file. "172 "Refusing to guess.",173 })174 continue175 ok = install_model(176 url, entry["folder"], filename,177 dry_run=args.dry_run, comfy_cmd=comfy_cmd,178 hf_token=args.hf_token, civitai_token=args.civitai_token,179 )180 (actions if ok else failures).append({181 "kind": "model", "filename": filename, "folder": entry["folder"],182 "url": url, "ok": ok,183 })184 185 # ---- Embeddings ----186 for entry in report["missing_embeddings"]:187 emb_name = entry["embedding_name"]188 # Try common extensions in user-supplied source map189 url = (sources.get(f"{emb_name}.pt")190 or sources.get(f"{emb_name}.safetensors")191 or sources.get(emb_name))192 if not url:193 failures.append({194 "kind": "embedding", "name": emb_name,195 "ok": False, "reason": "No URL provided in --models-from-file.",196 })197 continue198 target_filename = (199 f"{emb_name}.safetensors" if url.endswith(".safetensors")200 else f"{emb_name}.pt"201 )202 ok = install_model(203 url, "embeddings", target_filename,204 dry_run=args.dry_run, comfy_cmd=comfy_cmd,205 hf_token=args.hf_token, civitai_token=args.civitai_token,206 )207 (actions if ok else failures).append({208 "kind": "embedding", "name": emb_name, "url": url, "ok": ok,209 })210 211 needs_restart = any(a["kind"] == "node" and a.get("ok") for a in actions)212 213 emit_json({214 "status": "fixed" if not failures else "partial",215 "actions_taken": actions,216 "failures": failures,217 "needs_server_restart": needs_restart and not args.no_restart,218 "restart_hint": "comfy stop && comfy launch --background",219 "dry_run": args.dry_run,220 })221 return 0 if not failures else 1222 223 224if __name__ == "__main__":225 sys.exit(main())226