f12272d002
- docs/capabilities/INDEX.md - docs/capabilities/comfyui.md - python/functions/browser/comfyui_export_workflow_ui.md - python/functions/browser/comfyui_export_workflow_ui.py - python/functions/browser/comfyui_load_workflow_ui.md - python/functions/browser/comfyui_load_workflow_ui.py - python/functions/browser/comfyui_queue_prompt_ui.md - python/functions/browser/comfyui_queue_prompt_ui.py - python/functions/browser/comfyui_refresh_nodes_ui.md - python/functions/browser/comfyui_refresh_nodes_ui.py - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
138 lines
5.4 KiB
Python
138 lines
5.4 KiB
Python
"""Instala un custom node de ComfyUI: git clone + pip install de sus deps.
|
|
|
|
Clona el repo en `<comfyui_dir>/custom_nodes/<name>` y, si trae
|
|
`requirements.txt`, instala sus dependencias en el venv de ComfyUI
|
|
(`<comfyui_dir>/.venv`). El venv de ComfyUI suele crearse con uv y no trae pip;
|
|
por eso el instalador se autodetecta en orden: `python -m pip`, luego
|
|
`uv pip --python <venv>`, luego el binario `pip` del venv. NO reinicia el
|
|
servidor por defecto (restart=False): el nodo no se carga hasta el siguiente
|
|
arranque de ComfyUI, asi que reiniciar es una decision explicita del caller (un
|
|
restart en caliente corta cualquier generacion en curso).
|
|
|
|
Impura: ejecuta subprocess (git, pip/uv) y escribe en disco. Solo stdlib.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
|
|
_GIT_TIMEOUT = 300.0
|
|
_PIP_TIMEOUT = 600.0
|
|
|
|
|
|
def _pip_install_cmd(base: str, req: str):
|
|
"""Comando para instalar requirements en el venv de ComfyUI, o None.
|
|
|
|
Prueba en orden: `python -m pip` (si el venv tiene pip), `uv pip` apuntando
|
|
al python del venv (venvs uv sin pip), y por ultimo el binario `pip` del
|
|
venv. Devuelve la lista de args lista para subprocess o None si no hay
|
|
instalador disponible.
|
|
"""
|
|
venv_py = os.path.join(base, ".venv", "bin", "python")
|
|
if os.path.isfile(venv_py):
|
|
probe = subprocess.run(
|
|
[venv_py, "-m", "pip", "--version"],
|
|
capture_output=True, text=True,
|
|
)
|
|
if probe.returncode == 0:
|
|
return [venv_py, "-m", "pip", "install", "-r", req]
|
|
if shutil.which("uv"):
|
|
return ["uv", "pip", "install", "-r", req, "--python", venv_py]
|
|
for cand in ("pip", "pip3"):
|
|
pip_bin = os.path.join(base, ".venv", "bin", cand)
|
|
if os.path.isfile(pip_bin):
|
|
return [pip_bin, "install", "-r", req]
|
|
return None
|
|
|
|
|
|
def comfyui_install_custom_node(
|
|
repo_url: str,
|
|
*,
|
|
comfyui_dir: str = "~/ComfyUI",
|
|
pip_install: bool = True,
|
|
restart: bool = False,
|
|
) -> dict:
|
|
"""Clona un custom node y (opcional) instala sus requirements.
|
|
|
|
Args:
|
|
repo_url: URL del repositorio git del custom node
|
|
(ej. "https://github.com/rgthree/rgthree-comfy").
|
|
comfyui_dir: raiz de la instalacion de ComfyUI (se expande ~).
|
|
keyword-only.
|
|
pip_install: si True y el repo trae requirements.txt, instala sus
|
|
dependencias en el venv de ComfyUI. keyword-only.
|
|
restart: NO soportado de forma segura desde aqui (por defecto False).
|
|
El nodo se carga al reiniciar el servidor; hazlo tu cuando no haya
|
|
generaciones en curso. Si se pasa True se anota en el error pero NO
|
|
se reinicia (evita cortar trabajo del servidor). keyword-only.
|
|
|
|
Returns:
|
|
dict {ok, path, pip_done, error}. ok=True si el nodo quedo clonado en
|
|
disco (o ya estaba). pip_done=True si se instalaron las dependencias.
|
|
error describe el fallo de git/pip o la advertencia de restart.
|
|
"""
|
|
base = os.path.expanduser(comfyui_dir)
|
|
custom_dir = os.path.join(base, "custom_nodes")
|
|
name = os.path.basename(repo_url.rstrip("/"))
|
|
if name.endswith(".git"):
|
|
name = name[:-4]
|
|
if not name:
|
|
return {"ok": False, "path": "", "pip_done": False,
|
|
"error": f"repo_url invalido: {repo_url!r}"}
|
|
|
|
dest = os.path.join(custom_dir, name)
|
|
already = os.path.isdir(dest)
|
|
if not already:
|
|
os.makedirs(custom_dir, exist_ok=True)
|
|
try:
|
|
proc = subprocess.run(
|
|
["git", "clone", "--depth", "1", repo_url, dest],
|
|
capture_output=True, text=True, timeout=_GIT_TIMEOUT,
|
|
)
|
|
except (subprocess.TimeoutExpired, OSError) as exc:
|
|
return {"ok": False, "path": "", "pip_done": False,
|
|
"error": f"git clone fallo: {exc}"}
|
|
if proc.returncode != 0:
|
|
return {"ok": False, "path": "", "pip_done": False,
|
|
"error": f"git clone fallo ({proc.returncode}): {proc.stderr.strip()[:300]}"}
|
|
|
|
notes = []
|
|
if already:
|
|
notes.append(f"ya existia en {dest} (no se re-clono)")
|
|
|
|
pip_done = False
|
|
if pip_install:
|
|
req = os.path.join(dest, "requirements.txt")
|
|
if os.path.isfile(req):
|
|
cmd = _pip_install_cmd(base, req)
|
|
if cmd is None:
|
|
notes.append(f"no se encontro instalador (pip/uv) para {base}/.venv (deps omitidas)")
|
|
else:
|
|
try:
|
|
pproc = subprocess.run(
|
|
cmd, capture_output=True, text=True, timeout=_PIP_TIMEOUT,
|
|
)
|
|
pip_done = pproc.returncode == 0
|
|
if not pip_done:
|
|
notes.append(f"pip install fallo: {pproc.stderr.strip()[:300]}")
|
|
except (subprocess.TimeoutExpired, OSError) as exc:
|
|
notes.append(f"pip install fallo: {exc}")
|
|
else:
|
|
notes.append("sin requirements.txt (nada que instalar)")
|
|
|
|
if restart:
|
|
notes.append(
|
|
"restart=True ignorado: reinicia ComfyUI manualmente cuando no haya "
|
|
"generaciones en curso para cargar el nodo"
|
|
)
|
|
|
|
return {"ok": True, "path": dest, "pip_done": pip_done, "error": "; ".join(notes)}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import json
|
|
|
|
out = comfyui_install_custom_node(
|
|
"https://github.com/rgthree/rgthree-comfy", restart=False,
|
|
)
|
|
print(json.dumps(out, ensure_ascii=False, indent=2))
|