@happyvertical/comfyui 0.74.8
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/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/README.md +283 -0
- package/dist/index.d.ts +506 -0
- package/dist/index.js +478 -0
- package/dist/index.js.map +1 -0
- package/metadata.json +29 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { createLogger } from "@happyvertical/logger";
|
|
2
|
+
import { ValidationError } from "@happyvertical/utils";
|
|
3
|
+
class ComfyUIClient {
|
|
4
|
+
options;
|
|
5
|
+
ws = null;
|
|
6
|
+
logger;
|
|
7
|
+
clientId;
|
|
8
|
+
messageHandlers = /* @__PURE__ */ new Map();
|
|
9
|
+
reconnectAttempts = 0;
|
|
10
|
+
isConnecting = false;
|
|
11
|
+
username;
|
|
12
|
+
password;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = {
|
|
15
|
+
url: options.url ?? "http://localhost:8188",
|
|
16
|
+
timeout: options.timeout ?? 6e5,
|
|
17
|
+
headers: options.headers ?? {},
|
|
18
|
+
reconnect: {
|
|
19
|
+
enabled: options.reconnect?.enabled ?? true,
|
|
20
|
+
maxAttempts: options.reconnect?.maxAttempts ?? 5,
|
|
21
|
+
delay: options.reconnect?.delay ?? 1e3
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
if (options.username && options.password) {
|
|
25
|
+
this.username = options.username;
|
|
26
|
+
this.password = options.password;
|
|
27
|
+
const credentials = btoa(`${options.username}:${options.password}`);
|
|
28
|
+
this.options.headers.Authorization = `Basic ${credentials}`;
|
|
29
|
+
}
|
|
30
|
+
this.logger = createLogger({ level: "info" });
|
|
31
|
+
this.clientId = crypto.randomUUID();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the HTTP base URL
|
|
35
|
+
*/
|
|
36
|
+
get httpUrl() {
|
|
37
|
+
return this.options.url;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the WebSocket URL
|
|
41
|
+
*/
|
|
42
|
+
get wsUrl() {
|
|
43
|
+
const url = new URL(this.options.url);
|
|
44
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
45
|
+
url.pathname = "/ws";
|
|
46
|
+
url.searchParams.set("clientId", this.clientId);
|
|
47
|
+
if (this.username && this.password) {
|
|
48
|
+
url.username = this.username;
|
|
49
|
+
url.password = this.password;
|
|
50
|
+
}
|
|
51
|
+
return url.toString();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Connect to the ComfyUI WebSocket
|
|
55
|
+
*/
|
|
56
|
+
async connect() {
|
|
57
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (this.isConnecting) {
|
|
61
|
+
await new Promise((resolve, reject) => {
|
|
62
|
+
const checkConnection = setInterval(() => {
|
|
63
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
64
|
+
clearInterval(checkConnection);
|
|
65
|
+
resolve();
|
|
66
|
+
} else if (!this.isConnecting) {
|
|
67
|
+
clearInterval(checkConnection);
|
|
68
|
+
reject(new Error("Connection failed"));
|
|
69
|
+
}
|
|
70
|
+
}, 100);
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.isConnecting = true;
|
|
75
|
+
try {
|
|
76
|
+
await this.establishConnection();
|
|
77
|
+
this.reconnectAttempts = 0;
|
|
78
|
+
} finally {
|
|
79
|
+
this.isConnecting = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Establish WebSocket connection
|
|
84
|
+
*/
|
|
85
|
+
async establishConnection() {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
try {
|
|
88
|
+
this.ws = new WebSocket(this.wsUrl);
|
|
89
|
+
const timeout = setTimeout(() => {
|
|
90
|
+
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
91
|
+
this.ws?.close();
|
|
92
|
+
reject(new Error("WebSocket connection timeout"));
|
|
93
|
+
}
|
|
94
|
+
}, 1e4);
|
|
95
|
+
this.ws.onopen = () => {
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
this.logger.info("Connected to ComfyUI");
|
|
98
|
+
resolve();
|
|
99
|
+
};
|
|
100
|
+
this.ws.onerror = (error) => {
|
|
101
|
+
clearTimeout(timeout);
|
|
102
|
+
this.logger.error("WebSocket error", { error });
|
|
103
|
+
reject(new Error("WebSocket connection error"));
|
|
104
|
+
};
|
|
105
|
+
this.ws.onclose = () => {
|
|
106
|
+
this.logger.info("Disconnected from ComfyUI");
|
|
107
|
+
this.handleDisconnect();
|
|
108
|
+
};
|
|
109
|
+
this.ws.onmessage = (event) => {
|
|
110
|
+
this.handleMessage(event.data);
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
reject(error);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Handle WebSocket disconnection
|
|
119
|
+
*/
|
|
120
|
+
async handleDisconnect() {
|
|
121
|
+
const reconnect = this.options.reconnect;
|
|
122
|
+
if (!reconnect.enabled) return;
|
|
123
|
+
if (this.reconnectAttempts < reconnect.maxAttempts) {
|
|
124
|
+
this.reconnectAttempts++;
|
|
125
|
+
this.logger.info(
|
|
126
|
+
`Attempting reconnection ${this.reconnectAttempts}/${reconnect.maxAttempts}`
|
|
127
|
+
);
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, reconnect.delay));
|
|
129
|
+
try {
|
|
130
|
+
await this.connect();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.error("Reconnection failed", { error });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Handle incoming WebSocket messages
|
|
138
|
+
*/
|
|
139
|
+
handleMessage(data) {
|
|
140
|
+
try {
|
|
141
|
+
const message = JSON.parse(data);
|
|
142
|
+
const event = this.parseProgressEvent(message);
|
|
143
|
+
if (event?.promptId) {
|
|
144
|
+
const handlers = this.messageHandlers.get(event.promptId);
|
|
145
|
+
if (handlers) {
|
|
146
|
+
for (const handler of handlers) {
|
|
147
|
+
handler(event);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.logger.warn("Failed to parse WebSocket message", { error, data });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse a WebSocket message into a ProgressEvent
|
|
157
|
+
*/
|
|
158
|
+
parseProgressEvent(message) {
|
|
159
|
+
switch (message.type) {
|
|
160
|
+
case "executing":
|
|
161
|
+
return {
|
|
162
|
+
type: "executing",
|
|
163
|
+
nodeId: message.data?.node,
|
|
164
|
+
promptId: message.data?.prompt_id
|
|
165
|
+
};
|
|
166
|
+
case "progress":
|
|
167
|
+
return {
|
|
168
|
+
type: "progress",
|
|
169
|
+
value: message.data?.value,
|
|
170
|
+
max: message.data?.max,
|
|
171
|
+
promptId: message.data?.prompt_id
|
|
172
|
+
};
|
|
173
|
+
case "execution_cached":
|
|
174
|
+
return {
|
|
175
|
+
type: "execution_cached",
|
|
176
|
+
promptId: message.data?.prompt_id
|
|
177
|
+
};
|
|
178
|
+
case "executed":
|
|
179
|
+
return {
|
|
180
|
+
type: "executed",
|
|
181
|
+
nodeId: message.data?.node,
|
|
182
|
+
promptId: message.data?.prompt_id,
|
|
183
|
+
output: message.data?.output
|
|
184
|
+
};
|
|
185
|
+
case "execution_error":
|
|
186
|
+
return {
|
|
187
|
+
type: "execution_error",
|
|
188
|
+
promptId: message.data?.prompt_id,
|
|
189
|
+
error: message.data?.exception_message
|
|
190
|
+
};
|
|
191
|
+
default:
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Disconnect from ComfyUI
|
|
197
|
+
*/
|
|
198
|
+
async disconnect() {
|
|
199
|
+
this.options.reconnect.enabled = false;
|
|
200
|
+
this.messageHandlers.clear();
|
|
201
|
+
if (this.ws) {
|
|
202
|
+
this.ws.close();
|
|
203
|
+
this.ws = null;
|
|
204
|
+
}
|
|
205
|
+
this.logger.info("Disconnected from ComfyUI");
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Make an HTTP request to ComfyUI
|
|
209
|
+
*/
|
|
210
|
+
async request(path, options = {}) {
|
|
211
|
+
const url = `${this.httpUrl}${path}`;
|
|
212
|
+
const headers = {
|
|
213
|
+
...this.options.headers
|
|
214
|
+
};
|
|
215
|
+
if (options.body && !options.formData) {
|
|
216
|
+
headers["Content-Type"] = "application/json";
|
|
217
|
+
}
|
|
218
|
+
const response = await fetch(url, {
|
|
219
|
+
method: options.method ?? "GET",
|
|
220
|
+
headers,
|
|
221
|
+
body: options.formData ?? (options.body ? JSON.stringify(options.body) : void 0),
|
|
222
|
+
signal: AbortSignal.timeout(this.options.timeout)
|
|
223
|
+
});
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
const errorText = await response.text();
|
|
226
|
+
throw new Error(
|
|
227
|
+
`ComfyUI request failed: ${response.status} ${errorText}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return response.json();
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Queue a workflow for execution
|
|
234
|
+
*
|
|
235
|
+
* @param workflow - The ComfyUI workflow to execute
|
|
236
|
+
* @param clientId - Optional client ID (uses default if not provided)
|
|
237
|
+
* @returns Promise resolving to prompt result with ID
|
|
238
|
+
*/
|
|
239
|
+
async queuePrompt(workflow, clientId) {
|
|
240
|
+
const response = await this.request("/prompt", {
|
|
241
|
+
method: "POST",
|
|
242
|
+
body: {
|
|
243
|
+
prompt: workflow,
|
|
244
|
+
client_id: clientId ?? this.clientId
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
promptId: response.prompt_id,
|
|
249
|
+
number: response.number,
|
|
250
|
+
nodeErrors: response.node_errors
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Subscribe to progress events for a prompt
|
|
255
|
+
*
|
|
256
|
+
* @param promptId - The prompt ID to monitor
|
|
257
|
+
* @param handler - Callback for progress events
|
|
258
|
+
* @returns Unsubscribe function
|
|
259
|
+
*/
|
|
260
|
+
onProgress(promptId, handler) {
|
|
261
|
+
if (!this.messageHandlers.has(promptId)) {
|
|
262
|
+
this.messageHandlers.set(promptId, /* @__PURE__ */ new Set());
|
|
263
|
+
}
|
|
264
|
+
this.messageHandlers.get(promptId)?.add(handler);
|
|
265
|
+
return () => {
|
|
266
|
+
this.messageHandlers.get(promptId)?.delete(handler);
|
|
267
|
+
if (this.messageHandlers.get(promptId)?.size === 0) {
|
|
268
|
+
this.messageHandlers.delete(promptId);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Wait for a prompt to complete
|
|
274
|
+
*
|
|
275
|
+
* @param promptId - The prompt ID to wait for
|
|
276
|
+
* @param options - Wait options including progress callback
|
|
277
|
+
* @returns Promise resolving to the history entry
|
|
278
|
+
*/
|
|
279
|
+
async waitForCompletion(promptId, options = {}) {
|
|
280
|
+
const timeout = options.timeout ?? this.options.timeout;
|
|
281
|
+
const startTime = Date.now();
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
let resolved = false;
|
|
284
|
+
const timeoutId = timeout > 0 ? setTimeout(() => {
|
|
285
|
+
if (!resolved) {
|
|
286
|
+
resolved = true;
|
|
287
|
+
unsubscribe();
|
|
288
|
+
reject(new Error(`Timeout waiting for prompt ${promptId}`));
|
|
289
|
+
}
|
|
290
|
+
}, timeout) : null;
|
|
291
|
+
const unsubscribe = this.onProgress(promptId, async (event) => {
|
|
292
|
+
options.onProgress?.(event);
|
|
293
|
+
if (event.type === "executed" && event.nodeId === null) {
|
|
294
|
+
if (!resolved) {
|
|
295
|
+
resolved = true;
|
|
296
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
297
|
+
unsubscribe();
|
|
298
|
+
try {
|
|
299
|
+
const history = await this.getHistory(promptId);
|
|
300
|
+
resolve(history);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
reject(error);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} else if (event.type === "execution_error") {
|
|
306
|
+
if (!resolved) {
|
|
307
|
+
resolved = true;
|
|
308
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
309
|
+
unsubscribe();
|
|
310
|
+
reject(new Error(`Workflow execution error: ${event.error}`));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
const pollHistory = async () => {
|
|
315
|
+
while (!resolved && (timeout === 0 || Date.now() - startTime < timeout)) {
|
|
316
|
+
await new Promise((r) => setTimeout(r, options.pollInterval ?? 1e3));
|
|
317
|
+
if (resolved) break;
|
|
318
|
+
try {
|
|
319
|
+
const history = await this.getHistory(promptId);
|
|
320
|
+
if (history.status.completed) {
|
|
321
|
+
if (!resolved) {
|
|
322
|
+
resolved = true;
|
|
323
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
324
|
+
unsubscribe();
|
|
325
|
+
resolve(history);
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
pollHistory().catch(() => {
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Get history for a specific prompt
|
|
339
|
+
*
|
|
340
|
+
* @param promptId - The prompt ID to get history for
|
|
341
|
+
* @returns Promise resolving to the history entry
|
|
342
|
+
*/
|
|
343
|
+
async getHistory(promptId) {
|
|
344
|
+
const response = await this.request(
|
|
345
|
+
`/history/${promptId}`
|
|
346
|
+
);
|
|
347
|
+
const entry = response[promptId];
|
|
348
|
+
if (!entry) {
|
|
349
|
+
throw new ValidationError(`History not found for prompt ${promptId}`);
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
promptId,
|
|
353
|
+
status: entry.status,
|
|
354
|
+
outputs: entry.outputs,
|
|
355
|
+
prompt: entry.prompt
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Download an output file from ComfyUI
|
|
360
|
+
*
|
|
361
|
+
* @param filename - Name of the file to download
|
|
362
|
+
* @param subfolder - Subfolder where file is located
|
|
363
|
+
* @param type - Type of file location ('output', 'temp', or 'input')
|
|
364
|
+
* @returns Promise resolving to the file buffer
|
|
365
|
+
*/
|
|
366
|
+
async downloadOutput(filename, subfolder = "", type = "output") {
|
|
367
|
+
const params = new URLSearchParams({
|
|
368
|
+
filename,
|
|
369
|
+
subfolder,
|
|
370
|
+
type
|
|
371
|
+
});
|
|
372
|
+
const url = `${this.httpUrl}/view?${params}`;
|
|
373
|
+
const response = await fetch(url, {
|
|
374
|
+
headers: this.options.headers,
|
|
375
|
+
signal: AbortSignal.timeout(this.options.timeout)
|
|
376
|
+
});
|
|
377
|
+
if (!response.ok) {
|
|
378
|
+
throw new Error(`Failed to download file: ${response.status}`);
|
|
379
|
+
}
|
|
380
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
381
|
+
return Buffer.from(arrayBuffer);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Upload a file to ComfyUI
|
|
385
|
+
*
|
|
386
|
+
* @param file - File buffer to upload
|
|
387
|
+
* @param filename - Name for the file
|
|
388
|
+
* @param type - Where to upload ('input' or 'temp')
|
|
389
|
+
* @param overwrite - Whether to overwrite existing file
|
|
390
|
+
* @returns Promise resolving to upload result
|
|
391
|
+
*/
|
|
392
|
+
async uploadFile(file, filename, type = "input", overwrite = false) {
|
|
393
|
+
const formData = new FormData();
|
|
394
|
+
formData.append("image", new Blob([new Uint8Array(file)]), filename);
|
|
395
|
+
formData.append("type", type);
|
|
396
|
+
formData.append("overwrite", String(overwrite));
|
|
397
|
+
const response = await this.request("/upload/image", {
|
|
398
|
+
method: "POST",
|
|
399
|
+
formData
|
|
400
|
+
});
|
|
401
|
+
return {
|
|
402
|
+
name: response.name,
|
|
403
|
+
subfolder: response.subfolder,
|
|
404
|
+
type: response.type
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get system statistics from ComfyUI
|
|
409
|
+
*
|
|
410
|
+
* @returns Promise resolving to system stats
|
|
411
|
+
*/
|
|
412
|
+
async getSystemStats() {
|
|
413
|
+
return this.request("/system_stats");
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get the current queue status
|
|
417
|
+
*
|
|
418
|
+
* @returns Promise resolving to queue status
|
|
419
|
+
*/
|
|
420
|
+
async getQueue() {
|
|
421
|
+
const response = await this.request("/queue");
|
|
422
|
+
return {
|
|
423
|
+
running: response.queue_running,
|
|
424
|
+
pending: response.queue_pending
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Interrupt the current execution
|
|
429
|
+
*/
|
|
430
|
+
async interrupt() {
|
|
431
|
+
await this.request("/interrupt", { method: "POST" });
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Clear the execution queue
|
|
435
|
+
*/
|
|
436
|
+
async clearQueue() {
|
|
437
|
+
await this.request("/queue", {
|
|
438
|
+
method: "POST",
|
|
439
|
+
body: { clear: true }
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Delete a specific item from the queue
|
|
444
|
+
*
|
|
445
|
+
* @param promptId - The prompt ID to delete
|
|
446
|
+
*/
|
|
447
|
+
async deleteFromQueue(promptId) {
|
|
448
|
+
await this.request("/queue", {
|
|
449
|
+
method: "POST",
|
|
450
|
+
body: { delete: [promptId] }
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function injectWorkflowParams(workflow, mapping, values) {
|
|
455
|
+
const result = JSON.parse(JSON.stringify(workflow));
|
|
456
|
+
for (const [key, nodeId] of Object.entries(mapping)) {
|
|
457
|
+
if (nodeId && values[key] !== void 0 && result[nodeId]) {
|
|
458
|
+
const node = result[nodeId];
|
|
459
|
+
const inputFieldMap = {
|
|
460
|
+
seedImage: "image",
|
|
461
|
+
audioFile: "audio",
|
|
462
|
+
baseVideo: "video",
|
|
463
|
+
prompt: "text",
|
|
464
|
+
negativePrompt: "text"
|
|
465
|
+
};
|
|
466
|
+
const inputField = inputFieldMap[key] ?? key;
|
|
467
|
+
if (node.inputs && inputField in node.inputs) {
|
|
468
|
+
node.inputs[inputField] = values[key];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return result;
|
|
473
|
+
}
|
|
474
|
+
export {
|
|
475
|
+
ComfyUIClient,
|
|
476
|
+
injectWorkflowParams
|
|
477
|
+
};
|
|
478
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/client.ts"],"sourcesContent":["/**\n * ComfyUI Client\n *\n * WebSocket and REST client for interacting with ComfyUI servers.\n * Supports workflow execution, progress monitoring, and file management.\n */\n\nimport { createLogger, type Logger } from '@happyvertical/logger';\nimport { ValidationError } from '@happyvertical/utils';\n\nimport type {\n ComfyUIClientOptions,\n ComfyWorkflow,\n HistoryEntry,\n NodeMapping,\n ProgressEvent,\n PromptResult,\n QueueStatus,\n ResolvedClientOptions,\n SystemStats,\n UploadResult,\n WaitOptions,\n} from './types.js';\n\n/**\n * WebSocket message types from ComfyUI\n */\ninterface WSMessage {\n type: string;\n data?: unknown;\n}\n\n/**\n * ComfyUI client for workflow execution and management\n *\n * @example\n * ```typescript\n * import { ComfyUIClient } from '@happyvertical/comfyui';\n *\n * const client = new ComfyUIClient({\n * url: 'http://localhost:8188',\n * });\n *\n * await client.connect();\n *\n * const result = await client.queuePrompt(workflow);\n * await client.waitForCompletion(result.promptId, {\n * onProgress: (event) => console.log(`Progress: ${event.value}%`),\n * });\n *\n * const output = await client.downloadOutput('video.mp4');\n * await client.disconnect();\n * ```\n */\nexport class ComfyUIClient {\n private options: ResolvedClientOptions;\n private ws: WebSocket | null = null;\n private logger: Logger;\n private clientId: string;\n private messageHandlers: Map<string, Set<(event: ProgressEvent) => void>> =\n new Map();\n private reconnectAttempts = 0;\n private isConnecting = false;\n private username?: string;\n private password?: string;\n\n constructor(options: ComfyUIClientOptions = {}) {\n this.options = {\n url: options.url ?? 'http://localhost:8188',\n timeout: options.timeout ?? 600000,\n headers: options.headers ?? {},\n reconnect: {\n enabled: options.reconnect?.enabled ?? true,\n maxAttempts: options.reconnect?.maxAttempts ?? 5,\n delay: options.reconnect?.delay ?? 1000,\n },\n };\n\n if (options.username && options.password) {\n this.username = options.username;\n this.password = options.password;\n const credentials = btoa(`${options.username}:${options.password}`);\n this.options.headers.Authorization = `Basic ${credentials}`;\n }\n\n this.logger = createLogger({ level: 'info' });\n this.clientId = crypto.randomUUID();\n }\n\n /**\n * Get the HTTP base URL\n */\n private get httpUrl(): string {\n return this.options.url;\n }\n\n /**\n * Get the WebSocket URL\n */\n private get wsUrl(): string {\n const url = new URL(this.options.url);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = '/ws';\n url.searchParams.set('clientId', this.clientId);\n if (this.username && this.password) {\n url.username = this.username;\n url.password = this.password;\n }\n return url.toString();\n }\n\n /**\n * Connect to the ComfyUI WebSocket\n */\n async connect(): Promise<void> {\n if (this.ws?.readyState === WebSocket.OPEN) {\n return;\n }\n\n if (this.isConnecting) {\n // Wait for existing connection attempt\n await new Promise<void>((resolve, reject) => {\n const checkConnection = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n clearInterval(checkConnection);\n resolve();\n } else if (!this.isConnecting) {\n clearInterval(checkConnection);\n reject(new Error('Connection failed'));\n }\n }, 100);\n });\n return;\n }\n\n this.isConnecting = true;\n\n try {\n await this.establishConnection();\n this.reconnectAttempts = 0;\n } finally {\n this.isConnecting = false;\n }\n }\n\n /**\n * Establish WebSocket connection\n */\n private async establishConnection(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(this.wsUrl);\n\n const timeout = setTimeout(() => {\n if (this.ws?.readyState !== WebSocket.OPEN) {\n this.ws?.close();\n reject(new Error('WebSocket connection timeout'));\n }\n }, 10000);\n\n this.ws.onopen = () => {\n clearTimeout(timeout);\n this.logger.info('Connected to ComfyUI');\n resolve();\n };\n\n this.ws.onerror = (error) => {\n clearTimeout(timeout);\n this.logger.error('WebSocket error', { error });\n reject(new Error('WebSocket connection error'));\n };\n\n this.ws.onclose = () => {\n this.logger.info('Disconnected from ComfyUI');\n this.handleDisconnect();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Handle WebSocket disconnection\n */\n private async handleDisconnect(): Promise<void> {\n const reconnect = this.options.reconnect;\n if (!reconnect.enabled) return;\n\n if (this.reconnectAttempts < reconnect.maxAttempts) {\n this.reconnectAttempts++;\n this.logger.info(\n `Attempting reconnection ${this.reconnectAttempts}/${reconnect.maxAttempts}`,\n );\n\n await new Promise((resolve) => setTimeout(resolve, reconnect.delay));\n\n try {\n await this.connect();\n } catch (error) {\n this.logger.error('Reconnection failed', { error });\n }\n }\n }\n\n /**\n * Handle incoming WebSocket messages\n */\n private handleMessage(data: string): void {\n try {\n const message: WSMessage = JSON.parse(data);\n const event = this.parseProgressEvent(message);\n\n if (event?.promptId) {\n const handlers = this.messageHandlers.get(event.promptId);\n if (handlers) {\n for (const handler of handlers) {\n handler(event);\n }\n }\n }\n } catch (error) {\n this.logger.warn('Failed to parse WebSocket message', { error, data });\n }\n }\n\n /**\n * Parse a WebSocket message into a ProgressEvent\n */\n private parseProgressEvent(message: WSMessage): ProgressEvent | null {\n switch (message.type) {\n case 'executing':\n return {\n type: 'executing',\n nodeId: message.data?.node,\n promptId: message.data?.prompt_id,\n };\n\n case 'progress':\n return {\n type: 'progress',\n value: message.data?.value,\n max: message.data?.max,\n promptId: message.data?.prompt_id,\n };\n\n case 'execution_cached':\n return {\n type: 'execution_cached',\n promptId: message.data?.prompt_id,\n };\n\n case 'executed':\n return {\n type: 'executed',\n nodeId: message.data?.node,\n promptId: message.data?.prompt_id,\n output: message.data?.output,\n };\n\n case 'execution_error':\n return {\n type: 'execution_error',\n promptId: message.data?.prompt_id,\n error: message.data?.exception_message,\n };\n\n default:\n return null;\n }\n }\n\n /**\n * Disconnect from ComfyUI\n */\n async disconnect(): Promise<void> {\n this.options.reconnect.enabled = false; // Prevent auto-reconnect\n this.messageHandlers.clear();\n\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n this.logger.info('Disconnected from ComfyUI');\n }\n\n /**\n * Make an HTTP request to ComfyUI\n */\n private async request<T>(\n path: string,\n options: {\n method?: 'GET' | 'POST' | 'DELETE';\n body?: unknown;\n formData?: FormData;\n } = {},\n ): Promise<T> {\n const url = `${this.httpUrl}${path}`;\n\n const headers: Record<string, string> = {\n ...this.options.headers,\n };\n\n if (options.body && !options.formData) {\n headers['Content-Type'] = 'application/json';\n }\n\n const response = await fetch(url, {\n method: options.method ?? 'GET',\n headers,\n body:\n options.formData ??\n (options.body ? JSON.stringify(options.body) : undefined),\n signal: AbortSignal.timeout(this.options.timeout),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `ComfyUI request failed: ${response.status} ${errorText}`,\n );\n }\n\n return response.json();\n }\n\n /**\n * Queue a workflow for execution\n *\n * @param workflow - The ComfyUI workflow to execute\n * @param clientId - Optional client ID (uses default if not provided)\n * @returns Promise resolving to prompt result with ID\n */\n async queuePrompt(\n workflow: ComfyWorkflow,\n clientId?: string,\n ): Promise<PromptResult> {\n const response = await this.request<{\n prompt_id: string;\n number: number;\n node_errors?: Record<string, string[]>;\n }>('/prompt', {\n method: 'POST',\n body: {\n prompt: workflow,\n client_id: clientId ?? this.clientId,\n },\n });\n\n return {\n promptId: response.prompt_id,\n number: response.number,\n nodeErrors: response.node_errors,\n };\n }\n\n /**\n * Subscribe to progress events for a prompt\n *\n * @param promptId - The prompt ID to monitor\n * @param handler - Callback for progress events\n * @returns Unsubscribe function\n */\n onProgress(\n promptId: string,\n handler: (event: ProgressEvent) => void,\n ): () => void {\n if (!this.messageHandlers.has(promptId)) {\n this.messageHandlers.set(promptId, new Set());\n }\n\n this.messageHandlers.get(promptId)?.add(handler);\n\n return () => {\n this.messageHandlers.get(promptId)?.delete(handler);\n if (this.messageHandlers.get(promptId)?.size === 0) {\n this.messageHandlers.delete(promptId);\n }\n };\n }\n\n /**\n * Wait for a prompt to complete\n *\n * @param promptId - The prompt ID to wait for\n * @param options - Wait options including progress callback\n * @returns Promise resolving to the history entry\n */\n async waitForCompletion(\n promptId: string,\n options: WaitOptions = {},\n ): Promise<HistoryEntry> {\n const timeout = options.timeout ?? this.options.timeout;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n let resolved = false;\n\n // Set up timeout\n const timeoutId =\n timeout > 0\n ? setTimeout(() => {\n if (!resolved) {\n resolved = true;\n unsubscribe();\n reject(new Error(`Timeout waiting for prompt ${promptId}`));\n }\n }, timeout)\n : null;\n\n // Subscribe to progress events\n const unsubscribe = this.onProgress(promptId, async (event) => {\n // Forward progress events\n options.onProgress?.(event);\n\n // Check for completion or error\n if (event.type === 'executed' && event.nodeId === null) {\n // Workflow completed (null nodeId means no more nodes to execute)\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n\n try {\n const history = await this.getHistory(promptId);\n resolve(history);\n } catch (error) {\n reject(error);\n }\n }\n } else if (event.type === 'execution_error') {\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n reject(new Error(`Workflow execution error: ${event.error}`));\n }\n }\n });\n\n // Fallback: poll history if WebSocket doesn't give completion\n const pollHistory = async () => {\n while (\n !resolved &&\n (timeout === 0 || Date.now() - startTime < timeout)\n ) {\n await new Promise((r) => setTimeout(r, options.pollInterval ?? 1000));\n\n if (resolved) break;\n\n try {\n const history = await this.getHistory(promptId);\n if (history.status.completed) {\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n resolve(history);\n }\n break;\n }\n } catch {\n // History not available yet, continue polling\n }\n }\n };\n\n // Start polling as fallback\n pollHistory().catch(() => {});\n });\n }\n\n /**\n * Get history for a specific prompt\n *\n * @param promptId - The prompt ID to get history for\n * @returns Promise resolving to the history entry\n */\n async getHistory(promptId: string): Promise<HistoryEntry> {\n const response = await this.request<Record<string, any>>(\n `/history/${promptId}`,\n );\n\n const entry = response[promptId];\n if (!entry) {\n throw new ValidationError(`History not found for prompt ${promptId}`);\n }\n\n return {\n promptId,\n status: entry.status,\n outputs: entry.outputs,\n prompt: entry.prompt,\n };\n }\n\n /**\n * Download an output file from ComfyUI\n *\n * @param filename - Name of the file to download\n * @param subfolder - Subfolder where file is located\n * @param type - Type of file location ('output', 'temp', or 'input')\n * @returns Promise resolving to the file buffer\n */\n async downloadOutput(\n filename: string,\n subfolder: string = '',\n type: 'output' | 'temp' | 'input' = 'output',\n ): Promise<Buffer> {\n const params = new URLSearchParams({\n filename,\n subfolder,\n type,\n });\n\n const url = `${this.httpUrl}/view?${params}`;\n const response = await fetch(url, {\n headers: this.options.headers,\n signal: AbortSignal.timeout(this.options.timeout),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download file: ${response.status}`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n /**\n * Upload a file to ComfyUI\n *\n * @param file - File buffer to upload\n * @param filename - Name for the file\n * @param type - Where to upload ('input' or 'temp')\n * @param overwrite - Whether to overwrite existing file\n * @returns Promise resolving to upload result\n */\n async uploadFile(\n file: Buffer,\n filename: string,\n type: 'input' | 'temp' = 'input',\n overwrite = false,\n ): Promise<UploadResult> {\n const formData = new FormData();\n formData.append('image', new Blob([new Uint8Array(file)]), filename);\n formData.append('type', type);\n formData.append('overwrite', String(overwrite));\n\n const response = await this.request<{\n name: string;\n subfolder: string;\n type: string;\n }>('/upload/image', {\n method: 'POST',\n formData,\n });\n\n return {\n name: response.name,\n subfolder: response.subfolder,\n type: response.type as 'input' | 'temp' | 'output',\n };\n }\n\n /**\n * Get system statistics from ComfyUI\n *\n * @returns Promise resolving to system stats\n */\n async getSystemStats(): Promise<SystemStats> {\n return this.request<SystemStats>('/system_stats');\n }\n\n /**\n * Get the current queue status\n *\n * @returns Promise resolving to queue status\n */\n async getQueue(): Promise<QueueStatus> {\n const response = await this.request<{\n queue_running: any[];\n queue_pending: any[];\n }>('/queue');\n\n return {\n running: response.queue_running,\n pending: response.queue_pending,\n };\n }\n\n /**\n * Interrupt the current execution\n */\n async interrupt(): Promise<void> {\n await this.request('/interrupt', { method: 'POST' });\n }\n\n /**\n * Clear the execution queue\n */\n async clearQueue(): Promise<void> {\n await this.request('/queue', {\n method: 'POST',\n body: { clear: true },\n });\n }\n\n /**\n * Delete a specific item from the queue\n *\n * @param promptId - The prompt ID to delete\n */\n async deleteFromQueue(promptId: string): Promise<void> {\n await this.request('/queue', {\n method: 'POST',\n body: { delete: [promptId] },\n });\n }\n}\n\n/**\n * Helper function to inject parameters into a workflow\n *\n * @param workflow - The base workflow\n * @param mapping - Node mapping defining where to inject\n * @param values - Values to inject\n * @returns Modified workflow with injected values\n */\nexport function injectWorkflowParams(\n workflow: ComfyWorkflow,\n mapping: NodeMapping,\n values: Record<string, any>,\n): ComfyWorkflow {\n const result = JSON.parse(JSON.stringify(workflow));\n\n for (const [key, nodeId] of Object.entries(mapping)) {\n if (nodeId && values[key] !== undefined && result[nodeId]) {\n // Find the appropriate input field and update it\n const node = result[nodeId];\n\n // Common input field names for different node types\n const inputFieldMap: Record<string, string> = {\n seedImage: 'image',\n audioFile: 'audio',\n baseVideo: 'video',\n prompt: 'text',\n negativePrompt: 'text',\n };\n\n const inputField = inputFieldMap[key] ?? key;\n if (node.inputs && inputField in node.inputs) {\n node.inputs[inputField] = values[key];\n }\n }\n }\n\n return result;\n}\n"],"names":[],"mappings":";;AAsDO,MAAM,cAAc;AAAA,EACjB;AAAA,EACA,KAAuB;AAAA,EACvB;AAAA,EACA;AAAA,EACA,sCACF,IAAA;AAAA,EACE,oBAAoB;AAAA,EACpB,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EAER,YAAY,UAAgC,IAAI;AAC9C,SAAK,UAAU;AAAA,MACb,KAAK,QAAQ,OAAO;AAAA,MACpB,SAAS,QAAQ,WAAW;AAAA,MAC5B,SAAS,QAAQ,WAAW,CAAA;AAAA,MAC5B,WAAW;AAAA,QACT,SAAS,QAAQ,WAAW,WAAW;AAAA,QACvC,aAAa,QAAQ,WAAW,eAAe;AAAA,QAC/C,OAAO,QAAQ,WAAW,SAAS;AAAA,MAAA;AAAA,IACrC;AAGF,QAAI,QAAQ,YAAY,QAAQ,UAAU;AACxC,WAAK,WAAW,QAAQ;AACxB,WAAK,WAAW,QAAQ;AACxB,YAAM,cAAc,KAAK,GAAG,QAAQ,QAAQ,IAAI,QAAQ,QAAQ,EAAE;AAClE,WAAK,QAAQ,QAAQ,gBAAgB,SAAS,WAAW;AAAA,IAC3D;AAEA,SAAK,SAAS,aAAa,EAAE,OAAO,QAAQ;AAC5C,SAAK,WAAW,OAAO,WAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAY,UAAkB;AAC5B,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAY,QAAgB;AAC1B,UAAM,MAAM,IAAI,IAAI,KAAK,QAAQ,GAAG;AACpC,QAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,QAAI,WAAW;AACf,QAAI,aAAa,IAAI,YAAY,KAAK,QAAQ;AAC9C,QAAI,KAAK,YAAY,KAAK,UAAU;AAClC,UAAI,WAAW,KAAK;AACpB,UAAI,WAAW,KAAK;AAAA,IACtB;AACA,WAAO,IAAI,SAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC1C;AAAA,IACF;AAEA,QAAI,KAAK,cAAc;AAErB,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,cAAM,kBAAkB,YAAY,MAAM;AACxC,cAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC1C,0BAAc,eAAe;AAC7B,oBAAA;AAAA,UACF,WAAW,CAAC,KAAK,cAAc;AAC7B,0BAAc,eAAe;AAC7B,mBAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,UACvC;AAAA,QACF,GAAG,GAAG;AAAA,MACR,CAAC;AACD;AAAA,IACF;AAEA,SAAK,eAAe;AAEpB,QAAI;AACF,YAAM,KAAK,oBAAA;AACX,WAAK,oBAAoB;AAAA,IAC3B,UAAA;AACE,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,sBAAqC;AACjD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI;AACF,aAAK,KAAK,IAAI,UAAU,KAAK,KAAK;AAElC,cAAM,UAAU,WAAW,MAAM;AAC/B,cAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC1C,iBAAK,IAAI,MAAA;AACT,mBAAO,IAAI,MAAM,8BAA8B,CAAC;AAAA,UAClD;AAAA,QACF,GAAG,GAAK;AAER,aAAK,GAAG,SAAS,MAAM;AACrB,uBAAa,OAAO;AACpB,eAAK,OAAO,KAAK,sBAAsB;AACvC,kBAAA;AAAA,QACF;AAEA,aAAK,GAAG,UAAU,CAAC,UAAU;AAC3B,uBAAa,OAAO;AACpB,eAAK,OAAO,MAAM,mBAAmB,EAAE,OAAO;AAC9C,iBAAO,IAAI,MAAM,4BAA4B,CAAC;AAAA,QAChD;AAEA,aAAK,GAAG,UAAU,MAAM;AACtB,eAAK,OAAO,KAAK,2BAA2B;AAC5C,eAAK,iBAAA;AAAA,QACP;AAEA,aAAK,GAAG,YAAY,CAAC,UAAU;AAC7B,eAAK,cAAc,MAAM,IAAI;AAAA,QAC/B;AAAA,MACF,SAAS,OAAO;AACd,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBAAkC;AAC9C,UAAM,YAAY,KAAK,QAAQ;AAC/B,QAAI,CAAC,UAAU,QAAS;AAExB,QAAI,KAAK,oBAAoB,UAAU,aAAa;AAClD,WAAK;AACL,WAAK,OAAO;AAAA,QACV,2BAA2B,KAAK,iBAAiB,IAAI,UAAU,WAAW;AAAA,MAAA;AAG5E,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,UAAU,KAAK,CAAC;AAEnE,UAAI;AACF,cAAM,KAAK,QAAA;AAAA,MACb,SAAS,OAAO;AACd,aAAK,OAAO,MAAM,uBAAuB,EAAE,OAAO;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,MAAoB;AACxC,QAAI;AACF,YAAM,UAAqB,KAAK,MAAM,IAAI;AAC1C,YAAM,QAAQ,KAAK,mBAAmB,OAAO;AAE7C,UAAI,OAAO,UAAU;AACnB,cAAM,WAAW,KAAK,gBAAgB,IAAI,MAAM,QAAQ;AACxD,YAAI,UAAU;AACZ,qBAAW,WAAW,UAAU;AAC9B,oBAAQ,KAAK;AAAA,UACf;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,KAAK,qCAAqC,EAAE,OAAO,MAAM;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,SAA0C;AACnE,YAAQ,QAAQ,MAAA;AAAA,MACd,KAAK;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ,QAAQ,MAAM;AAAA,UACtB,UAAU,QAAQ,MAAM;AAAA,QAAA;AAAA,MAG5B,KAAK;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,OAAO,QAAQ,MAAM;AAAA,UACrB,KAAK,QAAQ,MAAM;AAAA,UACnB,UAAU,QAAQ,MAAM;AAAA,QAAA;AAAA,MAG5B,KAAK;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,UAAU,QAAQ,MAAM;AAAA,QAAA;AAAA,MAG5B,KAAK;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ,QAAQ,MAAM;AAAA,UACtB,UAAU,QAAQ,MAAM;AAAA,UACxB,QAAQ,QAAQ,MAAM;AAAA,QAAA;AAAA,MAG1B,KAAK;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,UAAU,QAAQ,MAAM;AAAA,UACxB,OAAO,QAAQ,MAAM;AAAA,QAAA;AAAA,MAGzB;AACE,eAAO;AAAA,IAAA;AAAA,EAEb;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,SAAK,QAAQ,UAAU,UAAU;AACjC,SAAK,gBAAgB,MAAA;AAErB,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAA;AACR,WAAK,KAAK;AAAA,IACZ;AAEA,SAAK,OAAO,KAAK,2BAA2B;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,QACZ,MACA,UAII,IACQ;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAElC,UAAM,UAAkC;AAAA,MACtC,GAAG,KAAK,QAAQ;AAAA,IAAA;AAGlB,QAAI,QAAQ,QAAQ,CAAC,QAAQ,UAAU;AACrC,cAAQ,cAAc,IAAI;AAAA,IAC5B;AAEA,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ,QAAQ,UAAU;AAAA,MAC1B;AAAA,MACA,MACE,QAAQ,aACP,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,MACjD,QAAQ,YAAY,QAAQ,KAAK,QAAQ,OAAO;AAAA,IAAA,CACjD;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAA;AACjC,YAAM,IAAI;AAAA,QACR,2BAA2B,SAAS,MAAM,IAAI,SAAS;AAAA,MAAA;AAAA,IAE3D;AAEA,WAAO,SAAS,KAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YACJ,UACA,UACuB;AACvB,UAAM,WAAW,MAAM,KAAK,QAIzB,WAAW;AAAA,MACZ,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,QAAQ;AAAA,QACR,WAAW,YAAY,KAAK;AAAA,MAAA;AAAA,IAC9B,CACD;AAED,WAAO;AAAA,MACL,UAAU,SAAS;AAAA,MACnB,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,IAAA;AAAA,EAEzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,WACE,UACA,SACY;AACZ,QAAI,CAAC,KAAK,gBAAgB,IAAI,QAAQ,GAAG;AACvC,WAAK,gBAAgB,IAAI,UAAU,oBAAI,KAAK;AAAA,IAC9C;AAEA,SAAK,gBAAgB,IAAI,QAAQ,GAAG,IAAI,OAAO;AAE/C,WAAO,MAAM;AACX,WAAK,gBAAgB,IAAI,QAAQ,GAAG,OAAO,OAAO;AAClD,UAAI,KAAK,gBAAgB,IAAI,QAAQ,GAAG,SAAS,GAAG;AAClD,aAAK,gBAAgB,OAAO,QAAQ;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,kBACJ,UACA,UAAuB,IACA;AACvB,UAAM,UAAU,QAAQ,WAAW,KAAK,QAAQ;AAChD,UAAM,YAAY,KAAK,IAAA;AAEvB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,WAAW;AAGf,YAAM,YACJ,UAAU,IACN,WAAW,MAAM;AACf,YAAI,CAAC,UAAU;AACb,qBAAW;AACX,sBAAA;AACA,iBAAO,IAAI,MAAM,8BAA8B,QAAQ,EAAE,CAAC;AAAA,QAC5D;AAAA,MACF,GAAG,OAAO,IACV;AAGN,YAAM,cAAc,KAAK,WAAW,UAAU,OAAO,UAAU;AAE7D,gBAAQ,aAAa,KAAK;AAG1B,YAAI,MAAM,SAAS,cAAc,MAAM,WAAW,MAAM;AAEtD,cAAI,CAAC,UAAU;AACb,uBAAW;AACX,gBAAI,wBAAwB,SAAS;AACrC,wBAAA;AAEA,gBAAI;AACF,oBAAM,UAAU,MAAM,KAAK,WAAW,QAAQ;AAC9C,sBAAQ,OAAO;AAAA,YACjB,SAAS,OAAO;AACd,qBAAO,KAAK;AAAA,YACd;AAAA,UACF;AAAA,QACF,WAAW,MAAM,SAAS,mBAAmB;AAC3C,cAAI,CAAC,UAAU;AACb,uBAAW;AACX,gBAAI,wBAAwB,SAAS;AACrC,wBAAA;AACA,mBAAO,IAAI,MAAM,6BAA6B,MAAM,KAAK,EAAE,CAAC;AAAA,UAC9D;AAAA,QACF;AAAA,MACF,CAAC;AAGD,YAAM,cAAc,YAAY;AAC9B,eACE,CAAC,aACA,YAAY,KAAK,KAAK,IAAA,IAAQ,YAAY,UAC3C;AACA,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,QAAQ,gBAAgB,GAAI,CAAC;AAEpE,cAAI,SAAU;AAEd,cAAI;AACF,kBAAM,UAAU,MAAM,KAAK,WAAW,QAAQ;AAC9C,gBAAI,QAAQ,OAAO,WAAW;AAC5B,kBAAI,CAAC,UAAU;AACb,2BAAW;AACX,oBAAI,wBAAwB,SAAS;AACrC,4BAAA;AACA,wBAAQ,OAAO;AAAA,cACjB;AACA;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAGA,kBAAA,EAAc,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WAAW,UAAyC;AACxD,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B,YAAY,QAAQ;AAAA,IAAA;AAGtB,UAAM,QAAQ,SAAS,QAAQ;AAC/B,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,gBAAgB,gCAAgC,QAAQ,EAAE;AAAA,IACtE;AAEA,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,QAAQ,MAAM;AAAA,IAAA;AAAA,EAElB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,eACJ,UACA,YAAoB,IACpB,OAAoC,UACnB;AACjB,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAED,UAAM,MAAM,GAAG,KAAK,OAAO,SAAS,MAAM;AAC1C,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,SAAS,KAAK,QAAQ;AAAA,MACtB,QAAQ,YAAY,QAAQ,KAAK,QAAQ,OAAO;AAAA,IAAA,CACjD;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,4BAA4B,SAAS,MAAM,EAAE;AAAA,IAC/D;AAEA,UAAM,cAAc,MAAM,SAAS,YAAA;AACnC,WAAO,OAAO,KAAK,WAAW;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,WACJ,MACA,UACA,OAAyB,SACzB,YAAY,OACW;AACvB,UAAM,WAAW,IAAI,SAAA;AACrB,aAAS,OAAO,SAAS,IAAI,KAAK,CAAC,IAAI,WAAW,IAAI,CAAC,CAAC,GAAG,QAAQ;AACnE,aAAS,OAAO,QAAQ,IAAI;AAC5B,aAAS,OAAO,aAAa,OAAO,SAAS,CAAC;AAE9C,UAAM,WAAW,MAAM,KAAK,QAIzB,iBAAiB;AAAA,MAClB,QAAQ;AAAA,MACR;AAAA,IAAA,CACD;AAED,WAAO;AAAA,MACL,MAAM,SAAS;AAAA,MACf,WAAW,SAAS;AAAA,MACpB,MAAM,SAAS;AAAA,IAAA;AAAA,EAEnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,iBAAuC;AAC3C,WAAO,KAAK,QAAqB,eAAe;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WAAiC;AACrC,UAAM,WAAW,MAAM,KAAK,QAGzB,QAAQ;AAEX,WAAO;AAAA,MACL,SAAS,SAAS;AAAA,MAClB,SAAS,SAAS;AAAA,IAAA;AAAA,EAEtB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAC/B,UAAM,KAAK,QAAQ,cAAc,EAAE,QAAQ,QAAQ;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,UAAM,KAAK,QAAQ,UAAU;AAAA,MAC3B,QAAQ;AAAA,MACR,MAAM,EAAE,OAAO,KAAA;AAAA,IAAK,CACrB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,gBAAgB,UAAiC;AACrD,UAAM,KAAK,QAAQ,UAAU;AAAA,MAC3B,QAAQ;AAAA,MACR,MAAM,EAAE,QAAQ,CAAC,QAAQ,EAAA;AAAA,IAAE,CAC5B;AAAA,EACH;AACF;AAUO,SAAS,qBACd,UACA,SACA,QACe;AACf,QAAM,SAAS,KAAK,MAAM,KAAK,UAAU,QAAQ,CAAC;AAElD,aAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,QAAI,UAAU,OAAO,GAAG,MAAM,UAAa,OAAO,MAAM,GAAG;AAEzD,YAAM,OAAO,OAAO,MAAM;AAG1B,YAAM,gBAAwC;AAAA,QAC5C,WAAW;AAAA,QACX,WAAW;AAAA,QACX,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,gBAAgB;AAAA,MAAA;AAGlB,YAAM,aAAa,cAAc,GAAG,KAAK;AACzC,UAAI,KAAK,UAAU,cAAc,KAAK,QAAQ;AAC5C,aAAK,OAAO,UAAU,IAAI,OAAO,GAAG;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;"}
|
package/metadata.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyvertical/comfyui",
|
|
3
|
+
"path": "packages/comfyui",
|
|
4
|
+
"position": {
|
|
5
|
+
"index": 6,
|
|
6
|
+
"count": 30
|
|
7
|
+
},
|
|
8
|
+
"description": "ComfyUI API client for workflow orchestration and video generation",
|
|
9
|
+
"provides": [
|
|
10
|
+
"ComfyUI API client for workflow orchestration and video generation"
|
|
11
|
+
],
|
|
12
|
+
"implements": [],
|
|
13
|
+
"requires": {
|
|
14
|
+
"workspace": [
|
|
15
|
+
"@happyvertical/logger",
|
|
16
|
+
"@happyvertical/utils"
|
|
17
|
+
],
|
|
18
|
+
"externalHappyVertical": [],
|
|
19
|
+
"external": []
|
|
20
|
+
},
|
|
21
|
+
"dependents": [],
|
|
22
|
+
"stability": {
|
|
23
|
+
"level": "stable",
|
|
24
|
+
"reason": "Primary package surface is described as implemented and production-oriented."
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"comfyui"
|
|
28
|
+
]
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyvertical/comfyui",
|
|
3
|
+
"version": "0.74.8",
|
|
4
|
+
"description": "ComfyUI API client for workflow orchestration and video generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@happyvertical/logger": "0.74.8",
|
|
16
|
+
"@happyvertical/utils": "0.74.8"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "25.0.10",
|
|
20
|
+
"typescript": "^5.9.3",
|
|
21
|
+
"vite": "7.3.2",
|
|
22
|
+
"vite-plugin-dts": "4.5.4",
|
|
23
|
+
"vitest": "^4.1.5"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"AGENT.md",
|
|
30
|
+
"metadata.json"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"registry": "https://registry.npmjs.org",
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/happyvertical/sdk.git",
|
|
39
|
+
"directory": "packages/comfyui"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/happyvertical/sdk/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/happyvertical/sdk/tree/main/packages/comfyui#readme",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "npx vitest run",
|
|
48
|
+
"test:watch": "npx vitest",
|
|
49
|
+
"build": "vite build",
|
|
50
|
+
"build:watch": "vite build --watch",
|
|
51
|
+
"clean": "rm -rf dist",
|
|
52
|
+
"dev": "npm run build:watch & npm run test:watch"
|
|
53
|
+
}
|
|
54
|
+
}
|