Extensions/ComfyUI-Alchemine-Pack
ComfyUI Extension

ComfyUI-Alchemine-Pack

Custom nodes pack for ComfyUI

By alchemine·Created about a year ago·Updated 3 days ago· 3
alchemine/comfyui-alchemine-pack
Nodes28
On cloudLocal install
CategoryAlcheminePack/Image, AlcheminePack/Everywhere
Stars3
Updated3 days ago

Nodes (28)

Adjust Image

One node to fix a flat or soft image before it ever leaves ComfyUI

AlcheminePack/Image
Everywhere

The ComfyUI node with no outputs that rewires your whole graph

AlcheminePack/Everywhere
Api Collect

The half of fire-and-forget remote rendering nobody ships you

AlcheminePack/API
Api Generate

Run a full workflow on a remote ComfyUI and get the image back, no screen required

AlcheminePack/API
Api Submit

Fire a workflow at a remote ComfyUI and keep your local queue moving

AlcheminePack/API
Boy Subject Filter

The node that untangles `1girl, solo, sex`

AlcheminePack/Prompt
Cached Load LoRA Tag

...> tags, without re-patching every run

AlcheminePack/Model
Download Image

Grab an image from a URL and load it as a tensor in one step

AlcheminePack/Lora
Filter Colors

Two dresses, two colours, one very confused model

AlcheminePack/Prompt
Filter Plurals

`arm up` or `arms up`, pick one before the model picks both

AlcheminePack/Prompt
Filter Subtags

Delete the redundant tags your LLM insists on keeping

AlcheminePack/Prompt
Filter Tags

Strip blacklisted tags from your prompt without nuking the rest

AlcheminePack/Prompt
Fix Break After TIPO

Clean up the weird BREAK artifacts TIPO leaves in your prompts

AlcheminePack/Prompt
Grok Collect

Pick up your finished Grok video when it's done, without babysitting the API

AlcheminePack/Grok
Grok Generate

Animate a still image with Grok's video model, from inside ComfyUI

AlcheminePack/Grok
Grok Submit

Queue a Grok image-to-video job without blocking your whole graph

AlcheminePack/Grok
Lazy Execution

The node that makes sequential execution and gated loops actually work in ComfyUI

AlcheminePack/FlowControl
Load Workflow

Stop hand-copying workflow JSON into your remote-graph nodes

AlcheminePack/API
OpenAI Inference

One node for every OpenAI-compatible LLM backend, with vision and thinking mode

AlcheminePack/Inference
Process Tags

One node to clean, filter, and auto-BREAK your entire tag prompt

AlcheminePack/Prompt
Remove Weights

Strip the (cat:1.2) noise out of a prompt in one pass

AlcheminePack/Prompt
Replace Underscores

The one-line fix for Danbooru-style tag prompts

AlcheminePack/Prompt
Save Image With Text

Assemble a LoRA dataset without leaving ComfyUI

AlcheminePack/Lora
SDXL Auto Break

Stop your prompt from silently truncating at 75 tokens

AlcheminePack/Prompt
SDXL Token Analyzer

Finally see where your prompt is getting truncated

AlcheminePack/Prompt
Separate Lora Tags

Pull <lora:...> out of your prompt before it hits the encoder

AlcheminePack/Prompt
Substitute Tags

Regex prompt surgery that only fires when it should

AlcheminePack/Prompt
Text Prompt

A text box that stops jumping your cursor to the end when you type {a|b}

AlcheminePack/Prompt
Readme

ComfyUI-Alchemine-Pack

A custom node pack for ComfyUI that provides utility nodes for prompt processing, LLM inference, LoRA-tag loading, Grok image-to-video, remote ComfyUI API execution, workflow control, image adjustment, and input broadcasting.

Installation

  1. Clone or copy this repository into the custom_nodes directory of your ComfyUI installation.
  2. Install dependencies:
    pip install -r requirements.txt
    
  3. Restart ComfyUI.

Provided Nodes

Prompt Nodes (AlcheminePack/Prompt)

Prompt Workflow

