Compare commits

...

13 Commits

Author SHA1 Message Date
Juggernaut
17c0923cfc
Merge 2757728935 into 037987b917 2026-07-07 03:36:05 +09:00
Dr.Lt.Data
037987b917 fix(custom-node-list): §4.7-C-bis #2958 younestft/Comfy_EverAnimate — manual register (CONFLICTING)
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-07-06 19:48:37 +09:00
Dr.Lt.Data
8076c200fc fix(custom-node-list): §4.7-C-bis-variant #3017 Moon-NE/MoonNe_Nodes — manual register (DESTRUCTIVE + CONFLICTING + non-standard schema) 2026-07-06 19:38:11 +09:00
Dr.Lt.Data
69bad851e0 fix(custom-node-list): §4.7-C-bis #2939 TechnoWarrior2 2-pack — manual register (CONFLICTING) + node_db/new sync
- ComfyUI Image Picker / ComfyUI Live Preview
- New author, appended at end of git-clone block
- live-preview code-scan verified CLEAN (no security surface, English UI, WEB_DIRECTORY only)
- Prepend both to node_db/new (newest-first)
2026-07-06 19:27:05 +09:00
Dr.Lt.Data
47df9c702c fix(custom-node-list): §4.2 MERGE_RELOCATE #2965 shootthesound 4-pack — root relocate + node_db/new sync
- Lighthouse / CleanFreak / Finding LoRA / Image of the Day
- Relocate 4 entries from array head to adjacent to existing shootthesound block
- Prepend 4 entries to node_db/new (newest-first)
2026-07-06 19:25:33 +09:00
Dr.Lt.Data
d6df033a21
Merge pull request #2965 from shootthesound/add-shootthesound-nodes-batch
Add Lighthouse, CleanFreak, Finding LoRA, and Image of the Day
2026-07-06 19:24:00 +09:00
Dr.Lt.Data
d72007d1a8 update DB 2026-07-06 19:22:15 +09:00
Dr.Lt.Data
79a5258dd2 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-07-06 08:48:46 +09:00
Dr.Lt.Data
16a212aa70 update db
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-07-05 21:16:42 +09:00
shootthesound
396308854d Add Lighthouse, CleanFreak, Finding LoRA, Image of the Day to custom-node-list 2026-06-07 19:29:39 +01:00
Juggernaut
2757728935
Merge branch 'Comfy-Org:main' into main 2026-02-09 16:53:01 +05:30
Juggernaut
bb46126fbd
Merge branch 'Comfy-Org:main' into main 2026-02-06 12:31:48 +05:30
ashish
af1c698117 dependency analysis 2026-01-18 16:54:22 +05:30
10 changed files with 8122 additions and 6720 deletions

View File

@ -24207,6 +24207,16 @@
"install_type": "git-clone",
"description": "Custom utility nodes for ComfyUI, providing helpers for tasks like video frame manipulation and advanced audio saving. Part of the 'Burgstall Enabling The Awesomeness' suite."
},
{
"author": "burgstall-labs",
"title": "ComfyUI-Domemaster-Outpaint",
"reference": "https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint",
"files": [
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint"
],
"install_type": "git-clone",
"description": "Outpaint flat video into fulldome / planetarium shows: domemaster rendering with tilted-venue presets for ComfyUI, on top of the Burgstall Labs 360 outpaint pipeline."
},
{
"author": "Kidev",
"title": "ComfyUI Fisheye Effects Nodes",
@ -40985,6 +40995,46 @@
"install_type": "git-clone",
"description": "Click-to-refine sampler. Generate, then click or paint regions to refine them in place. First-class support for FLUX 2 Klein 9B and Qwen-Image-Edit (Smart Inpaint / Smart Guided Inpaint / Xtra-Fine), works with any sampler-compatible model for Refine + Area Prompt. Optional SAM 3 Detect for text-prompted segmentation, and a companion Overrides node for custom samplers (power-sigma, Flux 2 scheduler, NAG)."
},
{
"author": "shootthesound",
"title": "Lighthouse",
"reference": "https://github.com/shootthesound/comfyui-lighthouse",
"files": [
"https://github.com/shootthesound/comfyui-lighthouse"
],
"install_type": "git-clone",
"description": "A non-destructive overlay that lights up every node by graph distance from the one you click. Diagnostic for big workflows, educational for understanding workflows others built, and surgical when you need to focus on one neighbourhood at a time."
},
{
"author": "shootthesound",
"title": "CleanFreak",
"reference": "https://github.com/shootthesound/comfyui-CleanFreak",
"files": [
"https://github.com/shootthesound/comfyui-CleanFreak"
],
"install_type": "git-clone",
"description": "One-click tidy-by-role for any ComfyUI workflow — loaders, encoders, samplers, decoders, and outputs each sorted into their own column with coloured group cards and connections preserved. 1200+ node classes pre-classified out of the box."
},
{
"author": "shootthesound",
"title": "Finding LoRA",
"reference": "https://github.com/shootthesound/comfyui-lora-FindingLora",
"files": [
"https://github.com/shootthesound/comfyui-lora-FindingLora"
],
"install_type": "git-clone",
"description": "A LoRA loader with bookmarks, trigger-word storage and click-to-copy, fuzzy search, and a one-click button to drop another loader inline and wire it into the model chain automatically."
},
{
"author": "shootthesound",
"title": "Image of the Day",
"reference": "https://github.com/shootthesound/comfyui-image-of-the-day",
"files": [
"https://github.com/shootthesound/comfyui-image-of-the-day"
],
"install_type": "git-clone",
"description": "Fetch a fresh image straight into your workflow from 8 daily/random sources. Useful for prompt testing, style references, daily-image automations, or just kicking off a workflow with something interesting."
},
{
"author": "HackAfterDark",
"title": "AfterDark Film AR Selector",
@ -46306,6 +46356,16 @@
"install_type": "git-clone",
"description": "ComfyUI custom nodes for scoped, graph-based string template variables."
},
{
"author": "boobkake22",
"title": "Sampling Planner",
"reference": "https://github.com/boobkake22/ComfyUI-SamplingPlanner",
"files": [
"https://github.com/boobkake22/ComfyUI-SamplingPlanner"
],
"install_type": "git-clone",
"description": "Task-aware Wan 2.2 sampling plans and sampler-specific breakout nodes for ComfyUI."
},
{
"author": "wobba",
"title": "ComfyUI-ChatterBox-Turbo",
@ -54053,6 +54113,66 @@
],
"install_type": "git-clone",
"description": "One-node studio UI package for Z-Image Turbo, Flux.2 Klein, Qwen Image Edit 2511, and Krea 2 workflows, including T2I, I2I, inpaint, outpaint, edit, faceswap, angle, and upscale modes."
},
{
"author": "depth-layer-rasterizer",
"title": "Depth Layer Rasterizer",
"reference": "https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer",
"files": [
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer"
],
"install_type": "git-clone",
"description": "Depth-map paper-cut and parallax layer rasterizer for ComfyUI"
},
{
"author": "iljung1106",
"title": "Anima Safe PAG",
"reference": "https://github.com/iljung1106/comfyui-anima-safe-pag",
"files": [
"https://github.com/iljung1106/comfyui-anima-safe-pag"
],
"install_type": "git-clone",
"description": "A soft, Anima-friendly Perturbed Attention Guidance node for ComfyUI."
},
{
"author": "TechnoWarrior2",
"title": "ComfyUI Image Picker",
"reference": "https://github.com/TechnoWarrior2/comfyui-image-picker",
"files": [
"https://github.com/TechnoWarrior2/comfyui-image-picker"
],
"install_type": "git-clone",
"description": "Pauses your workflow and opens a popup window to manually pick which generated images pass through to the rest of the graph."
},
{
"author": "TechnoWarrior2",
"title": "ComfyUI Live Preview",
"reference": "https://github.com/TechnoWarrior2/comfyui-live-preview",
"files": [
"https://github.com/TechnoWarrior2/comfyui-live-preview"
],
"install_type": "git-clone",
"description": "Displays a large draggable floating window with live denoising step previews as your image generates, similar to A1111 and Forge."
},
{
"author": "Moon-NE",
"title": "MoonNe_Nodes",
"reference": "https://github.com/Moon-NE/MoonNe_Nodes",
"files": [
"https://github.com/Moon-NE/MoonNe_Nodes"
],
"install_type": "git-clone",
"description": "Custom nodes for text operations, including concatenation, horizontal-to-vertical conversion, and batch processing."
},
{
"author": "younestft",
"title": "Comfy EverAnimate",
"reference": "https://github.com/younestft/Comfy_EverAnimate",
"files": [
"https://github.com/younestft/Comfy_EverAnimate"
],
"install_type": "git-clone",
"description": "Native ComfyUI nodes for EverAnimate-style chunked WanAnimate workflows, including master/chunk settings, chunk calculation, image carry-over, and boundary color correction."
}
]
}

