* feat(sidebar): recursive subdirectories under workflows + rebrand to Kforge Labs
## Recursive subdirectories
Workflow directories can now host nested subdirectories at arbitrary
depth. Each nested directory behaves like a top-level one: it can hold
workflows, its own Archive subfolder, and further subdirectories.
- **Create**: right-click a directory → "Create subdirectory…"
- **Save into**: pick the path from the save modal's flat directory
dropdown (`UP-scale / Type-A`)
- **Move across**: right-click a workflow → "→ <other path>" submenu
lists every other path in the tree
- **Reserved name**: subdirectory names cannot be "Archive" (case-
insensitive) at non-root levels — collides with the synthetic Archive
folder rendering. Top-level "Archive" still allowed (root has no
archived workflows of its own).
### Schema (recursive, back-compat)
workflowsCache = { directories: { [name]: DirNode } }
DirNode = { workflows: { [wfName]: WorkflowEntry }, directories: { [name]: DirNode } }
Pre-v0.3 stores without the nested `directories` field still load fine —
`normalizeWorkflowsStore` treats a missing `directories` as `{}` and
adds the empty field during normalization. No data loss on upgrade.
### Path-based store API
Every mutator/lookup now takes a `path: string[]`:
addDirectory(parentPath, name)
renameDirectory(parentPath, old, new)
deleteDirectory(parentPath, name)
saveWorkflowEntry(path, wfName, graph)
archiveWorkflow(path, wfName) / unarchiveWorkflow(...)
renameWorkflow(path, old, new) / deleteWorkflow(path, wfName)
moveWorkflow(srcPath, wfName, dstPath)
getWorkflowGraph(path, wfName)
New helpers: `listDirectoryNames(parentPath = [])`,
`listAllDirectoryPaths()` (DFS), `pathsEqual(a, b)`. `dirOf([])` returns
a synthetic root wrapper so callers don't need a special-case for root.
### UI changes
- **Save modal**: directory dropdown is now a flat list of every path in
the tree (`JSON.stringify(path)` values, `path.join(" / ")` labels).
"+ New directory…" creates a top-level dir; subdirs go via right-click.
- **Directory header counts**: now show recursive descendant total
(active + archived + all subtree). A parent with empty direct
workflows but populated subdirs no longer shows "0".
- **Save flow**: `saveAndToast` pins every ancestor (`workflows`,
`workflows/seg1`, `workflows/seg1/seg2`, …) so a save into a deeply
nested path opens the whole chain.
- **Render order per directory**: subdirs first → Archive folder →
active workflows last (so the freshest version sits closest to the
bottom).
## Rebrand: "Curated Nodes" → "Kforge Labs"
- `TAB_TITLE`: "Curated Nodes" → "Kforge Labs"
- `TAB_TOOLTIP`: "Curated selection of ComfyUI nodes" →
"Kforge Labs — curated nodes & workflows"
- Canvas right-click menu item: "Add to Curated Sidebar" → "Add to Kforge Labs"
- Empty-state hint in the curated-nodes section follows the new label
`TAB_ID` (`koolook.curatedNodes`) and the `app.registerExtension` name
(`koolook.curated_sidebar`) are deliberately unchanged so existing
per-user tab state (pinning, tab order, idempotency sentinel) is
preserved.
## Files
- web/sidebar/constants.js (rebrand)
- web/sidebar/canvas_menu.js (rebrand)
- web/sidebar/canvas_io.js (loadWorkflowOntoCanvas takes path)
- web/sidebar/workflows_store.js (recursive schema + path-based API)
- web/sidebar/modals.js (save modal flat-path picker)
- web/sidebar/tree.js (gather recursion + recursive renderer + path-based context menus)
- docs/maintainers/workflows-sidebar.md (subdirectories section + recursive JSON shape)
- CHANGELOG.md ([Unreleased] entry)
Encapsulation invariants preserved: `workflowsCache` only in
`workflows_store.js`; `pathStates` only in `tree.js`.
Closes the maintainer's outstanding feature request for subfolders under
workflow directories.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): show empty directories in tree when no search is active
User-reported: right-click → Create subdirectory → name → Submit
appeared to do nothing. The directory was actually created in the cache
(and showed up correctly in the save modal's path picker), but
gatherDirsAt was dropping any directory with zero matched workflows AND
zero matched descendants — including freshly-created empty subdirs.
Fix: scope the empty-drop check to "search active" only. With no search,
every directory in the cache renders. With a search, the filter still
collapses paths that contributed no hits, so search results stay clean.
Verified via Node dry-test (.tmp_subdir_test.js, since deleted):
- Pre-fix: addDirectory(['UP-scale'], 'Test') succeeds; gather('') drops Test
- Post-fix: gather('') includes Test; gather('hires') still drops empties
Also confirmed there is NO case-sensitivity restriction — 'Test' and
'test' coexist as separate directories. The user's earlier observation
that "only the lowercase one shows" was the empty-dir bug masquerading
as a case issue (whichever directory had been saved into earlier was
visible; the other was hidden).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): cascading directory picker in save modal (multi-step)
User feedback: the flat path list was unusable with deep nesting — every
existing path appeared in one giant dropdown, you couldn't pick a parent
and then narrow to a child. Replaced with cascading selects.
Behavior:
- Top-level select shows existing top-level dirs + "+ New directory…"
- Picking a top-level with children reveals a level-2 select. The first
option is "(save in <path>)" which stops drilling at this level.
Following options are the children.
- Picking a level-2 child with grandchildren reveals a level-3 select
with the same shape, etc., to arbitrary depth.
- "(save here)" at any level commits the save at that depth.
- Picking a different top-level / parent collapses every cascade level
below it and rebuilds.
- `defaultDir` (a path array) auto-drills the cascade to that path on
open, with onCascadeChange recursing through pre-selected levels.
The save modal's Base-on-existing / Action / Workflow-name sections
re-derive from `getSelectedPath()` whenever any cascade level changes,
so they reflect the current target dir.
Verified via Node dry-test (.tmp_cascade_test.js, since deleted) over 6
scenarios: fresh open, deep defaultDir pre-selection, level-2 collapse,
top-change-to-leafless-dir, "+ New directory…" + name input, manual
drill into a no-children leaf.
The store API is unchanged — `saveWorkflowEntry(path, wfName, graph)`
still takes a path array, and the cascade just produces the same array
through interactive selection instead of a flat dropdown.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sidebar): right-click Archive folder → Delete archive (N items)
Adds a context menu to the synthetic Archive folder that bulk-removes
every archived workflow in its parent directory. Active workflows at the
same level are untouched. The menu only appears when archivedCount > 0
(Archive folder is synthetic and only renders when there are archived
workflows present), so the no-op path is unreachable in practice — the
clearArchive function still defends against it (returns false if no
archived entries to remove or directory missing).
User-asked gap from the subdirectories review. Other adjacent gaps
considered:
- Restore-all from archive: SKIP. Each archived entry has a
"(archived <ts>)" name suffix; bulk-restoring leaves messy long names
AND can collide with the still-active version. Per-item is the right
granularity.
- Move a real Directory across paths: SKIP for this PR. Useful but needs
cycle prevention + path-prefix updates; separate scope.
- Move workflow to a NEW directory inline: SKIP. Two-step workaround
(right-click → Create subdirectory, then move) works.
- Move synthetic Archive folder: N/A. The Archive folder is auto-derived
from `archived: true` flags; nothing to move.
Verified via Node dry-test (deleted): clearArchive correctly removes
only archived entries, preserves active ones, returns false on
empty-archive and missing-dir paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): pull 'Base on existing' candidates from ancestors too
Reported regression: navigating into an empty subdirectory (e.g.
UP-scale → seedvr2) disabled "Use existing name" / "Modify existing
name" because getActiveWorkflowsInCurrentDir only looked at the leaf
dir's workflows. With nested dirs, leaf dirs are often empty (just
created via right-click), making those actions unreachable for the
common "save a variant of an existing workflow into a new subdir" flow.
Fix: rename to getCandidatesForBase and walk the destination path from
deepest up to root, collecting active workflows at every level. Dedup
by name (deepest wins). The dropdown shows the bare name when the
candidate is from the current dir, or "name · in <path>" when it
comes from an ancestor — so the user knows which dir they're basing on.
The submit path is unchanged: baseSelect.value is still the bare name,
which feeds the workflow-name field for "Use existing"/"Modify existing"
actions. The Save destination path is whatever the cascade resolves to,
not the ancestor path — so basing UP-scale/seedvr2's new save on
"Hires-FIX-wanT2V-v01 · in UP-scale" creates UP-scale/seedvr2/Hires-
FIX-wanT2V-v01 (no archive happens because the destination didn't have
that name before).
Verified via Node dry-test (deleted): empty subdirs now expose the
parent's active workflows; deep empties expose every ancestor's actives;
non-empty leaves show their own first then ancestors with path
disambiguation; archived entries excluded; alphabetical within each
level.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sidebar): drag-and-drop moves in workflows tree (Tier 1)
User: "if we can fix this [DnD] then everything is fixed". Tier 1 ships
the moves-only piece — drag-to-move workflows, drag-to-nest dirs, drag
onto Archive to archive. No custom-ordering schema change (sort within a
level stays alphabetical); that would be Tier 2.
Drag/drop semantics:
workflow row → directory row move workflow (no-op if same dir)
workflow row → Archive folder archive in that dir (move first if
cross-dir, all in one persist)
directory row → directory row nest dragged dir as child of target
Cycle prevention in moveDirectory: rejects same-parent, dropping a dir
into itself, or into any of its descendants. Plus collision (target
already has a sibling with that name) and reserved-name "Archive" at
non-root.
Implementation:
- workflows_store.js: new moveDirectory(srcParentPath, name,
dstParentPath) with isPathDescendantOrSame() helper.
- constants.js CSS: grab/grabbing cursor on draggable rows; blue
outline + tinted background on .koolook-drop-target.
- tree.js: decorateDraggable(row, payload) and decorateDropTarget(row,
target) helpers; handleDndDrop dispatcher; threaded through
makeFolderRow / makeWorkflowLeafRow / buildFolder / makeSectionCtx
so renderGatheredDir can attach DnD opts via the section ctx.
DnD uses the native HTML5 Drag-and-Drop API (no libs). MIME type
"application/x-koolook-row" filters out file/external drags.
Verified via Node dry-test (deleted) over 7 scenarios:
1. legal cross-parent move (Type-A from UP-scale to Depth)
2. cycle: parent → its child → blocked
3. same-parent → blocked (no-op)
4. cycle: dir → its own descendant → blocked
5. name collision in destination → blocked
6. legal sibling-becomes-child nest
7. moving a dir literally named "Archive" to non-root → blocked
Visual feedback: drop targets get .koolook-drop-target on dragover
(blue outline + tinted background), removed on dragleave/drop. Failed
drops surface "Could not move (cycle, collision, …)" toast through the
existing persistMutation onNoOp path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): hide Action dropdown entirely when no base to act on
User feedback: when saving into a directory whose path has zero active
workflows in any ancestor (genuinely empty case), the Action dropdown
showed three options with "Use existing name" and "Modify existing
name" disabled. Disabled <option> renders subtly across browsers — most
users just see "New name" with no obvious why. They proposed two
options: (1) gray out clearly, or (2) hide the dropdown entirely. Going
with (2) — Action only matters when a base candidate exists; without
one, the only useful flow is "type a name and save," which the
Workflow Name field already provides.
So now:
- hasBase=true (current dir or any ancestor has active workflows) →
full Action picker, all three options enabled.
- hasBase=false (truly empty path, all the way up to root) →
Action label + dropdown hidden; underlying actionSelect.value pinned
to "new" so submit takes the by-name path through the Workflow Name
field.
The ancestor-pull fix from e7da864 means this empty branch only fires
for genuinely-fresh paths (a brand-new top-level + subdir created
together with no workflows anywhere). Common case is hasBase=true.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): apply verified PR-review findings on #54
Verified review (3 review agents → synthesizer → verifier) on the
recursive-subdirectories + Kforge Labs PR flagged 3 HIGH / 1 MEDIUM /
1 LOW. All confirmed; no false positives. Applying every actionable
fix in one commit.
[HIGH] CHANGELOG disclosure for two undocumented Save-modal shifts:
1) Base on existing now walks ancestor paths leaf-up, dedupes deepest-
wins, labels ancestor entries "<name> · in <path>".
2) Action dropdown is hidden (not just disabled) when no base
candidate exists; underlying value pinned to "new" so submit takes
the by-name path.
Both behaviors landed in late commits (e7da864 and cd9b2c3); the [Unreleased]
entry now describes them under ### Changed alongside the Kforge Labs
rebrand bullets.
[HIGH] Self-drop directory misleading toast (tree.js handleDndDrop):
The same-parent guard caught dir-on-its-own-parent but missed dir-on-
its-own-row, because the dir's row carries `target.path =
[...srcParentPath, name]` which doesn't equal `srcParentPath`.
moveDirectory's cycle check would still reject the move, but the user
got "Could not move directory (cycle, name collision, or invalid
target)" for what is just a no-op gesture. Adds a second early-return
matching `[...srcParentPath, dirName]` against `target.path` so
self-drops are silent.
[MEDIUM] addDirectory(undefined, "x") crash hardening: moveDirectory
already had `Array.isArray` guards on its parent-path inputs;
addDirectory lacked the equivalent and would throw on
`parentPath.length` if a future caller ever passed `undefined`. Adds
the symmetric guard.
[LOW] Drops the dead `isArchiveReservedName` export — the predicate
was never imported. Reserved-name enforcement remains inline in every
mutator that creates or renames a directory. A short comment notes the
removal so future contributors know to revive the export only when a
caller (e.g. modal pre-validation) actually needs it.
Verified via Node dry-test (deleted) covering:
- addDirectory(undefined / null / "string" / []) — all behave correctly
- Self-drop on root 'A' → "no-op-self-drop" (silent)
- Legal cross-parent move 'A' → 'B' → "moved"
- Self-drop on nested 'B/A' → "no-op-self-drop"
- Drop 'B/A' onto own parent 'B' → "no-op-same-parent"
Spec alignment: every fix was verified against PR intent. The two
Save-modal shifts are organic late-stage refinements, not regressions —
disclosure is the only ask and is now delivered. The self-drop guard
and Array.isArray hardening tighten contracts that were already the
clear intent. The dead-export drop is housekeeping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ComfyUI-Koolook-Nodes
This repository provides custom nodes for ComfyUI tailored for VFX and AI image/video generation workflows.
For change history see CHANGELOG.md. For workflow conventions and naming see CLAUDE.md and the glossary. Full project documentation lives under docs/.
Koolook nodes (root package)
| Node | Display name | Category | Source |
|---|---|---|---|
EasyWan22Prompt |
Wan 2.2 Easy Prompt (Koolook) | Koolook/Wan_Video |
k_easy_wan22_prompt.py |
EasyResize_Koolook |
Easy Resize (Koolook) | Koolook/Image |
k_easy_resize.py |
EasyAIPipeline |
Easy AI Pipeline (Koolook) | Koolook/Pipeline |
k_ai_pipeline.py |
easy_ImageBatch |
Easy Image Batch (Koolook) | Koolook/Image |
k_easy_image_batch.py |
KoolookLoadCameraPosesAbsolute |
Koolook Load Camera Poses (Absolute Path) | Koolook/Camera |
k_easy_track.py |
Brief descriptions:
- Wan 2.2 Easy Prompt: Dynamic prompt builder driven by
config.json(light source, lighting type, time of day, etc.). Outputs a comma-separated prompt string and optional CLIP conditioning. - Easy Resize (Koolook) —
EasyResize_Koolook: Aspect-aware image resize that enforces divisibility (e.g.,32) for model compatibility. Modes: stretch, letterbox, pillarbox; padding and cropping supported. Originally inspired by theResize Image V2node from kijai/ComfyUI-KJNodes (GPL-3.0 — seeforks/THIRD_PARTY.mdfor the full attribution + change log). The legacy bare-nameEasyResizeID is kept as a deprecated alias for saved-workflow compatibility. - Easy AI Pipeline: Aggregates VFX shot parameters (shot duration, seed, base path, shot name, AI method, version) into a fully formatted output file path with overwrite protection. For organized writes from Save EXR / Save Image.
- Easy Image Batch: Builds an
IMAGE+MASKbatch of lengthtotal_framesfrom 1–4 keyframe images placed at explicit frame indices (withstart_frameoffset). Empty frames are filled with black/gray and the mask marks empty=white / occupied=black. Useful for sparse keyframe control with Wan-style video models. - Koolook Load Camera Poses (Absolute Path): Loads RealEstate10k-style
CAMERACTRL_POSESfrom any absolute path (no ComfyUI path stripping), with overrideable pose width/height. Pairs with AnimateDiff CameraCtrl downstream.
Radiance Koolook nodes (v2_3_3 fork — video-friendly VAE)
These come from forks/radiance_koolook/versions/v2_3_3/ and load through the root __init__.py. They are slim, rank-agnostic re-implementations of the upstream Radiance v2.3.3 VAE encoder/decoder that work correctly with video VAEs (Wan 2.2, Hunyuan, CogVideoX, LTX) — they skip the upstream's 4K cosine-blend tile engine, which conflicts with video VAEs that already handle their own internal stitching.
| Node ID | Display name | Category |
|---|---|---|
Easy_hdr_VAE_encode |
Easy HDR VAE Encode (Koolook) | Koolook/VAE |
Easy_hdr_VAE_decode |
Easy HDR VAE Decode (Koolook) | Koolook/VAE |
History note: the v0.1.0–v0.1.4 releases also shipped a much larger
versions/v1_0_1/fork that wrapped 26 Radiance v1.0 classes (HDR, EXR, OCIO, log curves, etc.) under the__koolook_v1_0_1namespace. Those wrappers were vestigial — never used by Koolook authors — and were removed in v0.1.5. Users who want that functionality should install upstream Radiance directly. Seeforks/THIRD_PARTY.mdfor full attribution and removal rationale.
See forks/README.md for the full fork workflow.
Features
- Dynamic Inputs for Prompts: Fields and dropdowns in Wan 2.2 Easy Prompt are defined in
config.json, allowing easy customization without code changes—great for VFX artists iterating on lighting or camera parameters. - Image Resizing with Aspect Control: Easy Resize ensures precise resolutions for AI models, with options for keep_proportion preservation, padding colors, and device selection (CPU/CUDA). Supports masks for compositing in VFX.
- Pipeline Path Generation: Easy AI Pipeline assembles VFX-style output paths and filenames from shot/version inputs, with optional version disabling and overwrite protection.
- Sparse Keyframe Batches: Easy Image Batch constructs aligned
IMAGE+MASKbatches from up to four keyframe images, ready for sparse video-model control. - Camera Pose Loading: Koolook Load Camera Poses (Absolute Path) reads RealEstate10k pose TXT files from any disk location and emits
CAMERACTRL_POSES. - Startup Loading: Nodes load configurations at ComfyUI launch; edit
config.jsonand restart to update. - Output Formatting: Wan 2.2 Easy Prompt outputs comma-separated strings (skipping "none"), compatible with prompt builders. Easy Resize outputs resized images, masks, and width/height integers.
- Error Handling: Fallbacks for missing/invalid JSON in Wan 2.2 Easy Prompt; validation for aspect ratios and divisibility in Easy Resize.
- Compatibility: Works with ComfyUI-Manager for easy installation via Git URL. The Koolook root nodes and the slim v2_3_3 VAE wrappers have no extra Python dependencies beyond
torch(already required by ComfyUI itself).
RunPod
RunPod management has been moved to a separate private repository for active operations and iteration.
- Runtime/deployment scaffolding is no longer maintained in this public repo.
- This public repo remains focused on the ComfyUI nodes/plugin code.
Release & Stability
For production usage, install a pinned stable tag/commit instead of following moving branch heads.
- Release procedure:
docs/maintainers/releasing.md(canonical, step-by-step) - Comfy Registry API (deprecate / yank versions):
docs/maintainers/registry-api.md - Node versioning rules (how to change a node without breaking saved workflows):
docs/maintainers/node-versioning.md - Per-release checklist template:
.github/ISSUE_TEMPLATE/release_checklist.md - Change history:
CHANGELOG.md - Fork tracking:
forks/THIRD_PARTY.md,forks/forks_manifest.yaml - Fork workflow guide:
forks/README.md
Tagging Policy (from now on)
- Repo releases:
vMAJOR.MINOR.PATCH(example:v0.2.0). - Fork code versions in paths use underscore format:
v1_0_1,v2_3_3. - Comfy node ID namespace suffixes match fork version paths:
__koolook_v1_0_1. - External raw checkout naming:
- pinned:
<repo>-v<version>-koolook - rolling upstream:
<repo>-main-upstream
- pinned:
Sibling Projects
Some related work lives in sibling folders/repositories outside MAIN. They are consulted (read-only knowledge sources, references, etc.) but never imported by MAIN at runtime. To keep the public repo free of machine-specific absolute paths, every sibling is referenced through an environment variable.
Conventions
- Variable prefix:
KOLOOK_*(matches the existingKOLOOK_FORKS_DIR). - Real paths live in a local, gitignored
.envfile. The committed.env.exampledeclares the variable names with placeholder values. - Public docs and committed code reference siblings only via the env var name (and an optional portable relative default like
../<folder>). Never hardcode an absolute path with a username. - A sibling project is a runtime-optional reference. If the variable is unset and no default is documented, agents/code must treat the sibling as unavailable and continue gracefully.
Registered siblings
| Role | Env var | Portable default | Status |
|---|---|---|---|
| External forks root (raw upstream clones) | KOLOOK_FORKS_DIR |
../ComfyUI-Forks |
documented in forks/forks_manifest.yaml |
| ComfyUI knowledge database | KOLOOK_COMFYUI_KB_DIR |
../ComfyUI_knowledge___DB |
read-only knowledge source |
| Personal knowledge database | KOLOOK_PERSONAL_KB_DIR |
(no default — user-specific) | optional |
Local setup
cp .env.example .env
# Edit .env to point at the real paths on your machine.
Do not commit .env. The default .gitignore already excludes .env, .env.local, and .env.*.local.
External Fork Workflow (No Vendoring in MAIN)
This repository is the MAIN control repo. Large third-party repositories must stay outside MAIN.
- Default external forks root (relative):
../ComfyUI-Forks - MAIN stores only:
- wrappers under
forks/ - provenance/tracking under
forks/
- wrappers under
- MAIN does not store full external trees (no vendored copies).
Radiance Koolook Version Layout
- Package entry path:
forks/radiance_koolook/__init__.py - Active version path:
forks/radiance_koolook/versions/v2_3_3/ - Modified node source is local and tracked in MAIN:
forks/radiance_koolook/versions/v2_3_3/nodes_vae.py
- External checkout is reference-only raw upstream:
- default location:
../ComfyUI-Forks/radiance-v2.3.3-koolook - baseline used for comparison: upstream commit
f262f47ddfda01ece154bf80c22769b1e4cef795(the v2.3.3 release commit; the cleanest tag pointing at it is the typo'dradinace2.3.3, so we pin by SHA)
- default location:
- Node IDs in the v2_3_3 fork are exposed verbatim as
Easy_hdr_VAE_encode/Easy_hdr_VAE_decode(the__koolook_v2_3_3namespace suffix is skipped viaSKIP_VERSION_SUFFIXinversions/v2_3_3/__init__.pybecause the Koolook-specific names already differentiate them from upstream). - Each version folder should include
UPSTREAM_PIN.yamlto keep parity with external pinned references. - The historical
v1_0_1folder was removed in v0.1.5; seeforks/THIRD_PARTY.md.
Version Pinning (Portable Across PCs/macOS)
- Track every external repo in
forks/forks_manifest.yamlwith:source_ref(tag/version)pinned_commit(exact hash used in production)external_checkout.relative_path_from_forks_root
- Keep checkout folder names versioned, e.g.:
radiance-v1.0.1-koolookradiance-v2.3.3-koolook
Machine Setup Example
From the parent directory that contains ComfyUI-Koolook:
mkdir -p ../ComfyUI-Forks
cd ../ComfyUI-Forks
git clone https://github.com/fxtdstudios/radiance.git radiance-v1.0.1-koolook
cd radiance-v1.0.1-koolook
git checkout f1b8ae330848fa08aba24c9d3e355cb432d3515b
This external folder is for upstream comparison/sync work only.
Installation
Via ComfyUI-Manager (Recommended)
- Open ComfyUI and access the Manager interface.
- Go to "Install Custom Nodes" > "Install via Git URL".
- Paste the repository URL:
https://github.com/malkuthro/ComfyUI-Koolook.git. - Install and restart ComfyUI.
- Nodes appear under these categories in the node search:
Koolook/Camera— Koolook Load Camera Poses (Absolute Path)Koolook/Image— Easy Resize (Koolook), Easy Image Batch (Koolook)Koolook/Pipeline— Easy AI Pipeline (Koolook)Koolook/VAE— Easy HDR VAE Encode/Decode (Koolook)Koolook/Wan_Video— Wan 2.2 Easy Prompt (Koolook)
Manual Installation
- Clone this repository into your
ComfyUI/custom_nodes/directory:git clone https://github.com/malkuthro/ComfyUI-Koolook.git - Ensure
__init__.pyandconfig.jsonare in the cloned folder. - Restart ComfyUI.
The root Koolook nodes have no additional Python dependencies. To enable the Radiance Koolook v1.0.1 nodes that handle EXR / OCIO / HDR work, install the optional packages: OpenEXR, Imath, PyOpenColorIO, colour-science, opencv-python, and imageio.
Usage
Wan 2.2 Easy Prompt
- Add the "Wan 2.2 Easy Prompt" node to your workflow under "Koolook/Wan_Video".
- Select options from the dropdowns (e.g., Light Source: "Sunny lighting", Lighting Type: "Soft lighting").
- Add custom text in the "body" field for main prompt content.
- Optionally connect a CLIP for conditioning output.
- Connect the STRING outputs (combined_prompt or fields_only) to a prompt input, such as the "prompt" slot in the PromptJSON node (from NeuralSamurAI/ComfyUI-PromptJSON).
- To customize fields:
- Edit
config.json(see example below). - Restart ComfyUI to apply changes.
- Edit
- VFX Tip: Use for generating consistent lighting/shot descriptions in video generation workflows, like with AnimateDiff or SVD.
Example config.json
This is the default configuration based on Wan 2.2 parameters:
{
"category": "Koolook/Wan_Video",
"node_name": "Wan_2.2",
"fields": [
{
"name": "light_source",
"type": "combo",
"options": ["none", "Sunny lighting", "Artificial lighting", "Moonlighting", "Practical lighting", "Firelighting", "Fluorescent lighting", "Overcast lighting", "Mixed lighting"],
"default": "none"
},
{
"name": "lighting_type",
"type": "combo",
"options": ["none", "Soft lighting", "Hard lighting", "Top lighting", "Side lighting", "Underlighting", "Edge lighting", "Silhouette lighting", "Low contrast lighting", "High contrast lighting"],
"default": "none"
},
// ... (additional fields like time_of_day, shot_size, etc.)
],
"output_type": "STRING"
}
Easy Resize (Koolook)
- Add the "Easy Resize (Koolook)" node (ID
EasyResize_Koolook) to your workflow under "Koolook/Image". - Connect an IMAGE (and optional MASK) input.
- Set the base dimension (Width or Height), base size, aspect ratio (e.g., "16:9" for landscape, "9:16" for portrait), and other options like keep_proportion (stretch, letterbox, pillarbox), pad_color (e.g., "0, 0, 0" for black), crop_position, divisible_by, and device.
- Connect the outputs: IMAGE and MASK for further processing, INT width/height for resolution-aware nodes like samplers or upscalers.
- VFX Tip: Use for preprocessing footage in multi-resolution pipelines, ensuring divisibility for efficient GPU processing in tools like IPAdapter or ControlNet.
Node Code Overview
The core logic is split across files for modularity:
Root Koolook nodes:
- k_easy_wan22_prompt.py — loads
config.jsonat import time, builds inputs dynamically (combos for dropdowns, strings for text), and formats selections into prompt strings with optional CLIP conditioning. - k_easy_resize.py — parses aspect ratios, computes divisible dimensions, resizes with
common_upscale, and handles padding/cropping. Inspired by Resize Image V2 from ComfyUI-KJNodes. - k_ai_pipeline.py —
EasyAIPipelinebuilds VFX output paths/filenames from shot/version inputs and creates the output directory. - k_easy_image_batch.py —
easy_ImageBatchbuilds alignedIMAGE+MASKkeyframe batches with placeholder fill. - k_easy_track.py —
KoolookLoadCameraPosesAbsoluteloads RealEstate10k pose TXT files from any path.
Forked/wrapped nodes:
- forks/radiance_koolook/ — version-namespaced wrapper for the Radiance fork. Currently exposes the
v2_3_3set; modified node sources live inforks/radiance_koolook/versions/v2_3_3/nodes_vae.py. The wrapper supports the__koolook_vX_Y_ZID suffix pattern but lets specific Koolook-named IDs (likeEasy_hdr_VAE_encode/decode) ship without the suffix via theSKIP_VERSION_SUFFIXset. - forks/forks_manifest.yaml, forks/THIRD_PARTY.md, forks/README.md — fork tracking metadata.
Glue and frontend:
- __init__.py — imports all node packages, merges
NODE_CLASS_MAPPINGS/NODE_DISPLAY_NAME_MAPPINGS, and exposesWEB_DIRECTORY = "./web". - web/ — JavaScript extensions loaded by ComfyUI for the Koolook nodes (e.g.,
ai_pipeline.js).
For advanced customizations (e.g., different output formats or additional resize modes), modify the execute or adjust_to_aspect methods in the respective files.
Troubleshooting
- ComfyUI Won't Start: Check console logs for errors (run
python main.py). Common: Missingconfig.jsonor invalid JSON—use a validator like jsonlint.com. - No Fields Visible in Wan 2.2 Easy Prompt: Indicates JSON load failure; the node falls back to empty inputs.
- Errors in Easy Resize: Ensure aspect ratio is "w:h" format, divisible_by >=1, and pad_color is "r, g, b" floats (0.0-1.0).
- Updates: Pull latest from GitHub and restart.
Credits
- Based on ComfyUI custom node examples.
- Parameters for Wan 2.2 Easy Prompt derived from Wan 2.2 docs (https://alidocs.dingtalk.com/i/nodes/EpGBa2Lm8aZxe5myC99MelA2WgN7R35y).
- Easy Resize inspired by ComfyUI-KJNodes (https://github.com/kijai/ComfyUI-KJNodes), specifically the Resize Image V2 node in image_nodes.py.
- Compatible with nodes like PromptJSON for LLM workflows.
License
GPL-3.0 (see LICENSE).
This package incorporates and adapts code from
fxtdstudios/radiance (also
GPL-3.0) under forks/radiance_koolook/. Per GPL-3.0 §5(c), the entire
work is licensed as GPL-3.0. Attribution and per-fork modification notes
are tracked in forks/THIRD_PARTY.md and
forks/forks_manifest.yaml.
Note for downstream users: any work that incorporates, links to, or derives from
ComfyUI-Koolookmust itself be distributed under GPL-3.0 (or a compatible license). If you need a non-copyleft license for any piece of this code, contact the maintainers — some Koolook-original nodes (those with no upstream Radiance lineage) may be available under a more permissive license on request.