| Node | Description | |------|-------------| | ProcessTags | Full pipeline for tag processing. Combines ReplaceUnderscores → FilterTags → FilterSubtags → FilterColors → FilterPlurals → SDXLAutoBreak in sequence. | | FilterTags | Removes blacklisted tags from prompts. Supports wildcards defined in resources/wildcards.yaml. | | FilterSubtags | Removes duplicate/unnecessary subtags (e.g., dog, white dog → white dog). | | ReplaceUnderscores | Converts all underscores (_) to spaces. | | FixBreakAfterTIPO | Fixes BREAK token formatting after TIPO output (removes weights like (BREAK:-1)). | | SDXLTokenAnalyzer | Analyzes CLIP tokens in a prompt (SDXL only). Returns g/l tokenizer results with token counts. | | RemoveWeights | Removes all weight notations from tags (e.g., (cat:1.2) → cat). | | FilterColors | Keeps one colour per thing: of red dress, blue dress, the first. Colours come from the color key of resources/wildcards.yaml. | | FilterPlurals | Of two tags that differ only by a plural s (arm up, arms up), keeps the first. | | BoySubjectFilter | When a tag needs a man (sex, hetero, penis, anything spelled with another), removes solo and, if no boy is counted, adds ((1boy)) and add_tags. | | SDXLAutoBreak | Automatically inserts BREAK to keep each segment within 75 tokens (SDXL only). | | SubstituteTags | Regex-based tag substitution with conditional execution (run_if, skip_if). | | SeparateLoraTags | Separates lora tags (<lora:...>) from a prompt. If the same lora appears multiple times, the last weight is used. | | TextPrompt | Plain multiline text input with dynamicPrompts off, so typing {a|b} no longer jumps the cursor to the end. Wildcards are expanded in Python instead. |

ℹ️ The tag generation nodes moved out of this pack. They now live in comfyui-generator-pack as Tags Generator, Tags Conflict Filter, Classify Tags and Group Tags.

ProcessTags

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text | | replace_underscores | BOOLEAN | True | Replace underscores with spaces | | filter_tags | BOOLEAN | True | Remove blacklisted tags | | filter_subtags | BOOLEAN | True | Remove duplicate/unnecessary subtags | | filter_colors | BOOLEAN | True | Keep one colour per thing (FilterColors) | | filter_plurals | BOOLEAN | True | Keep the first of two tags that differ only by a plural s (FilterPlurals) | | auto_break | BOOLEAN | False | Auto-insert BREAK for 75-token limit | | clip | CLIP | (optional) | Required for auto_break | | blacklist_tags | STRING | "" | Comma-separated blacklist (supports wildcards) | | fixed_tags | STRING | "" | Tags to preserve regardless of filtering |

| Output | Description | |--------|-------------| | processed_text | The processed prompt text | | filtered_tags_list | List of removed-tag groups (one entry each from the FilterTags / FilterSubtags / FilterColors / FilterPlurals steps) |

⚠️ 6.0.0: filter_colors and filter_plurals sit between filter_subtags and auto_break. In a workflow saved before 6.0.0 the values of auto_break, blacklist_tags and fixed_tags shift by two widgets; set them again. An API-format workflow needs the two new inputs added.

FilterTags

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text | | blacklist_tags | STRING | "" | Comma-separated blacklist (supports wildcards) | | fixed_tags | STRING | "" | Tags to preserve regardless of filtering |

| Output | Description | |--------|-------------| | processed_text | Prompt with blacklisted tags removed | | filtered_tags | Comma-separated list of the removed tags |

FilterSubtags

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text | | fixed_tags | STRING | "" | Tags to preserve regardless of subtag filtering |

| Output | Description | |--------|-------------| | processed_text | Prompt with redundant subtags removed | | filtered_tags | Comma-separated list of the removed subtags |

SDXLTokenAnalyzer

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | clip | CLIP | (required) | CLIP model (must expose clip_g / clip_l) | | text | STRING | (required) | Input prompt text |

