f8793f96ac
El generador de runner de fn run (cmd/fn/pyrunner.go::generatePyRunner)
parsea la signature de la funcion desde el frontmatter del .md y emite
`<param> = _args[i]` por cada parametro posicional. Cuando la firma es
keyword-only (`def f(*, ...)`), el `*` se trata como un nombre de parametro
y genera la linea invalida `* = _args[0]`, que rompe el runner con
`SyntaxError: invalid syntax` antes de ejecutar la funcion.
Se quita el separador keyword-only (`*,`) de la firma — tanto en la `def`
del .py como en el campo `signature:` del .md (la fuente que lee el
indexer y el runner) — convirtiendo los parametros keyword-only en
parametros normales con su mismo default. No cambia nombres, defaults ni
comportamiento: las llamadas con keyword siguen siendo validas.
Afecta a 5 funciones detectadas en el report 0208 §3.3, todas con
SyntaxError reproducido via `fn run <id>`:
- comfyui_fetch_civitai_image_meta
- comfyui_load_skill
- comfyui_save_skill
- comfyui_import_workflow_png
- comfyui_list_skills
Se completa ademas el fix de comfyui_interrupt_queue: el commit 643ebfb8
quito el `*,` del .py pero dejo el `*,` en el campo `signature:` del .md,
que es justo lo que lee el runner — por eso `fn run comfyui_interrupt_queue`
seguia fallando. Aqui se corrige el .md.
Verificado: tras el cambio las 6 despachan sin SyntaxError (las 4 con
primer arg requerido devuelven el `missing required arg` esperado del
runner; list_skills e interrupt_queue ejecutan `ok:true`). Tests
existentes verdes (comfyui_fetch_civitai_image_meta_test.py +
tests/test_comfyui_interrupt_queue.py: 8 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
"""comfyui_load_skill — lee una receta de *skill* ComfyUI de la libreria de disco.
|
|
|
|
Carga `recipe.json` (version actual) o un snapshot concreto `versions/vN.json` de una skill
|
|
guardada por `comfyui_save_skill`. Hermana inversa de save: el round-trip
|
|
save(recipe) -> load(slug) devuelve un dict identico al guardado.
|
|
|
|
`library_dir` por defecto `~/ComfyUI/skills_library`. Un slug inexistente devuelve
|
|
``{ok: False}`` sin lanzar excepcion.
|
|
|
|
Impura: lee archivos de disco.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
DEFAULT_LIBRARY = "~/ComfyUI/skills_library"
|
|
|
|
|
|
def _lib_dir(library_dir):
|
|
return os.path.expanduser(library_dir or DEFAULT_LIBRARY)
|
|
|
|
|
|
def _version_filename(version):
|
|
"""Normaliza la version a un nombre de archivo `vN.json`.
|
|
|
|
Acepta int (1), str de digitos ("1") o ya prefijada ("v1"). Devuelve None si no
|
|
se puede interpretar.
|
|
"""
|
|
if isinstance(version, int):
|
|
return f"v{version}.json"
|
|
s = str(version).strip()
|
|
if s.startswith("v"):
|
|
s = s[1:]
|
|
if s.isdigit():
|
|
return f"v{s}.json"
|
|
return None
|
|
|
|
|
|
def comfyui_load_skill(slug: str, version=None, library_dir: str = None) -> dict:
|
|
"""Lee la receta de una skill (version actual o un snapshot concreto).
|
|
|
|
Args:
|
|
slug: slug de la skill (nombre de su carpeta en la libreria).
|
|
version: si None, lee `recipe.json` (version actual). Si se pasa (int, "1" o
|
|
"v1"), lee el snapshot `versions/vN.json`.
|
|
library_dir: raiz de la libreria. Default `~/ComfyUI/skills_library`.
|
|
|
|
Returns:
|
|
dict ``{ok, recipe, slug, path, version, error}``. En exito ``ok=True`` y `recipe`
|
|
es el dict guardado. Si el slug, la version o el archivo no existen, ``ok=False``,
|
|
``recipe=None`` y ``error`` describe la causa; nunca lanza.
|
|
"""
|
|
if not slug or not isinstance(slug, str):
|
|
return {"ok": False, "recipe": None, "slug": slug, "path": "", "version": version,
|
|
"error": "slug requerido (string no vacio)"}
|
|
|
|
lib = _lib_dir(library_dir)
|
|
skill_dir = os.path.join(lib, slug)
|
|
if not os.path.isdir(skill_dir):
|
|
return {"ok": False, "recipe": None, "slug": slug, "path": skill_dir, "version": version,
|
|
"error": f"skill no encontrada: {slug!r}"}
|
|
|
|
if version is None:
|
|
target = os.path.join(skill_dir, "recipe.json")
|
|
else:
|
|
fname = _version_filename(version)
|
|
if fname is None:
|
|
return {"ok": False, "recipe": None, "slug": slug, "path": skill_dir,
|
|
"version": version, "error": f"version invalida: {version!r}"}
|
|
target = os.path.join(skill_dir, "versions", fname)
|
|
|
|
if not os.path.isfile(target):
|
|
return {"ok": False, "recipe": None, "slug": slug, "path": target, "version": version,
|
|
"error": f"archivo de receta no encontrado: {target}"}
|
|
|
|
try:
|
|
with open(target, encoding="utf-8") as fh:
|
|
recipe = json.load(fh)
|
|
except (OSError, json.JSONDecodeError) as exc:
|
|
return {"ok": False, "recipe": None, "slug": slug, "path": target, "version": version,
|
|
"error": f"no se pudo leer la receta: {exc}"}
|
|
|
|
return {"ok": True, "recipe": recipe, "slug": slug, "path": target, "version": version,
|
|
"error": ""}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
res = comfyui_load_skill("demo_skill", library_dir="/tmp/skills_demo")
|
|
print(json.dumps(res, indent=2, ensure_ascii=False))
|