comfyui-plugin/skills/comfyui-node-scaffold/SKILL.md
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.
npx skillsauth add laurigates/claude-plugins comfyui-node-scaffoldInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.)testing
Verify accumulated bug claims at upstream HEAD and dedup against trackers before filing issues. Use when filing upstream reports from backlogs, audit docs, or git-history findings.
documentation
Gate outward-bound text (upstream issues, docs, PR bodies) through isolated haiku fresh-reader critique before publishing. Use when an artifact must survive a reader with zero project context.
tools
Suggest improvements to SKILL.md content, descriptions, or tool config from eval results. Use when raising pass rates, fixing triggering, or iterating on a skill after evaluation.
tools
deadbranch CLI for stale-branch cleanup — dry-run preview, TUI or non-interactive delete, protects main/develop/WIP. Use when asked to clean up branches, prune branches, or remove stale branches.