| Output | Description | |--------|-------------| | g_tokens | Tokens decoded by clip_g (segments separated by BREAK) | | g_token_count | Per-segment clip_g token counts, comma-separated | | l_tokens | Tokens decoded by clip_l | | l_token_count | Per-segment clip_l token counts, comma-separated |

SDXLAutoBreak

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | clip | CLIP | (required) | CLIP model (uses clip_g token count) | | text | STRING | (required) | Input prompt text |

ReplaceUnderscores / FixBreakAfterTIPO / RemoveWeights

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text |

FilterColors / FilterPlurals

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text |

| Output | Description | |--------|-------------| | processed_text | Prompt with the repeated tags removed | | filtered_tags | The removed tags |

BoySubjectFilter

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text | | add_tags | STRING | "(hetero:1.1), (couple:1.1), (deep skin:1.1)" | Tags added after ((1boy)) when no boy is counted |

A prompt without a tag that needs a man is returned unchanged. Tags are matched whole (sex toy, sexy do not match), tags starting with after are skipped, and underscores and case are ignored.

SubstituteTags

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text | | pattern | STRING | "" | Regex pattern to match | | repl | STRING | "" | Replacement string | | run_if | STRING | "" | Only run if this pattern exists | | skip_if | STRING | "" | Skip if this pattern exists |

SeparateLoraTags

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Input prompt text |

| Output | Description | |--------|-------------| | text_without_lora | Text with all lora tags removed (whitespace/newlines preserved as much as possible) | | text_with_lora | Deduplicated lora tags joined by spaces (when the same lora appears multiple times, the last weight wins) |

TextPrompt

ComfyUI's built-in text widget has dynamicPrompts enabled, which re-parses the field as you type: writing {a|b} moves the cursor to the end on every keystroke. This node turns the flag off, so the widget behaves like a plain text box, and resolves the {option1|option2|...} syntax in Python at execution time instead (one random pick per group, nesting supported).

When ComfyUI-Impact-Pack is installed, its wildcard engine is used instead, so __wildcard__ file references, $$ multi-select and # comments are also resolved — a TextPrompt can stand in for an ImpactWildcardProcessor node. Without it, only the {a|b} syntax is resolved.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | text | STRING | (required) | Multiline prompt text; {a|b} groups are expanded on execution | | seed | INT | 0 | Input-only; seeds the wildcard picks so a run is reproducible |

| Output | Description | |--------|-------------| | text | Prompt with every wildcard group resolved |


Image Nodes (AlcheminePack/Image)

AdjustImage

Colour correction, a suite of sharpen/denoise filters and optional resizing in one node. Every knob defaults to a no-op, so the node passes the image through untouched until a slider is moved. All adjustments are torch ops running on the image's own device, so a GPU tensor never round-trips through numpy; RGB is processed and an existing alpha channel is passed through untouched.

The order is fixed and deliberate: brightness → contrast → saturation → gamma → denoise → edge enhance → CAS → local contrast → resize. Denoise runs before any sharpening so the sharpeners do not amplify the noise they were meant to remove.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | image | IMAGE | (required) | Input image | | brightness | FLOAT | 1.0 | 1.0 = no change, <1.0 darker, >1.0 brighter | | contrast | FLOAT | 1.0 | Scales around mid-gray 0.5 | | saturation | FLOAT | 1.0 | 0.0 = grayscale, >1.0 = more vivid | | gamma | FLOAT | 1.0 | <1.0 brightens mid-tones, >1.0 darkens them | | edge_enhance | FLOAT | 0.0 | Blend factor for a fixed edge-enhance kernel | | cas | FLOAT | 0.0 | Contrast Adaptive Sharpening (AMD FidelityFX). Sharpens flat regions more and already-sharp edges less, so it avoids crunchy line art | | local_contrast | FLOAT | 0.0 | Large-radius unsharp ("clarity"); adds mid-scale depth | | denoise | FLOAT | 0.0 | Edge-preserving bilateral filter; removes flat noise and banding while keeping lines | | upscale_method | COMBO | lanczos | Resampling used when scale_by != 1.0 | | scale_by | FLOAT | 1.0 | Scale factor; 1.0 = no resize |

