en un clic
comfyui-node-scaffold
Scaffold a new ComfyUI custom-node repo (TypeScript + bun build, CI, release-please, vitest+pytest) consuming @laurigates/comfy-modal-kit. Use when bootstrapping or init-ing a comfyui node pack.
Menu
Scaffold a new ComfyUI custom-node repo (TypeScript + bun build, CI, release-please, vitest+pytest) consuming @laurigates/comfy-modal-kit. Use when bootstrapping or init-ing a comfyui node pack.
Refresh the plan to focus on the task at hand. Use when context grew, completed steps muddy it, or you want to clear context and continue in auto mode.
Sequential-wave dispatch for WO chains where output of one feeds the next, shared locks, or shared files prevent fan-out. Use when planning dependent multi-WO landings.
Publish npm packages built with Bun: package.json config, CLI tool packaging, provenance signing, release automation. Use when setting up `publishConfig`/`files`/`bin`, packaging a CLI, enabling `--provenance`, or wiring release-please.
Dispatch contract for spawning parallel agents covering worktree collisions, scope overflow, and silent exits. Use when fanning out concurrent agents or authoring a lead prompt.
Claude Code health check — scans plugins, settings, hooks, MCP, runtime state, permissions, marketplace with optional fixes. Use when checking project health or troubleshooting setup.
Resume development from current project state. Use when the user asks to continue work, pick up where we left off, find the next task, or resume a TDD cycle after a break.
| created | "2026-06-04T00:00:00.000Z" |
| modified | "2026-06-06T00:00:00.000Z" |
| reviewed | "2026-06-06T00:00:00.000Z" |
| name | comfyui-node-scaffold |
| description | Scaffold a new ComfyUI custom-node repo (TypeScript + bun build, CI, release-please, vitest+pytest) consuming @laurigates/comfy-modal-kit. Use when bootstrapping or init-ing a comfyui node pack. |
| allowed-tools | Bash, Read, Write, Edit, Grep, Glob, TodoWrite |
Bootstrap a new ComfyUI custom-node pack that matches the established
mobile-first TypeScript + bun build architecture of comfyui-gallery-loader,
comfyui-sampler-info, and comfyui-touch-numeric, leaving only the actual
node logic to implement.
| Use this skill when... | Use the alternative when... |
|---|---|
| Starting a new ComfyUI usability pack repo — CI-green TS toolchain, Comfy Registry publish, and the widget-intercept/gesture skeleton before writing pack logic | You want the full pipeline (repo created + seeded + gitops-adopted) → comfy-node |
Spinning up a project:comfyui-nodes backlog idea (touch-numeric, prompt-editor, model-gallery…) | Adding a node to an existing pack — this creates a new repo |
TypeScript source in src/ (entry src/index.ts), built to web/dist/ via
bun build — typed authoring against @comfyorg/comfyui-frontend-types,
browser-ESM output, the /scripts/app.js runtime import left unbundled. This
supersedes the old vanilla-JS (web/js/*.js + copied modal primitives)
template. The generated pack starts with an ADR (docs/blueprint/adrs/0001-…)
recording the decision, mirroring sampler-info's ADR-0010.
bun run typecheck → tsc --noEmit (never emits).bun build ./src/index.ts --target browser --format esm --outdir web/dist --external '/scripts/*'. If the pack ships a static data corpus,
append && cp -R web/data web/dist/data.__init__.py sets WEB_DIRECTORY = "./web/dist"; ComfyUI serves
that tree at /extensions/<name>/.web/dist/ is git-ignored; [tool.comfy] includes = ["web/dist"] force-ships it, and publish.yml runs bun run build first./scripts/app.js type shim: a paths mapping in tsconfig.json
points the rooted import at src/comfyui-shims.d.ts (TypeScript will not
match an ambient declare module against a /… specifier). The emitted
import string stays /scripts/app.js and --external '/scripts/*' keeps it
unbundled.A frontend extension that intercepts widget.onPointerDown (modern Vue
frontend, comfyui-frontend-package >= 1.40) and opens a touch-friendly HTML
modal in place of a clunky native LiteGraph control. Widgets are matched by
name (generic across node packs); the enhancement is additive (graceful
fallback, never breaks serialized workflows); the modal is touch-first (16px
inputs, big tap targets, momentum scroll). The modal primitives come from
@laurigates/comfy-modal-kit (openModalShell / fuzzyRank /
highlightMatches) — imported, not copied — and bun build inlines them.
| Variant | Use when | Shape | Modal kit |
|---|---|---|---|
frontend (default) | No Python needed — pure widget UX (seed/numeric keypad, prompt editor, tooltips, enum recipes). | Empty NODE_CLASS_MAPPINGS; widget-intercept modal in src/index.ts. Like sampler-info / touch-numeric. | imports the kit |
backend | Needs to read disk / serve thumbnails / add a node (model thumbnails, file listings). | Adds <module>.py (node + aiohttp endpoints, ComfyUI-bundled libs only) + a tests/conftest.py that stubs aiohttp/server so pytest is green. Like gallery-loader. | imports the kit |
gesture | The UX is a canvas interaction, not a widget — pinch/drag/long-press on nodes or groups (resize, move, region-box). | Empty NODE_CLASS_MAPPINGS; a canvas pointer layer in src/index.ts with exported pure geometry helpers. Like touch-resize. | no kit |
Decision rule: frontend for a per-widget modal; gesture when the
interaction is on the canvas/node frame itself (no widget to hook); backend
only when the feature genuinely needs the server to read files or serve data. A
non-bundled Python dependency is never allowed — if you reach for one, it
belongs in a separate companion pack.
The gesture variant intercepts the canvas pointer stream (capture-phase
pointerdown/move/up on app.canvas.canvas), hit-tests against selected
nodes/groups in screen space (via ds.scale/ds.offset), and acts only when
the gesture lands on a selected target. It is a no-op when app.canvas is
absent, so the native control always survives. Pure math (distance, hit-test,
scale-clamp) lives in exported, unit-tested helpers; DOM/canvas wiring stays
below them. It has no @laurigates/comfy-modal-kit dependency.
scaffold.py is stdlib-only. Run from the workspace root (repos/laurigates/)
so the new repo lands as a sibling of the reference packs.
Frontend-only pack:
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name comfyui-touch-numeric --display "Touch Numeric" --desc "Touch-friendly keypad + slider modal for seed and INT/FLOAT widgets." --variant frontend --widgets seed,noise_seed,cfg,steps,denoise
Pack with a Python backend:
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name comfyui-model-gallery --display "Model Gallery" --desc "Touch-first card-grid picker for the folder-backed model combos." --variant backend --widgets lora_name,ckpt_name,vae_name,control_net_name
Canvas-gesture pack (resize/move/region — no widget, no modal, no kit):
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name comfyui-touch-resize --display "Touch Resize" --desc "Selection-gated pinch-to-resize for ComfyUI nodes and groups on touch devices." --variant gesture
Flags: --name (repo + served URL segment), --display (Comfy DisplayName),
--desc, --variant {frontend,backend,gesture}, --widgets (CSV → the TS
stub's TARGET_WIDGETS; modal variants only), --publisher (default
laurigates), --dir (parent dir, default cwd).
It refuses to overwrite an existing directory.
A repo where just check (typecheck + build + lint + test) passes from the
first commit: pyproject.toml ([tool.comfy] metadata with includes = ["web/dist"], ruff config, dev deps), .github/workflows/ (ci.yml,
publish.yml, release-please.yml), dependabot.yml, strict tsconfig.json,
biome.json, knip.json, .pre-commit-config.yaml,
release-please-config.json + manifest, vitest.config.js, package.json
(bun scripts; modal variants add @laurigates/comfy-modal-kit), tests/ (a
green pytest + vitest smoke test), src/index.ts + src/comfyui-shims.d.ts,
__init__.py (WEB_DIRECTORY = "./web/dist"), CLAUDE.md, the migration ADR,
README, LICENSE, and RELEASE-CHECKLIST.md. The backend variant
additionally gets <module>.py (node + endpoint + whitelist gate) and
tests/conftest.py (stubs aiohttp/server).
The generator prints the exact next steps. In order:
cd comfyui-<name>
git init -b main
uv sync --group dev
bun install
pre-commit install
just check
Seed main directly (the repo is unprotected until gitops adopts it) — pushing
a feature branch first would leave main missing on origin and force a rename
Then implement, and wire up infra:
src/index.ts — tune TARGET_WIDGETS and replace
the openPicker stub with the real modal body (import { fuzzyRank } from "@laurigates/comfy-modal-kit" for search, openModalShell for the dialog).
For the backend variant, fill in <module>.py's node + endpoints; widen
ALLOWED_EXTENSIONS explicitly for any new file type read off disk. For the
gesture variant, tune the pinch layer
(selectedNodes/nodeScreenRect/scaledSize).gitops/repositories.tf with comfy_registry = true
(and release_please = true). On apply, gitops pushes both the release-please
App credentials and the REGISTRY_ACCESS_TOKEN secret. No per-repo secret
creation is needed.Or skip steps 1–2 entirely: run the /comfy-node orchestrator, which
chains scaffold → gh repo create → seed main → the gitops PR.
src/; build to web/dist/.
tsc --noEmit checks, bun build emits — decoupled. Never hand-edit
web/dist/ (it is generated and git-ignored).@laurigates/comfy-modal-kit (modal variants)
— import them; never copy modal-shell.js/modal-fuzzy.js into the pack.
bun build inlines the imported code. The gesture variant has no kit./extensions/<name>/index.js).dependencies is comfyui-frontend-package
only; the backend variant may use ComfyUI-bundled aiohttp / folder_paths /
server and nothing else.CHANGELOG.md or the version field — release-please
owns them.openModalShell has NO body option. It returns a controller
({ bodyEl, close, setBusy, setStatus, ... }) whose bodyEl starts empty;
fill it after opening (const m = openModalShell({title}); m.bodyEl.appendChild(el)).
Passing body: is silently ignored and the dialog renders empty — a bug that
passes green unit tests because modal builders are DOM-uncovered. The stub
does it right.| Context | Command |
|---|---|
| Scaffold a frontend pack | python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name comfyui-X --display "X" --desc "…" --variant frontend --widgets a,b |
| Scaffold a gesture pack | python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name comfyui-X --display "X" --desc "…" --variant gesture |
| Verify a generated pack | cd comfyui-X && bun install && just check |
screenshots/ Docker + Playwright) and the full
docs/blueprint/ PRD/ADR set (beyond the single migration ADR) are not
generated — they are heavy and pack-specific. Add them later (the
comfyui-screenshot-pipeline skill wires the screenshots).scaffold.py's BIOME_VERSION constant so biome.json,
pre-commit, CI, and the justfile never drift (a guard in
scripts/plugin-compliance-check.sh enforces this).openModalShell; add fuzzyRank /
highlightMatches from @laurigates/comfy-modal-kit when the real modal's
search lands.modal.bodyEl after openX()). The generated
pytest + vitest gate covers pure helpers only; modal DOM is otherwise left to
the manual browser smoke matrix — which is exactly the gap that let an
empty-modal bug ship green. (vitest --environment jsdom.)