ComfyUI SDK

repository·main·Indexed 42 days ago

https://github.com/comfy-addons/comfyui-sdk

A TypeScript-based toolkit for interacting with the ComfyUI API. It features the cfli CLI for executing workflows and generating type-safe WorkflowBuilders, a PromptBuilder for mapping JSON inputs, and ComfyPool for managing and load-balancing multiple ComfyUI instances. Version 0.3.2.

Tokens
29.1K
Snippets
97
Records
118
Agent score
53%

What's inside ComfyUI SDK

  1. Understand type coercion for input overrides (`-i`)

    main

    When passing values via the -i flag, cfli automatically coerces strings into appropriate types. The parser can also peek at the workflow JSON to match the existing type of a field (e.g., ensuring a seed is a number or a model is a tuple). Supported coercions include:

    • Numbers: 1024 $\rightarrow$ 1024 (number), 1.5 $\rightarrow$ 1.5 (float)
    • Booleans: true / false $\rightarrow$ boolean
    • Arrays/Tuples: ["a","b"] $\rightarrow$ ["a", "b"] (via JSON parse)
    • Strings: Default behavior for unparsed text.
  2. Understand the `cfli` Terminal UI

    main

    By default, cfli provides an interactive Terminal UI (built with Ink) that displays real-time execution status. It shows connection details, workflow information, per-node progress (done/busy/wait), and a progress bar for KSampler nodes. Once complete, it lists output files with metadata and can even render inline terminal images if your terminal supports Sixel or iTerm2.

    cfli  Connected to http://192.168.14.93:8188  Queue #47
    
     Workflow  sample.json  12 nodes
    
     Progress
       [done] CheckpointLoaderSimple (4)
       [busy] KSampler (3)         step 12/20  ████████░░░░  60%
       [wait] VAEDecode (9)
    
     Output (1 image)
       ./output/ComfyUI_00001_.png  1024x1024  1.2MB
  3. Run ComfyUI workflows with `cfli`

    main

    Use cfli to execute ComfyUI workflows from your terminal. You can override node inputs directly via the command line using the -i (input) or -p (prompt) flags. The syntax for overrides is nodePath.inputName=value.

    # Run a workflow with input overrides
    cfli -f workflow.json \
      -i 4.inputs.ckpt_name=SDXL/realvisxlV40_v40LightningBakedvae.safetensors \
      -i 5.inputs.width=1024 \
      -i 5.inputs.height=1024 \
      -p 6.inputs.text="A picture of cute cat"
    
    # Save outputs to a specific directory
    cfli -f workflow.json -i 6.inputs.text="sunset" --output ./renders
    
    # Watch mode: re-run automatically when the workflow file changes
    cfli -f workflow.json -i 6.inputs.text="cat" --watch
    
    # Custom server with authentication
    cfli -f workflow.json --host https://comfy.example.com --token my-secret-token
  4. Generate a typed Workflow Builder with `cfli codegen`

    main

    You can generate a fully typed WorkflowBuilder based on your specific ComfyUI server's node definitions. This enables type-safe node inputs/outputs and autocomplete in your TypeScript code.

    Run the codegen command pointing to your server URL and specifying an output file path.

    cfli codegen -H http://localhost:8188 -o ./src/comfyui/nodes.ts
  5. Manage multiple ComfyUI instances with ComfyPool

    main

    Use ComfyPool to manage a collection of ComfyApi instances. This is useful for load balancing or distributing jobs across multiple ComfyUI servers.

    • Initialization: Pass an array of ComfyApi instances and an EQueueMode (e.g., EQueueMode.PICK_ZERO) to the constructor.
    • Events: The pool emits events like init, add_job, and added.
    • Batching: Use ApiPool.batch(tasks, weight) to run multiple asynchronous tasks across the pool. This returns a promise that resolves when the batch is complete.
    import {
      ComfyApi,
      CallWrapper,
      ComfyPool,
      EQueueMode,
      PromptBuilder,
      seed,
      TSamplerName,
      TSchedulerName
    } from "@saintno/comfyui-sdk";
    import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
    
    const ApiPool = new ComfyPool(
      [new ComfyApi("http://localhost:8188"), new ComfyApi("http://localhost:8189")],
      EQueueMode.PICK_ZERO
    )
      .on("init", () => console.log("Pool in initializing"))
      .on("add_job", (ev) => console.log("Job added at index", ev.detail.jobIdx, "weight:", ev.detail.weight))
      .on("added", (ev) => console.log("Client added", ev.detail.clientIdx));
    
    const generateFn = async (api: ComfyApi, clientIdx?: number) => {
      const workflow = new PromptBuilder(
        ExampleTxt2ImgWorkflow,
        ["positive", "negative", "checkpoint", "seed", "batch", "step", "cfg", "sampler", "sheduler", "width", "height"],
        ["images"]
      )
        .setInputNode("checkpoint", "4.inputs.ckpt_name")
        .setInputNode("seed", "3.inputs.seed")
        .setInputNode("batch", "5.inputs.batch_size")
        .setInputNode("negative", "7.inputs.text")
        .setInputNode("positive", "6.inputs.text")
        .setInputNode("step", "3.inputs.steps")
        .setInputNode("width", "5.inputs.width")
        .setInputNode("height", "5.inputs.height")
        .setInputNode("cfg", "3.inputs.cfg")
        .setInputNode("sampler", "3.inputs.sampler_name")
        .setInputNode("sheduler", "3.inputs.scheduler")
        .setOutputNode("images", "9")
        .input("checkpoint", "SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
        .input("seed", seed())
        .input("step", 6)
        .input("width", 512)
        .input("height", 512)
        .input("batch", 2)
        .input("cfg", 1)
        .input<TSamplerName>("sampler", "dpmpp_2m_sde_gpu")
        .input<TSchedulerName>("scheduler", "sgm_uniform")
        .input("positive", "A close up picture of cute Cat")
        .input("negative", "text, blurry, bad picture, nsfw");
    
      return new Promise<string[]>((resolve) => {
        new CallWrapper(api, workflow)
          .onFinished((data) => {
            const url = data.images?.images.map((img: any) => api.getPathImage(img));
            resolve(url as string[]);
          })
          .run();
      });
    };
    
    const jobA = ApiPool.batch(Array(5).fill(generateFn), 10).then((res) => {
      console.log("Batch A done");
      return res.flat();
    });
    
    const jobB = ApiPool.batch(Array(5).fill(generateFn), 0).then((res) => {
      console.log("Batch B done");
      return res.flat();
    });
    
    console.log(await Promise.all([jobA, jobB]).then((res) => res.flat()));
  6. Install the ComfyUI CLI (cfli)

    main

    You can use the ComfyUI CLI (cfli) in several ways depending on your workflow:

    • Global Installation: Install it permanently to your system using npm.
    • No Installation (npx): Run it immediately without installing using npx.
    • Bun: Use bun for high-performance execution.

    To verify the installation, run the command and check for the help output.

    # Install globally
    npm install -g @saintno/comfyui-sdk
    
    # Or use without installing
    npx @saintno/comfyui-sdk -f workflow.json
    
    # Or with bun
    bun x @saintno/comfyui-sdk -f workflow.json
  7. Install the `cfli` CLI

    main

    The cfli (ComfyUI CLI) tool is included with the @saintno/comfyui-sdk package. You can install it globally, run it without installation using npx, or use bunx.

    # Install globally
    npm install -g @saintno/comfyui-sdk
    
    # Or use npx (no install needed)
    npx @saintno/comfyui-sdk -f workflow.json -i 6.inputs.text="hello"
    
    # Or with bun
    bunx @saintno/comfyui-sdk -f workflow.json -i 6.inputs.text="hello"
    
    # Verify installation
    cfli --version
  8. Use the `cfli` CLI to run workflows

    main

    The cfli command-line interface allows you to execute ComfyUI workflows directly from your terminal. You can install it globally or run it using npx without a permanent installation.

    Quick Start Example

    To run a workflow file while overriding specific node inputs (like a checkpoint name, dimensions, or a text prompt), use the -f flag for the file and the -i (or -p) flag for inputs using the nodeId.inputs.key syntax.

    # Run without installing
    npx @saintno/comfyui-sdk -f workflow.json --json
    
    # Execute with input overrides
    cfli -f workflow.json \
      -i 4.inputs.ckpt_name=SDXL/realvisxlV40_v40LightningBakedvae.safetensors \
      -i 5.inputs.width=1024 \
      -i 5.inputs.height=1024 \
      -p 6.inputs.text="A picture of cute cat"
  9. Configure `cfli` Autocomplete

    main

    The cfli autocomplete system uses two layers: Static completions (parsing the local workflow JSON for node IDs and input paths) and Dynamic completions (fetching valid values like checkpoints or LoRAs from the ComfyUI server).

    To enable autocomplete in your shell, run the appropriate command for your shell type:

    # Install completions (one-time setup)
    eval "$(cfli completions bash)"
    # or
    cfli completions zsh > ~/.zfunc/_cfli
    cfli completions fish > ~/.config/fish/completions/cfli.fish
  10. Generate a Type-Safe Workflow Builder with cfli codegen

    main

    Instead of manually editing JSON and using string-based paths, you can generate a type-safe WorkflowBuilder class. This allows you to build workflows directly in TypeScript, where node connections and inputs are checked at compile time.

    Workflow Generation Command: cfli codegen -H <host> -o ./src/comfyui/nodes.ts (Replace <host> with your ComfyUI server address).

    Usage Pattern:

    1. Instantiate WorkflowBuilder.
    2. Call node functions (e.g., wf.KSampler(...)) to create nodes. These return NodeRef objects.
    3. Connect nodes by passing the output property of one node (e.g., ckpt.MODEL) as an input to another.
    4. Use wf.build() to finalize the workflow, specifying logical inputs and outputs using the metadata provided by the generated nodes (e.g., smp.inputs.seed).
    import { ComfyApi, CallWrapper } from "@saintno/comfyui-sdk";
    import { WorkflowBuilder } from "./comfyui/nodes";
    
    const api = new ComfyApi("http://localhost:8188");
    await api.init(5, 2000).waitForReady();
    
    const wf = new WorkflowBuilder();
    const ckpt = wf.CheckpointLoaderSimple({ ckpt_name: "v1-5-pruned.safetensors" });
    const pos = wf.CLIPTextEncode({ text: "landscape", clip: ckpt.CLIP });
    const neg = wf.CLIPTextEncode({ text: "ugly", clip: ckpt.CLIP });
    const lat = wf.EmptyLatentImage({ width: 512, height: 512, batch_size: 1 });
    const smp = wf.KSampler({
      model: ckpt.MODEL,
      positive: pos.CONDITIONING,
      negative: neg.CONDITIONING,
      latent_image: lat.LATENT,
      seed: 42,
      steps: 20,
      cfg: 7,
      sampler_name: "euler",
      scheduler: "normal",
      denoise: 1
    });
    const vae = wf.VAEDecode({ samples: smp.LATENT, vae: ckpt.VAE });
    const save = wf.SaveImage({ images: vae.IMAGE, filename_prefix: "output" });
    
    const builder = wf.build({
      inputs: { seed: smp.inputs.seed },
      outputs: { images: save.__id }
    });
    
    await new CallWrapper(api, builder).run();
  11. Download output media via CLI

    main

    To automatically download all output images generated by a workflow to a specific directory, use the -d flag. You can specify the destination directory using the -o flag.

    # Download to default ./output directory
    cfli -f workflow.json -d
    
    # Download to a custom directory
    cfli -f workflow.json -d -o ./renders