| Output | Description | |--------|-------------| | image | Adjusted image |


ℹ️ The DAAM nodes moved out of this pack. Sampler Custom (DAAM) and DAAM Tag Explorer now live in comfyui-daam-pack. Node ids are unchanged, so existing workflows keep loading once that pack is installed.

Everywhere Nodes (AlcheminePack/Everywhere)

| Node | Description | |------|-------------| | Everywhere | A cg-use-everywhere broadcaster with fixed, named inputs (model, clip, vae, positive, negative, latent_image, seed, blacklist). The pinned input names keep UE's name-based routing working, so the two conditionings stay apart without renaming slots by hand. Requires cg-use-everywhere. |

ℹ️ The Danbooru nodes moved out of this pack. They now live in comfyui-danbooru-pack. Node ids are unchanged, so existing workflows keep loading once that pack is installed.


Inference Nodes (AlcheminePack/Inference)

Inference Workflow

| Node | Description | |------|-------------| | OpenAI Inference | Generate text via any OpenAI-compatible API. Supports vision and thinking mode. |

OpenAI Inference

A single node for every OpenAI-compatible backend — OpenAI, vLLM, Ollama's /v1 endpoint, and Gemini's OpenAI-compatible endpoint. Just point base_url/api_key/model at the server you want.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | prompt | STRING | "Hello, world!" | User prompt | | system_instruction | STRING | "You are a helpful assistant." | System prompt | | base_url | STRING | "" | API base URL, e.g. https://api.openai.com/v1 (or set OPENAI_BASE_URL in .env) | | api_key | STRING | "" | API key (or set OPENAI_API_KEY in .env) | | model | STRING | "" | Model name. If empty, auto-detected from /models when exactly one is available | | max_output_tokens | INT | 100 | Maximum output tokens (up to 131072) | | seed | INT | 0 | Random seed | | temperature | FLOAT | 0.7 | Sampling temperature (0.0–2.0) | | think | BOOLEAN | False | Enable thinking mode | | image | IMAGE | (optional) | Input image for vision tasks |

| Output | Description | |--------|-------------| | response | The model's answer (with any <think> block stripped out) | | reasoning | The reasoning/thinking trace, from reasoning_content or an inline <think>...</think> block (empty if none) |

Note: Responses are cached in-memory (LRU, last 10 unique input combinations) — re-running an identical request returns the cached response without calling the API.


ℹ️ The Evaluate node moved out of this pack. It now lives in comfyui-evaluate-pack. The node id is unchanged, so existing workflows keep loading once that pack is installed.


Flow Control Nodes (AlcheminePack/FlowControl)

Flow Control Workflow

| Node | Description | |------|-------------| | Lazy Execution | Passes value through only after signal resolves. Controls execution order, and propagates an upstream ExecutionBlocker on signal to gate downstream nodes. |

Lazy Execution

| Parameter | Type | Description | |-----------|------|-------------| | value | ANY | Value to pass through | | signal | ANY | Gate input — value is only forwarded once this resolves |

| Output | Description | |--------|-------------| | value | The value input, forwarded once signal has resolved |

Use Case: When you need sequential execution (e.g., run generation B only after generation A completes), or want a downstream branch to be skipped while an upstream node returns an ExecutionBlocker (e.g. gate an OpenAI Inference + Api Submit chain on Api Collect so a new job is only built once the previous one finishes).

Muting to prime a gated graph: because value is the first input, muting (bypassing) this node passes value straight through, ignoring signal. This is handy for cold-starting a loop that would otherwise be blocked forever — e.g. when Api Collect has no job yet and keeps emitting an ExecutionBlocker, mute the gate for one run to fire the first Api Submit, then un-mute it to resume normal gating.


Lora Nodes (AlcheminePack/Lora) (Experimental)

| Node | Description | |------|-------------| | DownloadImage | Downloads an image from a URL into the ComfyUI output directory. | | SaveImageWithText | Saves an image alongside a .txt caption file (for LoRA training dataset preparation). |

