ComfyUI-Alchemine-Pack
Custom nodes pack for ComfyUI
Nodes (28)
One node to fix a flat or soft image before it ever leaves ComfyUI
The ComfyUI node with no outputs that rewires your whole graph
The half of fire-and-forget remote rendering nobody ships you
Run a full workflow on a remote ComfyUI and get the image back, no screen required
Fire a workflow at a remote ComfyUI and keep your local queue moving
The node that untangles `1girl, solo, sex`
...> tags, without re-patching every run
Grab an image from a URL and load it as a tensor in one step
Two dresses, two colours, one very confused model
`arm up` or `arms up`, pick one before the model picks both
Delete the redundant tags your LLM insists on keeping
Strip blacklisted tags from your prompt without nuking the rest
Clean up the weird BREAK artifacts TIPO leaves in your prompts
Pick up your finished Grok video when it's done, without babysitting the API
Animate a still image with Grok's video model, from inside ComfyUI
Queue a Grok image-to-video job without blocking your whole graph
The node that makes sequential execution and gated loops actually work in ComfyUI
Stop hand-copying workflow JSON into your remote-graph nodes
One node for every OpenAI-compatible LLM backend, with vision and thinking mode
One node to clean, filter, and auto-BREAK your entire tag prompt
Strip the (cat:1.2) noise out of a prompt in one pass
The one-line fix for Danbooru-style tag prompts
Assemble a LoRA dataset without leaving ComfyUI
Stop your prompt from silently truncating at 75 tokens
Finally see where your prompt is getting truncated
Pull <lora:...> out of your prompt before it hits the encoder
Regex prompt surgery that only fires when it should
A text box that stops jumping your cursor to the end when you type {a|b}
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
- Clone or copy this repository into the
custom_nodesdirectory of your ComfyUI installation. - Install dependencies:
pip install -r requirements.txt - Restart ComfyUI.
Provided Nodes
Prompt Nodes (AlcheminePack/Prompt)

| 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 TagsandGroup 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_colorsandfilter_pluralssit betweenfilter_subtagsandauto_break. In a workflow saved before 6.0.0 the values ofauto_break,blacklist_tagsandfixed_tagsshift 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)andDAAM Tag Explorernow 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)

| 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)

| 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
valueis the first input, muting (bypassing) this node passesvaluestraight through, ignoringsignal. 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 anExecutionBlocker, 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_IDfrom 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. nameis matched as a prefix against files in thelorasfolder; unmatched tags are skipped.- The patched result is cached while
text/model/clipare unchanged, skipping LoRA re-loading and re-patching.
API Nodes (AlcheminePack/API)

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
.envis optional — the pack always loads without it. Copy.env.exampleto.envand 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.safetensorsabove is1.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