UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

152 lines (143 loc) • 7.24 kB
#!/usr/bin/env node // Generate one-click install-windows.bat + install-runpod.sh for each pack from // its pack.yaml + manifest.yaml. The manifest is the single source of truth // (also consumed by apply_manifest); never hand-edit the generated scripts. // // node scripts/gen-pack-installers.mjs # all packs/* // node scripts/gen-pack-installers.mjs packs/anima # one pack import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "node:fs"; import { join, basename, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { parse } from "yaml"; const repoRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); const packsRoot = join(repoRoot, "packs"); function packDirsFromArgs() { const arg = process.argv[2]; if (arg) return [join(repoRoot, arg)]; return readdirSync(packsRoot) .map((n) => join(packsRoot, n)) .filter((p) => statSync(p).isDirectory() && existsSync(join(p, "manifest.yaml"))); } // Folder a `git clone <url>` lands in (basename, minus .git / trailing ref). function repoFolder(url) { const noRef = url.replace(/(?<!^)@[^@/]+$/, "").replace(/[?#].*$/, "").replace(/\/+$/, ""); return basename(noRef).replace(/\.git$/i, ""); } // Target path of a model, relative to models/. function modelRel(m) { if (m.local_path) return m.local_path; const fn = m.filename || basename(new URL(m.url).pathname); return `${m.model_type || "checkpoints"}/${fn}`; } function genBat(pack, manifest) { const nodes = manifest.custom_nodes || []; const models = manifest.models || []; const pip = manifest.pip || []; const workflow = pack.workflow || "workflow.json"; const L = []; L.push("@echo off"); L.push("setlocal EnableExtensions EnableDelayedExpansion"); L.push("chcp 65001 >nul"); L.push(`rem ===== Auto-generated by scripts/gen-pack-installers.mjs — do not edit =====`); L.push(`rem Pack: ${pack.display_name || pack.name}`); L.push("rem Run from your ComfyUI root (the folder containing custom_nodes\\ and models\\)."); L.push('if not exist "custom_nodes" ( echo [ERROR] Run from your ComfyUI root ^(custom_nodes\\ not found^). & pause & exit /b 1 )'); L.push('where git >nul 2>&1 || ( echo [ERROR] git not found in PATH. & pause & exit /b 1 )'); L.push('where curl >nul 2>&1 || ( echo [ERROR] curl not found in PATH. & pause & exit /b 1 )'); L.push("rem Resolve the ComfyUI python: venv first, then portable embed, then PATH python."); L.push('set "PY=%CD%\\.venv\\Scripts\\python.exe"'); L.push('if not exist "%PY%" set "PY=%CD%\\venv\\Scripts\\python.exe"'); L.push('if not exist "%PY%" set "PY=%CD%\\..\\python_embeded\\python.exe"'); L.push('if not exist "%PY%" set "PY=python"'); L.push('echo using python: %PY%'); L.push(""); L.push("echo -------- custom nodes --------"); for (const url of nodes) L.push(`call :clone "${repoFolder(url)}" "${url}"`); if (pip.length) { L.push(""); L.push("echo -------- pip (manifest extras) --------"); for (const p of pip) L.push(`"%PY%" -m pip install "${p}"`); } L.push(""); L.push("echo -------- models --------"); for (const m of models) L.push(`call :grab "models\\${modelRel(m).replace(/\//g, "\\")}" "${m.url}"`); L.push(""); L.push(`echo DONE. Restart ComfyUI, then load ${workflow}.`); L.push("pause"); L.push("exit /b"); L.push(""); L.push(":clone"); L.push('if not exist "custom_nodes\\%~1" ('); L.push(" echo cloning %~1"); L.push(' git clone --depth 1 "%~2" "custom_nodes\\%~1"'); L.push(' if exist "custom_nodes\\%~1\\requirements.txt" ( echo installing %~1 requirements.txt & "%PY%" -m pip install -r "custom_nodes\\%~1\\requirements.txt" )'); L.push(") else ( echo %~1 present - skip )"); L.push("goto :eof"); L.push(""); L.push(":grab"); L.push('if not exist "%~dp1" mkdir "%~dp1"'); L.push('if not exist "%~1" ( echo downloading %~nx1 & curl -L --ssl-no-revoke -o "%~1" "%~2" ) else ( echo %~nx1 present - skip )'); L.push("goto :eof"); return L.join("\r\n") + "\r\n"; } function genSh(pack, manifest) { const nodes = manifest.custom_nodes || []; const models = manifest.models || []; const pip = manifest.pip || []; const workflow = pack.workflow || "workflow.json"; const L = []; L.push("#!/usr/bin/env bash"); L.push("set -euo pipefail"); L.push("# ===== Auto-generated by scripts/gen-pack-installers.mjs — do not edit ====="); L.push(`# Pack: ${pack.display_name || pack.name} (Linux / RunPod)`); L.push("# Run from your ComfyUI root (the folder containing custom_nodes/ and models/)."); L.push('[ -d custom_nodes ] || { echo "[ERROR] run from your ComfyUI root (custom_nodes/ not found)"; exit 1; }'); L.push("# Resolve the ComfyUI python: its venv first, then portable embed, then \\$PYTHON/python3."); L.push('# Installing requirements into the wrong interpreter is why nodes silently fail to load.'); L.push('if [ -n "${PYTHON:-}" ]; then PY="$PYTHON";'); L.push('elif [ -x ".venv/bin/python" ]; then PY=".venv/bin/python";'); L.push('elif [ -x "venv/bin/python" ]; then PY="venv/bin/python";'); L.push('elif [ -x "../python_embeded/python" ]; then PY="../python_embeded/python";'); L.push('else PY="python3"; fi'); L.push('echo "using python: $PY"'); L.push(""); L.push("clone() { # folder url"); L.push(' if [ ! -d "custom_nodes/$1" ]; then'); L.push(' echo " cloning $1"; git clone --depth 1 "$2" "custom_nodes/$1"'); L.push(' if [ -f "custom_nodes/$1/requirements.txt" ]; then echo " installing $1 requirements.txt"; "$PY" -m pip install -r "custom_nodes/$1/requirements.txt"; fi'); L.push(' else echo " $1 present - skip"; fi'); L.push("}"); L.push("grab() { # relpath url"); L.push(' mkdir -p "$(dirname "$1")"'); L.push(' if [ ! -f "$1" ]; then echo " downloading $(basename "$1")"; curl -L -o "$1" "$2"; else echo " $(basename "$1") present - skip"; fi'); L.push("}"); L.push(""); L.push('echo "-------- custom nodes --------"'); for (const url of nodes) L.push(`clone "${repoFolder(url)}" "${url}"`); if (pip.length) { L.push(""); L.push('echo "-------- pip (manifest extras) --------"'); for (const p of pip) L.push(`"$PY" -m pip install "${p}"`); } L.push(""); L.push('echo "-------- models --------"'); for (const m of models) L.push(`grab "models/${modelRel(m)}" "${m.url}"`); L.push(""); L.push(`echo "DONE. Restart ComfyUI, then load ${workflow}."`); return L.join("\n") + "\n"; } const dirs = packDirsFromArgs(); if (!dirs.length) { console.error("No packs found under packs/."); process.exit(1); } for (const dir of dirs) { const manifest = parse(readFileSync(join(dir, "manifest.yaml"), "utf8")) || {}; const packPath = join(dir, "pack.yaml"); const pack = existsSync(packPath) ? parse(readFileSync(packPath, "utf8")) || {} : { name: basename(dir) }; writeFileSync(join(dir, "install-windows.bat"), genBat(pack, manifest)); writeFileSync(join(dir, "install-runpod.sh"), genSh(pack, manifest)); const n = (manifest.custom_nodes || []).length; const m = (manifest.models || []).length; console.log(`${basename(dir)}: wrote install-windows.bat + install-runpod.sh (${n} nodes, ${m} models)`); }