DownloadImage

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | url | STRING | (required) | Image URL to download | | dir_path | STRING | "output/images" | Destination directory (relative to ComfyUI output) |

| Output | Description | |--------|-------------| | image | Loaded image tensor | | file_path | Path of the saved file (relative to output dir) |

SaveImageWithText

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | image | IMAGE | (required) | Image to save | | text | STRING | (required) | Caption text saved as a sibling .txt | | dir_path | STRING | (required) | Destination directory (relative to ComfyUI output) | | prefix | STRING | "" | Filename prefix; auto-increments index when set |

| Output | Description | |--------|-------------| | image_path | Path of the saved .png | | text_path | Path of the saved .txt |


Grok Nodes (AlcheminePack/Grok)

| Node | Description | |------|-------------| | Grok Generate | Generates a Grok Imagine image-to-video clip from a single image and saves it to the output folder as a native VIDEO (with inline preview). | | Grok Submit | Fire-and-forget submit; sends the generation request and returns immediately with a request_id (does not wait). | | Grok Collect | Collects the in-flight job submitted by Grok Submit; returns the VIDEO when ready, otherwise blocks downstream. |

Grok Generate

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | image | IMAGE | (required) | Source image (first frame) | | prompt | STRING | "" | Motion / scene description (optional) | | duration | INT | 5 | Clip length in seconds (1–15) | | resolution | ENUM | "720p" | "720p" or "480p" | | model | STRING | "grok-imagine-video-1.5-preview" | Grok video model | | filename_prefix | STRING | "grok/GrokVideo" | Output path prefix under the ComfyUI output directory | | poll_interval | INT | 5 | Seconds between status polls (1–60) | | timeout | INT | 600 | Max seconds to wait for generation (30–3600) | | access_token | STRING | "" | Optional. Falls back to GROK_ACCESS_TOKEN env var | | refresh_token | STRING | "" | Optional. Falls back to GROK_REFRESH_TOKEN env var | | client_id | STRING | "" | Optional. Falls back to GROK_CLIENT_ID env var |

| Output | Description | |--------|-------------| | video | Generated clip (with audio), also previewed inline on the node |

Credentials: Provide the three tokens as node inputs, or leave them empty to read GROK_ACCESS_TOKEN / GROK_REFRESH_TOKEN / GROK_CLIENT_ID from the environment. The access token is auto-refreshed on a 401/403.

Grok Submit

Same inputs as Grok Generate (minus poll_interval/timeout), plus an optional label. This is an OUTPUT_NODE, so it runs even when nothing consumes its output. The mp4 output path is reserved at submit time and recorded in the lock; Collect writes to it when the clip is ready.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | label | STRING | "" | Optional label recorded with the job (returned later by Grok Collect) |

| Output | Description | |--------|-------------| | request_id | The submitted job's request id (empty string if a Grok job is already in progress) |

Grok Collect

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | wait_sec | INT | 0 | 0 = collect if ready, else skip immediately; otherwise wait up to this many seconds (0–3600) | | poll_interval | FLOAT | 5.0 | Seconds between polls while waiting (0.5–60.0) | | access_token | STRING | "" | Optional. Falls back to GROK_ACCESS_TOKEN env var | | refresh_token | STRING | "" | Optional. Falls back to GROK_REFRESH_TOKEN env var | | client_id | STRING | "" | Optional. Falls back to GROK_CLIENT_ID env var |

| Output | Description | |--------|-------------| | video | Collected clip when ready; otherwise an ExecutionBlocker that skips downstream | | label | The label recorded at submit time |

Credentials at collect: Collect also calls the Grok API (to poll/download), so it re-reads the tokens from its inputs or the env — tokens are not persisted in the lock file. Provide them the same way as on Grok Generate.

One in-flight job per kind: The Grok and API nodes share a single jobs.lock (in the pack directory) but each kind gets its own slot — a Grok job and an API job can both be in flight, but only one of each. Submit skips if a job of that kind is already in flight, and Collect frees the slot once it finishes. Run Collect in a loop (e.g. /loop) to pick up the clip when it's done.


