# Comfy Workflows — agent digestGenerated from canonical docs for @stepupgaming/comfy-workflows@0.2.16.Not a dump of the entire site. Prefer SKILL.md + a single reference when possible. --- # Source: docs/concepts/mental-model.md # Mental model ``` Human-maintained TypeScript ↓ Graph semantics ↓ Graph IR ↓ compiler ↓ Comfy API JSON ``` ## Canonical vs authored **Graph IR is the canonical semantic representation of a workflow.** **TypeScript can be the canonical author-maintained source that generates that IR.** Those sentences do not fight. "IR is canonical" does **not** mean developers should edit IR JSON. They should not. Comfy API JSON is a build artifact. Never hand-edit it. ## Two package situations ### 1. Imported package ``` workflow.json ↓ import workflow.ir.json ``` `workflow.ts` may be emitted as an editable convenience. You can adopt it, or keep treating IR as the thing `cwf expose` mutates. ### 2. First-party / code-authored package ``` ir.build.ts ↓ build workflow.ir.json comfy.workflow.json ``` `ir.build.ts` is what the developer edits. Generated files say do not hand-edit. ## Layers you can drop through ``` Workflow packages → Recipes → Typed node SDK → Graph IR → Compiler → Runtime → Comfy ``` Work at the highest level that still says what you mean. [What do I edit?](/start/what-do-i-edit) · [Graph IR](/concepts/graph-ir) --- # Source: docs/start/what-do-i-edit.md # What do I edit? This table is the whole point. Bookmark it. | File | Edit? | Meaning | | ---- | ----- | ------- | | `ir.build.ts` | **Yes** | First-party / code-authored source. Change topology here. | | `workflow.ts` (imported package) | Yes, if you adopt it | Convenience emission from `cwf import` / `cwf init`. Optional. | | Generated node SDK (`*/nodes`, `src/nodes/gen`) | **No** | From `/object_info`. Regenerated by `cwf codegen`. | | `workflow.ir.json` | **No** | Generated semantic artifact. | | `comfy.workflow.json` | Usually generated | Package manifest. Edit only when you intend to change published metadata. | | `comfy.lock.json` | Update on purpose | Environment lock. Recapture with `cwf lock`, do not tweak hashes. | | Comfy API JSON / `prompt.template.json` | **No** | Compiled execution artifact. | | `object_info.json` | **No** | Snapshot of a live Comfy. Recapture, don't patch. | | `NODES.md` / `catalog.json` | **No** | Codegen companions. | means a human or agent maintains it. means a tool wrote it. ## Two normal package shapes ### Imported package ``` workflow.json ← you already had this ↓ cwf init / import workflow.ir.json ← generated workflow.ts ← optional, editable convenience comfy.workflow.json ← generated, then you expose params ``` ### First-party / code-authored package ``` ir.build.ts ← you edit this ↓ build workflow.ir.json ← generated comfy.workflow.json ← generated / derived ↓ compile API JSON ← generated ``` ## Rule of thumb If changing a value changes **which nodes exist**, that is topology. Put it in TypeScript. If it only changes a widget (prompt, seed, steps, a filename on the server), that is a [runtime parameter](/concepts/templates). ## Linked from This page is linked from the homepage, the code-first quickstart, the package guide, and product integration on purpose. The mix-up (editing IR because "IR is canonical") is the most expensive conceptual bug in this project. --- # Source: docs/code/quickstart.md # Code-first quickstart This tutorial starts from **no** workflow JSON. The graph lives in TypeScript. You should finish it thinking: to change the workflow, I change TypeScript. You need Node.js ≥ 22 and a ComfyUI instance at `http://127.0.0.1:8188`. If Comfy is down, snapshot a fixture `object_info.json` instead and skip `run`. ## 1. Install ```sh pnpm add @stepupgaming/comfy-workflows ``` ## 2. Snapshot the live node universe ```sh cwf snapshot --url http://127.0.0.1:8188 -o object_info.json cwf lock --url http://127.0.0.1:8188 cwf codegen --from object_info.json -o src/nodes/gen ``` `snapshot` writes `/object_info`. `lock` records Comfy version + that hash in `comfy.lock.json`. `codegen` writes typed wrappers, `registry.ts`, `defs.json`, `catalog.json`, and `NODES.md`, all stamped with `objectInfoHash`. Do not hand-edit the generated directory. When you install or update node packs, recapture and regenerate. For this tutorial the **bundled** `@stepupgaming/comfy-workflows/nodes` registry is enough (core SD1.x classes). After codegen, switch the import to `./src/nodes/gen/registry.ts` so custom nodes type-check too. ## 3. Author the graph Save this as `workflow.ts`. The checked-in copy is [`docs/examples-src/code-first.ts`](https://github.com/stepupgaming/comfy-workflows/blob/main/docs/examples-src/code-first.ts). ```ts /** * Code-first quickstart graph. Edit this file; do not hand-edit compiled JSON. */ import { compile, workflow, type Graph } from "@stepupgaming/comfy-workflows"; import { conditioning, image, latent, loaders, sampling } from "@stepupgaming/comfy-workflows/nodes"; export function build(): Graph { const g = workflow("docs-t2i"); const ckpt = g.add(loaders.CheckpointLoaderSimple, { ckpt_name: "v1-5-pruned-emaonly.safetensors", }); const positive = g.add(conditioning.CLIPTextEncode, { text: "a red cube on a table, still camera", clip: ckpt.CLIP, }); const negative = g.add(conditioning.CLIPTextEncode, { text: "blurry, text, watermark", clip: ckpt.CLIP, }); const empty = g.add(latent.EmptyLatentImage, { width: 512, height: 512, batch_size: 1, }); const sampled = g.add(sampling.KSampler, { model: ckpt.MODEL, positive: positive.CONDITIONING, negative: negative.CONDITIONING, latent_image: empty.LATENT, seed: 42n, steps: 8, cfg: 7, sampler_name: "euler", scheduler: "normal", denoise: 1, }); const decoded = g.add(latent.VAEDecode, { samples: sampled.LATENT, vae: ckpt.VAE, }); g.add(image.SaveImage, { images: decoded.IMAGE, filename_prefix: "docs-t2i", }); g.output(decoded.IMAGE, { name: "image" }); return g.toGraph(); } export function compiledJson(): string { const result = compile(build()); if (!result.ok) { throw new Error(result.errors.map((e) => `${e.code}: ${e.message}`).join("\n")); } return result.json; } ``` What you are looking at: - `g.add(spec, params)` is the typed builder. - `.MODEL`, `.CLIP`, `.LATENT`, `.IMAGE` are handles over `{nodeId, outputIndex}`. - `seed: 42n` is a bigint. It will not round through JS number. - `g.output(...)` names the graph output the runtime should fetch. - `toGraph()` is Graph IR in memory. ## 4. Compile, validate, run ```sh cwf compile workflow.ts -o dist/prompt.json cwf validate workflow.ts --url http://127.0.0.1:8188 cwf run workflow.ts --url http://127.0.0.1:8188 --out out/ ``` - `compile` writes deterministic API JSON. That file is an artifact. Do not edit it. - `validate` never queues work. - `run` submits, streams progress, downloads artifacts into `out//`, writes `run.json`. ## What you edit next time Change `workflow.ts` (or `ir.build.ts` in a package). Rebuild. Do not patch `workflow.ir.json` or the prompt JSON. [What do I edit?](/start/what-do-i-edit) · [Parameters](/code/parameters) · [Generated node SDKs](/code/codegen) --- # Source: docs/code/codegen.md # Generate typed nodes ``` /object_info → snapshot → cwf codegen → typed specs ``` ```sh cwf codegen --from object_info.json -o src/nodes/gen cwf codegen --url http://127.0.0.1:8188 -o comfy-nodes --exact-combos ``` CLI codegen imports helpers from `"@stepupgaming/comfy-workflows"` so the output directory can live anywhere in your app. ## What gets written | File | Role | | ---- | ---- | | `.ts` | Specs grouped by `/object_info` category | | `registry.ts` | `specs` map + named re-exports | | `defs.json` | Parsed defs used by compile | | `catalog.json` / `NODES.md` | Searchable catalog | | `identifiers.json` | classType → export name | Header comment: ``` // GENERATED by comfy-workflows codegen — do not edit. // objectInfoHash: … // Regenerate with: cwf codegen ``` ## Why environment-specific A VHS node exists only if that pack is installed. A combo of checkpoint filenames is **that machine's disk**. Default codegen widens file-backed combos to `string[]` so the types stay portable. `--exact-combos` emits literal unions. Those fail on another server's options. Use exact combos only when you regenerate per instance. ## Built-in vs custom The published `@stepupgaming/comfy-workflows/nodes` registry is the **bundled core snapshot** (Checkpoint, KSampler, VAE, LoadImage, …). It is not your custom nodes. After codegen: ```ts import { VHS_LoadVideo, VHS_VideoCombine } from "./comfy-nodes/registry"; ``` Those names come from the snapshot. Do not invent them. If the class is absent from `/object_info`, codegen will not emit it. Use [rawNode](/code/escape-hatches) only then. ## Regeneration and drift Install a pack, restart Comfy, recapture `/object_info`, rerun codegen, commit. If generated types are stale, `g.add` will not know the new class, or compile will fail `E_UNKNOWN_NODE_TYPE` against live defs. CI pattern: [Drift gates](/product/ci). ## What codegen cannot express Dynamic / autogrow inputs, nodes whose `/object_info` is a lie, and classes that are missing entirely. Those are `rawNode` or `unsafe`. Prefer fixing the snapshot over spreading escape hatches. [Node catalog](/reference/node-catalog) is the bundled core list, not a universal catalog. --- # Source: docs/code/parameters.md # Parameters and templates A **literal** is compiled into the graph. A **ParamRef** is a hole filled later. ```ts seed: 42n // literal seed: paramRef("seed") // or g.param("seed", { type: "int" }) ``` Topology stays put. Values arrive at instantiate / `cwf run --param`. ```ts import { instantiateTemplate, workflow, type Graph, } from "@stepupgaming/comfy-workflows"; import { conditioning, image, latent, loaders, sampling } from "@stepupgaming/comfy-workflows/nodes"; export function buildTemplate(): Graph { const g = workflow("docs-t2i-template"); const checkpoint = g.param("checkpoint", { type: "combo", description: "Checkpoint filename on the Comfy server.", }); const prompt = g.param("prompt", { type: "string", description: "Positive prompt.", }); const seed = g.param("seed", { type: "int", default: 42n, description: "Sampling seed. Use bigint for the full 64-bit range.", }); const steps = g.param("steps", { type: "int", default: 20 }); const ckpt = g.add(loaders.CheckpointLoaderSimple, { ckpt_name: checkpoint }); const positive = g.add(conditioning.CLIPTextEncode, { text: prompt, clip: ckpt.CLIP }); const negative = g.add(conditioning.CLIPTextEncode, { text: "", clip: ckpt.CLIP }); const empty = g.add(latent.EmptyLatentImage, { width: 512, height: 512, batch_size: 1 }); const sampled = g.add(sampling.KSampler, { model: ckpt.MODEL, positive: positive.CONDITIONING, negative: negative.CONDITIONING, latent_image: empty.LATENT, seed, steps, cfg: 7, sampler_name: "euler", scheduler: "normal", denoise: 1, }); const decoded = g.add(latent.VAEDecode, { samples: sampled.LATENT, vae: ckpt.VAE }); g.add(image.SaveImage, { images: decoded.IMAGE, filename_prefix: "docs-template" }); g.output(decoded.IMAGE, { name: "image" }); return g.toGraph(); } export function bind(params: { checkpoint: string; prompt: string; seed?: bigint; steps?: number }): Graph { return instantiateTemplate(buildTemplate(), { params: { checkpoint: params.checkpoint, prompt: params.prompt, ...(params.seed !== undefined ? { seed: params.seed } : {}), ...(params.steps !== undefined ? { steps: params.steps } : {}), }, }); } ``` ## `g.param` ```ts const seed = g.param("seed", { type: "int", // int | float | string | boolean | combo default: 42n, description: "Sampling seed", // options: [...] // combo }); ``` Duplicate names throw. The returned `ParamRef` is `{ $param: "seed" }` and can sit in any widget slot. `paramRef("seed")` builds the same placeholder without declaring metadata. Recipes use it; declare the param on the builder (or let the recipe declare it) so instantiate knows the type/default. ## Binding ```ts import { instantiateTemplate } from "@stepupgaming/comfy-workflows"; const graph = instantiateTemplate(tpl, { params: { prompt: "a lighthouse", seed: 42n }, // inputs: { image: someSlotRef }, }); ``` Unbound param with no default → `E_UNBOUND_PARAM`. Unbound port → `E_UNBOUND_PORT`. Instantiation renumbers nodes `n1..nN` in topological order so the same template + bindings compile identically. CLI: ```sh cwf run workflow.ts --url http://127.0.0.1:8188 --param prompt=hello --param seed=42 ``` `--param` is repeatable (`-p`). ## Integer / bigint Use `bigint` (`42n`) for seeds. `number` is accepted but cannot hold values above 2^53 exactly. [Lossless integers](/concepts/lossless-integers). ## Files and paths Local files that must be uploaded are `AssetRef`, not string paths in a published default. Machine-local paths fail `cwf pack` (`E_PACK_LOCAL_PATH`). Checkpoint **names** on the server are portable parameters. Absolute `C:\Users\...` paths are not. [Assets](/guide/assets). ## Discovery Package manifests list parameters. `cwf inspect` prints required vs optional. `cwf suggest` proposes expose candidates without mutating. ## Topology vs parameter If changing a value changes **which nodes exist**, it is topology. Put it in TypeScript. If it only changes a node input, it is a runtime parameter. That rule keeps a Python/Rust binder stupid in a good way: replace `{$param}`, do not grow graphs. [No second compiler](/concepts/no-second-compiler). --- # Source: docs/code/connections.md # Connections and outputs Slot identity is: ```ts { node: NodeId, out: number } ``` Output **name** is convenience. Output **index** is identity. Unnamed, duplicated, and renamed outputs all round-trip because the compiler never keys on the name. ## Handles Generated specs expose named handles: ```ts ckpt.MODEL // first MODEL-typed output ckpt.CLIP ckpt.VAE ``` Positional forms always work: ```ts ks.slots[0] ks.out(0) ``` Custom nodes with duplicate output names, empty names, or "weird" `/object_info` still compile if you use index. ## Type mismatch Wiring a `MODEL` into a `CLIP` input fails in the type checker when both specs are generated. At compile time the same mistake is `E_TYPE_MISMATCH` with `expected` / `got` / `nodeId` / `input`. ```json { "error": { "code": "E_TYPE_MISMATCH", "nodeId": "n5", "input": "clip", "expected": "CLIP", "got": "MODEL" } } ``` ## `unsafe` ```ts import { unsafe } from "@stepupgaming/comfy-workflows"; g.add(someSpec, { clip: unsafe(ckpt.MODEL) }); ``` That is a deliberate type-system bypass. It does not disable validation of everything else. [Escape hatches](/code/escape-hatches). ## Graph outputs vs node outputs `g.output(handle, { name })` declares what the **runtime** should return as artifacts. Node output handles are how you wire the graph. Do not confuse the two. ## List-valued inputs Some nodes take arrays of connections. Pass an array of handles. IR stores `SlotRef[]`. --- # Source: docs/code/composition.md # Composition Work at the highest level that still says what you mean. ``` package → recipe / helper → typed nodes → raw IR ``` Drop down when the level above cannot express the graph. ## Author-defined helpers A function that returns or mutates a `Graph` is enough most of the time: ```ts function withReference(g: GraphBuilder, image: NodeOutput<"IMAGE">) { // add LoadImage / reference nodes, return handles } ``` Keep helpers topology-shaped. Do not hide runtime parameters inside `if (opts.quality === "high") addNode(...)` unless that really is a different graph. Prefer a second package or a second `ir.build.ts`. ## When to make a recipe Recipes in this repo (`textToImage`, `hiresFix`, `withLora`, …) are graphs-in / graphs-out and **preserve ParamRef**. Composition stays lazy until `instantiateTemplate`. If your helper needs to be that composable, follow the same rule: do not eagerly bind placeholders. ## When to make another package A second graph with its own parameters, node classes, and release cycle. Face-refine vs base generation is two packages, not an `if` in one builder. ## Observability ```ts import { explainGraph } from "@stepupgaming/comfy-workflows/recipes"; console.log(explainGraph(graph)); ``` ```sh cwf explain workflow.ts ``` That is how you answer "what did `hiresFix` actually create?" [Recipes](/code/recipes) · [Example](/examples/composition) --- # Source: docs/code/recipes.md # Recipes Recipes expand into many nodes. They return template graphs. Placeholders survive `withLora` / `hiresFix`, so you bind once at the end. ```ts import { hiresFix, instantiateTemplate, paramRef, textToImage, withLora, type Graph, } from "@stepupgaming/comfy-workflows"; import { explainGraph } from "@stepupgaming/comfy-workflows/recipes"; export function composed(): Graph { const tpl = textToImage({ checkpoint: paramRef("checkpoint"), positivePrompt: paramRef("prompt"), seed: paramRef("seed"), width: 512, height: 512, }); const withStyle = withLora(tpl, [ { lora_name: "detail_tweaker.safetensors", strength_model: 0.8, strength_clip: 0.8 }, ]); return hiresFix(withStyle, { scaleBy: 1.5, denoise: 0.45 }); } export function expansion(): string { return explainGraph(composed()); } export function bound(): Graph { return instantiateTemplate(composed(), { params: { checkpoint: "v1-5-pruned-emaonly.safetensors", prompt: "a lighthouse at dusk", seed: 42n, }, }); } ``` ## Built-in recipes | Recipe | What it builds | | ------ | -------------- | | `textToImage` | Checkpoint → CLIP encodes → empty latent → KSampler → VAE decode → Save | | `img2img` | LoadImage → VAE encode → KSampler(denoise) → decode → save | | `inpaint` | Image + mask → VAE encode for inpaint → KSampler | | `outpaint` | Pad for outpainting → encode for inpaint → KSampler | | `withLora` | Insert `LoraLoader` between model/clip sources and consumers | | `withControlNet` | ControlNet loader + apply on the first sampler | | `hiresFix` | Latent upscale + second `KSamplerAdvanced` before VAE decode | | `upscale` | Pixel upscale model pass | | `explainGraph` (from `/recipes`) | Text expansion of nodes / params / wiring | Signatures: [Recipe reference](/reference/recipes). ## `LoraSpec` ```ts { lora_name: string, strength_model?: number, strength_clip?: number } ``` There is no `name` / `strength` shorthand on the public type. ## Seeds `textToImage` requires `seed`. Reproducibility is the default, not an opt-in. ## When not to use a recipe Video graphs, speech graphs, anything whose nodes are not in the bundled core snapshot. Author those with generated specs. Recipes here are SD-image shaped on purpose. --- # Source: docs/code/escape-hatches.md # Escape hatches Two doors. Not the front entrance. ## `rawNode` For a class your defs snapshot cannot describe (missing from `/object_info`, or `/object_info` is junk). ```ts const n = g.rawNode( "SomeUnregisteredNode", { model: ckpt.MODEL, strength: 0.5 }, { outputs: [{ name: "MODEL", type: "MODEL" }], id: "9" }, ); n.out(0); ``` Params are validated structurally only. Imports of unknown custom nodes produce exactly this, with original JSON on `node.source`. **`rawNode` is not how you consume custom nodes.** Snapshot + codegen is. If the class appears in live `/object_info`, regenerate wrappers and `g.add(spec, …)`. `rawNode` is also not remote code execution. The Python still lives in Comfy. You are only naming a class the type system does not know. ## `unsafe` ```ts import { unsafe } from "@stepupgaming/comfy-workflows"; g.add(spec, { clip: unsafe(ckpt.MODEL) }); ``` Widens any output to any input. Use it when a custom node lies about socket types. It does **not** turn off combo checks, range checks, cycle detection, or unbound-param errors. ## What they do not mean | Phrase | Reality | | ------ | ------- | | "custom node" | Generate wrappers from `/object_info` | | "skip validation" | There is no such flag | | "the compiler will guess" | It will not. `E_UNRESOLVED_BYPASS` instead | ```ts import { unsafe, workflow } from "@stepupgaming/comfy-workflows"; import { loaders } from "@stepupgaming/comfy-workflows/nodes"; /** * rawNode is for classes your defs snapshot cannot describe. * It is not the normal custom-node path. Prefer codegen from /object_info. */ export function unknownClassGraph() { const g = workflow("escape"); const ckpt = g.add(loaders.CheckpointLoaderSimple, { ckpt_name: "v1-5-pruned-emaonly.safetensors", }); const mystery = g.rawNode( "SomeUnregisteredNode", { model: ckpt.MODEL, strength: 0.5 }, { outputs: [{ name: "MODEL", type: "MODEL" }] }, ); void mystery.out(0); void unsafe(ckpt.MODEL); return g.toGraph(); } ``` --- # Source: docs/guide/custom-nodes.md # Custom-node dependencies A published workflow should declare which Comfy custom-node packs it needs. Comfy Workflows resolves those declarations against the Comfy Registry. `cwf setup` prepares a **local** Comfy installation after you approve the exact plan. ```sh pnpm add @alice/some-workflow cwf inspect @alice/some-workflow --url http://127.0.0.1:8188 cwf setup @alice/some-workflow --comfy C:\ComfyUI # restart Comfy if setup says so cwf inspect @alice/some-workflow --url http://127.0.0.1:8188 cwf run @alice/some-workflow --url http://127.0.0.1:8188 ``` You should not have to hunt GitHub for missing custom nodes. Package format is host-agnostic. The same `inspect` / `resolve-nodes` / `setup` path works whether the tarball came from npm, GitHub Packages, a GitHub Release, or a local file. [Distribution](/product/distribution). A project-local `@stepupgaming:registry=https://npm.pkg.github.com` mapping is valid for authenticated installs because core and first-party workflows are all on GitHub Packages. It remaps the **entire** scope — keep it out of `~/.npmrc` unless you want that on every project. Anonymous installs use the GitHub Release `.tgz`. [Distribution](/product/distribution). ## Security contract Custom nodes are executable Python. - **`cwf run` never installs them.** Missing classes fail at compile/validate time. - **`cwf inspect` never installs them.** It only reports. - **`cwf init` never installs them.** With `--url` it may discover **verified** registry metadata. - **Installation happens only through `cwf setup`.** - Default confirmation is **No**. `--yes` means “approve this **verified** plan”, not “allow arbitrary untrusted sources”. - Registered Comfy Registry packs are eligible for setup **after version-level verification**. Arbitrary Git URLs and pip specs are **not** auto-installed. - Workflow-package JavaScript is never executed to inspect dependency metadata. - Manifests are declarative: no `install`, `script`, `command`, `shell`, `pip`, or `git` fields. - Registry names, descriptions, and repository prose never become shell commands or argv. This SDK **consumes** `/object_info`. It does not author Python node implementations. [Consume vs author](/guide/consume-vs-author-nodes). ## nodeClasses vs nodePacks - **`requires.nodeClasses`** — the non-negotiable set of Comfy `class_type` names the graph uses. `cwf pack` requires this to match the IR. - **`requires.nodePacks`** — installable packs that provide those classes. Identity is the Comfy Registry package id (for example `comfyui-videohelpersuite`). ### Manifest spec versions | specVersion | `nodePacks` wire format | | ----------- | ----------------------- | | **1** | `string[]` of registry ids (legacy) | | **2** | `NodePackRequirement[]` objects | Existing published v1 packages remain valid. Rich dependency metadata is **specVersion 2**. The parser never silently writes objects under specVersion 1. Source defaults: | Wire form | Normalized `source` | | --------- | ------------------- | | v1 bare string id | `manual` | | v2 object with `source` omitted | `registry` (a claim, not install proof) | | v2 `source: "manual"` | explicit manual | | v2 `source: "registry"` | explicit registry claim | Automatic installation still requires positive per-version Registry verification. An omitted v2 source is not a skip-install signal. A v2 pack entry: ```json { "id": "comfyui-videohelpersuite", "name": "ComfyUI-VideoHelperSuite", "version": "^1.7.9", "repository": "https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite", "provides": ["VHS_LoadVideo", "VHS_VideoCombine"], "source": "registry" } ``` `repository` is informational. It is never an instruction to clone a URL. ## How resolution works ```sh cwf resolve-nodes . --url http://127.0.0.1:8188 cwf resolve-nodes . --url http://127.0.0.1:8188 --write ``` Without `--write`, nothing is mutated. With `--write`, **verified** packs are merged into `comfy.workflow.json` as specVersion 2. `GET https://api.comfy.org/nodes/search?comfy_node_search={className}` is the candidate universe (paginated). `GET /comfy-nodes/{className}/node` is an additional ranked hint only, never the complete set. Official Comfy source treats the ranked endpoint as a preempted “best” pack. It can attribute a core class to a third-party pack, hide other claimants, or 404 for a real custom class. Verification pipeline: 1. Required class 2. Live `/object_info` (availability — a present class needs no install) 3. Known-core evidence (bundled defs snapshot plus known newer stock classes such as `CLIPLoader` / `UNETLoader`) 4. Author-declared `provides` (explicit mapping — a claim, not proof) 5. Registry search candidates (all pages) plus ranked hint 6. Exact pack **version** (`GET /nodes/{id}/versions`, then `/install?version=`) 7. Pack-version definitions (`GET /nodes/{id}/versions/{version}/comfy-nodes`, paginated) 8. Verified provider set A publisher `source: "registry"` declaration is a **claim**. Only `provided === true` for the selected version authorizes automatic installation. `provided === false` or `provided === undefined` is UNKNOWN / unverifiable and never reaches the installer. Outcomes per class: | Outcome | Meaning | | ------- | ------- | | `CORE` | Known stock class. Never installed as a custom pack. | | `RESOLVED_CUSTOM` | Exactly one **verified** pack/version supplies the class. | | `AMBIGUOUS` | More than one **verified** pack supplies the class. Author must pick. `E_NODE_PACK_AMBIGUOUS`. | | `UNKNOWN` | No verified provider. Not “definitely core” and not “definitely custom”. `E_NODE_PACK_UNKNOWN`. | A ranked hint that does not list the class in that version’s definitions is dropped. `--write` never records an unverified guess. Manual authoring when the registry cannot help: ```sh cwf node-pack add comfyui-videohelpersuite --provides VHS_LoadVideo,VHS_VideoCombine cwf node-pack map SomeInternalNode my-internal-pack ``` Manual entries still pass manifest validation. They are `source: "manual"` and are **not** auto-installed by `cwf setup`. Mapping is author intent, not a shell escape. `cwf pack` warns (`W_PACK_UNRESOLVED_NODE_PACK`) when a class has no owning pack. That stays a warning: absence from the bundled core snapshot is not proof the class is custom. `cwf pack --publish` still fails contradictory/invalid pack metadata. ## Exact versions A manifest `version` of `^1.7.9` does **not** mean “install latest”. Setup resolves the range against published Registry versions and records: - `requestedVersion`: `^1.7.9` - `resolvedVersion`: an exact published version that satisfies the range The installer is then invoked with the **exact** resolved version. If no published version satisfies the range: `E_NODE_PACK_VERSION_UNSATISFIED`. No silent latest. ## How `cwf setup` works ```sh cwf setup @alice/cool-video-workflow --comfy C:\ComfyUI ``` 1. Load the manifest + IR as data (no package JS). 2. Diff required classes against live `/object_info` when `--url` is given, and against the local `custom_nodes` tree. 3. Verify missing classes; resolve exact compatible pack versions. 4. Build an install plan (library API: `buildDependencyReport` / `createSetupPlan` / `applySetupPlan`). 5. Print exactly which registered packs and versions will be installed. 6. Ask `Continue? [y/N]` (default No). `--yes` skips the prompt; `--dry-run` prints the plan and exits. 7. Delegate to ComfyUI-Manager **`cm-cli.py install @`** with an argument array (no shell concatenation). `COMFYUI_PATH` is set to the target root. The subprocess uses the **target** Python (`python_embeded\python.exe` on portable Windows, otherwise the target venv). If Python cannot be established: `E_COMFY_PYTHON_UNKNOWN` — nothing is installed. 8. Report that Comfy must be restarted. Setup never kills a running Comfy process. Agent / CI shape: ```sh cwf setup workflow --comfy C:\ComfyUI --dry-run --json cwf setup workflow --comfy C:\ComfyUI --yes --json ``` `--yes` still refuses unresolved, ambiguous, unregistered, and version-unsatisfied packs. JSON distinguishes `alreadyInstalled`, `toInstall`, `unresolved`, `ambiguous`, `failed`, `restartRequired`, `ready`, `availabilityKnown`. `ready: true` means every required node class is **known available** on the target Comfy instance (`/object_info`). Installing a pack is not readiness: after a successful install the plan is `installed` / `restartRequired: true` / `ready: false` until availability is re-verified. Manual-source skipped dependencies never make `ready` true while their classes are still missing. Inspect JSON classifies required classes as `coreNodeClasses`, `resolvedCustomNodeClasses`, `unknownNodeClasses`, and `ambiguousNodeClasses`. UNKNOWN is not CUSTOM. ## Local vs remote Comfy `cwf inspect workflow --url https://remote-comfy` is fine: `/object_info` is readable. `cwf setup --url remote` without `--comfy` produces a plan and states that **local filesystem access** is required to apply it. There is no remote shell, no invented Manager HTTP install against a stranger's server. `--comfy` always wins over detection. Supported layouts: a git checkout (`main.py` + `comfy/` + venv), a portable Windows tree (`python_embeded` / inner `ComfyUI/`), and `COMFYUI_PATH`. Personal machine paths are never hard-coded. If more than one install could match, pass `--comfy`. Paths containing spaces are supported. [Windows](/guide/windows). ## Models `requires.models` is reported. There is no model downloader. `cwf setup` does not install checkpoints. [Models](/guide/models). ## JSON / agent mode `cwf inspect`, `cwf resolve-nodes`, and `cwf setup` all accept `--json`. Library entry: `@stepupgaming/comfy-workflows/deps` — `resolveNodeClasses`, `createSetupPlan`, `applySetupPlan`, `buildDependencyReport`. [Typed node codegen](/code/codegen) is how you **author** against custom nodes. This page is how you **declare and install** them. --- # Source: docs/product/architecture.md # Production architecture A product that talks to Comfy should not grow a second graph language in Python or Rust. The pattern that holds up: ``` real Comfy environment ↓ /object_info snapshot ↓ generated typed node SDK ↓ hand-authored ir.build.ts ↓ generated Graph IR + manifest ↓ application runtime (any language) ↓ Comfy ``` TypeScript is a **build-time** authoring tool. The application runtime binds parameter values and posts compiled JSON. It does not construct `class_type` nodes. ## Two architectures ### A. The app is already Node/TypeScript Call `workflow()`, `compile()`, `createClient().run()` in process. Fine. ### B. The app is Rust, Python, Go, C#, … Keep Node off the production box if you want. At build time: 1. Codegen per environment 2. Author `ir.build.ts` 3. Emit `workflow.ir.json` and/or a prompt template with `{$param}` holes 4. CI fails if generated artifacts drift At run time the host language only: - fills declared parameters - posts to Comfy - collects artifacts It must not grow nodes, rewrite wiring, or pick output indexes by folklore. [Build-time vs runtime](/product/build-time-vs-runtime) · [No second compiler](/concepts/no-second-compiler) ## Multiple Comfy trees Image, video, and speech installs are different node universes. Snapshot each. Generate each. Do not invent a mega-registry. [Environments](/product/environments) ## What the application owns - Product UX, job queue, storage - Parameter values (prompt, seed, paths staged onto Comfy) - Which **package** to run (that choice is product logic, not graph surgery) ## What Comfy Workflows owns - Graph topology - Types - Deterministic compile - Manifest / inspect - Verified custom-node setup (explicit `cwf setup` only) ## Case study A generalized walkthrough of this architecture: [Case study](/product/case-study). --- # Source: docs/product/build-time-vs-runtime.md # Build-time vs runtime You do not need to add Node as a daemon to a Rust or Python application merely because Comfy Workflows authored its workflows. ## Option A — JavaScript/TypeScript application The app can: - build / instantiate - compile - `createClient` - `run` SDK in process. Comfy still executes. ## Option B — non-Node product ``` TypeScript at BUILD TIME ↓ generated IR / prompt template ↓ application runtime binds values ↓ Comfy ``` ```ts /** * Build-time authoring. A non-Node runtime later binds {$param} values in the * generated IR / compiled prompt. It does not reimplement the compiler. */ import { serializeGraph, workflow } from "@stepupgaming/comfy-workflows"; import { conditioning, image, latent, loaders, sampling } from "@stepupgaming/comfy-workflows/nodes"; export function buildTemplate() { const g = workflow("product-demo"); const prompt = g.param("prompt", { type: "string" }); const seed = g.param("seed", { type: "int", default: 42n }); const ckpt = g.add(loaders.CheckpointLoaderSimple, { ckpt_name: "v1-5-pruned-emaonly.safetensors", }); const positive = g.add(conditioning.CLIPTextEncode, { text: prompt, clip: ckpt.CLIP }); const negative = g.add(conditioning.CLIPTextEncode, { text: "", clip: ckpt.CLIP }); const empty = g.add(latent.EmptyLatentImage, { width: 512, height: 512, batch_size: 1 }); const sampled = g.add(sampling.KSampler, { model: ckpt.MODEL, positive: positive.CONDITIONING, negative: negative.CONDITIONING, latent_image: empty.LATENT, seed, steps: 8, cfg: 7, sampler_name: "euler", scheduler: "normal", denoise: 1, }); const decoded = g.add(latent.VAEDecode, { samples: sampled.LATENT, vae: ckpt.VAE }); g.add(image.SaveImage, { images: decoded.IMAGE, filename_prefix: "product" }); return g.toGraph(); } export function emitArtifacts(): { ir: string } { return { ir: serializeGraph(buildTemplate(), { pretty: true }) }; } ``` The binder on the other side of that wall should stay **narrow and topology-free**. Replace `{"$param":"seed"}` with a value. Do not create a `KSampler`. Do not decide that "continue" means a different class_type. A tiny JS illustration of that binder (the same idea in Python or Rust): ```js /** * Tiny non-Node-style binder. It only replaces {$param} placeholders in a * compiled prompt template. It does not invent nodes or rewrite topology. */ /** @param {any} template @param {Record} params */ export function bindParams(template, params) { const walk = (value) => { if (Array.isArray(value)) return value.map(walk); if (value && typeof value === "object") { const keys = Object.keys(value); if (keys.length === 1 && keys[0] === "$param") { const name = value.$param; if (!(name in params)) { throw new Error(`unbound parameter: ${name}`); } return params[name]; } const out = {}; for (const [k, v] of Object.entries(value)) out[k] = walk(v); return out; } return value; }; return walk(template); } ``` ## Authority The Comfy Workflows compiler is the authority for lowering Graph IR to API JSON. Other runtimes may consume: - compiled artifacts - generated templates - narrow parameter binding They should not silently fork compiler semantics (bypass lowering, lossless ints, slot indexes). If you think you need a second compiler, you probably need another `ir.build.ts` instead. ## Node at runtime? | Situation | Need Node? | | --------- | ---------- | | Authoring / CI | Yes | | TS app calling `createClient` | Yes | | Python/Rust posting compiled JSON | No | | `cwf run` on a workstation | Yes (the CLI) | [No second compiler](/concepts/no-second-compiler) --- # Source: docs/concepts/no-second-compiler.md # No second compiler For non-JS integrations: Do not reimplement Graph IR lowering in Python, Rust, Go, etc. The Comfy Workflows compiler is the authority. Other runtimes may consume compiled artifacts, generated templates, and narrow parameter binding. They should not fork bypass lowering, lossless integer emit, or slot identity. If a flag needs different nodes, author another graph. If it needs a different number, bind a parameter. This is the production rule that keeps 30+ workflows from growing a shadow compiler in the worker. --- # Source: docs/product/security.md # Security model Short version. The dedicated page is [Project: Security](/project/security). - Workflow packages are inspected as pure data (`package.json`, manifest, IR). - Package JavaScript is not executed during inspect or package discovery for `run`. - Custom nodes **are** executable Python. Installing them is a trust decision. - Only `cwf setup` installs, after a printed plan and approval (default No). - Registry verification is per pack **version** definitions, not a publisher claim. - Manifests have no `install` / `script` / `shell` / `pip` / `git` command fields. - Manager is invoked with an argument array, not a concatenated shell string. - `repository` URLs are informational. Never cloned automatically. - Remote `--url` without `--comfy` cannot apply setup. - `rawNode` names a class. It does not download or exec code by itself. Models are **not** auto-downloaded. [Models](/guide/models) --- # Source: docs/concepts/packages.md # Packages A workflow package is an npm-shaped directory whose **payload** is Graph IR. ``` package.json # comfyWorkflow pointer, keywords comfy.workflow.json # manifest workflow.ir.json # canonical IR README.md ir.build.ts # optional authoring source (build-time) workflow.ts # optional convenience dist/ # optional typed wrapper; never required to inspect ``` Inspect reads the three JSON files. It does not execute `ir.build.ts` or `dist/`. Package **format** vs package **host**: GitHub Packages (canonical authenticated), GitHub Release tarball (canonical anonymous), npmjs (convenience mirror), local path. The resolver should not care. [Package guide](/migrate/package) · [Manifest](/reference/manifest) · [Distribution](/product/distribution) --- # Source: docs/product/distribution.md # Package distribution A workflow package is files: - `package.json` with a `comfyWorkflow` pointer - `comfy.workflow.json` - `workflow.ir.json` The **format** does not care which registry hosted the tarball. The **host** is distribution metadata, not workflow semantics. Releasing Comfy Workflows does not depend on any single external package registry. ## Canonical host GitHub is the release authority. ``` package source ↓ validated artifact ↓ GitHub Release / tag ↓ GitHub Packages ← authenticated npm-compatible registry ↓ GitHub Release .tgz ← public / anonymous artifact ↓ release COMPLETE ``` Then, independently: ``` optional npmjs mirror ↓ mirrored | deferred | not-requested ``` A failed, rate-limited, or delayed npm publish does **not** make a GitHub release incomplete. ## Three consumer paths ### 1. Convenience install (npmjs mirror) When the package name already exists on npmjs, this is the shortest public command: ```sh pnpm add @stepupgaming/comfy-workflows ``` That is a **mirror**. The source of truth is the GitHub release of the same version. ### 2. Authenticated GitHub Packages GitHub currently requires authentication to install from `npm.pkg.github.com`, including public packages. Use a **project-local** `.npmrc`, not a global `~/.npmrc`, unless you explicitly want the whole machine remapped. Token permission for install: `read:packages`. Never commit the token. ::: code-group ```powershell [PowerShell] $env:GITHUB_PACKAGES_TOKEN = "ghp_…" # PAT with read:packages @' @stepupgaming:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN} '@ | Set-Content -Encoding utf8 .npmrc pnpm add @stepupgaming/comfy-workflows pnpm add @stepupgaming/comfy-workflow-t2i Remove-Item Env:GITHUB_PACKAGES_TOKEN ``` ```sh [shell] export GITHUB_PACKAGES_TOKEN=ghp_… # PAT with read:packages cat > .npmrc <<'EOF' @stepupgaming:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN} EOF pnpm add @stepupgaming/comfy-workflows pnpm add @stepupgaming/comfy-workflow-t2i unset GITHUB_PACKAGES_TOKEN ``` ::: A scope mapping applies to the **entire** `@stepupgaming` scope. That is now a valid authenticated path because core and first-party workflow packages are all on GitHub Packages. Keep it project-local so unrelated `@stepupgaming` names on npmjs are not stolen. Do not paste a permanent secret into a source-controlled `.npmrc`. Prefer `${GITHUB_PACKAGES_TOKEN}` / `${GITHUB_TOKEN}` interpolation. ### 3. Anonymous GitHub Release tarball No npmjs. No GitHub Packages PAT. Download the exact `.tgz` from the [GitHub Release](https://github.com/stepupgaming/comfy-workflows/releases) and install it: ```sh pnpm add ./stepupgaming-comfy-workflows-.tgz pnpm add ./stepupgaming-comfy-workflow-t2i-0.2.0.tgz ``` Tarball names are deterministic: `{owner}-{unscoped-name}-{version}.tgz`. This is the public path when you do not want to authenticate to GitHub Packages. ## npmjs is a mirror Existing names may be mirrored after a GitHub release. New names are **not** created automatically. If npm returns `E429`, auth failure, or an outage, the mirror is recorded as `deferred` and the GitHub release stays complete. Do not bump versions to work around npm infrastructure. ## Resolver `cwf inspect` / `cwf run ` resolve npm names through Node's ordinary package lookup, or a path, or an already-installed tarball. They never execute package JavaScript. They never call the GitHub API. ## Package index Generated from repository metadata: [First-party packages](/product/packages). --- # Source: docs/reference/cli.md # CLI The `cwf` CLI (`comfy-workflows` is an alias) mirrors the SDK. JSON on stdout when useful; **every error is JSON on stderr**. ## Agent-safe usage Prefer read-only JSON before anything that installs Python: ```sh cwf inspect --json cwf setup --dry-run --json cwf suggest --json cwf pack --json cwf resolve-nodes --json ``` ### `cwf agent` ``` cwf agent install [--project dir] [--force] [--json] cwf agent check [--project dir] [--json] ``` Copies the bundled skill from the **installed** package into `/.agents/skills/comfy-workflows/`. No network. No symlinks. `--force` is required if the destination has local edits. `check` reports `missing` / `current` / `outdated` / `modified`. Default project is the current working directory. `--json` is supported on `init`, `suggest`, `pack`, `inspect`, `resolve-nodes`, `setup`, `node-pack`, and `agent`. Success JSON goes to **stdout**. Failures are `{ "error": { "code": "E_…", … } }` on **stderr** and a non-zero exit. Do not run `cwf setup --yes` unless the user named a Comfy directory and asked to install. `inspect`, `explain`, and `catalog` do not guess and do not execute package JavaScript. `run` never installs Python. Compile is deterministic. This help text is generated from `src/cli/cli.ts` (`pnpm docs:gen`). If a command is missing here, `docs:check` fails. ``` cwf — code-first, typed, composable workflows for ComfyUI cwf import [--out foo.ir.json] [--ts dir/workflow.ts] [--from defs.json] cwf snapshot --url URL -o object_info.json cwf lock --url URL [-o comfy.lock.json] cwf codegen [--url URL | --from snapshot.json] -o src/nodes/gen [--exact-combos] cwf compile [-o out.api.json] [--defs defs.json] [--pretty] cwf validate [--url URL] [--defs defs.json] cwf run --url URL [--param k=v ...] [--out outdir] cwf init [name] --from [--out dir] [--git] [--json] cwf expose --node --input [--required] [--description ...] [--default ...] cwf suggest [dir] [--json] # deterministic parameter suggestions (no mutation) cwf pack [dir] [--json] [--publish] # validate a workflow package cwf inspect [--url URL] [--json] # inspect without running JS cwf resolve-nodes [--url URL] [--write] [--json] cwf node-pack add --provides ClassA,ClassB [--dir pkg] [--name ...] [--version ...] cwf setup --comfy [--yes] [--dry-run] [--json] cwf explain # what does this expand into? cwf catalog [query] [--from catalog.json] cwf agent install [--project dir] [--force] [--json] # copy bundled skills to .agents/skills cwf agent check [--project dir] [--json] # project skills vs installed package ``` ## Notes the one-line help compresses ### `cwf import` ``` cwf import [--out foo.ir.json] [--ts dir/workflow.ts] [--from defs.json] ``` Editor v0.4, workflow v1, or API format. `--registry ` routes classes to a generated registry; missing specs become `rawNode(...)`. ### `cwf snapshot` / `cwf lock` / `cwf codegen` ``` cwf snapshot --url URL -o object_info.json cwf lock --url URL [-o comfy.lock.json] cwf codegen [--url URL | --from snapshot.json] -o src/nodes/gen [--exact-combos] ``` ### `cwf compile` / `cwf validate` / `cwf run` Accept `workflow.ts`, `.ir.json`, and Comfy JSON. `--lock` / `comfy.lock.json` → `E_LOCK_DRIFT` warning. `--param k=v` repeatable (`-p`). `validate` never queues. `run` never installs Python. ### `cwf init` / `expose` / `suggest` / `pack` / `inspect` See [Convert a workflow](/migrate/import). ### `cwf resolve-nodes` / `node-pack` / `setup` See [Custom nodes](/guide/custom-nodes). ### `cwf explain` / `cwf catalog` ``` cwf explain cwf catalog [query] [--from catalog.json] ``` ## Shared flags Short aliases: `-o` out, `-u` url, `-d` defs, `-p` param, `-t` ts, `-f` from. Defs order: `--defs` → live `--url` → bundled core (`E_LIVE_DEFS_UNAVAILABLE` if live fetch failed). --- # Source: docs/reference/errors.md # Error codes Generated from `src/errors.ts`. Meanings below are the source comments. | Code | Meaning | | ---- | ------- | | `E_UNKNOWN_NODE_TYPE` | Node class not present in the provided defs. | | `E_MISSING_INPUT` | Required input (connection or widget) has no value and no default. | | `E_TYPE_MISMATCH` | Connected output type does not match the input's declared type. | | `E_BAD_COMBO` | Combo param value is not one of the allowed options. | | `E_RANGE` | Numeric param out of the declared [min, max] range. | | `E_UNKNOWN_INPUT` | Param references an input the node def does not declare. | | `E_INVALID_INPUT` | Input key conflicts with the def (e.g. a widget name used as a connection). | | `E_CYCLE` | Graph contains a cycle. | | `E_MUTED_CONSUMED` | A muted node is still referenced by a consumer. | | `E_UNRESOLVED_BYPASS` | A bypassed node's pass-through could not be resolved unambiguously. | | `E_INVALID_GRAPH` | Structural graph problem: dangling ref, out-of-range slot, malformed IR. | | `E_INVALID_PARAM` | Param value present but not a valid value for its declared kind. | | `E_UNBOUND_PARAM` | A template placeholder was never bound before compilation. | | `E_UNBOUND_PORT` | A template input port was never bound before compilation. | | `E_ASSET_UNSTAGED` | An AssetRef reached compilation without being staged by a runtime. | | `E_ASSET_STAGE_FAILED` | Asset upload to the server failed. | | `E_SUBMIT_FAILED` | HTTP submit failed (network, 4xx/5xx). | | `E_NODE_EXECUTION_ERROR` | ComfyUI reported a node-level execution error. | | `E_TIMEOUT` | Run did not complete within the configured timeout. | | `E_CONNECTION_FAILED` | Transport-level failure talking to the Comfy instance. | | `E_UNSUPPORTED_FEATURE` | Imported workflow uses a construct the importer cannot represent yet. | | `E_NODE_PACK_AMBIGUOUS` | Multiple verified registry packs provide the same node class. | | `E_NODE_PACK_UNKNOWN` | No verified registered pack could be identified for a node class. | | `E_INVALID_NODE_PACK` | Node-pack metadata in the manifest is invalid. | | `E_NODE_PACK_VERSION_UNSATISFIED` | Declared version range matches no active Registry version. | | `E_COMFY_PYTHON_UNKNOWN` | Target Comfy Python interpreter could not be established. | | `E_SETUP_DECLINED` | User declined the setup plan (or non-interactive without --yes). | | `E_SETUP_NOT_APPLICABLE` | Setup cannot be applied (remote URL, missing Comfy path, missing installer). | | `E_SETUP_FAILED` | Official installer returned a failure. | | `E_AGENT_SKILL_MISSING` | Bundled skill is missing from the installed package. | | `E_AGENT_SKILL_MODIFIED` | Project skill copy has local edits; --force required to overwrite. | ## CLI envelope codes (not in `ErrorCodes`) | Code | Meaning | | ---- | ------- | | `E_LOCK_DRIFT` | Lockfile/defs hash mismatch. **Warning.** | | `E_LIVE_DEFS_UNAVAILABLE` | `/object_info` fetch failed; bundled defs used. **Warning.** | | `E_UNCAUGHT` | Non-`ComfyError` crash | | `E_PACK_LOCAL_PATH` | Pack validation: machine-local path in IR (see `cwf pack`) | ## Remediation (common) | Code | Typical fix | | ---- | ----------- | | `E_UNKNOWN_NODE_TYPE` | Snapshot the Comfy that has the class; codegen; or `rawNode` | | `E_TYPE_MISMATCH` | Wire the right handle; `unsafe` only if the node lies | | `E_UNBOUND_PARAM` / `E_UNBOUND_PORT` | Pass bindings or defaults | | `E_UNRESOLVED_BYPASS` | `g.setBypassMap` | | `E_MUTED_CONSUMED` | Unmute or disconnect | | `E_NODE_PACK_AMBIGUOUS` | `cwf node-pack add` / pick a provider | | `E_NODE_PACK_UNKNOWN` | Manual map, or the class is core/unregistered | | `E_NODE_PACK_VERSION_UNSATISFIED` | Relax the range or publish that version | | `E_COMFY_PYTHON_UNKNOWN` | Pass `--comfy` at the tree that has Python | | `E_SETUP_DECLINED` | Answer y or pass `--yes` | | `E_SETUP_NOT_APPLICABLE` | Local `--comfy` required to apply | [Debugging](/guide/errors) --- # Source: docs/reference/api/index.md # Public API Package: `@stepupgaming/comfy-workflows` (current docs built against the repo's `package.json` version). | Subpath | What it is | | ------- | ---------- | | `@stepupgaming/comfy-workflows` | Graph builder, IR, compile, recipes, errors, `createClient` | | `/nodes` | Bundled core node specs (from `fixtures/object_info/core.json`) | | `/recipes` | Same recipes as the root named exports | | `/ir` | Graph IR types and operations | | `/runtime` | `createClient`, assets | | `/wfpack` | Manifest, discover, pack helpers | | `/deps` | Registry resolve + setup planning | | `/schema` | `comfy.workflow.schema.json` | Do not dump generated per-node pages into the sidebar. Search [Node catalog](/reference/node-catalog) / `cwf catalog` instead. Curated modules: - [Graph API](/reference/api/graph) - [Node SDK](/reference/api/nodes) - [IR API](/reference/api/ir) - [Runtime API](/reference/api/runtime) - [Workflow package API](/reference/api/wfpack) - [Dependency / setup API](/reference/api/deps) - [Recipes](/reference/recipes) - [CLI](/reference/cli) - [Error codes](/reference/errors) Guides for the same ideas: [Author a graph](/code/build-a-graph) · [Run](/code/run) · [Custom nodes](/guide/custom-nodes) --- # Source: docs/concepts/lossless-integers.md # Lossless integers JavaScript `number` is IEEE-754 float. Integers above 2^53 (9_007_199_254_740_991) cannot be represented exactly. Comfy seeds are 64-bit. `JSON.parse` / `JSON.stringify` will silently change them. Journey: ``` JS bigint ↓ Graph IR: { "$int": "18446744073709551615" } ↓ compiler ↓ raw exact JSON numeric literal in the /prompt body ↓ Comfy (Python) ``` IR stays safe under ordinary `JSON.parse` because of the tag. The wire form is assembled by string concatenation so bigints never pass through `JSON.stringify`. Use `42n` in TypeScript. `cwf run --param seed=42` parses as integer; large seeds should be passed without going through JS `Number`. `run.json.compiledJson` is stored as a **string** for this reason. Do not parse it if you need the seed intact. --- # Source: docs/guide/agents.md # Coding agents Three different surfaces. Do not collapse them. | Who | File | Job | | --- | ---- | --- | | Agent modifying this repository | [AGENTS.md](https://github.com/stepupgaming/comfy-workflows/blob/main/AGENTS.md) | Repo invariants, commands, source map | | Agent using the SDK in another app | Skills in the installed package: `skills/comfy-workflows/` (graphs) and `skills/comfy-custom-nodes/` (codegen / setup) | Operating manuals + progressive references | | Agent that found the docs site | [llms.txt](/llms.txt) | Routing to raw Markdown | Deeper single-file digest: [llms-full.txt](/llms-full.txt). Discovery JSON: [agent-index.json](/agent-index.json). This page is for people configuring agents. It is not the skill. ## Skill (portable) After `pnpm add @stepupgaming/comfy-workflows` the tarball contains: ``` skills/comfy-workflows/SKILL.md skills/comfy-workflows/references/ skills/comfy-custom-nodes/SKILL.md skills/comfy-custom-nodes/references/ ``` Compatible Agent Skills clients look in `.agents/skills/`, not `node_modules`. Copy the bundled skill into the project: ```sh cwf agent install cwf agent check --json ``` That writes `.agents/skills/comfy-workflows/` and `.agents/skills/comfy-custom-nodes/` from the **installed** package (same version as the SDK). Rerun after upgrading the core. Local edits are not overwritten unless you pass `--force`. There is no `postinstall` hook. This does not mutate `AGENTS.md`. Some clients also have their own skill directories. The portable project location this command uses is `.agents/skills/`. `comfy-workflows` teaches: edit TypeScript not generated IR, topology vs ParamRef, no second compiler, packages. `comfy-custom-nodes` teaches: snapshot + codegen, `rawNode` as escape hatch, Registry resolution, explicit `cwf setup`. Deep human-doc links from an **installed** skill pin the matching git tag (`references/_links.md`) so an old package does not point at newer APIs. The live `llms.txt` on this site tracks `main`. ## Raw Markdown Prefer GitHub raw files over scraping VitePress HTML: https://raw.githubusercontent.com/stepupgaming/comfy-workflows/main/docs/start/what-do-i-edit.md Each rendered page also exposes `rel="alternate"` `text/markdown` and a “View Markdown source” link. ## JSON CLI Do not have an agent run `cwf setup --yes` on a laptop as a surprise. Prefer: ```sh cwf inspect --json cwf setup --dry-run --json cwf suggest --json cwf pack --json cwf resolve-nodes --json ``` before any install. - `--json` on `init`, `suggest`, `pack`, `inspect`, `resolve-nodes`, `setup`, `node-pack` - Success JSON on **stdout**; every error is JSON on **stderr** with `ComfyError.code` - `inspect` / `explain` / `catalog` do not guess and do not execute package JavaScript - `run` never installs Python - Compile is deterministic: same graph → same bytes Full command list: [CLI reference](/reference/cli). Error codes: [errors](/reference/errors). ## Security agents get wrong - Workflow packages are data - Custom-node install executes Python and needs explicit user intent - Registry mapping must be verified; do not guess from GitHub names - Models are not auto-downloaded - `rawNode` does not download code - Release host is unrelated to graph semantics