Claude
Skills
Sign in
Back

comfyui-node-scaffold

Included with Lifetime
$97 forever

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.

Backend & APIs

What this skill does


# comfyui-node-scaffold

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.

## When to Use This Skill

| 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 |

## The architecture it scaffolds

**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.

- **Type gate**: `bun run typecheck` → `tsc --noEmit` (never emits).
- **Emit**: `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`.
- **Serve**: `__init__.py` sets `WEB_DIRECTORY = "./web/dist"`; ComfyUI serves
  that tree at `/extensions/<name>/`.
- **Distribute**: `web/dist/` is git-ignored; `[tool.comfy] includes =
  ["web/dist"]` force-ships it, and `publish.yml` runs `bun run build` first.
- **The `/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.

## The vein

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.

## Three variants

| 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.

## How to run

`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:

```sh
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:

```sh
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):

```sh
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.

## What you get

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).

## After scaffolding

The generator prints the exact next steps. In order:

```sh
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
+ default-branch fixup later.

Then implement, and wire up infra:

1. **Implement the modal** in `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`).
2. **Add the repo to `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 

Related in Backend & APIs