Model Nodes (AlcheminePack/Model)

| Node | Description | |------|-------------| | Cached Load LoRA Tag | Loads the LoRAs referenced by <lora:name:weight> tags in the text and caches the patched result. |

Cached Load LoRA Tag

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | model | MODEL | (required) | Base model to patch | | clip | CLIP | (required) | Base CLIP to patch | | text | STRING (multiline) | (required) | Prompt containing <lora:...> tags |

| Output | Description | |--------|-------------| | MODEL | Model patched with the referenced LoRAs | | CLIP | CLIP patched with the referenced LoRAs | | STRING | The prompt with all lora tags stripped out |

  • Tag format: <lora:name:model_weight:clip_weight> — the clip weight is optional and defaults to the model weight. A tag without any numeric weight (<lora:name>) is not recognized as a lora tag: it is neither loaded nor stripped from the output text. Use <lora:name:0> to load at weight 0.
  • name is matched as a prefix against files in the loras folder; unmatched tags are skipped.
  • The patched result is cached while text/model/clip are unchanged, skipping LoRA re-loading and re-patching.

API Nodes (AlcheminePack/API)

API Workflow

Run a workflow on a remote ComfyUI instance over its HTTP API (e.g. a RunPod pod or any reachable ComfyUI). All nodes take the API-format workflow JSON (ComfyUI menu: "Save (API Format)"), not the UI workflow format. api_url is the remote base URL, e.g. https://xxxx-8188.proxy.runpod.net/ or http://127.0.0.1:8188.

| Node | Description | |------|-------------| | Load Workflow | Loads an API-format workflow JSON from ComfyUI/user/default/workflows/ and returns it as a STRING. | | Api Generate | Sends a workflow to a remote ComfyUI, waits for completion, and returns the output image/frames. | | Api Submit | Fire-and-forget submit; records the job and returns immediately with a job_id (does not wait). | | Api Collect | Collects the in-flight job submitted by Api Submit; returns frames when ready, otherwise blocks downstream. |

Load Workflow

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | filename | ENUM | (required) | A .json file under user/default/workflows/ |

| Output | Description | |--------|-------------| | text | The workflow JSON contents |

Api Generate

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | workflow | STRING (input) | (required) | API-format workflow JSON string, or path to a file | | positive_prompt | STRING (input) | (required) | Positive prompt, injected into positive_prompt_id | | positive_prompt_id | STRING | (required) | Node id receiving the positive prompt | | negative_prompt_id | STRING | "" | Node id receiving negative_prompt (when provided) | | output_id | STRING | "" | Node id whose images/gifs output is fetched and decoded | | seed | INT | -1 | -1 keeps the workflow's existing seed | | seed_id | STRING | "" | Node id whose seed/noise_seed input receives seed | | api_url | STRING | "" | Remote ComfyUI base URL, e.g. https://xxxx-8188.proxy.runpod.net/ or http://127.0.0.1:8188 | | image_node_id | STRING | "" | LoadImage node id receiving the uploaded image | | timeout_sec | INT | 300 | Max polling time in seconds (1–36000) | | negative_prompt | STRING (input) | "" | Optional. Skipped if empty | | image | IMAGE | (optional) | Optional. Uploaded to the remote and bound to image_node_id | | overrides | STRING (input) | "" | Optional JSON {node_id: <full node dict>}; each entry replaces the entire node entry, applied last |

| Output | Description | |--------|-------------| | output | Decoded image/frame tensor (animated outputs are expanded to frames) |

Api Submit

Same inputs as Api Generate (minus timeout_sec), plus an optional label. This is an OUTPUT_NODE, so it runs even when nothing consumes its output.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | label | STRING | "" | Optional label recorded with the job (returned later by Api Collect) |

| Output | Description | |--------|-------------| | job_id | The submitted job id (empty string if a job is already in progress) |

Api Collect

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | wait_sec | INT | 0 | 0 = collect if ready, else skip immediately; otherwise wait up to this many seconds (0–36000) | | poll_interval | FLOAT | 2.0 | Seconds between polls while waiting (0.5–60.0) |

