comfyui-node 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,11 @@ TypeScript SDK for interacting with the [ComfyUI](https://github.com/comfyanonym
14
14
 
15
15
  - [Features](#features)
16
16
  - [Installation](#installation)
17
- - [Quick Start](#quick-start)
17
+ - [Cheat Sheet](#cheat-sheet)
18
+ - [Recent Enhancements (Ergonomics & Typing)](#recent-enhancements-ergonomics--typing)
19
+ - [High‑Level Workflow API (Experimental) – Quick Intro](#highlevel-workflow-api-experimental--quick-intro)
20
+ - [Choosing: Workflow vs PromptBuilder](#choosing-workflow-vs-promptbuilder)
21
+ - [Result Object Anatomy](#result-object-anatomy)
18
22
  - [Multi-Instance Pool](#multi-instance-pool)
19
23
  - [Authentication](#authentication)
20
24
  - [Custom WebSocket](#custom-websocket)
@@ -24,6 +28,8 @@ TypeScript SDK for interacting with the [ComfyUI](https://github.com/comfyanonym
24
28
  - [Reference Overview](#reference-overview)
25
29
  - [Examples](#examples)
26
30
  - [Errors & Diagnostics](#errors--diagnostics)
31
+ - [Troubleshooting](#troubleshooting)
32
+ - [Published Smoke Test](#published-smoke-test)
27
33
  - [Contributing](#contributing)
28
34
  - [License](#license)
29
35
 
@@ -41,57 +47,314 @@ TypeScript SDK for interacting with the [ComfyUI](https://github.com/comfyanonym
41
47
  - Structured errors & narrow fetch helper
42
48
  - Validation utilities for prompt graphs (missing mappings, immediate cycles)
43
49
  - JSON round‑trip support for builder state persistence
50
+ - High‑level `Workflow` abstraction (rapid parameter tweaking of existing JSON graphs)
51
+ - Input sugar helpers: `wf.input(...)`, `wf.batchInputs(...)`
52
+ - Soft autocomplete mode for sampler / scheduler (`Workflow.fromAugmented`)
53
+ - Progressive typed outputs inferred from `wf.output(...)` declarations
54
+ - Per‑node output shape heuristics (e.g. `SaveImage*`, `KSampler`)
55
+ - Automatic random seed substitution for `seed: -1` with `_autoSeeds` metadata
44
56
 
45
57
  ## Installation
46
58
 
59
+ Requires Node.js >= 22 (modern WebSocket + fetch + ES2023 features). Works with Bun as well.
60
+
47
61
  ```bash
48
- bun add comfyui-node
49
- # or
50
62
  npm install comfyui-node
63
+ # or
64
+ pnpm add comfyui-node
65
+ # or
66
+ bun add comfyui-node
67
+ ```text
68
+
69
+ TypeScript types are bundled; no extra install needed.
70
+
71
+ Minimal ESM usage example:
72
+
73
+ ```ts
74
+ import { ComfyApi, Workflow } from 'comfyui-node';
75
+ import BaseWorkflow from './example-txt2img-workflow.json';
76
+
77
+ async function main() {
78
+ const api = await new ComfyApi('http://127.0.0.1:8188').ready();
79
+ const wf = Workflow.from(BaseWorkflow)
80
+ .set('6.inputs.text', 'Hello ComfyUI SDK')
81
+ .output('images:9');
82
+ const job = await api.run(wf, { autoDestroy: true });
83
+ const result = await job.done();
84
+ for (const img of (result.images?.images || [])) {
85
+ console.log('image path:', api.ext.file.getPathImage(img));
86
+ }
87
+ }
88
+ main();
89
+ ```text
90
+
91
+ ## Cheat Sheet
92
+
93
+ Fast reference for common operations. See deeper sections for narrative explanations.
94
+
95
+ ### Workflow (High-Level)
96
+
97
+ ```ts
98
+ import { ComfyApi, Workflow } from 'comfyui-node';
99
+ const api = await new ComfyApi('http://127.0.0.1:8188').ready();
100
+ const wf = Workflow.from(json)
101
+ .set('3.inputs.steps', 20) // dotted path set
102
+ .input('SAMPLER','cfg', 4) // input helper
103
+ .batchInputs('SAMPLER', { steps: 15, cfg: 3 })
104
+ .output('images:9'); // alias:nodeId
105
+ const job = await api.run(wf); // acceptance barrier
106
+ job.on('progress_pct', p => console.log(p,'%'));
107
+ const result = await job.done();
108
+ for (const img of (result.images?.images||[])) console.log(api.ext.file.getPathImage(img));
51
109
  ```
52
110
 
53
- Requires Node.js 22+ (Active / Current). Earlier Node versions are not supported in 1.0+.
111
+ ### PromptBuilder (Lower-Level)
112
+
113
+ ```ts
114
+ import { PromptBuilder } from 'comfyui-node';
115
+ const builder = new PromptBuilder(base,[ 'positive','seed' ],[ 'images' ])
116
+ .setInputNode('positive','6.inputs.text')
117
+ .setInputNode('seed','3.inputs.seed')
118
+ .setOutputNode('images','9')
119
+ .input('positive','A misty forest')
120
+ .input('seed', 1234)
121
+ .validateOutputMappings();
122
+ ```
54
123
 
55
- ## Quick Start
124
+ ### Running (Alternate APIs)
56
125
 
57
126
  ```ts
58
- import { ComfyApi, CallWrapper, PromptBuilder, seed, TSamplerName, TSchedulerName } from "comfyui-node";
59
- import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
127
+ await api.run(wf); // high-level (Workflow)
128
+ await api.runWorkflow(wf); // alias
129
+ new CallWrapper(api, builder)
130
+ .onFinished(o => console.log(o.images?.images?.length))
131
+ .run(); // builder execution
132
+ ```
60
133
 
61
- const api = new ComfyApi("http://localhost:8189").init();
62
- const workflow = new PromptBuilder(
63
- ExampleTxt2ImgWorkflow,
64
- ["positive","negative","checkpoint","seed","batch","step","cfg","sampler","sheduler","width","height"],
65
- ["images"]
66
- )
67
- .setInputNode("checkpoint","4.inputs.ckpt_name")
68
- .setInputNode("seed","3.inputs.seed")
69
- .setInputNode("batch","5.inputs.batch_size")
70
- .setInputNode("negative","7.inputs.text")
71
- .setInputNode("positive","6.inputs.text")
72
- .setInputNode("cfg","3.inputs.cfg")
73
- .setInputNode("sampler","3.inputs.sampler_name")
74
- .setInputNode("sheduler","3.inputs.scheduler")
75
- .setInputNode("step","3.inputs.steps")
76
- .setInputNode("width","5.inputs.width")
77
- .setInputNode("height","5.inputs.height")
78
- .setOutputNode("images","9")
79
- .input("checkpoint","SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
80
- .input("seed", seed())
81
- .input("step", 6)
82
- .input("cfg", 1)
83
- .input<TSamplerName>("sampler","dpmpp_2m_sde_gpu")
84
- .input<TSchedulerName>("sheduler","sgm_uniform")
85
- .input("width",1024)
86
- .input("height",1024)
87
- .input("batch",1)
88
- .input("positive","A picture of a dog on the street");
89
-
90
- new CallWrapper(api, workflow)
91
- .onFinished(d => console.log(d.images?.images.map((img: any) => api.ext.file.getPathImage(img))))
92
- .run();
134
+ ### Declaring Outputs
135
+
136
+ ```ts
137
+ wf.output('alias:NodeId');
138
+ wf.output('alias','NodeId');
139
+ wf.output('NodeId'); // key = id
140
+ // none declared -> auto collect SaveImage nodes
93
141
  ```
94
142
 
143
+ ### Events (WorkflowJob)
144
+
145
+ ```txt
146
+ pending -> start -> progress/progress_pct/preview -> output* -> finished (or failed)
147
+ ```
148
+
149
+ | Event | Notes |
150
+ | ----- | ----- |
151
+ | pending | accepted into queue |
152
+ | start | first execution step began |
153
+ | progress_pct | integer 0-100 (deduped) |
154
+ | preview | live frame (Blob) |
155
+ | output | a declared / auto-detected node produced data |
156
+ | finished | all requested nodes resolved |
157
+ | failed | execution error / interruption |
158
+
159
+ ### Seed Handling
160
+
161
+ ```ts
162
+ // -1 sentinel => randomized & reported under _autoSeeds
163
+ wf.batchInputs('SAMPLER', { seed: -1 });
164
+ ```
165
+
166
+ ### Type Extraction
167
+
168
+ ```ts
169
+ type Result = ReturnType<typeof wf.typedResult>;
170
+ ```
171
+
172
+ ### Pool Quick Start
173
+
174
+ ```ts
175
+ const pool = new ComfyPool([
176
+ new ComfyApi('http://localhost:8188'),
177
+ new ComfyApi('http://localhost:8189')
178
+ ]);
179
+ const job2 = await pool.clients[0].run(wf, { pool });
180
+ await job2.done();
181
+ ```
182
+
183
+ ### Selecting Workflow vs PromptBuilder
184
+
185
+ Use Workflow for 90% of: tweak existing JSON, few parameter edits, rapid prototyping. Use PromptBuilder when you must programmatically assemble / rewire node graphs or need validation utilities pre-submit.
186
+
187
+
188
+
189
+ ### High‑Level Workflow API (Experimental) – Quick Intro
190
+
191
+ Skip manual `PromptBuilder` wiring with `Workflow` when you just want to tweak an existing graph JSON and run it. A full step‑by‑step tutorial is below; here is the 10‑second overview:
192
+
193
+ - Load JSON – `Workflow.from(json)`
194
+ - Mutate values – `.set('nodeId.inputs.field', value)`
195
+ - Declare outputs – `.output('alias:nodeId')` (or just `.output('nodeId')`; falls back to auto‑detecting `SaveImage` nodes)
196
+ - Execute – `await api.run(wf)` (or `api.runWorkflow(wf)` alias) returning a `WorkflowJob` (Promise‑like)
197
+ - Subscribe to events – `progress`, `progress_pct`, `preview`, `output`, `finished`, `failed`
198
+ - Await final object – either `await job` or `await job.done()`
199
+
200
+ See the dedicated tutorial section for a narrated example and option details.
201
+
202
+ ### Recent Enhancements (Ergonomics & Typing)
203
+
204
+ The `Workflow` surface has gained several quality‑of‑life helpers and **progressive typing** features. All are additive (no breaking changes) and optional—fall back to raw `set()` / `output()` styles whenever you prefer.
205
+
206
+ | Feature | Purpose | Example | Type Effect |
207
+ | ------- | ------- | ------- | ----------- |
208
+ | `wf.input(nodeId, inputName, value)` | Concise single input mutation (vs dotted path) | `wf.input('SAMPLER','steps',30)` | none (runtime sugar) |
209
+ | `wf.batchInputs(nodeId, { ... })` | Set multiple inputs on one node | `wf.batchInputs('SAMPLER',{ steps:30, cfg:5 })` | none |
210
+ | `wf.batchInputs({ NODEA:{...} })` | Multi‑node batch mutation | `wf.batchInputs({ SAMPLER:{ cfg:6 } })` | none |
211
+ | `Workflow.fromAugmented(json)` | Soft autocomplete on sampler / scheduler but still accepts future values | `Workflow.fromAugmented(base)` | narrows fields to union \| (string & {}) |
212
+ | Typed output inference | `.output('alias:ID')` accumulates object keys | `wf.output('images:SAVE_IMAGE')` | widens result shape with `images` key |
213
+ | Per‑node output shape hints | Heuristic shapes for `SaveImage*`, `KSampler` | `result.images.images` | structural hints for nested fields |
214
+ | Multiple output syntaxes | Choose preferred style | `'alias:NodeId'` / `('alias','NodeId')` / `'NodeId'` | identical effect |
215
+ | `wf.typedResult()` | Get IDE type of final result | `type R = ReturnType<typeof wf.typedResult>` | captures accumulated generic |
216
+ | Auto seed substitution | `seed: -1` randomized before submit | `wf.input('SAMPLER','seed',-1)` | adds `_autoSeeds` map key |
217
+ | Acceptance barrier run | `await api.run(wf)` returns job handle pre-completion | `const job=await api.run(wf)` | result type unchanged |
218
+
219
+ > All typing is structural—no runtime validation. Unknown / future sampler names or new node classes continue to work.
220
+
221
+ #### Input Helpers
222
+
223
+ ```ts
224
+ const wf = Workflow.fromAugmented(baseJson)
225
+ .input('LOADER','ckpt_name','model.safetensors')
226
+ .batchInputs('SAMPLER', {
227
+ steps: 30,
228
+ cfg: 4,
229
+ sampler_name: 'euler_ancestral', // autocomplete + accepts future strings
230
+ scheduler: 'karras', // autocomplete + forward compatible
231
+ seed: -1 // -1 -> auto randomized before submit
232
+ })
233
+ .batchInputs({
234
+ CLIP_TEXT_ENCODE_POSITIVE: { text: 'A moody cinematic landscape' },
235
+ LATENT_IMAGE: { width: 896, height: 1152 }
236
+ });
237
+ ```
238
+
239
+ ### Output Declaration & Typing
240
+
241
+ Each `output()` call accumulates inferred keys:
242
+
243
+ ```ts
244
+ const wf2 = Workflow.fromAugmented(baseJson)
245
+ .output('gallery:SAVE_IMAGE') // key 'gallery'
246
+ .output('KSamplerNode') // key 'KSamplerNode'
247
+ .output('thumb','THUMBNAIL_NODE'); // key 'thumb'
248
+
249
+ // Type exploration (IDE only):
250
+ type Wf2Result = ReturnType<typeof wf2.typedResult>;
251
+ // Wf2Result ~ {
252
+ // gallery: { images?: any[] }; // SaveImage heuristic
253
+ // KSamplerNode: { samples?: any }; // KSampler heuristic
254
+ // thumb: any; // THUMBNAIL_NODE class not mapped yet
255
+ // _promptId?: string; _nodes?: string[]; _aliases?: Record<string,string>; _autoSeeds?: Record<string,number>;
256
+ // }
257
+
258
+ const job = await api.run(wf2);
259
+ const final = await job.done();
260
+ final.gallery.images?.forEach(img => console.log(api.ext.file.getPathImage(img)));
261
+ ```
262
+
263
+ Supported output forms (all equivalent semantically; choose your style):
264
+
265
+ ```ts
266
+ wf.output('alias:NodeId');
267
+ wf.output('alias','NodeId');
268
+ wf.output('NodeId'); // raw key = node id
269
+ ```
270
+
271
+ If you declare *no* outputs the SDK still auto‑collects all `SaveImage` nodes.
272
+
273
+ #### Per‑Node Output Shapes (Heuristics)
274
+
275
+ Currently recognized:
276
+
277
+ | class_type match | Inferred shape fragment |
278
+ | ---------------- | ----------------------- |
279
+ | `SaveImage`, `SaveImageAdvanced` | `{ images?: any[] }` |
280
+ | `KSampler` | `{ samples?: any }` |
281
+
282
+ All others are typed as `any` (you still get alias key inference). This table will expand; explicit contributions welcome.
283
+
284
+ #### Combining With Result Metadata
285
+
286
+ The object from `job.done()` (and `runAndWait`) is always the intersection:
287
+
288
+ ```ts
289
+ // final result shape (conceptual)
290
+ { ...yourDeclaredOutputs, _promptId?: string, _nodes?: string[], _aliases?: Record<string,string>, _autoSeeds?: Record<string,number> }
291
+ ```
292
+
293
+ #### When to Use `Workflow.fromAugmented`
294
+
295
+ Use it when you want IDE suggestions for sampler / scheduler *without* losing forward compatibility. The widened types are `TSamplerName | (string & {})` and `TSchedulerName | (string & {})` internally—any new upstream values are valid.
296
+
297
+ #### Extracting a Stable Result Type
298
+
299
+ If you want to export a type for downstream modules:
300
+
301
+ ```ts
302
+ export type MyGenerationResult = ReturnType<typeof wf.typedResult>;
303
+ ```
304
+
305
+ This stays accurate as long as all `output()` calls run before the type is captured.
306
+
307
+ #### Limitations & Future Work
308
+
309
+ - Output shapes are heuristic; not all node classes annotated yet.
310
+ - Dynamic node creation using non‑strict `input()` cannot update the generic shape (TypeScript limitation). You can re‑wrap with `Workflow.fromAugmented` after structural edits if needed.
311
+ - Potential future API: `wf.withOutputShapes({ MyCustomNode: { customField: string } })` for user overrides.
312
+
313
+ ---
314
+
315
+ ## Choosing: Workflow vs PromptBuilder
316
+
317
+ | Criterion | Prefer Workflow | Prefer PromptBuilder |
318
+ | --------- | --------------- | -------------------- |
319
+ | Starting point | You already have a working JSON graph | You need to assemble nodes programmatically |
320
+ | Change pattern | Tweak a handful of numeric/text inputs | Add/remove/re‑wire nodes dynamically |
321
+ | Output declaration | Simple image node aliases | Complex multi‑node mapping / conditional outputs |
322
+ | Validation needs | Light (auto collect `SaveImage`) | Strong: explicit mapping + cycle checks |
323
+ | Type ergonomics | Progressive result typing via `.output()` + heuristics | Fully explicit generic parameters on construction |
324
+ | Autocomplete | Sampler / scheduler (augmented mode) | Input & output alias keys / builder fluency |
325
+ | Serialization | Not needed / reuse same base JSON | Need to persist & replay builder state |
326
+ | Scheduling | Direct `api.run(wf)` | Usually wrapped in `CallWrapper` (or converted later) |
327
+ | Learning curve | Minimal (few fluent methods) | Slightly higher (need to map inputs / outputs) |
328
+ | Migration path | Can drop down later to builder if requirements grow | Can export to JSON & wrap with `Workflow.from(...)` for simpler tweaking |
329
+
330
+ Rule of thumb: start with `Workflow`. Move to `PromptBuilder` when you feel friction needing structural graph edits or stronger pre‑submit validation.
331
+
332
+
333
+ Pool variant (experimental):
334
+
335
+ ```ts
336
+ import { ComfyApi, ComfyPool, Workflow } from 'comfyui-node';
337
+ import BaseWorkflow from './example-txt2img-workflow.json';
338
+
339
+ const pool = new ComfyPool([
340
+ new ComfyApi('http://localhost:8188'),
341
+ new ComfyApi('http://localhost:8189')
342
+ ]);
343
+
344
+ const wf = Workflow.from(BaseWorkflow)
345
+ .set('6.inputs.text', 'A macro photo of a dewdrop on a leaf')
346
+ .output('9');
347
+
348
+ // Run using one specific API (pool provided for scheduling context)
349
+ const api2 = pool.clients[0];
350
+ const job2 = await api2.run(wf, { pool });
351
+ await job2.done();
352
+ ```
353
+
354
+ Notes:
355
+
356
+ - Experimental surface: event names / helpers may refine before a stable minor release.
357
+
95
358
  ### PromptBuilder Validation & Serialization
96
359
 
97
360
  `PromptBuilder` now includes optional robustness helpers you can invoke before submission:
@@ -130,6 +393,60 @@ function safeBuild(wf: any) {
130
393
  }
131
394
  ```
132
395
 
396
+ ## Result Object Anatomy
397
+
398
+ All high‑level executions (`api.run(wf)` / `runWorkflow` / `runAndWait`) ultimately resolve to an object merging:
399
+
400
+ 1. Declared / inferred output aliases (each key value is the raw node output JSON for that node)
401
+ 2. Heuristic shape hints (currently only augmenting `SaveImage*` & `KSampler` nodes with friendly nested fields)
402
+ 3. Metadata fields: `_promptId`, `_nodes`, `_aliases`, `_autoSeeds`
403
+
404
+ Conceptual shape:
405
+
406
+ ```ts
407
+ type WorkflowResult = {
408
+ // Your keys:
409
+ [aliasOrNodeId: string]: any; // each node's output blob (heuristically narrowed)
410
+ // Metadata:
411
+ _promptId?: string;
412
+ _nodes?: string[]; // collected node ids
413
+ _aliases?: Record<string,string>; // nodeId -> alias
414
+ _autoSeeds?: Record<string,number>; // nodeId -> randomized seed (when -1 sentinel used)
415
+ };
416
+ ```
417
+
418
+ Example with heuristics:
419
+
420
+ ```ts
421
+ const wf = Workflow.fromAugmented(json)
422
+ .output('gallery:SAVE_IMAGE')
423
+ .output('sampler:KSampler');
424
+ type R = ReturnType<typeof wf.typedResult>; // => { gallery: { images?: any[] }; sampler: { samples?: any }; _promptId?: ... }
425
+ ```
426
+
427
+ Heuristics are intentionally shallow – they provide just enough structure for IDE discovery without locking you into specific upstream node versions. Missing shape? You still get the alias key with `any` type; open a PR to extend the mapping.
428
+
429
+ Access patterns:
430
+
431
+ ```ts
432
+ const job = await api.run(wf);
433
+ job.on('output', id => console.log('node completed', id));
434
+ const res = await job.done();
435
+ console.log(res._promptId, Object.keys(res));
436
+ for (const img of (res.gallery?.images || [])) {
437
+ console.log(api.ext.file.getPathImage(img));
438
+ }
439
+ ```
440
+
441
+ If you need a stable exported type for consumers:
442
+
443
+ ```ts
444
+ export type GenerationResult = ReturnType<typeof wf.typedResult>;
445
+ ```
446
+
447
+ Changing outputs later? Re‑generate the type after adding the new `.output()` call.
448
+
449
+
133
450
  ## Multi-Instance Pool
134
451
 
135
452
  `ComfyPool` provides weighted job scheduling & automatic client selection across multiple ComfyUI instances. It is transport‑agnostic and only relies on the standard `ComfyApi` event surface.
@@ -147,33 +464,154 @@ function safeBuild(wf: any) {
147
464
  ```ts
148
465
  import { ComfyApi, ComfyPool, EQueueMode, CallWrapper, PromptBuilder, seed } from "comfyui-node";
149
466
  import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
467
+ // ... pool basic example content (see earlier dedicated Workflow section for high-level abstraction)
468
+ ```
469
+
470
+ Pool variant (experimental):
471
+
472
+ ```ts
473
+ import { ComfyApi, ComfyPool, Workflow } from 'comfyui-node';
474
+ import BaseWorkflow from './example-txt2img-workflow.json';
150
475
 
151
- // Create two API clients (auth / headers etc still work as normal)
152
476
  const pool = new ComfyPool([
153
- new ComfyApi("http://localhost:8188"),
154
- new ComfyApi("http://localhost:8189")
155
- ]); // defaults to PICK_ZERO
156
-
157
- // A single generation task (returns file paths for convenience)
158
- const generate = async (api: ComfyApi) => {
159
- const wf = new PromptBuilder(ExampleTxt2ImgWorkflow,["positive","checkpoint","seed"],["images"])
160
- .setInputNode("checkpoint","4.inputs.ckpt_name")
161
- .setInputNode("seed","3.inputs.seed")
162
- .setInputNode("positive","6.inputs.text")
163
- .setOutputNode("images","9")
164
- .input("checkpoint","SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
165
- .input("seed", seed())
166
- .input("positive","A close up picture of a cat");
167
- return await new Promise<string[]>(resolve => {
168
- new CallWrapper(api, wf)
169
- .onFinished(data => resolve(data.images?.images.map((img: any) => api.ext.file.getPathImage(img)) || []))
170
- .run();
171
- });
172
- };
477
+ new ComfyApi('http://localhost:8188'),
478
+ new ComfyApi('http://localhost:8189')
479
+ ]);
480
+
481
+ const wf = Workflow.from(BaseWorkflow)
482
+ .set('6.inputs.text', 'A macro photo of a dewdrop on a leaf')
483
+ .output('9');
484
+
485
+ // Run using one specific API (pool provided for scheduling context)
486
+ const api = pool.clients[0];
487
+ const job = await api.run(wf, { pool });
488
+ await job.done();
489
+ ```
490
+
491
+ Notes:
492
+
493
+ - Experimental surface: event names / helpers may refine before a stable minor release.
494
+ - Falls back to `SaveImage` detection if you omit `output(...)`.
495
+ - For advanced validation, serialization, or complex key mapping prefer `PromptBuilder`.
496
+
497
+ ---
498
+
499
+ ## High‑Level Workflow Tutorial (New Users of This SDK)
500
+
501
+ Audience: You already understand ComfyUI graphs & node JSON, but are new to this TypeScript SDK.
502
+
503
+ Goals after this section you can: (a) clone a base workflow, (b) modify its parameters, (c) name your desired outputs, (d) track progress & previews, and (e) retrieve final image paths – with minimal boilerplate.
504
+
505
+ ### 1. Prepare a Base Workflow JSON
506
+
507
+ Export or copy a working ComfyUI txt2img graph (e.g. the one in `test/example-txt2img-workflow.json`). Ensure you know the node ID of the final `SaveImage` (here we assume `9`).
508
+
509
+ ### 2. Initialize the API
510
+
511
+ `api.ready()` handles connection & feature probing. It is idempotent (can be safely called multiple times). You can override the host using `COMFY_HOST`.
512
+
513
+ ### 3. Mutate Parameters & Declare Outputs
514
+
515
+ Use `.set('<nodeId>.inputs.<field>', value)` to change values. Call `.output('alias:nodeId')` to collect that node's result under a friendly key (`alias`). If you omit alias (`.output('9')`) the key will be the node ID. If you omit all outputs the SDK tries to collect every `SaveImage` node automatically.
516
+
517
+ Auto seed: If any node has an input field literally named `seed` with value `-1`, the SDK will replace it with a random 32‑bit integer before submission and expose the mapping in the final result under `_autoSeeds` (object keyed by node id). This lets you keep templates with `-1` sentinel for “random every run”.
518
+
519
+ ### 4. Run & Observe Progress
520
+
521
+ `api.run(workflow, { autoDestroy: true })` executes and (optionally) closes underlying sockets once finished/failed so the process can exit without manual cleanup. The returned `WorkflowJob` is an EventEmitter‑like object AND a Promise: `await job` works just like `await job.done()`.
522
+
523
+ ### 5. Extract Image Paths
524
+
525
+ Final structure includes your alias keys plus `_promptId`, `_nodes` and `_aliases` metadata. Use `api.ext.file.getPathImage(imageInfo)` to build a fetchable URL.
526
+
527
+ ### Complete Example
528
+
529
+ ```ts
530
+ import { ComfyApi, Workflow } from 'comfyui-node';
531
+ import BaseWorkflow from './example-txt2img-workflow.json';
532
+
533
+ async function main() {
534
+ const api = await new ComfyApi(process.env.COMFY_HOST || 'http://127.0.0.1:8188').ready();
535
+
536
+ const wf = Workflow.from(BaseWorkflow)
537
+ .set('4.inputs.ckpt_name', process.env.COMFY_MODEL || 'SDXL/realvisxlV40_v40LightningBakedvae.safetensors')
538
+ .set('6.inputs.text', 'A dramatic cinematic landscape, volumetric light')
539
+ .set('7.inputs.text', 'text, watermark')
540
+ .set('3.inputs.seed', Math.floor(Math.random() * 10_000_000))
541
+ .set('3.inputs.steps', 8)
542
+ .set('3.inputs.cfg', 2)
543
+ .set('3.inputs.sampler_name', 'dpmpp_sde')
544
+ .set('3.inputs.scheduler', 'sgm_uniform')
545
+ .set('5.inputs.width', 1024)
546
+ .set('5.inputs.height', 1024)
547
+ .output('images:9'); // alias 'images' -> node 9
548
+
549
+ const job = await api.runWorkflow(wf, { autoDestroy: true });
550
+
551
+ job
552
+ .on('pending', id => console.log('[queue]', id))
553
+ .on('start', id => console.log('[start]', id))
554
+ .on('progress_pct', pct => process.stdout.write(`\rprogress ${pct}% `))
555
+ .on('preview', blob => console.log('\npreview frame bytes=', blob.size))
556
+ .on('failed', err => console.error('\nerror', err));
557
+
558
+ const result = await job; // or await job.done();
559
+ console.log('\nPrompt ID:', result._promptId);
560
+ for (const img of (result.images?.images || [])) {
561
+ console.log('image path:', api.ext.file.getPathImage(img));
562
+ }
563
+ }
564
+
565
+ main().catch(e => { console.error(e); process.exit(1); });
566
+ ```
567
+
568
+ ### Key Options Recap
569
+
570
+ | Option | Where | Purpose |
571
+ | ------ | ----- | ------- |
572
+ | `autoDestroy` | `api.run(...)` | Automatically `destroy()` the client on finish/fail |
573
+ | `includeOutputs` | `api.run(wf,{ includeOutputs:['9'] })` | Force extra node IDs (in addition to `.output(...)`) |
574
+ | `pool` | (advanced) | Execute through a `ComfyPool` for multi‑instance scheduling |
575
+
576
+ ### Event Cheat Sheet (WorkflowJob)
577
+
578
+ | Event | Payload | Description |
579
+ | ----- | ------- | ----------- |
580
+ | `pending` | promptId | Enqueued, waiting to start |
581
+ | `start` | promptId | Execution began |
582
+ | `progress` | raw `{ value,max }` | Low‑level progress data |
583
+ | `progress_pct` | number (0‑100) | Deduped integer percentage (fires on change) |
584
+ | `preview` | `Blob` | Live image preview frame |
585
+ | `output` | nodeId | Partial node output arrived |
586
+ | `finished` | final object | All requested outputs resolved |
587
+ | `failed` | `Error` | Execution failed / interrupted |
588
+
589
+ ### Execution Flow & Await Semantics
590
+
591
+ `await api.run(wf)` resolves AFTER the job has been accepted (queued) and returns a `WorkflowJob` handle you can attach events to. You then explicitly `await job.done()` for final outputs.
592
+
593
+ ```ts
594
+ const job = await api.run(wf); // acceptance barrier reached -> you have prompt id via 'pending' event
595
+ job
596
+ .on('progress_pct', pct => console.log('progress', pct))
597
+ .on('preview', blob => console.log('preview frame', blob.size));
598
+
599
+ const outputs = await job.done(); // final mapped outputs + metadata
600
+ ```
601
+
602
+ This two‑stage await keeps early feedback (events available immediately after acceptance) while still letting you write linear code for final result consumption.
603
+
604
+ Auto‑generated metadata keys:
605
+
606
+ | Key | Meaning |
607
+ | --- | ------- |
608
+ | `_promptId` | Server prompt id assigned |
609
+ | `_nodes` | Array of collected node ids |
610
+ | `_aliases` | Mapping nodeId -> alias (where provided) |
611
+ | `_autoSeeds` | Mapping nodeId -> randomized seed (only when you used -1 sentinel) |
612
+
613
+ ---
173
614
 
174
- // Dispatch 3 parallel generations across the pool
175
- const results = await pool.batch(Array(3).fill(generate));
176
- console.log(results.flat());
177
615
  ```
178
616
 
179
617
  ### Job Weighting
@@ -455,6 +893,31 @@ Supporting classes:
455
893
 
456
894
  Enums & Types: `EQueueMode`, sampler / scheduler unions, `OSType`, plus exported response types found under `types/*`.
457
895
 
896
+ ## Monitoring: System vs Job Progress
897
+
898
+ "Monitoring" in this SDK refers to two unrelated event domains:
899
+
900
+ | Type | Source | Requires Extension | Events | Usage |
901
+ | ---- | ------ | ------------------ | ------ | ----- |
902
+ | System Monitoring | Crystools extension | Yes (ComfyUI-Crystools) | `system_monitor` (pool) + feature internals | Host CPU/GPU/RAM telemetry |
903
+ | Job Progress | Core ComfyUI | No | `executing`, `progress`, `executed`, `execution_success`, `execution_error`, `execution_interrupted`, `b_preview` | Per‑job progress %, live image previews |
904
+
905
+ System monitoring is toggled via env flags in the smoke script (`COMFY_MONITOR`, `COMFY_MONITOR_STRICT`, `COMFY_MONITOR_FORCE`) and is surfaced under `api.ext.monitor`.
906
+
907
+ Job progress monitoring is always active: subscribe directly (`api.on("progress", ...)`) or use higher‑level helpers:
908
+
909
+ ```ts
910
+ new CallWrapper(api, builder)
911
+ .onProgress(p => console.log(p.value, '/', p.max))
912
+ .onPreview(blob => /* show transient image */)
913
+ .onFinished(out => /* final outputs */)
914
+ .run();
915
+ ```
916
+
917
+ The published smoke test now logs job progress automatically and counts preview frames. Set `COMFY_PROGRESS_VERBOSE=1` to force log every step (not just percentage changes).
918
+
919
+ If you only need generation progress & previews you do NOT need the Crystools extension.
920
+
458
921
  ## Examples
459
922
 
460
923
  See the `examples` directory for text‑to‑image, image‑to‑image, upscaling and pool orchestration patterns.
@@ -529,10 +992,6 @@ const restored = PromptBuilder.fromJSON(snapshot)
529
992
 
530
993
  This is useful for deferred execution, cross‑process scheduling, or audit logging of the exact prompt graph sent to the server.
531
994
 
532
- ## Contributing
533
-
534
- Issues and PRs welcome. Please include focused changes and tests where sensible. Adhere to existing coding style and keep feature surfaces minimal & cohesive.
535
-
536
995
  ## Testing & Coverage
537
996
 
538
997
  This repository uses Bun's built-in test runner. Common scripts:
@@ -596,6 +1055,61 @@ bun test && bun run coverage
596
1055
 
597
1056
  before opening a PR, and prefer adding tests alongside new feature code.
598
1057
 
1058
+ ## Published Smoke Test
1059
+
1060
+ The script `scripts/published-e2e.ts` offers a zero‑config verification of the published npm artifact with **Bun auto‑install**. It dynamically imports `comfyui-node`, builds a small txt2img workflow (optionally an upscale branch), waits for completion and prints output image URLs.
1061
+
1062
+ ### Quick Run (Auto‑Install)
1063
+
1064
+ ```bash
1065
+ mkdir comfyui-node-smoke
1066
+ cd comfyui-node-smoke
1067
+ curl -o published-e2e.ts https://raw.githubusercontent.com/igorls/comfyui-node/main/scripts/published-e2e.ts
1068
+ COMFY_HOST=http://localhost:8188 bun run published-e2e.ts
1069
+ ```
1070
+
1071
+ ### Optional Explicit Install
1072
+
1073
+ ```bash
1074
+ mkdir comfyui-node-smoke
1075
+ cd comfyui-node-smoke
1076
+ bun add comfyui-node
1077
+ curl -o published-e2e.ts https://raw.githubusercontent.com/igorls/comfyui-node/main/scripts/published-e2e.ts
1078
+ COMFY_HOST=http://localhost:8188 bun run published-e2e.ts
1079
+ ```
1080
+
1081
+ ### Environment Variables
1082
+
1083
+ | Var | Default | Purpose |
1084
+ | --- | ------- | ------- |
1085
+ | `COMFY_HOST` | `http://127.0.0.1:8188` | Base ComfyUI server |
1086
+ | `COMFY_MODEL` | `SDXL/sd_xl_base_1.0.safetensors` | Checkpoint file name (must exist) |
1087
+ | `COMFY_POSITIVE_PROMPT` | scenic base prompt | Positive text |
1088
+ | `COMFY_NEGATIVE_PROMPT` | `text, watermark` | Negative text |
1089
+ | `COMFY_SEED` | random | Deterministic seed override |
1090
+ | `COMFY_STEPS` | `8` | Sampling steps |
1091
+ | `COMFY_CFG` | `2` | CFG scale |
1092
+ | `COMFY_SAMPLER` | `dpmpp_sde` | Sampler name |
1093
+ | `COMFY_SCHEDULER` | `sgm_uniform` | Scheduler name |
1094
+ | `COMFY_TIMEOUT_MS` | `120000` | Overall timeout (ms) |
1095
+ | `COMFY_UPSCALE` | unset | If set, adds RealESRGAN upscale branch |
1096
+ | `COMFY_MONITOR` | unset | If set, attempt to enable Crystools system monitor & log first event |
1097
+ | `COMFY_MONITOR_STRICT` | unset | With monitor enabled, fail (exit 5) if no events received |
1098
+
1099
+ Exit codes: 0 success, 1 import failure, 2 timeout, 3 enqueue failure, 4 other error, 5 monitor strict failure.
1100
+
1101
+ ### Rationale
1102
+
1103
+ Ensures the published `dist` is coherent and functional in a clean consumer environment; can later be wired into CI behind an opt‑in flag (e.g. `E2E_PUBLISHED=1`).
1104
+
1105
+ ### Future
1106
+
1107
+ Possible enhancement: GitHub Action that spins up a ComfyUI container, runs the smoke test, and archives generated images as artifacts.
1108
+
1109
+ ## Contributing
1110
+
1111
+ Issues and PRs welcome. Please include focused changes and tests where sensible. Adhere to existing coding style and keep feature surfaces minimal & cohesive.
1112
+
599
1113
  ## License
600
1114
 
601
1115
  MIT – see `LICENSE`.