View File

@ -1217,6 +1217,15 @@
"title_aux": "Pixel3DMM ComfyUI Nodes"
}
],
"https://github.com/A1-multiply/ComfyUI-LatentSaver": [
[
"A1LoadLatent",
"A1SaveLatent"
],
{
"title_aux": "ComfyUI LatentSaver"
}
],
"https://github.com/A4P7J1N7M05OT/ComfyUI-InlineVariableWildcards": [
[
"InlineWildcardGenerator",
@ -3867,6 +3876,7 @@
"Beaufort",
"Bifid",
"Binary2String",
"BitsCounter",
"BitwiseAND",
"BitwiseLS",
"BitwiseNOT",
@ -3888,6 +3898,11 @@
"ConcatKDFHash_Derive",
"ConcatKDFHash_Verify",
"ConstantTimeCompare",
"DERPrivateKey",
"DERPublicKey",
"ECPrivateKey",
"ECPublicKey",
"ECSign",
"EdDSAPrivateKeyFormat",
"EdDSAPublicKeyFormat",
"EdDSASignature",
@ -3912,6 +3927,8 @@
"Nihilist",
"PBKDF2HMAC_Derive",
"PBKDF2HMAC_Verify",
"PEMPrivateKey",
"PEMPublicKey",
"Padding",
"Playfair",
"Polybius",
@ -3947,6 +3964,7 @@
"X963KDF_Derive",
"X963KDF_Verify",
"XExchange",
"XOFHash",
"XPrivateKeyFormat",
"XPublicKeyFormat",
"Zigzag"
@ -5057,6 +5075,14 @@
"title_aux": "ComfyUI-BS_Kokoro-onnx"
}
],
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint": [
[
"EquirectToDomemaster"
],
{
"title_aux": "ComfyUI-Domemaster-Outpaint"
}
],
"https://github.com/Burve/ComfyUI_Burve_Tools": [
[
"BurveGoogleImageGen",
@ -6307,6 +6333,15 @@
"title_aux": "ComfyUI-CRZnodes"
}
],
"https://github.com/CoreyCorza/comfyui-lora-loader": [
[
"CorzaCleanAppliedLoRAs",
"CorzaLoRAClean"
],
{
"title_aux": "Corza LoRA Loader (Clean)"
}
],
"https://github.com/Cornmeister/ComfyUI-CornmeisterNL-PowerPack": [
[
"CornmeisterNL_PowerDiffusionModelLoader",
@ -6905,6 +6940,7 @@
],
"https://github.com/DailyMok/ComfyUI-dAIly": [
[
"PromptMixerV2dAIly",
"PromptMixerdAIly",
"TokenCounterdAIly"
],
@ -17399,26 +17435,24 @@
"KSampler Advanced (Dual Output)",
"Load Folder",
"Load VACE Module",
"Loop SCAIL Pose Frames",
"Match Color Palette",
"Native Wan Pose Strength",
"Pixelation Dimensions",
"Preview (webm)",
"PreviewAsMarkdown",
"PreviewImageAlpha",
"Remove Background",
"ReplaceAlpha",
"Resize Image and Mask by Side",
"SCAIL Pose Frames",
"Save Folder as ZIP",
"Save To Zip",
"Shift Image Batch",
"Snap to Divisible",
"Sprite Scale Calculator",
"Spritesheet Builder",
"Spritesheet Preview",
"String to List",
"Temporal Mask Cropper",
"Trim Conditioning",
"VACE Sampler",
"WAN Frame Calculator",
"WAN Frames to Add & Cut"
],
@ -25218,6 +25252,17 @@
"TTP_Expand_And_Mask",
"TTP_Image_Assy",
"TTP_Image_Tile_Batch",
"TTP_QwenVL3_Local_Loader_Experimental",
"TTP_Smart_Tile_Assemble_Experimental",
"TTP_Smart_Tile_Image_Upscale_Prep_Experimental",
"TTP_Smart_Tile_Interactive_Crop_Experimental",
"TTP_Smart_Tile_Loop_Collect_Experimental",
"TTP_Smart_Tile_Loop_Source_Experimental",
"TTP_Smart_Tile_Output_Size_Estimate_Experimental",
"TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental",
"TTP_Smart_Tile_Save_Final_Image_Experimental",
"TTP_Smart_Tile_Semantic_Rank_Experimental",
"TTP_Smart_Tile_Set_Preview_Experimental",
"TTP_Tile_image_size",
"TTP_condsetarea_merge",
"TTP_condsetarea_merge_test",
@ -26780,6 +26825,7 @@
"XB_AutoBubbleTextRenderer",
"XB_BatchFolderLoader",
"XB_BerniniConditioning",
"XB_BerniniPromptRouter",
"XB_CLIPNameBroadcaster",
"XB_CanvasLabel",
"XB_CheckpointBlockSwap",
@ -26791,6 +26837,7 @@
"XB_HumanSegModelLoader",
"XB_HumanSegmentation",
"XB_ImageParamsMaster",
"XB_K2StyleRouter",
"XB_KSampler",
"XB_KSamplerAdvanced",
"XB_MSR",
@ -30488,6 +30535,8 @@
"BFSHeadSwapLoRADebug",
"FaceSequenceBatch",
"FrameRangedFaceLoader",
"LTXIdentityCAN",
"LTXIdentityGemmaVision",
"LTXIdentityOverlapConditioning",
"LTXIdentityTransfer",
"LTXVApplyNeutralMask",
@ -33599,6 +33648,29 @@
"title_aux": "ComfyUI-ColorCorrectGPU"
}
],
"https://github.com/boobkake22/ComfyUI-SamplingPlanner": [
[
"AccelerationModelPair",
"AccelerationSelector",
"KSamplerBreakout",
"ModelPairBreakout",
"MoeSamplerBreakout",
"PrioritySelector",
"ProgressiveSigmaControl",
"RangeSplitOverrideLegacy",
"SamplerSelector",
"SamplingPlanWan22",
"SchedulerSelector",
"ShiftOverride",
"SigmaBreakout",
"StepBudget",
"StepSplitOverride",
"TaskSelector"
],
{
"title_aux": "Sampling Planner"
}
],
"https://github.com/boobkake22/ComfyUI-SimpleSwitch": [
[
"SimpleAudioLatentSwitch",
@ -39580,6 +39652,18 @@
"title_aux": "demofusion-comfyui"
}
],
"https://github.com/designloves2/ComfyUI-TJ_NODE_STUDIO_ONE": [
[
"Flux2KleinOneTJNode",
"Krea2OneTJNode",
"QwenImageEdit2511OneTJNode",
"SDXLOneTJNode",
"ZImageTurboOneNode"
],
{
"title_aux": "ComfyUI-TJ_NODE_STUDIO_ONE"
}
],
"https://github.com/destinyfaux/Z-Fuse": [
[
"ZFuseBake",
@ -43141,10 +43225,12 @@
"LTXAttentionTimestepEvolution",
"LTXAttentionZoneAnalysis",
"LTXLatentDims",
"LTXQKVMultiplier",
"LTXQKVStoreInspect",
"LTXQKVTransfer",
"LTXRFForwardSampler",
"LTXRFReverseSampler",
"LTXResetPatches",
"LTXStoreDump",
"LTXStoreLoad"
],
@ -46545,6 +46631,14 @@
"title_aux": "ComfyUI-iterationNode"
}
],
"https://github.com/iljung1106/comfyui-anima-safe-pag": [
[
"AnimaSafePAG"
],
{
"title_aux": "Anima Safe PAG"
}
],
"https://github.com/illuminatianon/comfyui-csvwildcards": [
[
"CSVWildcardNode",
@ -54478,6 +54572,14 @@
"title_aux": "h4 ToolKit"
}
],
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer": [
[
"DepthRasterizeLayers"
],
{
"title_aux": "Depth Layer Rasterizer"
}
],
"https://github.com/maartenharms/comfyui-flowpath": [
[
"FlowPath"
@ -59986,6 +60088,7 @@
[
"PJ_Image_Handler",
"PJ_Latent_Generator",
"PJ_Lora_Loader",
"PJ_Text_Translator",
"PJ_Video_Latent_Generator"
],
@ -67885,6 +67988,15 @@
"title_aux": "ComfyUI-API-DockerCPU"
}
],
"https://github.com/trx7111/ComfyUI-TrixNodes": [
[
"TrixBypasser",
"TrixBypasserSimple"
],
{
"title_aux": "ComfyUI-TrixNodes"
}
],
"https://github.com/tsogzark/ComfyUI-load-image-from-url": [
[
"LoadImageFromUrlOrPath"
@ -69744,7 +69856,10 @@
],
"https://github.com/wearekhepri/comfyui-kepri-nodes-pack": [
[
"KepriAspectRatio",
"KepriFramingAnalysis",
"KepriImageFinalize",
"KepriMaskAnalysis",
"KepriMaskMerge"
],
{

File diff suppressed because it is too large Load Diff

View File

@ -921,7 +921,7 @@ class UnifiedManager:
except:
return version.parse("0.0.0")
def execute_install_script(self, url, repo_path, instant_execution=False, lazy_mode=False, no_deps=False):
def execute_install_script(self, url, repo_path, instant_execution=False, lazy_mode=False, no_deps=False, selected_dependencies=None):
install_script_path = os.path.join(repo_path, "install.py")
requirements_path = os.path.join(repo_path, "requirements.txt")
@ -933,8 +933,19 @@ class UnifiedManager:
if os.path.exists(requirements_path) and not no_deps:
print("Install: pip packages")
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
# Create a set of selected dependency lines for quick lookup
selected_lines = set()
if selected_dependencies:
for dep in selected_dependencies:
selected_lines.add(dep.get('line', '').strip())
lines = manager_util.robust_readlines(requirements_path)
for line in lines:
# Skip if selected_dependencies is provided and this line is not in the selected list
if selected_dependencies is not None and line.strip() not in selected_lines:
continue
package_name = remap_pip_package(line.strip())
if package_name and not package_name.startswith('#') and package_name not in self.processed_install:
self.processed_install.add(package_name)
@ -1342,7 +1353,7 @@ class UnifiedManager:
return result
def repo_install(self, url: str, repo_path: str, instant_execution=False, no_deps=False, return_postinstall=False):
def repo_install(self, url: str, repo_path: str, instant_execution=False, no_deps=False, return_postinstall=False, selected_dependencies=None):
result = ManagedResult('install-git')
result.append(url)
@ -1369,7 +1380,7 @@ class UnifiedManager:
repo.close()
def postinstall():
return self.execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps)
return self.execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps, selected_dependencies=selected_dependencies)
if return_postinstall:
return result.with_postinstall(postinstall)
@ -1468,7 +1479,7 @@ class UnifiedManager:
else:
return self.cnr_switch_version(node_id, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall).with_ver('cnr')
async def install_by_id(self, node_id: str, version_spec=None, channel=None, mode=None, instant_execution=False, no_deps=False, return_postinstall=False):
async def install_by_id(self, node_id: str, version_spec=None, channel=None, mode=None, instant_execution=False, no_deps=False, return_postinstall=False, selected_dependencies=None):
"""
priority if version_spec == None
1. CNR latest
@ -1519,7 +1530,7 @@ class UnifiedManager:
self.unified_disable(node_id, False)
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), node_id))
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall)
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall, selected_dependencies=selected_dependencies)
if res.result:
if version_spec == 'unknown':
self.unknown_active_nodes[node_id] = repo_url, to_path
@ -1974,7 +1985,7 @@ def __win_check_git_pull(path):
process.wait()
def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=False, no_deps=False):
def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=False, no_deps=False, selected_dependencies=None):
# import ipdb; ipdb.set_trace()
install_script_path = os.path.join(repo_path, "install.py")
requirements_path = os.path.join(repo_path, "requirements.txt")
@ -1986,6 +1997,13 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
if os.path.exists(requirements_path) and not no_deps:
print("Install: pip packages")
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
# Create a set of selected dependency lines for quick lookup
selected_lines = set()
if selected_dependencies:
for dep in selected_dependencies:
selected_lines.add(dep.get('line', '').strip())
with open(requirements_path, "r") as requirements_file:
for line in requirements_file:
#handle comments
@ -1996,6 +2014,10 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
else:
line = line.split('#')[0].strip()
# Skip if selected_dependencies is provided and this line is not in the selected list
if selected_dependencies is not None and line.strip() not in selected_lines:
continue
package_name = remap_pip_package(line.strip())
if package_name and not package_name.startswith('#'):

View File

@ -517,7 +517,12 @@ async def task_worker():
global tasks_in_progress
async def do_install(item) -> str:
ui_id, node_spec_str, channel, mode, skip_post_install = item
if len(item) == 6:
ui_id, node_spec_str, channel, mode, skip_post_install, selected_dependencies = item
else:
# Backward compatibility
ui_id, node_spec_str, channel, mode, skip_post_install = item
selected_dependencies = []
try:
node_spec = core.unified_manager.resolve_node_spec(node_spec_str)
@ -526,7 +531,7 @@ async def task_worker():
return f"Cannot resolve install target: '{node_spec_str}'"
node_name, version_spec, is_specified = node_spec
res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install)
res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install, selected_dependencies=selected_dependencies)
# discard post install if skip_post_install mode
if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']:
@ -1421,7 +1426,9 @@ async def install_custom_node(request):
logging.error(SECURITY_MESSAGE_GENERAL)
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install
# Get selected dependencies if provided
selected_dependencies = json_data.get('selectedDependencies', [])
install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install, selected_dependencies
task_queue.put(("install", install_item))
return web.Response(status=200)
@ -1521,6 +1528,272 @@ async def install_custom_node_pip(request):
return web.Response(status=200)
@routes.post("/customnode/analyze_dependencies")
async def analyze_dependencies(request):
"""
Analyze dependencies for a custom node from git URL.
Fetches requirements.txt, checks installed packages, and returns dependency list with status.
"""
try:
json_data = await request.json()
url = json_data.get('url')
commit_id = json_data.get('commitId')
branch = json_data.get('branch')
if not url:
return web.json_response({'error': 'URL is required'}, status=400)
# Fetch requirements.txt from git repository
requirements_content = await fetch_requirements_from_git(url, commit_id, branch)
if requirements_content is None:
return web.json_response({
'success': True,
'requirements': None,
'dependencies': [],
'noRequirementsFile': True
})
# Parse requirements
dependencies = parse_requirements(requirements_content)
# Get installed packages
installed_packages = manager_util.get_installed_packages()
# Analyze each dependency with subdependencies
analyzed_dependencies = []
for dep_line in dependencies:
if not dep_line.strip() or dep_line.strip().startswith('#'):
continue
# Parse dependency line
parsed = manager_util.parse_requirement_line(dep_line)
if not parsed:
continue
package_name = parsed.get('package')
if not package_name:
# Fallback: extract from line if package is missing
import re
match = re.match(r'^([a-zA-Z0-9_.-]+)', dep_line.strip())
package_name = match.group(1) if match else "Unknown"
version_spec = parsed.get('version')
# Convert version_spec to string if it's a StrictVersion object
if version_spec is not None:
version_spec = str(version_spec)
normalized_name = package_name.lower().replace('-', '_')
# Check if already installed
installed_version = installed_packages.get(normalized_name)
status = 'new'
if installed_version:
status = 'installed'
# Convert version to string if it's not already (handle StrictVersion objects)
current_version_str = str(installed_version) if installed_version else None
# Get subdependencies using pip install --dry-run
# This is optional and failures should not block the main flow
subdependencies = []
# Skip subdependency analysis for already installed packages (not needed)
if status != 'installed':
try:
import subprocess
import sys
# Run pip install --dry-run to get subdependencies
# Some packages like pymeshlab can take longer due to complex dependency resolution
# Use a reasonable timeout - if it times out, we'll continue without subdependencies
result = subprocess.run(
[sys.executable, '-m', 'pip', 'install', '--dry-run', dep_line.strip()],
capture_output=True,
text=True,
timeout=45 # Increased timeout to 45 seconds
)
output = result.stdout + result.stderr
if output:
subdependencies = parse_dry_run_output(output, package_name, installed_packages)
except subprocess.TimeoutExpired:
# Timeout is not critical - continue without subdependencies
logging.debug(f"Subdependency analysis timed out for {package_name} (skipping subdependencies)")
subdependencies = []
except Exception as e:
# Any other error is not critical - continue without subdependencies
logging.debug(f"Failed to analyze subdependencies for {package_name}: {e}")
subdependencies = []
# Add main dependency (always add, even if subdependency analysis failed)
# Ensure all fields are properly set and clean
clean_package_name = str(package_name).strip() if package_name else "Unknown"
# Remove any None/null strings that might have been concatenated
clean_package_name = clean_package_name.replace('None', '').replace('null', '').strip()
if not clean_package_name:
clean_package_name = "Unknown"
analyzed_dependencies.append({
'name': clean_package_name,
'version': str(version_spec) if version_spec else None,
'line': dep_line.strip(),
'status': status,
'currentVersion': current_version_str,
'selected': status != 'installed', # Deselect if already installed
'subdependencies': subdependencies
})
return web.json_response({
'success': True,
'requirements': requirements_content,
'dependencies': analyzed_dependencies,
'noRequirementsFile': False
})
except Exception as e:
logging.error(f"Error analyzing dependencies: {e}")
traceback.print_exc()
return web.json_response({'error': str(e)}, status=500)
def parse_requirements(content):
"""Parse requirements.txt content into list of dependency lines."""
lines = []
for line in content.split('\n'):
line = line.strip()
if line and not line.startswith('#'):
lines.append(line)
return lines
def parse_dry_run_output(output, parent_name, installed_packages):
"""Parse pip install --dry-run output to extract subdependencies."""
import re
subdependencies = []
subdeps_map = {}
lines = output.split('\n')
for line in lines:
line = line.strip()
# Look for "Collecting package==version" lines
if 'Collecting ' in line and 'Using cached' not in line:
# Match: "Collecting package==version" or "Collecting package"
match = re.search(r'Collecting\s+([a-zA-Z0-9_.-]+(?:\[[^\]]+\])?)(?:\s*==\s*([^\s\(]+))?', line)
if match:
dep_name = match.group(1).split('[')[0].strip()
# Clean the name - remove any None/null strings
if dep_name:
dep_name = dep_name.replace('None', '').replace('null', '').strip()
dep_version = match.group(2).strip() if match.group(2) else None
# Clean version too
if dep_version:
dep_version = dep_version.replace('None', '').replace('null', '').strip() or None
# Skip the parent package itself
if dep_name.lower() == parent_name.lower():
continue
# Normalize name
normalized_name = dep_name.lower().replace('-', '_')
# Check if already in map (avoid duplicates)
if normalized_name not in subdeps_map:
# Check if already installed
installed_version = installed_packages.get(normalized_name)
status = 'installed' if installed_version else 'new'
current_version_str = str(installed_version) if installed_version else None
# Ensure name is always a string, not None
if not dep_name:
dep_name = "Unknown"
# Clean the name - remove any None/null strings
clean_dep_name = str(dep_name).strip().replace('None', '').replace('null', '').strip()
if not clean_dep_name:
clean_dep_name = "Unknown"
subdeps_map[normalized_name] = {
'name': clean_dep_name,
'version': str(dep_version) if dep_version else None,
'status': status,
'currentVersion': current_version_str,
'selected': status != 'installed'
}
# Also look for "Would install" lines which have more accurate version info
if 'Would install' in line:
# Match: "Would install package-version"
match = re.search(r'Would install\s+([a-zA-Z0-9_.-]+)-([\d.]+)', line)
if match:
dep_name = match.group(1)
dep_version = match.group(2)
normalized_name = dep_name.lower().replace('-', '_')
if normalized_name in subdeps_map:
# Update with more accurate version
subdeps_map[normalized_name]['version'] = dep_version
# Convert map to list
for normalized_name, dep_info in subdeps_map.items():
subdependencies.append(dep_info)
return subdependencies
async def fetch_requirements_from_git(url, commit_id=None, branch=None):
"""
Fetch requirements.txt from a git repository URL.
Supports GitHub URLs by converting to raw.githubusercontent.com.
"""
try:
# Extract GitHub repo info
if 'github.com' in url:
# Convert to raw GitHub URL
url = url.rstrip('/')
if url.endswith('.git'):
url = url[:-4]
# Extract owner/repo
match = re.search(r'github\.com[:/]([^/]+)/([^/]+)', url)
if not match:
return None
owner = match.group(1)
repo = match.group(2)
# Build raw URL
if commit_id:
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{commit_id}/requirements.txt"
elif branch:
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/requirements.txt"
else:
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/main/requirements.txt"
# Try to fetch using aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(raw_url) as response:
if response.status == 200:
return await response.text()
# Try with master branch if main fails
if 'main' in raw_url:
raw_url = raw_url.replace('/main/', '/master/')
async with session.get(raw_url) as response2:
if response2.status == 200:
return await response2.text()
else:
# For non-GitHub URLs, we'd need to clone temporarily
# For now, return None (can be enhanced later)
logging.warning(f"Non-GitHub URL not fully supported for dependency analysis: {url}")
return None
return None
except Exception as e:
logging.error(f"Error fetching requirements from git: {e}")
return None
@routes.post("/manager/queue/uninstall")
async def uninstall_custom_node(request):
if not is_allowed_security_level('middle'):

View File

@ -67,6 +67,7 @@ export class CustomNodesManager {
this.filter = '';
this.keywords = '';
this.restartMap = {};
this.analyzeDependenciesBeforeInstall = false; // Default: false
this.init();
@ -77,6 +78,36 @@ export class CustomNodesManager {
}
init() {
// Create checkbox for dependency analysis
const analyzeDepsCheckbox = $el("input", {
type: "checkbox",
id: "cn-analyze-deps-checkbox",
checked: this.analyzeDependenciesBeforeInstall,
onchange: (e) => {
this.analyzeDependenciesBeforeInstall = e.target.checked;
},
style: {
marginRight: "6px",
cursor: "pointer"
}
});
const analyzeDepsLabel = $el("label", {
for: "cn-analyze-deps-checkbox",
style: {
display: "flex",
alignItems: "center",
cursor: "pointer",
color: "#fff",
fontSize: "12px",
marginRight: "10px",
whiteSpace: "nowrap"
}
}, [
analyzeDepsCheckbox,
$el("span", { textContent: "Analyse dependencies before node installation" })
]);
const header = $el("div.cn-manager-header.px-2", {}, [
// $el("label", {}, [
// $el("span", { textContent: "Filter" }),
@ -84,6 +115,7 @@ export class CustomNodesManager {
// ]),
createSettingsCombo("Filter", $el("select.cn-manager-filter")),
$el("input.cn-manager-keywords.p-inputtext.p-component", { type: "search", placeholder: "Search" }),
analyzeDepsLabel,
$el("div.cn-manager-status"),
$el("div.cn-flex-auto"),
$el("div.cn-manager-channel")
@ -105,6 +137,421 @@ export class CustomNodesManager {
this.initGrid();
}
showDependencySelectorDialog(dependencies, onSelect) {
const dialog = new ComfyDialog();
dialog.element.style.zIndex = 1100;
dialog.element.style.width = "900px";
dialog.element.style.maxHeight = "80vh";
dialog.element.style.padding = "0";
dialog.element.style.backgroundColor = "#2a2a2a";
dialog.element.style.border = "1px solid #3a3a3a";
dialog.element.style.borderRadius = "8px";
dialog.element.style.boxSizing = "border-box";
dialog.element.style.overflow = "hidden";
const contentStyle = {
width: "100%",
display: "flex",
flexDirection: "column",
padding: "20px",
boxSizing: "border-box",
gap: "15px"
};
// Create scrollable table container with sticky header
const tableContainer = $el("div", {
style: {
maxHeight: "500px",
overflowY: "auto",
border: "1px solid #4a4a4a",
borderRadius: "4px",
backgroundColor: "#1a1a1a",
position: "relative"
}
});
// Create table
const table = $el("table", {
style: {
width: "100%",
borderCollapse: "separate",
borderSpacing: "0",
fontSize: "14px"
}
});
// Create table header with sticky positioning
const thead = $el("thead", {
style: {
position: "sticky",
top: "0",
zIndex: "10",
backgroundColor: "#2a2a2a",
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
}
}, [
$el("tr", {
style: {
backgroundColor: "#2a2a2a",
borderBottom: "2px solid #4a4a4a"
}
}, [
$el("th", {
textContent: "",
style: {
padding: "10px",
textAlign: "left",
width: "40px",
color: "#fff"
}
}),
$el("th", {
textContent: "Dependency Name",
style: {
padding: "10px",
textAlign: "left",
color: "#fff",
fontWeight: "bold"
}
}),
$el("th", {
textContent: "Current Version",
style: {
padding: "10px",
textAlign: "left",
color: "#fff",
fontWeight: "bold"
}
}),
$el("th", {
textContent: "Incoming Version",
style: {
padding: "10px",
textAlign: "left",
color: "#fff",
fontWeight: "bold"
}
})
])
]);
// Create table body
const tbody = $el("tbody", {});
// Create table rows for each dependency and its subdependencies
let rowIndex = 0;
dependencies.forEach((dep) => {
// Ensure name is not null/undefined and clean it
let depName = dep.name;
if (!depName || depName === 'null' || depName === 'None') {
// Fallback: extract from line
if (dep.line) {
depName = dep.line.split(/[=<>!~]/)[0].trim();
} else {
depName = "Unknown";
}
}
// Remove any "null" suffix that might have been appended
depName = String(depName).replace(/null$/i, '').trim();
const isInstalled = dep.status === 'installed';
const incomingVersion = dep.version || "NA";
const currentVersion = dep.currentVersion || "NA";
// Main dependency row
const row = $el("tr", {
style: {
backgroundColor: rowIndex % 2 === 0 ? "#1a1a1a" : "#222222",
borderBottom: "1px solid #3a3a3a"
}
}, [
$el("td", {
style: {
padding: "10px",
textAlign: "center"
}
}, [
$el("input", {
type: "checkbox",
checked: dep.selected,
onchange: (e) => {
dep.selected = e.target.checked;
},
style: {
cursor: "pointer",
width: "18px",
height: "18px"
}
})
]),
$el("td", {
style: {
padding: "10px",
color: isInstalled ? "#888" : "#fff"
}
}, [
$el("span", {
textContent: depName,
style: {
fontWeight: "500",
marginRight: isInstalled ? "8px" : "0"
}
}),
isInstalled ? $el("span", {
textContent: "Installed",
style: {
display: "inline-block",
backgroundColor: "#2a4a2a",
color: "#4a9",
padding: "2px 6px",
borderRadius: "3px",
fontSize: "10px",
fontWeight: "bold",
border: "1px solid #4a9"
}
}) : ''
]),
$el("td", {
textContent: currentVersion,
style: {
padding: "10px",
color: isInstalled ? "#4a9" : "#aaa",
fontFamily: "monospace"
}
}),
$el("td", {
textContent: incomingVersion,
style: {
padding: "10px",
color: "#fff",
fontFamily: "monospace"
}
})
]);
tbody.appendChild(row);
rowIndex++;
// Add subdependencies as indented rows
if(dep.subdependencies && dep.subdependencies.length > 0) {
dep.subdependencies.forEach((subdep) => {
// Ensure subdependency name is not null/undefined and clean it
let subdepName = subdep.name;
if (!subdepName || subdepName === 'null' || subdepName === 'None') {
subdepName = "Unknown";
}
// Remove any "null" suffix that might have been appended
subdepName = String(subdepName).replace(/null$/i, '').trim();
const subIsInstalled = subdep.status === 'installed';
const subIncomingVersion = subdep.version || "NA";
const subCurrentVersion = subdep.currentVersion || "NA";
const subRow = $el("tr", {
style: {
backgroundColor: rowIndex % 2 === 0 ? "#1a1a1a" : "#222222",
borderBottom: "1px solid #3a3a3a"
}
}, [
$el("td", {
style: {
padding: "10px",
textAlign: "center"
}
}, [
$el("input", {
type: "checkbox",
checked: subdep.selected,
onchange: (e) => {
subdep.selected = e.target.checked;
},
style: {
cursor: "pointer",
width: "18px",
height: "18px"
}
})
]),
$el("td", {
style: {
padding: "10px 10px 10px 30px",
color: subIsInstalled ? "#888" : "#aaa",
fontSize: "13px"
}
}, [
$el("span", {
textContent: "└─ " + subdepName,
style: {
fontWeight: "400",
marginRight: subIsInstalled ? "8px" : "0"
}
}),
subIsInstalled ? $el("span", {
textContent: "Installed",
style: {
display: "inline-block",
backgroundColor: "#2a4a2a",
color: "#4a9",
padding: "2px 6px",
borderRadius: "3px",
fontSize: "10px",
fontWeight: "bold",
border: "1px solid #4a9"
}
}) : ''
]),
$el("td", {
textContent: subCurrentVersion,
style: {
padding: "10px",
color: subIsInstalled ? "#4a9" : "#666",
fontFamily: "monospace",
fontSize: "13px"
}
}),
$el("td", {
textContent: subIncomingVersion,
style: {
padding: "10px",
color: "#aaa",
fontFamily: "monospace",
fontSize: "13px"
}
})
]);
tbody.appendChild(subRow);
rowIndex++;
});
}
});
table.appendChild(thead);
table.appendChild(tbody);
tableContainer.appendChild(table);
const content = $el("div", {
style: contentStyle
}, [
$el("h3", {
textContent: "Select Dependencies to Install",
style: {
color: "#ffffff",
backgroundColor: "#1a1a1a",
padding: "10px 15px",
margin: "0 0 10px 0",
width: "100%",
textAlign: "center",
borderRadius: "4px",
boxSizing: "border-box"
}
}),
$el("div", {
textContent: `${dependencies.filter(d => d.status === 'installed').length} already installed, ${dependencies.filter(d => d.status !== 'installed').length} to install`,
style: {
color: "#aaa",
fontSize: "12px",
marginBottom: "5px"
}
}),
tableContainer,
$el("div", {
style: {
display: "flex",
justifyContent: "space-between",
width: "100%",
gap: "10px",
marginTop: "10px"
}
}, [
$el("button", {
textContent: "Cancel",
onclick: () => {
onSelect(null); // Pass null to indicate cancellation
dialog.close();
},
style: {
flex: "1",
padding: "8px",
backgroundColor: "#4a4a4a",
color: "#ffffff",
border: "none",
borderRadius: "4px",
cursor: "pointer"
}
}),
$el("button", {
textContent: "Select All",
onclick: () => {
dependencies.forEach(dep => {
if (dep.status !== 'installed') {
dep.selected = true;
}
// Also select subdependencies
if(dep.subdependencies) {
dep.subdependencies.forEach(subdep => {
if(subdep.status !== 'installed') {
subdep.selected = true;
}
});
}
});
// Update checkboxes in the table
const checkboxes = tableContainer.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach((checkbox) => {
if(!checkbox.disabled) {
checkbox.checked = true;
}
});
},
style: {
padding: "8px 15px",
backgroundColor: "#4a6a4a",
color: "#ffffff",
border: "none",
borderRadius: "4px",
cursor: "pointer"
}
}),
$el("button", {
textContent: "Install Selected",
onclick: () => {
// Collect all selected dependencies (main + subdependencies)
const selected = [];
dependencies.forEach(d => {
if(d.selected) {
selected.push(d);
}
// Also include selected subdependencies
if(d.subdependencies) {
d.subdependencies.forEach(subdep => {
if(subdep.selected) {
selected.push(subdep);
}
});
}
});
onSelect(selected);
dialog.close();
},
style: {
flex: "1",
padding: "8px",
backgroundColor: "#4CAF50",
color: "#ffffff",
border: "none",
borderRadius: "4px",
cursor: "pointer"
}
}),
])
]);
console.log('[Dependency Dialog] Showing dialog with', dependencies.length, 'dependencies');
dialog.show(content);
console.log('[Dependency Dialog] Dialog shown');
}
showVersionSelectorDialog(versions, onSelect) {
const dialog = new ComfyDialog();
dialog.element.style.zIndex = 1100;
@ -1470,6 +1917,101 @@ export class CustomNodesManager {
}
}
// For install mode, analyze dependencies BEFORE starting installation
let selectedDependencies = [];
let dependencyDialogShown = false; // Track if dialog was shown
if(mode === "install" && this.analyzeDependenciesBeforeInstall) {
// Analyze dependencies for all items first (only if checkbox is enabled)
for (const hash of list) {
const item = this.grid.getRowItemBy("hash", hash);
if (!item) {
console.log('[Dependency Analysis] Item not found for hash:', hash);
continue;
}
const data = item.originalData;
console.log('[Dependency Analysis] Item data:', {
title: item.title,
files: data.files,
repository: data.repository,
hasFiles: !!data.files,
filesLength: data.files ? data.files.length : 0
});
// Try multiple ways to get the git URL
let gitUrl = null;
if(data.files && data.files.length > 0) {
gitUrl = data.files[0];
} else if(data.repository) {
gitUrl = data.repository;
}
if(gitUrl && (gitUrl.includes('github.com') || gitUrl.includes('.git'))) {
try {
this.showStatus(`Analyzing dependencies for ${item.title}...`);
console.log('[Dependency Analysis] Fetching dependencies for:', gitUrl);
const analyzeRes = await api.fetchApi('/customnode/analyze_dependencies', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: gitUrl,
commitId: data.commit_id,
branch: data.branch
})
});
console.log('[Dependency Analysis] Response status:', analyzeRes.status);
if(analyzeRes.status === 200) {
const analyzeData = await analyzeRes.json();
console.log('[Dependency Analysis] Response data:', {
success: analyzeData.success,
hasDependencies: !!analyzeData.dependencies,
dependenciesCount: analyzeData.dependencies ? analyzeData.dependencies.length : 0,
noRequirementsFile: analyzeData.noRequirementsFile
});
if(analyzeData.success && analyzeData.dependencies && analyzeData.dependencies.length > 0) {
console.log('[Dependency Analysis] Showing dialog with', analyzeData.dependencies.length, 'dependencies');
dependencyDialogShown = true;
// Show dependency selection dialog and wait for user
const userSelection = await new Promise((resolve) => {
this.showDependencySelectorDialog(analyzeData.dependencies, (selected) => {
console.log('[Dependency Analysis] User selected:', selected);
resolve(selected);
});
});
// If user cancelled (null), stop installation
if(userSelection === null) {
console.log('[Dependency Analysis] User cancelled installation');
this.showStatus("Installation cancelled");
return;
}
selectedDependencies = userSelection || [];
console.log('[Dependency Analysis] Selected dependencies:', selectedDependencies.length);
} else if(analyzeData.noRequirementsFile) {
console.log('[Dependency Analysis] No requirements.txt file found');
} else {
console.log('[Dependency Analysis] No dependencies to show');
}
} else {
const errorText = await analyzeRes.text();
console.error('[Dependency Analysis] API error:', analyzeRes.status, errorText);
}
} catch(e) {
console.error('[Dependency Analysis] Exception:', e);
// Continue with installation even if dependency analysis fails
}
} else {
console.log('[Dependency Analysis] Not a GitHub URL or no URL found:', gitUrl);
}
}
}
target.classList.add("cn-btn-loading");
this.showError("");
@ -1511,6 +2053,46 @@ export class CustomNodesManager {
data.mode = this.mode;
data.ui_id = hash;
// Add selected dependencies to data (including subdependencies)
// Only install selected dependencies - respect user's selection
const allSelected = [];
if(selectedDependencies.length > 0) {
selectedDependencies.forEach(d => {
// Add main dependency if selected
if(d.selected) {
allSelected.push({
name: d.name,
version: d.version,
line: d.line
});
}
// Add selected subdependencies
if(d.subdependencies) {
d.subdependencies.forEach(subdep => {
if(subdep.selected) {
allSelected.push({
name: subdep.name,
version: subdep.version,
line: `${subdep.name}${subdep.version ? '==' + subdep.version : ''}`
});
}
});
}
});
}
// Set selectedDependencies:
// - If dialog was shown: always set (even if empty) to respect user's selection
// - If dialog was not shown: don't set (install all dependencies - original behavior)
if(dependencyDialogShown) {
// User saw the dialog, respect their selection (even if empty)
data.selectedDependencies = allSelected;
} else if(allSelected.length > 0) {
// Dialog wasn't shown but we have selections (shouldn't happen, but just in case)
data.selectedDependencies = allSelected;
}
// If dialog wasn't shown and no selections, don't set selectedDependencies
// This means backend will install all dependencies (original behavior)
let install_mode = mode;
if(mode == 'switch') {
install_mode = 'install';

View File

@ -1874,9 +1874,14 @@
"DonutSave",
"DonutTiledUpscale",
"DonutUniversalDetailer",
"DonutWeightVectorScale",
"DonutWidenMergeCLIP",
"DonutWidenMergeUNet",
"DonutZitConditioningRebalance",
"DonutZitHybridApply",
"DonutZitHybridLoader",
"DonutZitHybridOverride",
"DonutZitHybridSave",
"DonutZitLayerBlendEncode",
"LoadLBW //Inspire",
"LoraBlockInfo //Inspire",
@ -3484,6 +3489,7 @@
"MpiReroute",
"MpiRoundToMultiple",
"MpiRoundToMultipleRes",
"MpiSaveVideo",
"MpiScaledDimensions",
"MpiSimpleBoolean",
"MpiString",
@ -7217,7 +7223,10 @@
"WanMoveSVI_FLF_Mask",
"WanMoveSVI_FLF_Stitch",
"WanMoveSVI_FLF_Visualize",
"WanMoveSVI_FLF_v2"
"WanMoveSVI_FLF_v2",
"Wan_Float",
"Wan_Integer",
"Wan_Length"
],
{
"title_aux": "ComfyUI-WanMoveSVI_FLF_v2"
@ -8889,6 +8898,7 @@
"ETNodes-Color-Selector",
"ETNodes-Gemini-API-Image",
"ETNodes-Gemini-API-Text",
"ETNodes-Gemini-API-Video",
"ETNodes-List-Items",
"ETNodes-List-Selector",
"ETNodes-Text-Preview"

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,125 @@
{
"custom_nodes": [
{
"author": "younestft",
"title": "Comfy EverAnimate",
"reference": "https://github.com/younestft/Comfy_EverAnimate",
"files": [
"https://github.com/younestft/Comfy_EverAnimate"
],
"install_type": "git-clone",
"description": "Native ComfyUI nodes for EverAnimate-style chunked WanAnimate workflows, including master/chunk settings, chunk calculation, image carry-over, and boundary color correction."
},
{
"author": "Moon-NE",
"title": "MoonNe_Nodes",
"reference": "https://github.com/Moon-NE/MoonNe_Nodes",
"files": [
"https://github.com/Moon-NE/MoonNe_Nodes"
],
"install_type": "git-clone",
"description": "Custom nodes for text operations, including concatenation, horizontal-to-vertical conversion, and batch processing."
},
{
"author": "TechnoWarrior2",
"title": "ComfyUI Image Picker",
"reference": "https://github.com/TechnoWarrior2/comfyui-image-picker",
"files": [
"https://github.com/TechnoWarrior2/comfyui-image-picker"
],
"install_type": "git-clone",
"description": "Pauses your workflow and opens a popup window to manually pick which generated images pass through to the rest of the graph."
},
{
"author": "TechnoWarrior2",
"title": "ComfyUI Live Preview",
"reference": "https://github.com/TechnoWarrior2/comfyui-live-preview",
"files": [
"https://github.com/TechnoWarrior2/comfyui-live-preview"
],
"install_type": "git-clone",
"description": "Displays a large draggable floating window with live denoising step previews as your image generates, similar to A1111 and Forge."
},
{
"author": "shootthesound",
"title": "Lighthouse",
"reference": "https://github.com/shootthesound/comfyui-lighthouse",
"files": [
"https://github.com/shootthesound/comfyui-lighthouse"
],
"install_type": "git-clone",
"description": "A non-destructive overlay that lights up every node by graph distance from the one you click. Diagnostic for big workflows, educational for understanding workflows others built, and surgical when you need to focus on one neighbourhood at a time."
},
{
"author": "shootthesound",
"title": "CleanFreak",
"reference": "https://github.com/shootthesound/comfyui-CleanFreak",
"files": [
"https://github.com/shootthesound/comfyui-CleanFreak"
],
"install_type": "git-clone",
"description": "One-click tidy-by-role for any ComfyUI workflow — loaders, encoders, samplers, decoders, and outputs each sorted into their own column with coloured group cards and connections preserved. 1200+ node classes pre-classified out of the box."
},
{
"author": "shootthesound",
"title": "Finding LoRA",
"reference": "https://github.com/shootthesound/comfyui-lora-FindingLora",
"files": [
"https://github.com/shootthesound/comfyui-lora-FindingLora"
],
"install_type": "git-clone",
"description": "A LoRA loader with bookmarks, trigger-word storage and click-to-copy, fuzzy search, and a one-click button to drop another loader inline and wire it into the model chain automatically."
},
{
"author": "shootthesound",
"title": "Image of the Day",
"reference": "https://github.com/shootthesound/comfyui-image-of-the-day",
"files": [
"https://github.com/shootthesound/comfyui-image-of-the-day"
],
"install_type": "git-clone",
"description": "Fetch a fresh image straight into your workflow from 8 daily/random sources. Useful for prompt testing, style references, daily-image automations, or just kicking off a workflow with something interesting."
},
{
"author": "boobkake22",
"title": "Sampling Planner",
"reference": "https://github.com/boobkake22/ComfyUI-SamplingPlanner",
"files": [
"https://github.com/boobkake22/ComfyUI-SamplingPlanner"
],
"install_type": "git-clone",
"description": "Task-aware Wan 2.2 sampling plans and sampler-specific breakout nodes for ComfyUI."
},
{
"author": "iljung1106",
"title": "Anima Safe PAG",
"reference": "https://github.com/iljung1106/comfyui-anima-safe-pag",
"files": [
"https://github.com/iljung1106/comfyui-anima-safe-pag"
],
"install_type": "git-clone",
"description": "A soft, Anima-friendly Perturbed Attention Guidance node for ComfyUI."
},
{
"author": "burgstall-labs",
"title": "ComfyUI-Domemaster-Outpaint",
"reference": "https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint",
"files": [
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint"
],
"install_type": "git-clone",
"description": "Outpaint flat video into fulldome / planetarium shows: domemaster rendering with tilted-venue presets for ComfyUI, on top of the Burgstall Labs 360 outpaint pipeline."
},
{
"author": "depth-layer-rasterizer",
"title": "Depth Layer Rasterizer",
"reference": "https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer",
"files": [
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer"
],
"install_type": "git-clone",
"description": "Depth-map paper-cut and parallax layer rasterizer for ComfyUI"
},
{
"author": "artokun",
"title": "ComfyUI Agent Panel",

View File

@ -1217,6 +1217,15 @@
"title_aux": "Pixel3DMM ComfyUI Nodes"
}
],
"https://github.com/A1-multiply/ComfyUI-LatentSaver": [
[
"A1LoadLatent",
"A1SaveLatent"
],
{
"title_aux": "ComfyUI LatentSaver"
}
],
"https://github.com/A4P7J1N7M05OT/ComfyUI-InlineVariableWildcards": [
[
"InlineWildcardGenerator",
@ -3867,6 +3876,7 @@
"Beaufort",
"Bifid",
"Binary2String",
"BitsCounter",
"BitwiseAND",
"BitwiseLS",
"BitwiseNOT",
@ -3888,6 +3898,11 @@
"ConcatKDFHash_Derive",
"ConcatKDFHash_Verify",
"ConstantTimeCompare",
"DERPrivateKey",
"DERPublicKey",
"ECPrivateKey",
"ECPublicKey",
"ECSign",
"EdDSAPrivateKeyFormat",
"EdDSAPublicKeyFormat",
"EdDSASignature",
@ -3912,6 +3927,8 @@
"Nihilist",
"PBKDF2HMAC_Derive",
"PBKDF2HMAC_Verify",
"PEMPrivateKey",
"PEMPublicKey",
"Padding",
"Playfair",
"Polybius",
@ -3947,6 +3964,7 @@
"X963KDF_Derive",
"X963KDF_Verify",
"XExchange",
"XOFHash",
"XPrivateKeyFormat",
"XPublicKeyFormat",
"Zigzag"
@ -5057,6 +5075,14 @@
"title_aux": "ComfyUI-BS_Kokoro-onnx"
}
],
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint": [
[
"EquirectToDomemaster"
],
{
"title_aux": "ComfyUI-Domemaster-Outpaint"
}
],
"https://github.com/Burve/ComfyUI_Burve_Tools": [
[
"BurveGoogleImageGen",
@ -6307,6 +6333,15 @@
"title_aux": "ComfyUI-CRZnodes"
}
],
"https://github.com/CoreyCorza/comfyui-lora-loader": [
[
"CorzaCleanAppliedLoRAs",
"CorzaLoRAClean"
],
{
"title_aux": "Corza LoRA Loader (Clean)"
}
],
"https://github.com/Cornmeister/ComfyUI-CornmeisterNL-PowerPack": [
[
"CornmeisterNL_PowerDiffusionModelLoader",
@ -6905,6 +6940,7 @@
],
"https://github.com/DailyMok/ComfyUI-dAIly": [
[
"PromptMixerV2dAIly",
"PromptMixerdAIly",
"TokenCounterdAIly"
],
@ -17399,26 +17435,24 @@
"KSampler Advanced (Dual Output)",
"Load Folder",
"Load VACE Module",
"Loop SCAIL Pose Frames",
"Match Color Palette",
"Native Wan Pose Strength",
"Pixelation Dimensions",
"Preview (webm)",
"PreviewAsMarkdown",
"PreviewImageAlpha",
"Remove Background",
"ReplaceAlpha",
"Resize Image and Mask by Side",
"SCAIL Pose Frames",
"Save Folder as ZIP",
"Save To Zip",
"Shift Image Batch",
"Snap to Divisible",
"Sprite Scale Calculator",
"Spritesheet Builder",
"Spritesheet Preview",
"String to List",
"Temporal Mask Cropper",
"Trim Conditioning",
"VACE Sampler",
"WAN Frame Calculator",
"WAN Frames to Add & Cut"
],
@ -25218,6 +25252,17 @@
"TTP_Expand_And_Mask",
"TTP_Image_Assy",
"TTP_Image_Tile_Batch",
"TTP_QwenVL3_Local_Loader_Experimental",
"TTP_Smart_Tile_Assemble_Experimental",
"TTP_Smart_Tile_Image_Upscale_Prep_Experimental",
"TTP_Smart_Tile_Interactive_Crop_Experimental",
"TTP_Smart_Tile_Loop_Collect_Experimental",
"TTP_Smart_Tile_Loop_Source_Experimental",
"TTP_Smart_Tile_Output_Size_Estimate_Experimental",
"TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental",
"TTP_Smart_Tile_Save_Final_Image_Experimental",
"TTP_Smart_Tile_Semantic_Rank_Experimental",
"TTP_Smart_Tile_Set_Preview_Experimental",
"TTP_Tile_image_size",
"TTP_condsetarea_merge",
"TTP_condsetarea_merge_test",
@ -26780,6 +26825,7 @@
"XB_AutoBubbleTextRenderer",
"XB_BatchFolderLoader",
"XB_BerniniConditioning",
"XB_BerniniPromptRouter",
"XB_CLIPNameBroadcaster",
"XB_CanvasLabel",
"XB_CheckpointBlockSwap",
@ -26791,6 +26837,7 @@
"XB_HumanSegModelLoader",
"XB_HumanSegmentation",
"XB_ImageParamsMaster",
"XB_K2StyleRouter",
"XB_KSampler",
"XB_KSamplerAdvanced",
"XB_MSR",
@ -30488,6 +30535,8 @@
"BFSHeadSwapLoRADebug",
"FaceSequenceBatch",
"FrameRangedFaceLoader",
"LTXIdentityCAN",
"LTXIdentityGemmaVision",
"LTXIdentityOverlapConditioning",
"LTXIdentityTransfer",
"LTXVApplyNeutralMask",
@ -33599,6 +33648,29 @@
"title_aux": "ComfyUI-ColorCorrectGPU"
}
],
"https://github.com/boobkake22/ComfyUI-SamplingPlanner": [
[
"AccelerationModelPair",
"AccelerationSelector",
"KSamplerBreakout",
"ModelPairBreakout",
"MoeSamplerBreakout",
"PrioritySelector",
"ProgressiveSigmaControl",
"RangeSplitOverrideLegacy",
"SamplerSelector",
"SamplingPlanWan22",
"SchedulerSelector",
"ShiftOverride",
"SigmaBreakout",
"StepBudget",
"StepSplitOverride",
"TaskSelector"
],
{
"title_aux": "Sampling Planner"
}
],
"https://github.com/boobkake22/ComfyUI-SimpleSwitch": [
[
"SimpleAudioLatentSwitch",
@ -39580,6 +39652,18 @@
"title_aux": "demofusion-comfyui"
}
],
"https://github.com/designloves2/ComfyUI-TJ_NODE_STUDIO_ONE": [
[
"Flux2KleinOneTJNode",
"Krea2OneTJNode",
"QwenImageEdit2511OneTJNode",
"SDXLOneTJNode",
"ZImageTurboOneNode"
],
{
"title_aux": "ComfyUI-TJ_NODE_STUDIO_ONE"
}
],
"https://github.com/destinyfaux/Z-Fuse": [
[
"ZFuseBake",
@ -43141,10 +43225,12 @@
"LTXAttentionTimestepEvolution",
"LTXAttentionZoneAnalysis",
"LTXLatentDims",
"LTXQKVMultiplier",
"LTXQKVStoreInspect",
"LTXQKVTransfer",
"LTXRFForwardSampler",
"LTXRFReverseSampler",
"LTXResetPatches",
"LTXStoreDump",
"LTXStoreLoad"
],
@ -46545,6 +46631,14 @@
"title_aux": "ComfyUI-iterationNode"
}
],
"https://github.com/iljung1106/comfyui-anima-safe-pag": [
[
"AnimaSafePAG"
],
{
"title_aux": "Anima Safe PAG"
}
],
"https://github.com/illuminatianon/comfyui-csvwildcards": [
[
"CSVWildcardNode",
@ -54478,6 +54572,14 @@
"title_aux": "h4 ToolKit"
}
],
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer": [
[
"DepthRasterizeLayers"
],
{
"title_aux": "Depth Layer Rasterizer"
}
],
"https://github.com/maartenharms/comfyui-flowpath": [
[
"FlowPath"
@ -59986,6 +60088,7 @@
[
"PJ_Image_Handler",
"PJ_Latent_Generator",
"PJ_Lora_Loader",
"PJ_Text_Translator",
"PJ_Video_Latent_Generator"
],
@ -67885,6 +67988,15 @@
"title_aux": "ComfyUI-API-DockerCPU"
}
],
"https://github.com/trx7111/ComfyUI-TrixNodes": [
[
"TrixBypasser",
"TrixBypasserSimple"
],
{
"title_aux": "ComfyUI-TrixNodes"
}
],
"https://github.com/tsogzark/ComfyUI-load-image-from-url": [
[
"LoadImageFromUrlOrPath"
@ -69744,7 +69856,10 @@
],
"https://github.com/wearekhepri/comfyui-kepri-nodes-pack": [
[
"KepriAspectRatio",
"KepriFramingAnalysis",
"KepriImageFinalize",
"KepriMaskAnalysis",
"KepriMaskMerge"
],
{