| Output | Description | |--------|-------------| | output | Collected frames when ready; otherwise an ExecutionBlocker that skips downstream | | label | The label recorded at submit time |

One in-flight job per kind: The API and Grok nodes share a single jobs.lock (in the pack directory) but each kind gets its own slot — an API job and a Grok job can both be in flight, but only one of each. Submit skips if a job of that kind is already in flight, and Collect frees the slot once it finishes. Run Collect in a loop (e.g. /loop) to pick up the result when it's done.


Wildcard Support

The FilterTags and ProcessTags nodes support wildcards defined in resources/wildcards.yaml.

Example: Using <color> in the blacklist expands to every color defined in the YAML file, joined into one alternation — (shiny|dark|colored|<color>) skin blocks blue skin, grey skin, two-tone skin and the rest in one line.

Angle brackets, not the __color__ form the wildcard packs use. A wildcard processor (ImpactWildcardProcessor, Dynamic Prompts) runs upstream of this node, so it resolves __color__ before FilterTags ever sees it — and it resolves it by picking one value, which is the opposite of what a blacklist wants. No wildcard processor claims <...>, so the token survives them and arrives here intact. The __color__ form still works for blacklists that never pass through one.

Configuration

.env is optional — the pack always loads without it. Copy .env.example to .env and set only the variables you need (or pass the same values as node inputs). A node that needs a credential it can't find raises a clear error (shown as a ComfyUI error dialog) when you run it; nothing fails at load time.

OpenAI Inference defaults (.env or node inputs)

The OpenAI Inference node reads base_url/api_key from the node inputs first, falling back to these .env variables when the inputs are empty:

OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_API_KEY=your-api-key

Grok credentials (.env or node inputs)

The Grok nodes (Generate / Submit / Collect) read their credentials from the node inputs first, falling back to these .env variables when the inputs are empty:

GROK_ACCESS_TOKEN=...
GROK_REFRESH_TOKEN=...
GROK_CLIENT_ID=...

The access token is auto-refreshed on a 401/403. If you hit a Grok token refresh failed (...) error, the refresh token or client_id has expired/been revoked — re-authenticate with x.ai and update these values.

Examples

ProcessTags Example

Input: dog, cat, white dog, black cat
Blacklist: ^cat$
Output: white dog, black cat
Filtered: ['cat', 'dog']   (one entry per step: FilterTags, then FilterSubtags)

Blacklist tokens are regexes matched anywhere in a tag, so a bare cat would also remove black cat — anchor with ^cat$ to match the tag exactly.

FilterSubtags Example

Input: dog, cat, white dog, black cat
Output: white dog, black cat
(Removes 'dog' and 'cat' as they are subtags of 'white dog' and 'black cat')

SeparateLoraTags Example

Input:
moriaruruka, <lora:characters\lulurka\1-moriaruruka.safetensors:0.7> blonde, gradient hair, jewelry,
<lora:characters\lulurka\2-moriaruruka.safetensors:0.7> <lora:characters\lulurka\3-moriaruruka.safetensors:0.7> <lora:characters\lulurka\3-moriaruruka.safetensors:1.0>

Output:
text_without_lora: moriaruruka, blonde, gradient hair, jewelry
text_with_lora: <lora:characters\lulurka\1-moriaruruka.safetensors:0.7> <lora:characters\lulurka\2-moriaruruka.safetensors:0.7> <lora:characters\lulurka\3-moriaruruka.safetensors:1.0>
  • When a lora block is followed by ,, the preceding comma/whitespace is also removed to avoid double commas.
  • When a lora block is not followed by ,, the preceding comma is preserved and only the preceding whitespace is removed.
  • When the same lora appears multiple times, the last specified weight wins (e.g., the final weight for 3-moriaruruka.safetensors above is 1.0).

SubstituteTags Example

# If "girl" doesn't exist, replace "1boy" with "1girl, 1boy"
pattern: 1boy
repl: 1girl, 1boy
skip_if: girl

License

GPL-3.0 License