mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-01-23 20:40:18 +08:00
Compare commits
14 Commits
ed1485cd7d
...
ffa8ba884b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffa8ba884b | ||
|
|
22acaa1d2c | ||
|
|
fe791ccee9 | ||
|
|
414557eee0 | ||
|
|
97d2741360 | ||
|
|
b95e5f1eae | ||
|
|
43b200dc91 | ||
|
|
29014699bb | ||
|
|
5576672957 | ||
|
|
5002606861 | ||
|
|
ba0fb343ff | ||
|
|
17e5ae6bc2 | ||
|
|
7a0186efc8 | ||
|
|
e7d4ea76d7 |
@ -2260,10 +2260,10 @@
|
||||
{
|
||||
"author": "bradsec",
|
||||
"title": "ResolutionSelector for ComfyUI",
|
||||
"id": "resolution-selector",
|
||||
"reference": "https://github.com/bradsec/ComfyUI_ResolutionSelector",
|
||||
"id": "comfyui_resolutionselectorplus",
|
||||
"reference": "https://github.com/bradsec/ComfyUI_ResolutionSelectorPlus",
|
||||
"files": [
|
||||
"https://github.com/bradsec/ComfyUI_ResolutionSelector"
|
||||
"https://github.com/bradsec/ComfyUI_ResolutionSelectorPlus"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for Stable Diffusion ComfyUI to enable easy selection of image resolutions for SDXL SD15 SD21"
|
||||
@ -11399,6 +11399,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "GRAG-Image-Editing : Group-Relative Attention Guidance for Image Editing,you can try it in comfyUI"
|
||||
},
|
||||
{
|
||||
"author": "smthemex",
|
||||
"title": "ComfyUI_UltraFlux",
|
||||
"reference": "https://github.com/smthemex/ComfyUI_UltraFlux",
|
||||
"files": [
|
||||
"https://github.com/smthemex/ComfyUI_UltraFlux"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "UltraFlux:Data-Model Co-Design for High-quality Native 4K Text-to-Image Generation across Diverse Aspect Ratios,try it in comfyUI"
|
||||
},
|
||||
{
|
||||
"author": "choey",
|
||||
"title": "Comfy-Topaz",
|
||||
@ -20180,10 +20190,9 @@
|
||||
{
|
||||
"author": "Black-Lioness",
|
||||
"title": "ComfyUI-PromptUtils",
|
||||
"reference": "https://github.com/Black-Lioness/ComfyUI-PromptUtils",
|
||||
"reference2": "https://github.com/RunningOverGlowies/ComfyUI-PromptUtils",
|
||||
"reference": "https://github.com/RunningOverGlowies/ComfyUI-PromptUtils",
|
||||
"files": [
|
||||
"https://github.com/Black-Lioness/ComfyUI-PromptUtils"
|
||||
"https://github.com/RunningOverGlowies/ComfyUI-PromptUtils"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A set of ComfyUI nodes designed to enhance your workflow with realistic filename generation and keyword generation."
|
||||
@ -24541,6 +24550,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A simple ComfyUI node for generating right eye disparity for VR videos"
|
||||
},
|
||||
{
|
||||
"author": "KAVVATARE",
|
||||
"title": "ComfyUI_SimpleGoogleTranslate",
|
||||
"reference": "https://github.com/KAVVATARE/ComfyUI_SimpleGoogleTranslate",
|
||||
"files": [
|
||||
"https://github.com/KAVVATARE/ComfyUI_SimpleGoogleTranslate"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Minimal and easy-to-use Google Translate text node for ComfyUI using the googletrans library with automatic language detection and full language support. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "fat-tire",
|
||||
"title": "ComfyUI Unified Media Suite",
|
||||
@ -24896,6 +24915,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Free trial of Tongyi Wanxiang wan2.1 model, this is a batch implementation of wan2.1 API, providing batch processing for your short video production."
|
||||
},
|
||||
{
|
||||
"author": "penposs",
|
||||
"title": "Comfyui-banana2",
|
||||
"reference": "https://github.com/penposs/Comfyui-banana2",
|
||||
"files": [
|
||||
"https://github.com/penposs/Comfyui-banana2"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom node for ComfyUI using Google's Gemini 2.5 Flash Image and Gemini 3 Pro Image Preview APIs to generate images with resolution scaling for 1K, 2K, 4K, 8K. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "cardenluo",
|
||||
"title": "ComfyUI-Apt_Preset",
|
||||
@ -25652,16 +25681,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Comfyui-raw-image provides the ability to load raw image files for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "DiffusionWave",
|
||||
"title": "PickResolution_DiffusionWave",
|
||||
"reference": "https://github.com/DiffusionWave/PickResolution_DiffusionWave",
|
||||
"files": [
|
||||
"https://github.com/DiffusionWave/PickResolution_DiffusionWave"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI that allows selecting a base resolution, applying a custom scaling value based on FLOAT (up to 10 decimal places), and adding an extra integer value. Outputs include both INT and FLOAT resolutions, making it perfect for you to play around with."
|
||||
},
|
||||
{
|
||||
"author": "Zar4X",
|
||||
"title": "ComfyUI-Batch-Process",
|
||||
@ -26934,6 +26953,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "wrapper for the TLB-VFI: Temporal-Aware Latent Brownian Bridge Diffusion for Video Frame Interpolation project"
|
||||
},
|
||||
{
|
||||
"author": "BobRandomNumber",
|
||||
"title": "ComfyUI-BasicOllama",
|
||||
"reference": "https://github.com/BobRandomNumber/ComfyUI-BasicOllama",
|
||||
"files": [
|
||||
"https://github.com/BobRandomNumber/ComfyUI-BasicOllama"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A simplified node that provides access to Ollama. It allows you to send prompts, system prompts, and images to your Ollama instance and receive text-based responses."
|
||||
},
|
||||
{
|
||||
"author": "santiagosamuel3455",
|
||||
"title": "ComfyUI-GeminiImageToPrompt",
|
||||
@ -27167,6 +27196,26 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A more advanced version of the original ImageScaleToTotalPixels node"
|
||||
},
|
||||
{
|
||||
"author": "BigStationW",
|
||||
"title": "ComfyUi-ConditioningNoiseInjection",
|
||||
"reference": "https://github.com/BigStationW/ComfyUi-ConditioningNoiseInjection",
|
||||
"files": [
|
||||
"https://github.com/BigStationW/ComfyUi-ConditioningNoiseInjection"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI that injects controlled noise into conditioning embeddings (like prompts) for a portion of the diffusion process."
|
||||
},
|
||||
{
|
||||
"author": "BigStationW",
|
||||
"title": "ComfyUi-ConditioningTimestepSwitch",
|
||||
"reference": "https://github.com/BigStationW/ComfyUi-ConditioningTimestepSwitch",
|
||||
"files": [
|
||||
"https://github.com/BigStationW/ComfyUi-ConditioningTimestepSwitch"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI that allows temporal switching between prompts."
|
||||
},
|
||||
{
|
||||
"author": "matoo",
|
||||
"title": "Compare Videos",
|
||||
@ -29296,16 +29345,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Load images with automatic prompt extraction from Civitai URLs, caption files, or EXIF metadata. Features smart dataset detection and dynamic preview updates."
|
||||
},
|
||||
{
|
||||
"author": "LargeModGames",
|
||||
"title": "ComfyUI LoRA Auto Downloader",
|
||||
"reference": "https://github.com/LargeModGames/comfyui-smart-lora-downloader",
|
||||
"files": [
|
||||
"https://github.com/LargeModGames/comfyui-smart-lora-downloader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Automatically download missing LoRAs from CivitAI and detect missing LoRAs in workflows. Features smart directory detection and easy installation."
|
||||
},
|
||||
{
|
||||
"author": "benjamin-bertram",
|
||||
"title": "ComfyUI OIDN Denoiser",
|
||||
@ -32117,6 +32156,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Modular ComfyUI nodes to run the vision-language model MiniCPM‑V‑4 in GGUF format, powered by llama‑cpp‑python."
|
||||
},
|
||||
{
|
||||
"author": "mamorett",
|
||||
"title": "ComfyUI-Flux2proReplicate",
|
||||
"reference": "https://github.com/mamorett/ComfyUI-Flux2proReplicate",
|
||||
"files": [
|
||||
"https://github.com/mamorett/ComfyUI-Flux2proReplicate"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Set of custom nodes for ComfyUI that generates images using the Flux 2 Pro model via Replicate API with flexible aspect ratios and secure API key management. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "Verolelb",
|
||||
"title": "ComfyUI-Qwen-Aspect-Ratio",
|
||||
@ -32474,6 +32523,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A powerful node for browsing and importing media from Pixabay directly within ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "Firetheft",
|
||||
"title": "ComfyUI_Simple_Web_Browser",
|
||||
"reference": "https://github.com/Firetheft/ComfyUI_Simple_Web_Browser",
|
||||
"files": [
|
||||
"https://github.com/Firetheft/ComfyUI_Simple_Web_Browser"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is a custom node for ComfyUI that embeds a simple web browser directly into the interface. It allows you to browse websites, find inspiration, and load images directly, which can help streamline your workflow."
|
||||
},
|
||||
{
|
||||
"author": "RegulusAlpha",
|
||||
"title": "ComfyUI Dynamic Prompting Simplified",
|
||||
@ -33705,6 +33764,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Google Gemini image generation node for ComfyUI supporting up to 6 input images with customizable prompts and parameters. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "dzy1128",
|
||||
"title": "ComfyUI-Vector-Engine",
|
||||
"reference": "https://github.com/dzy1128/ComfyUI-Vector-Engine",
|
||||
"files": [
|
||||
"https://github.com/dzy1128/ComfyUI-Vector-Engine"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom nodes for high-level image generation and editing with Vector Engine Gemini API integration. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "swfxliyiyu",
|
||||
"title": "ComfyUI-FastVideo",
|
||||
@ -33846,6 +33915,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI that integrates the Topaz Labs API for AI-powered image upscaling and enhancement."
|
||||
},
|
||||
{
|
||||
"author": "comrender",
|
||||
"title": "ComfyUI-NanoB-Edit-Gemini",
|
||||
"reference": "https://github.com/comrender/ComfyUI-NanoB-Edit-Gemini",
|
||||
"files": [
|
||||
"https://github.com/comrender/ComfyUI-NanoB-Edit-Gemini"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This custom node facilitates direct interaction with the Google Gemini API for advanced image editing tasks within ComfyUI with parallel request handling."
|
||||
},
|
||||
{
|
||||
"author": "DecartAI",
|
||||
"title": "Lucy-Edit-ComfyUI",
|
||||
@ -33969,16 +34048,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "This is a custom node for ComfyUI that provides a dynamic 'Switch' for routing purposes. It allows you to define a list of named labels and select one, outputting the corresponding index and label name. This is useful for controlling the flow of your workflow based on a selection."
|
||||
},
|
||||
{
|
||||
"author": "mcrataobrabo",
|
||||
"title": "comfyui-smart-lora-downloader - Automatically Fetch Missing LoRAs",
|
||||
"reference": "https://github.com/mcrataobrabo/comfyui-smart-lora-downloader",
|
||||
"files": [
|
||||
"https://github.com/mcrataobrabo/comfyui-smart-lora-downloader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Automatically detect and download missing LoRAs for ComfyUI workflows"
|
||||
},
|
||||
{
|
||||
"author": "3dgopnik",
|
||||
"title": "ComfyUI Arena Suite",
|
||||
@ -34857,6 +34926,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Load & save ComfyUI workflows embedded in image metadata — with optional zlib+base64 compression for JPEG/WEBP — and batch convert folders. PNG drops still work natively; JPEG/WEBP with compressed workflows are handled by this extension."
|
||||
},
|
||||
{
|
||||
"author": "jonstreeter",
|
||||
"title": "comfyui-Lora-Tag-Power-Loader",
|
||||
"reference": "https://github.com/jonstreeter/comfyui-Lora-Tag-Power-Loader",
|
||||
"files": [
|
||||
"https://github.com/jonstreeter/comfyui-Lora-Tag-Power-Loader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A powerful ComfyUI custom node that combines text-based LoRA tag detection with advanced dual noise weight support, perfect for WanVideo/Hunyuan Video workflows."
|
||||
},
|
||||
{
|
||||
"author": "PatrickBorkowicz",
|
||||
"title": "ComfyUI-ImmichUploader",
|
||||
@ -35246,6 +35325,16 @@
|
||||
"description": "Custom ComfyUI nodes for HYPIR image restoration using Stable Diffusion 2.1. Restores and upscales scanned or generated images with optional LM Studio prompt enhancement and tiled VAE processing for large resolutions.",
|
||||
"nodename_pattern": "HYPIR"
|
||||
},
|
||||
{
|
||||
"author": "EricRollei",
|
||||
"title": "Emu35-Comfyui-Nodes",
|
||||
"reference": "https://github.com/EricRollei/Emu35-Comfyui-Nodes",
|
||||
"files": [
|
||||
"https://github.com/EricRollei/Emu35-Comfyui-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI integration for BAAI's Emu3.5 multimodal models for text-to-image generation and multimodal understanding. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "nomadoor",
|
||||
"title": "ComfyUI Temporal Mask Tools",
|
||||
@ -35477,16 +35566,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Standalone ComfyUI custom node for Nunchaku QwenImage LoRA loading functionality."
|
||||
},
|
||||
{
|
||||
"author": "geltz",
|
||||
"title": "ComfyUI-geltz",
|
||||
"reference": "https://github.com/geltz/ComfyUI-geltz",
|
||||
"files": [
|
||||
"https://github.com/geltz/ComfyUI-geltz"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Various custom nodes; guidance, latents, sampling, tokenization, etc."
|
||||
},
|
||||
{
|
||||
"author": "cmeka",
|
||||
"title": "ComfyUI-WanMoEScheduler",
|
||||
@ -35827,6 +35906,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom Nodes to work with SAM3"
|
||||
},
|
||||
{
|
||||
"author": "wouterverweirder",
|
||||
"title": "comfyui_live_input_stream",
|
||||
"reference": "https://github.com/wouterverweirder/comfyui_live_input_stream",
|
||||
"files": [
|
||||
"https://github.com/wouterverweirder/comfyui_live_input_stream"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Webcam & Screen Share Input Nodes with Cropping and Live Preview of the Cropped Stream"
|
||||
},
|
||||
{
|
||||
"author": "Aishor",
|
||||
"title": "Meta-Sampler Guiado (Seed-WAN)",
|
||||
@ -36147,6 +36236,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A collection of utility nodes for image processing, workflow management, and generation metadata tracking."
|
||||
},
|
||||
{
|
||||
"author": "Seb-Lis",
|
||||
"title": "ComfyUI_Gemini_3_Pro_API_node",
|
||||
"reference": "https://github.com/Seb-Lis/ComfyUI_Gemini_3_Pro_API_node",
|
||||
"files": [
|
||||
"https://github.com/Seb-Lis/ComfyUI_Gemini_3_Pro_API_node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom node for ComfyUI that generates images using Google's Gemini API directly within ComfyUI, supporting resolutions up to 4K."
|
||||
},
|
||||
{
|
||||
"author": "gitcapoom",
|
||||
"title": "ComfyUI FOV Estimator",
|
||||
@ -36338,6 +36437,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Prompt history input node and sidebar gallery for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "x0x0b",
|
||||
"title": "ComfyUI-spritefusion-pixel-snapper",
|
||||
"reference": "https://github.com/x0x0b/ComfyUI-spritefusion-pixel-snapper",
|
||||
"files": [
|
||||
"https://github.com/x0x0b/ComfyUI-spritefusion-pixel-snapper"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom node port of Sprite Fusion Pixel Snapper that snaps pixel art to a clean grid and quantized palette."
|
||||
},
|
||||
{
|
||||
"author": "EnragedAntelope",
|
||||
"title": "comfy-modelopt",
|
||||
@ -36379,11 +36488,11 @@
|
||||
"description": "comfyui node TextEconder for qwen image edit with internal lanczos rescale instead of area rescale."
|
||||
},
|
||||
{
|
||||
"author": "Creditas",
|
||||
"title": "Adforge",
|
||||
"reference": "https://github.com/Creditas-labs/ComfyUI-Adforge",
|
||||
"author": "Creditas-labs",
|
||||
"title": "ComfyUI_Adforge",
|
||||
"reference": "https://github.com/Creditas-labs/ComfyUI_Adforge",
|
||||
"files": [
|
||||
"https://github.com/Creditas-labs/ComfyUI-Adforge"
|
||||
"https://github.com/Creditas-labs/ComfyUI_Adforge"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Creditas' Ad Creation Toolkit for ComfyUI"
|
||||
@ -36529,6 +36638,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Intelligent video frame selection node powered by Qwen3-VL-235B vision model. Automatically analyzes and selects the best quality frames from videos for further creative work."
|
||||
},
|
||||
{
|
||||
"author": "eddyhhlure1Eddy",
|
||||
"title": "ComfyUI-LLM-GGUF",
|
||||
"reference": "https://github.com/eddyhhlure1Eddy/ComfyUI-LLM-GGUF",
|
||||
"files": [
|
||||
"https://github.com/eddyhhlure1Eddy/ComfyUI-LLM-GGUF"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom nodes for GGUF LLM model inference with chat support, conversation history, session management, and GPU layer offloading."
|
||||
},
|
||||
{
|
||||
"author": "princepainter",
|
||||
"title": "ComfyUI-PainterI2V",
|
||||
@ -37123,14 +37242,14 @@
|
||||
},
|
||||
{
|
||||
"author": "akawana",
|
||||
"title": "Keybinding Extra",
|
||||
"reference": "https://github.com/akawana/ComfyUI-Keybinding-extra",
|
||||
"title": "Folded prompts",
|
||||
"reference": "https://github.com/akawana/ComfyUI-Folded-Prompts",
|
||||
"files": [
|
||||
"https://github.com/akawana/ComfyUI-Keybinding-extra"
|
||||
"https://github.com/akawana/ComfyUI-Folded-Prompts"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Adds shortcuts for commenting and deleting lines of text, with a configurable comment symbol. Includes a node that cleans comments from text and splits content using tags for Regional Prompting.",
|
||||
"tags": ["frontend", "shortcut", "utility"]
|
||||
"description": "Builds a folder-based tree from prompt lines. Lets you organize prompts into folders. Also adds several configurable keybindings for text editing (line commenting, regional prompting areas). Splits the prompt into regions for Regional Prompting.",
|
||||
"tags": ["frontend", "shortcut", "regional", "folders", "bookmarks"]
|
||||
},
|
||||
{
|
||||
"author": "akawana",
|
||||
@ -37508,7 +37627,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Three custom ComfyUI nodes: Empty Image RGB, Text Line Break, and Random Chars (Append) for convenient utilities. (Description by CC)"
|
||||
},
|
||||
|
||||
{
|
||||
"author": "Zeknes",
|
||||
"title": "Comfyui-Nanobanana-API",
|
||||
@ -37519,6 +37637,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom node for Gemini image generation via OpenRouter API, supporting text prompts and multiple image inputs (up to 4 images) with automatic saving to output directory. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "Zeknes",
|
||||
"title": "Comfyui-LLM-Chat",
|
||||
"reference": "https://github.com/Zeknes/Comfyui-LLM-Chat",
|
||||
"files": [
|
||||
"https://github.com/Zeknes/Comfyui-LLM-Chat"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom ComfyUI node providing unified access to multiple Large Language Models including OpenAI-compatible APIs and local Ollama instances with image support."
|
||||
},
|
||||
{
|
||||
"author": "xiaoshengyvlin",
|
||||
"title": "ComfyUI-MetaData-ZaKo",
|
||||
@ -37581,10 +37709,10 @@
|
||||
},
|
||||
{
|
||||
"author": "Randy420Marsh",
|
||||
"title": "ComfyUI-Civitai-API-Url-Resolver",
|
||||
"reference": "https://github.com/Randy420Marsh/ComfyUI-Civitai-API-Url-Resolver",
|
||||
"title": "Civitai URL Resolver for ComfyUI",
|
||||
"reference": "https://github.com/Randy420Marsh/civitai-api-url-resolver",
|
||||
"files": [
|
||||
"https://github.com/Randy420Marsh/ComfyUI-Civitai-API-Url-Resolver"
|
||||
"https://github.com/Randy420Marsh/civitai-api-url-resolver"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI custom node that converts Civitai share/model page URLs into direct download URLs, making it easier to integrate Civitai models into your workflows."
|
||||
@ -37609,16 +37737,17 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Collection of utility nodes for ComfyUI designed specifically for the Z-Image model with vision model support and LLM-powered prompt enhancement."
|
||||
},
|
||||
{
|
||||
{
|
||||
"author": "fredlef",
|
||||
"title": "ComfyUI FSL Nodes",
|
||||
"reference": "https://github.com/fredlef/Comfyui_FSL_Nodes",
|
||||
"title": "ComfyUI FSL Nodes",
|
||||
"reference": "https://github.com/fredlef/Comfyui_FSL_Nodes",
|
||||
"files": [
|
||||
"https://github.com/fredlef/Comfyui_FSL_Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"tags": ["image", "chat", "gemini", "fsl"]
|
||||
},
|
||||
"description": "Custom nodes: FSLGeminiChat, FSLGeminiGenerateImage, Transparent Background helpers, and more." ,
|
||||
"tags": ["image", "chat", "gemini", "fsl"]
|
||||
},
|
||||
{
|
||||
"author": "exedesign",
|
||||
"title": "ComfyUI-Hunyuan3D-v3",
|
||||
@ -37632,25 +37761,28 @@
|
||||
"nodename_pattern": "Hunyuan",
|
||||
"tags": ["3D", "generation", "text-to-3d", "image-to-3d", "hunyuan", "tencent"]
|
||||
},
|
||||
{
|
||||
"author": "btitkin",
|
||||
"title": "Random Prompt Builder",
|
||||
"id": "random-prompt-builder",
|
||||
"reference": "https://github.com/btitkin/ComfyUI-RandomPromptBuilder",
|
||||
"files": [
|
||||
"https://github.com/btitkin/ComfyUI-RandomPromptBuilder"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Advanced AI-powered prompt generation using local GGUF models. Generate detailed, structured prompts with character controls, style presets, and model-specific formatting for Pony, SDXL, Flux, and more. Supports GPU acceleration and runs completely offline."
|
||||
},
|
||||
{
|
||||
"author": "rjgoif",
|
||||
"title": "Img Label Tools",
|
||||
"id": "Img-Label-Tools",
|
||||
"reference": "https://github.com/rjgoif/ComfyUI-Img-Label-Tools",
|
||||
"install_type": "git-clone",
|
||||
"description": "Tools to help annotate images for sharing on Reddit, Discord, etc."
|
||||
},
|
||||
{
|
||||
"author": "btitkin",
|
||||
"title": "Random Prompt Builder",
|
||||
"id": "random-prompt-builder",
|
||||
"reference": "https://github.com/btitkin/ComfyUI-RandomPromptBuilder",
|
||||
"files": [
|
||||
"https://github.com/btitkin/ComfyUI-RandomPromptBuilder"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Advanced AI-powered prompt generation using local GGUF models. Generate detailed, structured prompts with character controls, style presets, and model-specific formatting for Pony, SDXL, Flux, and more. Supports GPU acceleration and runs completely offline."
|
||||
},
|
||||
{
|
||||
"author": "rjgoif",
|
||||
"title": "Img Label Tools",
|
||||
"id": "Img-Label-Tools",
|
||||
"reference": "https://github.com/rjgoif/ComfyUI-Img-Label-Tools",
|
||||
"files": [
|
||||
"https://github.com/rjgoif/ComfyUI-Img-Label-Tools"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Tools to help annotate images for sharing on Reddit, Discord, etc."
|
||||
},
|
||||
{
|
||||
"author": "mrf",
|
||||
"title": "ComfyPoe",
|
||||
@ -37681,7 +37813,7 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A logic engine for ComfyUI prompts that transforms static prompts into dynamic, context-aware workflows with persistent variables, conditional logic, native LoRA loading, and external data fetching. (Description by CC)"
|
||||
},
|
||||
{
|
||||
{
|
||||
"author": "Transhumai",
|
||||
"title": "ComfyUI-LegionPower",
|
||||
"reference": "https://github.com/Transhumai/ComfyUI-LegionPower",
|
||||
@ -37769,13 +37901,338 @@
|
||||
"description": "A collection of utility nodes for ComfyUI, including resolution presets for film and photography aspect ratios.",
|
||||
"tags": ["utility", "presets", "aspect ratio", "film"]
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"author": "jomakaze",
|
||||
"title": "ComfyUI_JomaNodes",
|
||||
"reference": "https://github.com/jomakaze/ComfyUI_JomaNodes",
|
||||
"files": [
|
||||
"https://github.com/jomakaze/ComfyUI_JomaNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A collection of quality-of-life and convenience nodes."
|
||||
},
|
||||
{
|
||||
"author": "UmeAiRT",
|
||||
"title": "ComfyUI-UmeAiRT-Sync",
|
||||
"reference": "https://github.com/UmeAiRT/ComfyUI-UmeAiRT-Sync",
|
||||
"files": [
|
||||
"https://github.com/UmeAiRT/ComfyUI-UmeAiRT-Sync"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Automatic workflow synchronizer/loader for UmeAiRT Workflows (Flux, SDXL, WAN, etc.)."
|
||||
},
|
||||
{
|
||||
"author": "DayMan84",
|
||||
"title": "ComfyUI-Ugromana",
|
||||
"id": "comfyui-usgromana",
|
||||
"reference": "https://github.com/DayMan84/ComfyUI-Usgromana",
|
||||
"files": [
|
||||
"https://github.com/DayMan84/ComfyUI-Usgromana"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "The next-generation security, governance, permissions, and multi‑user control system for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "DemonNCoding",
|
||||
"title": "PromptGenerator 12-Columns",
|
||||
"id": "promptgenerator12columns",
|
||||
"reference": "https://github.com/DemonNCoding/PromptGenerator12Columns",
|
||||
"files": [
|
||||
"https://github.com/DemonNCoding/PromptGenerator12Columns"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A fast and flexible random prompt generator with 12 columns (Empty / Pre-filled SFW / Pre-filled NSFW). Pre filled with text so that you can get started right away. Supports comma or newline output and always adds quality tags."
|
||||
},
|
||||
{
|
||||
"author": "Kebolder",
|
||||
"title": "ComfyUI-Jax-Nodes",
|
||||
"reference": "https://github.com/Kebolder/ComfyUI-Jax-Nodes",
|
||||
"files": [
|
||||
"https://github.com/Kebolder/ComfyUI-Jax-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Some custom nodes for weird niche needs when working with Krita"
|
||||
},
|
||||
{
|
||||
"author": "Ysthara",
|
||||
"title": "ComfyUI-Resolution-AR-Node",
|
||||
"reference": "https://github.com/Ysthara/ComfyUI-Resolution-AR-Node",
|
||||
"files": [
|
||||
"https://github.com/Ysthara/ComfyUI-Resolution-AR-Node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A simple ComfyUI utility node that calculates image width and height from a target longest-side value and an aspect ratio."
|
||||
},
|
||||
{
|
||||
"author": "TuonoMindCode",
|
||||
"title": "ComfyUI-MultiClip-Text-Script",
|
||||
"reference": "https://github.com/TuonoMindCode/ComfyUI-MultiClip-Text-Script",
|
||||
"files": [
|
||||
"https://github.com/TuonoMindCode/ComfyUI-MultiClip-Text-Script"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Two simple nodes that help you author sequence-friendly prompts from a single script that contains multiple clips. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "sooxt98",
|
||||
"title": "comfyui_longcat_image",
|
||||
"reference": "https://github.com/sooxt98/comfyui_longcat_image",
|
||||
"files": [
|
||||
"https://github.com/sooxt98/comfyui_longcat_image"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI integration of the LongCat-Image pipeline for text-to-image generation and image editing with excellent Chinese text rendering capabilities. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "t22m003",
|
||||
"title": "ComfyUI_LoopNode",
|
||||
"reference": "https://github.com/t22m003/ComfyUI_LoopNode",
|
||||
"files": [
|
||||
"https://github.com/t22m003/ComfyUI_LoopNode"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom node that provides loop (for-loop) functionality in ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "thatname",
|
||||
"title": "Comfyui_CharaConsist",
|
||||
"reference": "https://github.com/thatname/Comfyui_CharaConsist",
|
||||
"files": [
|
||||
"https://github.com/thatname/Comfyui_CharaConsist"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Training-free CharaConsist algorithm implementation for ComfyUI - Generate consistent subjects across multiple generations with enhanced mask generation and multi-model support."
|
||||
},
|
||||
{
|
||||
"author": "thrakotool",
|
||||
"title": "ComfyUI-KiraLoraEQ",
|
||||
"reference": "https://github.com/thrakotool/ComfyUI-KiraLoraEQ",
|
||||
"files": [
|
||||
"https://github.com/thrakotool/ComfyUI-KiraLoraEQ"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "20-band LoRA equalizer for ComfyUI that surgically adjusts LoRA influence across frequency-like bands, cross-attention, and MLP layers. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "ckinpdx",
|
||||
"title": "ComfyUI-WanKeyframeBuilder",
|
||||
"reference": "https://github.com/ckinpdx/ComfyUI-WanKeyframeBuilder",
|
||||
"files": [
|
||||
"https://github.com/ckinpdx/ComfyUI-WanKeyframeBuilder"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom node for building keyframe timelines in Wan video generation with adjustable influence strength, supporting 1-8 keyframes. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "jessesep",
|
||||
"title": "SimpleVariables",
|
||||
"reference": "https://github.com/jessesep/SimpleVariables",
|
||||
"files": [
|
||||
"https://github.com/jessesep/SimpleVariables"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Lightweight ComfyUI node pack for storing and retrieving any data by name across workflows. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "siray-ai",
|
||||
"title": "siray-comfyui",
|
||||
"reference": "https://github.com/siray-ai/siray-comfyui",
|
||||
"files": [
|
||||
"https://github.com/siray-ai/siray-comfyui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom ComfyUI nodes for Siray image/video models with dynamic schema-based inputs, authentication, and video streaming. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "mayunejie",
|
||||
"title": "ComfyUI-Allen-Yinpin",
|
||||
"reference": "https://github.com/mayunejie/ComfyUI-Allen-Yinpin",
|
||||
"files": [
|
||||
"https://github.com/mayunejie/ComfyUI-Allen-Yinpin"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Enhanced audio loading node for ComfyUI supporting .wav, .mp3, .flac, .ogg, .m4a formats with filename text output. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "reeoi",
|
||||
"title": "ComfyUI-WebROI",
|
||||
"reference": "https://github.com/reeoi/ComfyUI-WebROI",
|
||||
"files": [
|
||||
"https://github.com/reeoi/ComfyUI-WebROI"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Browser-based screen capture node enabling real-time region of interest selection and cropping for ComfyUI deployments. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "ritik-devsecops",
|
||||
"title": "ComfyUI-Floyo-Flux2-API-node",
|
||||
"reference": "https://github.com/ritik-devsecops/ComfyUI-Floyo-Flux2-API-node",
|
||||
"files": [
|
||||
"https://github.com/ritik-devsecops/ComfyUI-Floyo-Flux2-API-node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI node integrating Black Forest Labs FLUX.2 [pro] API via Floyo for text-to-image and image editing. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "purzbeats",
|
||||
"title": "ComfyUI-Purz",
|
||||
"reference": "https://github.com/purzbeats/ComfyUI-Purz",
|
||||
"files": [
|
||||
"https://github.com/purzbeats/ComfyUI-Purz"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A comprehensive node pack for ComfyUI that provides powerful image effects, pattern generation, and animated pattern creation capabilities."
|
||||
},
|
||||
{
|
||||
"author": "TheArtOfficial",
|
||||
"title": "ComfyUI-MaskMorph",
|
||||
"reference": "https://github.com/TheArtOfficial/ComfyUI-MaskMorph",
|
||||
"files": [
|
||||
"https://github.com/TheArtOfficial/ComfyUI-MaskMorph"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Directional mask expansion node that extends masks along specified directions with pixel-precise control, plus separate pinch/widen morphology operations."
|
||||
},
|
||||
{
|
||||
"author": "brahianrosswill",
|
||||
"title": "ComfyUi-RandomNoiseCustom",
|
||||
"reference": "https://github.com/brahianrosswill/ComfyUi-RandomNoiseCustom",
|
||||
"files": [
|
||||
"https://github.com/brahianrosswill/ComfyUi-RandomNoiseCustom"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Provides custom noise generation capabilities for ComfyUI workflows with configurable parameters. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "amtarr",
|
||||
"title": "ComfyUI-TextureAlchemy",
|
||||
"reference": "https://github.com/amtarr/ComfyUI-TextureAlchemy",
|
||||
"files": [
|
||||
"https://github.com/amtarr/ComfyUI-TextureAlchemy"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Complete workflow suite for ComfyUI that transforms images into PBR material sets ready for game engines and 3D software."
|
||||
},
|
||||
{
|
||||
"author": "beyondprompting",
|
||||
"title": "ComfyUI-Beyond_nodes",
|
||||
"reference": "https://github.com/beyondprompting/ComfyUI-Beyond_nodes",
|
||||
"files": [
|
||||
"https://github.com/beyondprompting/ComfyUI-Beyond_nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Create complex compositions the FAST and EASY way."
|
||||
},
|
||||
{
|
||||
"author": "Zone-Roam",
|
||||
"title": "ComfyUI-Live-Search",
|
||||
"reference": "https://github.com/Zone-Roam/ComfyUI-Live-Search",
|
||||
"files": [
|
||||
"https://github.com/Zone-Roam/ComfyUI-Live-Search"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "🌐 Live Search: Real-time web search and AI summarization. Internet search, web scraping, fact checking, weather, news, GPS coordinate conversion. DuckDuckGo search with GPT-5.1, DeepSeek-V3, Gemini 3 Pro, Claude 4.5, Qwen3, Doubao, SiliconFlow (69+ models), Llama 4, Ollama. Modular architecture with API loader. Toggle web search on/off for pure LLM mode."
|
||||
},
|
||||
{
|
||||
"author": "AJbeckliy",
|
||||
"title": "SynVow-Comfyui-Nanoapi",
|
||||
"reference": "https://github.com/AJbeckliy/SynVow-Comfyui-Nanoapi",
|
||||
"files": [
|
||||
"https://github.com/AJbeckliy/SynVow-Comfyui-Nanoapi"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom nodes for AI image generation using SynVow API supporting T2I (Text-to-Image) and I2I (Image-to-Image) with multiple API sources and batch generation. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "Daohoi123",
|
||||
"title": "ComfyUI-Gemini3",
|
||||
"reference": "https://github.com/Daohoi123/ComfyUI-Gemini3",
|
||||
"files": [
|
||||
"https://github.com/Daohoi123/ComfyUI-Gemini3"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom ComfyUI node integrating Google's Gemini 3 Pro model for native image generation and processing, featuring multi-API key load balancing and auto-retry mechanisms. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "FeiMao-326",
|
||||
"title": "Comfyui-General-API-Node",
|
||||
"reference": "https://github.com/FeiMao-326/Comfyui-General-API-Node",
|
||||
"files": [
|
||||
"https://github.com/FeiMao-326/Comfyui-General-API-Node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A suite of powerful and versatile utility nodes for ComfyUI, designed to streamline complex workflows involving Large Language Models and text manipulation."
|
||||
},
|
||||
{
|
||||
"author": "systemaiofinterest-wq",
|
||||
"title": "ComfyUI-MetaAI",
|
||||
"reference": "https://github.com/systemaiofinterest-wq/ComfyUI-MetaAI",
|
||||
"files": [
|
||||
"https://github.com/systemaiofinterest-wq/ComfyUI-MetaAI"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial ComfyUI environment using web scraping methods to enable Meta AI integration for text-to-image and image-to-video generation. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "phaserblast",
|
||||
"title": "ComfyUI-DGXSparkSafetensorsLoader",
|
||||
"reference": "https://github.com/phaserblast/ComfyUI-DGXSparkSafetensorsLoader",
|
||||
"files": [
|
||||
"https://github.com/phaserblast/ComfyUI-DGXSparkSafetensorsLoader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI model loader that uses the fastsafetensors library to perform very fast, zero-copy loading from storage to VRAM."
|
||||
},
|
||||
{
|
||||
"author": "Merserk",
|
||||
"title": "ComfyUI-Flow-Assistor",
|
||||
"reference": "https://github.com/Merserk/ComfyUI-Flow-Assistor",
|
||||
"files": [
|
||||
"https://github.com/Merserk/ComfyUI-Flow-Assistor"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Essential utility nodes for ComfyUI including prompt queue, batch processing, camera angle control, and resolution selector. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "Zeknes",
|
||||
"title": "Comfyui-LLM-Chat",
|
||||
"reference": "https://github.com/Zeknes/Comfyui-LLM-Chat",
|
||||
"files": [
|
||||
"https://github.com/Zeknes/Comfyui-LLM-Chat"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom ComfyUI node providing unified access to multiple Large Language Models including OpenAI-compatible APIs and local Ollama instances with image support."
|
||||
},
|
||||
{
|
||||
"author": "dandancow874",
|
||||
"title": "ComfyUI-LMStudio-Controller",
|
||||
"reference": "https://github.com/dandancow874/ComfyUI-LMStudio-Controller",
|
||||
"files": [
|
||||
"https://github.com/dandancow874/ComfyUI-LMStudio-Controller"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Integrates LM Studio's CLI to perform Vision Language Model inference locally in ComfyUI with advanced memory management and anti-OOM protection."
|
||||
},
|
||||
{
|
||||
"author": "xiangyuT",
|
||||
"title": "ComfyUI-PromptEnhance",
|
||||
"reference": "https://github.com/xiangyuT/ComfyUI-PromptEnhance",
|
||||
"files": [
|
||||
"https://github.com/xiangyuT/ComfyUI-PromptEnhance"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI custom node for enhancing prompts using OpenAI-compatible APIs (like DeepSeek, OpenAI, etc.)."
|
||||
},
|
||||
{
|
||||
"author": "anilstream",
|
||||
"title": "ComfyUI-Ideogram-Character",
|
||||
"reference": "https://github.com/anilstream/ComfyUI-Ideogram-Character",
|
||||
"files": [
|
||||
"https://github.com/anilstream/ComfyUI-Ideogram-Character"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom ComfyUI node for generating consistent character images using Ideogram API v3's character reference feature. (Description by CC)"
|
||||
},
|
||||
|
||||
|
||||
|
||||
@ -38192,6 +38649,6 @@
|
||||
],
|
||||
"install_type": "unzip",
|
||||
"description": "This is a node to convert an image into a CMYK Halftone dot image."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
22094
github-stats-cache.json
Normal file
22094
github-stats-cache.json
Normal file
File diff suppressed because it is too large
Load Diff
8574
github-stats.json
8574
github-stats.json
File diff suppressed because it is too large
Load Diff
273
json-checker.py
273
json-checker.py
@ -1,25 +1,264 @@
|
||||
import json
|
||||
import argparse
|
||||
#!/usr/bin/env python3
|
||||
"""JSON Entry Validator
|
||||
|
||||
def check_json_syntax(file_path):
|
||||
Validates JSON entries based on content structure.
|
||||
|
||||
Validation rules based on JSON content:
|
||||
- {"custom_nodes": [...]}: Validates required fields (author, title, reference, files, install_type, description)
|
||||
- {"models": [...]}: Validates JSON syntax only (no required fields)
|
||||
- Other JSON structures: Validates JSON syntax only
|
||||
|
||||
Git repository URL validation (for custom_nodes):
|
||||
1. URLs must NOT end with .git
|
||||
2. URLs must follow format: https://github.com/{author}/{reponame}
|
||||
3. .py and .js files are exempt from this check
|
||||
|
||||
Supported formats:
|
||||
- Array format: [{...}, {...}]
|
||||
- Object format: {"custom_nodes": [...]} or {"models": [...]}
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
# Required fields for each entry type
|
||||
REQUIRED_FIELDS_CUSTOM_NODE = ['author', 'title', 'reference', 'files', 'install_type', 'description']
|
||||
REQUIRED_FIELDS_MODEL = [] # model-list.json doesn't require field validation
|
||||
|
||||
# Pattern for valid GitHub repository URL (without .git suffix)
|
||||
GITHUB_REPO_PATTERN = re.compile(r'^https://github\.com/[^/]+/[^/]+$')
|
||||
|
||||
|
||||
def get_entry_context(entry: Dict) -> str:
|
||||
"""Get identifying information from entry for error messages
|
||||
|
||||
Args:
|
||||
entry: JSON entry
|
||||
|
||||
Returns:
|
||||
String with author and reference info
|
||||
"""
|
||||
parts = []
|
||||
if 'author' in entry:
|
||||
parts.append(f"author={entry['author']}")
|
||||
if 'reference' in entry:
|
||||
parts.append(f"ref={entry['reference']}")
|
||||
if 'title' in entry:
|
||||
parts.append(f"title={entry['title']}")
|
||||
|
||||
if parts:
|
||||
return " | ".join(parts)
|
||||
else:
|
||||
# No identifying info - show actual entry content (truncated)
|
||||
import json
|
||||
entry_str = json.dumps(entry, ensure_ascii=False)
|
||||
if len(entry_str) > 100:
|
||||
entry_str = entry_str[:100] + "..."
|
||||
return f"content={entry_str}"
|
||||
|
||||
|
||||
def validate_required_fields(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]:
|
||||
"""Validate that all required fields are present
|
||||
|
||||
Args:
|
||||
entry: JSON entry to validate
|
||||
entry_index: Index of entry in array (for error reporting)
|
||||
required_fields: List of required field names
|
||||
|
||||
Returns:
|
||||
List of error descriptions (without entry prefix/context)
|
||||
"""
|
||||
errors = []
|
||||
|
||||
for field in required_fields:
|
||||
if field not in entry:
|
||||
errors.append(f"Missing required field '{field}'")
|
||||
elif entry[field] is None:
|
||||
errors.append(f"Field '{field}' is null")
|
||||
elif isinstance(entry[field], str) and not entry[field].strip():
|
||||
errors.append(f"Field '{field}' is empty")
|
||||
elif field == 'files' and not entry[field]: # Empty array
|
||||
errors.append(f"Field 'files' is empty array")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_git_repo_urls(entry: Dict, entry_index: int) -> List[str]:
|
||||
"""Validate git repository URLs in 'files' array
|
||||
|
||||
Requirements:
|
||||
- Git repo URLs must NOT end with .git
|
||||
- Must follow format: https://github.com/{author}/{reponame}
|
||||
- .py and .js files are exempt
|
||||
|
||||
Args:
|
||||
entry: JSON entry to validate
|
||||
entry_index: Index of entry in array (for error reporting)
|
||||
|
||||
Returns:
|
||||
List of error descriptions (without entry prefix/context)
|
||||
"""
|
||||
errors = []
|
||||
|
||||
if 'files' not in entry or not isinstance(entry['files'], list):
|
||||
return errors
|
||||
|
||||
for file_url in entry['files']:
|
||||
if not isinstance(file_url, str):
|
||||
continue
|
||||
|
||||
# Skip .py and .js files - they're exempt from git repo validation
|
||||
if file_url.endswith('.py') or file_url.endswith('.js'):
|
||||
continue
|
||||
|
||||
# Check if it's a GitHub URL (likely a git repo)
|
||||
if 'github.com' in file_url:
|
||||
# Error if URL ends with .git
|
||||
if file_url.endswith('.git'):
|
||||
errors.append(f"Git repo URL must NOT end with .git: {file_url}")
|
||||
continue
|
||||
|
||||
# Validate format: https://github.com/{author}/{reponame}
|
||||
if not GITHUB_REPO_PATTERN.match(file_url):
|
||||
errors.append(f"Invalid git repo URL format (expected https://github.com/author/reponame): {file_url}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_entry(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]:
|
||||
"""Validate a single JSON entry
|
||||
|
||||
Args:
|
||||
entry: JSON entry to validate
|
||||
entry_index: Index of entry in array (for error reporting)
|
||||
required_fields: List of required field names
|
||||
|
||||
Returns:
|
||||
List of error messages (empty if valid)
|
||||
"""
|
||||
errors = []
|
||||
|
||||
# Check required fields
|
||||
errors.extend(validate_required_fields(entry, entry_index, required_fields))
|
||||
|
||||
# Check git repository URLs
|
||||
errors.extend(validate_git_repo_urls(entry, entry_index))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_json_file(file_path: str) -> Tuple[bool, List[str]]:
|
||||
"""Validate JSON file containing entries
|
||||
|
||||
Args:
|
||||
file_path: Path to JSON file
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error_messages)
|
||||
"""
|
||||
errors = []
|
||||
|
||||
# Check file exists
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
return False, [f"File not found: {file_path}"]
|
||||
|
||||
# Load JSON
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
json_str = file.read()
|
||||
json.loads(json_str)
|
||||
print(f"[ OK ] {file_path}")
|
||||
except UnicodeDecodeError as e:
|
||||
print(f"Unicode decode error: {e}")
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[FAIL] {file_path}\n\n {e}\n")
|
||||
except FileNotFoundError:
|
||||
print(f"[FAIL] {file_path}\n\n File not found\n")
|
||||
return False, [f"Invalid JSON: {e}"]
|
||||
except Exception as e:
|
||||
return False, [f"Error reading file: {e}"]
|
||||
|
||||
# Determine required fields based on JSON content
|
||||
required_fields = []
|
||||
|
||||
# Validate structure - support both array and object formats
|
||||
entries_to_validate = []
|
||||
|
||||
if isinstance(data, list):
|
||||
# Direct array format: [{...}, {...}]
|
||||
entries_to_validate = data
|
||||
elif isinstance(data, dict):
|
||||
# Object format: {"custom_nodes": [...]} or {"models": [...]}
|
||||
# Determine validation based on keys
|
||||
if 'custom_nodes' in data and isinstance(data['custom_nodes'], list):
|
||||
required_fields = REQUIRED_FIELDS_CUSTOM_NODE
|
||||
entries_to_validate = data['custom_nodes']
|
||||
elif 'models' in data and isinstance(data['models'], list):
|
||||
required_fields = REQUIRED_FIELDS_MODEL
|
||||
entries_to_validate = data['models']
|
||||
else:
|
||||
# Other JSON structures (extension-node-map.json, etc.) - just validate JSON syntax
|
||||
return True, []
|
||||
else:
|
||||
return False, ["JSON root must be either an array or an object containing arrays"]
|
||||
|
||||
# Validate each entry
|
||||
for idx, entry in enumerate(entries_to_validate, start=1):
|
||||
if not isinstance(entry, dict):
|
||||
# Show actual value for type errors
|
||||
entry_str = json.dumps(entry, ensure_ascii=False) if not isinstance(entry, str) else repr(entry)
|
||||
if len(entry_str) > 150:
|
||||
entry_str = entry_str[:150] + "..."
|
||||
errors.append(f"\n❌ Entry #{idx}: Must be an object, got {type(entry).__name__}")
|
||||
errors.append(f" Actual value: {entry_str}")
|
||||
continue
|
||||
|
||||
entry_errors = validate_entry(entry, idx, required_fields)
|
||||
if entry_errors:
|
||||
# Group errors by entry with context
|
||||
context = get_entry_context(entry)
|
||||
errors.append(f"\n❌ Entry #{idx} ({context}):")
|
||||
for error in entry_errors:
|
||||
errors.append(f" - {error}")
|
||||
|
||||
is_valid = len(errors) == 0
|
||||
return is_valid, errors
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="JSON File Syntax Checker")
|
||||
parser.add_argument("file_path", type=str, help="Path to the JSON file for syntax checking")
|
||||
"""Main entry point"""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python json-checker.py <json-file>")
|
||||
print("\nValidates JSON entries based on content:")
|
||||
print(" - {\"custom_nodes\": [...]}: Validates required fields (author, title, reference, files, install_type, description)")
|
||||
print(" - {\"models\": [...]}: Validates JSON syntax only (no required fields)")
|
||||
print(" - Other JSON structures: Validates JSON syntax only")
|
||||
print("\nGit repo URL validation (for custom_nodes):")
|
||||
print(" - URLs must NOT end with .git")
|
||||
print(" - URLs must follow: https://github.com/{author}/{reponame}")
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
check_json_syntax(args.file_path)
|
||||
file_path = sys.argv[1]
|
||||
|
||||
if __name__ == "__main__":
|
||||
is_valid, errors = validate_json_file(file_path)
|
||||
|
||||
if is_valid:
|
||||
print(f"✅ {file_path}: Validation passed")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(f"Validating: {file_path}")
|
||||
print("=" * 60)
|
||||
print("❌ Validation failed!\n")
|
||||
print("Errors:")
|
||||
# Count actual errors (lines starting with " -")
|
||||
error_count = sum(1 for e in errors if e.strip().startswith('-'))
|
||||
for error in errors:
|
||||
# Don't add ❌ prefix to grouped entries (they already have it)
|
||||
if error.strip().startswith('❌'):
|
||||
print(error)
|
||||
else:
|
||||
print(error)
|
||||
print(f"\nTotal errors: {error_count}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -1,5 +1,85 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "anilstream",
|
||||
"title": "ComfyUI-NanoBananaPro",
|
||||
"reference": "https://github.com/anilstream/ComfyUI-NanoBananaPro",
|
||||
"files": [
|
||||
"https://github.com/anilstream/ComfyUI-NanoBananaPro"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI node implementing basic functionality with NanoBananaBasicNode. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "Toxic1228",
|
||||
"title": "Eleven-labs-comfyui-sts",
|
||||
"reference": "https://github.com/Toxic1228/Eleven-labs-comfyui-sts",
|
||||
"files": [
|
||||
"https://github.com/Toxic1228/Eleven-labs-comfyui-sts"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI integration node for Eleven Labs text-to-speech service (requires API key). (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "NeoTech",
|
||||
"title": "comfyui-laserprep",
|
||||
"reference": "https://github.com/NeoTech/comfyui-laserprep",
|
||||
"files": [
|
||||
"https://github.com/NeoTech/comfyui-laserprep"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI node implementing laser preparation functionality with LaserPrep node. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "Enferlain",
|
||||
"title": "ComfyUI-extra-schedulers [WIP]",
|
||||
"reference": "https://github.com/Enferlain/ComfyUI-extra-schedulers",
|
||||
"files": [
|
||||
"https://github.com/Enferlain/ComfyUI-extra-schedulers"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom nodes providing additional scheduler implementations for advanced sampling control. (Description by CC)\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "tiange-tree",
|
||||
"title": "BLUEAI_ComfyUI_OpenAI",
|
||||
"reference": "https://github.com/tiange-tree/BLUEAI_ComfyUI_OpenAI",
|
||||
"files": [
|
||||
"https://github.com/tiange-tree/BLUEAI_ComfyUI_OpenAI"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: BLUEAI_OpenAI_Node"
|
||||
},
|
||||
{
|
||||
"author": "nestflow",
|
||||
"title": "ComfyUI-WanPlus",
|
||||
"reference": "https://github.com/nestflow/ComfyUI-WanPlus",
|
||||
"files": [
|
||||
"https://github.com/nestflow/ComfyUI-WanPlus"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI nodes for video frame manipulation and image-to-video conversion. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "twdockery",
|
||||
"title": "ComfyUI_Prompt_Batch_Generator",
|
||||
"reference": "https://github.com/twdockery/ComfyUI_Prompt_Batch_Generator",
|
||||
"files": [
|
||||
"https://github.com/twdockery/ComfyUI_Prompt_Batch_Generator"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for batch image generation with Stable Diffusion 1.5, optimized for low VRAM systems. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "tuxiansheng-ld",
|
||||
"title": "Comfyui-tuxiansheng-nodes",
|
||||
"reference": "https://github.com/tuxiansheng-ld/Comfyui-tuxiansheng-nodes",
|
||||
"files": [
|
||||
"https://github.com/tuxiansheng-ld/Comfyui-tuxiansheng-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: StringToListNode"
|
||||
},
|
||||
{
|
||||
"author": "krakenunbound",
|
||||
"title": "Kraken Discord Bot",
|
||||
@ -71,16 +151,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI node for tracking and displaying LoRA parameters. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "anilsathyan7",
|
||||
"title": "ComfyUI-Crystal-Upscaler",
|
||||
"reference": "https://github.com/anilsathyan7/ComfyUI-Crystal-Upscaler",
|
||||
"files": [
|
||||
"https://github.com/anilsathyan7/ComfyUI-Crystal-Upscaler"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom node for image upscaling using crystal upscaling technology. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "SleazySleaze",
|
||||
"title": "aesthetic-persona-comfyui-node",
|
||||
@ -441,16 +511,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Integrated Qwen-Image node for ComfyUI with all-in-one model loading, 4 LoRA slots, memory optimization via BlockSwap reducing VRAM usage by 30-60%, and multiple quantization options.\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "nohikomiso",
|
||||
"title": "ComfyUI-ImageFolderPicker [UNSAFE]",
|
||||
"reference": "https://github.com/nohikomiso/ComfyUI-ImageFolderPicker",
|
||||
"files": [
|
||||
"https://github.com/nohikomiso/ComfyUI-ImageFolderPicker"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom ComfyUI node for browsing local server folders and selecting images via thumbnail display in a grid interface. (Description by CC)[w/This nodepack has a vulnerability that allows it to retrieve a list of files from arbitrary paths.]"
|
||||
},
|
||||
{
|
||||
"author": "tori29umai0123",
|
||||
"title": "ComfyUI-SDXLGenerateFromTextFile [UNSAFE]",
|
||||
@ -1184,16 +1244,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI-CC-ImageLoader is an enhanced image loading node designed for ComfyUI. It is developed based on two excellent projects: ComfyUI-Thumbnails and ComfyUI_Local_Media_Manager.[w/This nodepack includes an endpoint that access files from arbitrary paths.]"
|
||||
},
|
||||
{
|
||||
"author": "rzasharp79",
|
||||
"title": "ComfyUI--SolarFlare",
|
||||
"reference": "https://github.com/rzasharp79/ComfyUI--SolarFlare",
|
||||
"files": [
|
||||
"https://github.com/rzasharp79/ComfyUI--SolarFlare"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Qwen Image, ..."
|
||||
},
|
||||
{
|
||||
"author": "A1rCHAN",
|
||||
"title": "Eric's Prompt Enhancers for ComfyUI# Eric's Prompt Enhancers for ComfyUI",
|
||||
@ -4729,7 +4779,8 @@
|
||||
"description": "NODES: Face Detector Selector, YC Human Parts Ultra(Advance), Color Match (YC)"
|
||||
},
|
||||
{
|
||||
"author": "virallover",
|
||||
"author": "maizerrr",
|
||||
"title": "comfyui-code-nodes",
|
||||
"reference": "https://github.com/maizerrr/comfyui-code-nodes",
|
||||
"files": [
|
||||
"https://github.com/maizerrr/comfyui-code-nodes"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,95 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "mcrataobrabo",
|
||||
"title": "comfyui-smart-lora-downloader - Automatically Fetch Missing LoRAs [REMOVED]",
|
||||
"reference": "https://github.com/mcrataobrabo/comfyui-smart-lora-downloader",
|
||||
"files": [
|
||||
"https://github.com/mcrataobrabo/comfyui-smart-lora-downloader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Automatically detect and download missing LoRAs for ComfyUI workflows"
|
||||
},
|
||||
{
|
||||
"author": "KANAsho34636",
|
||||
"title": "ComfyUI-NaturalSort-ImageLoader [REMOVED]",
|
||||
"reference": "https://github.com/KANAsho34636/ComfyUI-NaturalSort-ImageLoader",
|
||||
"files": [
|
||||
"https://github.com/KANAsho34636/ComfyUI-NaturalSort-ImageLoader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom image loader node supporting natural number sorting with multiple sort modes (natural, lexicographic, modification time, creation time, reverse natural). (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "johninthewinter",
|
||||
"title": "comfyui-fal-flux-2-John [REMOVED]",
|
||||
"reference": "https://github.com/johninthewinter/comfyui-fal-flux-2-John",
|
||||
"files": [
|
||||
"https://github.com/johninthewinter/comfyui-fal-flux-2-John"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for ComfyUI that integrate with fal.ai's FLUX 2 and FLUX 1 LoRA APIs for text-to-image generation."
|
||||
},
|
||||
{
|
||||
"author": "LargeModGames",
|
||||
"title": "ComfyUI LoRA Auto Downloader [REMOVED]",
|
||||
"reference": "https://github.com/LargeModGames/comfyui-smart-lora-downloader",
|
||||
"files": [
|
||||
"https://github.com/LargeModGames/comfyui-smart-lora-downloader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Automatically download missing LoRAs from CivitAI and detect missing LoRAs in workflows. Features smart directory detection and easy installation."
|
||||
},
|
||||
{
|
||||
"author": "DiffusionWave",
|
||||
"title": "PickResolution_DiffusionWave [DEPRECATED]",
|
||||
"reference": "https://github.com/DiffusionWave/PickResolution_DiffusionWave",
|
||||
"files": [
|
||||
"https://github.com/DiffusionWave/PickResolution_DiffusionWave"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI that allows selecting a base resolution, applying a custom scaling value based on FLOAT (up to 10 decimal places), and adding an extra integer value. Outputs include both INT and FLOAT resolutions, making it perfect for you to play around with."
|
||||
},
|
||||
{
|
||||
"author": "geltz",
|
||||
"title": "ComfyUI-geltz [REMOVED]",
|
||||
"reference": "https://github.com/geltz/ComfyUI-geltz",
|
||||
"files": [
|
||||
"https://github.com/geltz/ComfyUI-geltz"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Various custom nodes; guidance, latents, sampling, tokenization, etc."
|
||||
},
|
||||
{
|
||||
"author": "anilsathyan7",
|
||||
"title": "ComfyUI-Crystal-Upscaler [REMOVED]",
|
||||
"reference": "https://github.com/anilsathyan7/ComfyUI-Crystal-Upscaler",
|
||||
"files": [
|
||||
"https://github.com/anilsathyan7/ComfyUI-Crystal-Upscaler"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom node for image upscaling using crystal upscaling technology. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "nohikomiso",
|
||||
"title": "ComfyUI-ImageFolderPicker [REMOVED/UNSAFE]",
|
||||
"reference": "https://github.com/nohikomiso/ComfyUI-ImageFolderPicker",
|
||||
"files": [
|
||||
"https://github.com/nohikomiso/ComfyUI-ImageFolderPicker"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom ComfyUI node for browsing local server folders and selecting images via thumbnail display in a grid interface. (Description by CC)[w/This nodepack has a vulnerability that allows it to retrieve a list of files from arbitrary paths.]"
|
||||
},
|
||||
{
|
||||
"author": "rzasharp79",
|
||||
"title": "ComfyUI--SolarFlare [REMOVED]",
|
||||
"reference": "https://github.com/rzasharp79/ComfyUI--SolarFlare",
|
||||
"files": [
|
||||
"https://github.com/rzasharp79/ComfyUI--SolarFlare"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Qwen Image, ..."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-no-one-above-me [REMOVED]",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
609
scanner.py
609
scanner.py
@ -16,6 +16,108 @@ import sys
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from github import Github, Auth
|
||||
from pathlib import Path
|
||||
from typing import Set, Dict, Optional
|
||||
|
||||
# Scanner version for cache invalidation
|
||||
SCANNER_VERSION = "2.0.11" # Multi-layer detection: class existence + display names
|
||||
|
||||
# Cache for extract_nodes and extract_nodes_enhanced results
|
||||
_extract_nodes_cache: Dict[str, Set[str]] = {}
|
||||
_extract_nodes_enhanced_cache: Dict[str, Set[str]] = {}
|
||||
_file_mtime_cache: Dict[Path, float] = {}
|
||||
|
||||
|
||||
def _get_repo_root(file_path: Path) -> Optional[Path]:
|
||||
"""Find the repository root directory containing .git"""
|
||||
current = file_path if file_path.is_dir() else file_path.parent
|
||||
while current != current.parent:
|
||||
if (current / ".git").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
return None
|
||||
|
||||
|
||||
def _get_repo_hash(repo_path: Path) -> str:
|
||||
"""Get git commit hash or fallback identifier"""
|
||||
git_dir = repo_path / ".git"
|
||||
if not git_dir.exists():
|
||||
return ""
|
||||
|
||||
try:
|
||||
# Read HEAD to get current commit
|
||||
head_file = git_dir / "HEAD"
|
||||
if head_file.exists():
|
||||
head_content = head_file.read_text().strip()
|
||||
if head_content.startswith("ref:"):
|
||||
# HEAD points to a ref
|
||||
ref_path = git_dir / head_content[5:].strip()
|
||||
if ref_path.exists():
|
||||
commit_hash = ref_path.read_text().strip()
|
||||
return commit_hash[:16] # First 16 chars
|
||||
else:
|
||||
# Detached HEAD
|
||||
return head_content[:16]
|
||||
except:
|
||||
pass
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def _load_per_repo_cache(repo_path: Path) -> Optional[tuple]:
|
||||
"""Load nodes and metadata from per-repo cache
|
||||
|
||||
Returns:
|
||||
tuple: (nodes_set, metadata_dict) or None if cache invalid
|
||||
"""
|
||||
cache_file = repo_path / ".git" / "nodecache.json"
|
||||
|
||||
if not cache_file.exists():
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(cache_file, 'r') as f:
|
||||
cache_data = json.load(f)
|
||||
|
||||
# Verify scanner version
|
||||
if cache_data.get('scanner_version') != SCANNER_VERSION:
|
||||
return None
|
||||
|
||||
# Verify git hash
|
||||
current_hash = _get_repo_hash(repo_path)
|
||||
if cache_data.get('git_hash') != current_hash:
|
||||
return None
|
||||
|
||||
# Return nodes and metadata
|
||||
nodes = cache_data.get('nodes', [])
|
||||
metadata = cache_data.get('metadata', {})
|
||||
return (set(nodes) if nodes else set(), metadata)
|
||||
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def _save_per_repo_cache(repo_path: Path, all_nodes: Set[str], metadata: dict = None):
|
||||
"""Save nodes and metadata to per-repo cache"""
|
||||
cache_file = repo_path / ".git" / "nodecache.json"
|
||||
|
||||
if not cache_file.parent.exists():
|
||||
return
|
||||
|
||||
git_hash = _get_repo_hash(repo_path)
|
||||
cache_data = {
|
||||
"scanner_version": SCANNER_VERSION,
|
||||
"git_hash": git_hash,
|
||||
"scanned_at": datetime.datetime.now().isoformat(),
|
||||
"nodes": sorted(list(all_nodes)),
|
||||
"metadata": metadata if metadata else {}
|
||||
}
|
||||
|
||||
try:
|
||||
with open(cache_file, 'w') as f:
|
||||
json.dump(cache_data, f, indent=2)
|
||||
except:
|
||||
pass # Silently fail - cache is optional
|
||||
|
||||
|
||||
def download_url(url, dest_folder, filename=None):
|
||||
@ -51,11 +153,12 @@ Examples:
|
||||
# Standard mode
|
||||
python3 scanner.py
|
||||
python3 scanner.py --skip-update
|
||||
python3 scanner.py --skip-all --force-rescan
|
||||
|
||||
# Scan-only mode
|
||||
python3 scanner.py --scan-only temp-urls-clean.list
|
||||
python3 scanner.py --scan-only urls.list --temp-dir /custom/temp
|
||||
python3 scanner.py --scan-only urls.list --skip-update
|
||||
python3 scanner.py --scan-only urls.list --skip-update --force-rescan
|
||||
'''
|
||||
)
|
||||
|
||||
@ -69,6 +172,8 @@ Examples:
|
||||
help='Skip GitHub stats collection')
|
||||
parser.add_argument('--skip-all', action='store_true',
|
||||
help='Skip all update operations')
|
||||
parser.add_argument('--force-rescan', action='store_true',
|
||||
help='Force rescan all nodes (ignore cache)')
|
||||
|
||||
# Backward compatibility: positional argument for temp_dir
|
||||
parser.add_argument('temp_dir_positional', nargs='?', metavar='TEMP_DIR',
|
||||
@ -94,6 +199,11 @@ parse_cnt = 0
|
||||
def extract_nodes(code_text):
|
||||
global parse_cnt
|
||||
|
||||
# Check cache first
|
||||
cache_key = hash(code_text)
|
||||
if cache_key in _extract_nodes_cache:
|
||||
return _extract_nodes_cache[cache_key].copy()
|
||||
|
||||
try:
|
||||
if parse_cnt % 100 == 0:
|
||||
print(".", end="", flush=True)
|
||||
@ -128,12 +238,458 @@ def extract_nodes(code_text):
|
||||
if key is not None and isinstance(key.value, str):
|
||||
s.add(key.value.strip())
|
||||
|
||||
# Cache the result
|
||||
_extract_nodes_cache[cache_key] = s
|
||||
return s
|
||||
else:
|
||||
# Cache empty result
|
||||
_extract_nodes_cache[cache_key] = set()
|
||||
return set()
|
||||
except:
|
||||
# Cache empty result on error
|
||||
_extract_nodes_cache[cache_key] = set()
|
||||
return set()
|
||||
|
||||
def extract_nodes_from_repo(repo_path: Path, verbose: bool = False, force_rescan: bool = False) -> tuple:
|
||||
"""
|
||||
Extract all nodes and metadata from a repository with per-repo caching.
|
||||
|
||||
Automatically caches results in .git/nodecache.json.
|
||||
Cache is invalidated when:
|
||||
- Git commit hash changes
|
||||
- Scanner version changes
|
||||
- force_rescan flag is True
|
||||
|
||||
Args:
|
||||
repo_path: Path to repository root
|
||||
verbose: If True, print UI-only extension detection messages
|
||||
force_rescan: If True, ignore cache and force fresh scan
|
||||
|
||||
Returns:
|
||||
tuple: (nodes_set, metadata_dict)
|
||||
"""
|
||||
# Ensure path is absolute
|
||||
repo_path = repo_path.resolve()
|
||||
|
||||
# Check per-repo cache first (unless force_rescan is True)
|
||||
if not force_rescan:
|
||||
cached_result = _load_per_repo_cache(repo_path)
|
||||
if cached_result is not None:
|
||||
return cached_result
|
||||
|
||||
# Cache miss - scan all .py files
|
||||
all_nodes = set()
|
||||
all_metadata = {}
|
||||
py_files = list(repo_path.rglob("*.py"))
|
||||
|
||||
# Filter out __pycache__, .git, and other hidden directories
|
||||
filtered_files = []
|
||||
for f in py_files:
|
||||
try:
|
||||
rel_path = f.relative_to(repo_path)
|
||||
# Skip __pycache__, .git, and any directory starting with .
|
||||
if '__pycache__' not in str(rel_path) and not any(part.startswith('.') for part in rel_path.parts):
|
||||
filtered_files.append(f)
|
||||
except:
|
||||
continue
|
||||
py_files = filtered_files
|
||||
|
||||
for py_file in py_files:
|
||||
try:
|
||||
# Read file with proper encoding
|
||||
with open(py_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
code = f.read()
|
||||
|
||||
if code:
|
||||
# Extract nodes using SAME logic as scan_in_file
|
||||
# V1 nodes (enhanced with fallback patterns)
|
||||
nodes = extract_nodes_enhanced(code, py_file, visited=set(), verbose=verbose)
|
||||
all_nodes.update(nodes)
|
||||
|
||||
# V3 nodes detection
|
||||
v3_nodes = extract_v3_nodes(code)
|
||||
all_nodes.update(v3_nodes)
|
||||
|
||||
# Dict parsing - exclude commented NODE_CLASS_MAPPINGS lines
|
||||
pattern = r"_CLASS_MAPPINGS\s*(?::\s*\w+\s*)?=\s*(?:\\\s*)?{([^}]*)}"
|
||||
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
||||
|
||||
for match_obj in regex.finditer(code):
|
||||
# Get the line where NODE_CLASS_MAPPINGS is defined
|
||||
match_start = match_obj.start()
|
||||
line_start = code.rfind('\n', 0, match_start) + 1
|
||||
line_end = code.find('\n', match_start)
|
||||
if line_end == -1:
|
||||
line_end = len(code)
|
||||
line = code[line_start:line_end]
|
||||
|
||||
# Skip if line starts with # (commented)
|
||||
if re.match(r'^\s*#', line):
|
||||
continue
|
||||
|
||||
match = match_obj.group(1)
|
||||
|
||||
# Filter out commented lines from dict content
|
||||
match_lines = match.split('\n')
|
||||
match_filtered = '\n'.join(
|
||||
line for line in match_lines
|
||||
if not re.match(r'^\s*#', line)
|
||||
)
|
||||
|
||||
# Extract key-value pairs with double quotes
|
||||
key_value_pairs = re.findall(r"\"([^\"]*)\"\s*:\s*([^,\n]*)", match_filtered)
|
||||
for key, value in key_value_pairs:
|
||||
all_nodes.add(key.strip())
|
||||
|
||||
# Extract key-value pairs with single quotes
|
||||
key_value_pairs = re.findall(r"'([^']*)'\s*:\s*([^,\n]*)", match_filtered)
|
||||
for key, value in key_value_pairs:
|
||||
all_nodes.add(key.strip())
|
||||
|
||||
# Handle .update() pattern (AFTER comment removal)
|
||||
code_cleaned = re.sub(r'^#.*?$', '', code, flags=re.MULTILINE)
|
||||
|
||||
update_pattern = r"_CLASS_MAPPINGS\.update\s*\(\s*{([^}]*)}\s*\)"
|
||||
update_match = re.search(update_pattern, code_cleaned, re.DOTALL)
|
||||
if update_match:
|
||||
update_dict_text = update_match.group(1)
|
||||
# Extract key-value pairs (double quotes)
|
||||
update_pairs = re.findall(r'"([^"]*)"\s*:\s*([^,\n]*)', update_dict_text)
|
||||
for key, value in update_pairs:
|
||||
all_nodes.add(key.strip())
|
||||
# Extract key-value pairs (single quotes)
|
||||
update_pairs_single = re.findall(r"'([^']*)'\s*:\s*([^,\n]*)", update_dict_text)
|
||||
for key, value in update_pairs_single:
|
||||
all_nodes.add(key.strip())
|
||||
|
||||
# Additional regex patterns (AFTER comment removal)
|
||||
patterns = [
|
||||
r'^[^=]*_CLASS_MAPPINGS\["(.*?)"\]',
|
||||
r'^[^=]*_CLASS_MAPPINGS\[\'(.*?)\'\]',
|
||||
r'@register_node\("(.+)",\s*\".+"\)',
|
||||
r'"(\w+)"\s*:\s*{"class":\s*\w+\s*'
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
keys = re.findall(pattern, code_cleaned)
|
||||
all_nodes.update(key.strip() for key in keys)
|
||||
|
||||
# Extract metadata from this file
|
||||
metadata = extract_metadata_only(str(py_file))
|
||||
all_metadata.update(metadata)
|
||||
except Exception as e:
|
||||
# Silently skip files that can't be read
|
||||
continue
|
||||
|
||||
# Save to per-repo cache
|
||||
_save_per_repo_cache(repo_path, all_nodes, all_metadata)
|
||||
|
||||
return (all_nodes, all_metadata)
|
||||
|
||||
|
||||
def _verify_class_exists(node_name: str, code_text: str, file_path: Optional[Path] = None) -> tuple[bool, Optional[str], Optional[int]]:
|
||||
"""
|
||||
Verify that a node class exists and has ComfyUI node structure.
|
||||
|
||||
Returns: (exists: bool, file_path: str, line_number: int)
|
||||
|
||||
A valid ComfyUI node must have:
|
||||
- Class definition (not commented)
|
||||
- At least one of: INPUT_TYPES, RETURN_TYPES, FUNCTION method/attribute
|
||||
"""
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||
tree = ast.parse(code_text)
|
||||
except:
|
||||
return (False, None, None)
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
if node.name == node_name or node.name.replace('_', '') == node_name.replace('_', ''):
|
||||
# Found class definition - check if it has ComfyUI interface
|
||||
has_input_types = False
|
||||
has_return_types = False
|
||||
has_function = False
|
||||
|
||||
for item in node.body:
|
||||
# Check for INPUT_TYPES method
|
||||
if isinstance(item, ast.FunctionDef) and item.name == 'INPUT_TYPES':
|
||||
has_input_types = True
|
||||
# Check for RETURN_TYPES attribute
|
||||
elif isinstance(item, ast.Assign):
|
||||
for target in item.targets:
|
||||
if isinstance(target, ast.Name):
|
||||
if target.id == 'RETURN_TYPES':
|
||||
has_return_types = True
|
||||
elif target.id == 'FUNCTION':
|
||||
has_function = True
|
||||
# Check for FUNCTION method
|
||||
elif isinstance(item, ast.FunctionDef):
|
||||
has_function = True
|
||||
|
||||
# Valid if has any ComfyUI signature
|
||||
if has_input_types or has_return_types or has_function:
|
||||
file_str = str(file_path) if file_path else None
|
||||
return (True, file_str, node.lineno)
|
||||
|
||||
return (False, None, None)
|
||||
|
||||
|
||||
def _extract_display_name_mappings(code_text: str) -> Set[str]:
|
||||
"""
|
||||
Extract node names from NODE_DISPLAY_NAME_MAPPINGS.
|
||||
|
||||
Pattern:
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"node_key": "Display Name",
|
||||
...
|
||||
}
|
||||
|
||||
Returns:
|
||||
Set of node keys from NODE_DISPLAY_NAME_MAPPINGS
|
||||
"""
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||
tree = ast.parse(code_text)
|
||||
except:
|
||||
return set()
|
||||
|
||||
nodes = set()
|
||||
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and target.id == 'NODE_DISPLAY_NAME_MAPPINGS':
|
||||
if isinstance(node.value, ast.Dict):
|
||||
for key in node.value.keys:
|
||||
if isinstance(key, ast.Constant) and isinstance(key.value, str):
|
||||
nodes.add(key.value.strip())
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
def extract_nodes_enhanced(
|
||||
code_text: str,
|
||||
file_path: Optional[Path] = None,
|
||||
visited: Optional[Set[Path]] = None,
|
||||
verbose: bool = False
|
||||
) -> Set[str]:
|
||||
"""
|
||||
Enhanced node extraction with multi-layer detection system.
|
||||
|
||||
Scanner 2.0.11 - Comprehensive detection strategy:
|
||||
- Phase 1: NODE_CLASS_MAPPINGS dict literal
|
||||
- Phase 2: Class.NAME attribute access (e.g., FreeChat.NAME)
|
||||
- Phase 3: Item assignment (NODE_CLASS_MAPPINGS["key"] = value)
|
||||
- Phase 4: Class existence verification (detects active classes even if registration commented)
|
||||
- Phase 5: NODE_DISPLAY_NAME_MAPPINGS cross-reference
|
||||
- Phase 6: Empty dict detection (UI-only extensions, logging only)
|
||||
|
||||
Fixed Bugs:
|
||||
- Scanner 2.0.9: Fallback cascade prevented Phase 3 execution
|
||||
- Scanner 2.0.10: Missed active classes with commented registrations (15 false negatives)
|
||||
|
||||
Args:
|
||||
code_text: Python source code
|
||||
file_path: Path to file (for logging and caching)
|
||||
visited: Visited paths (for circular import prevention)
|
||||
verbose: If True, print UI-only extension detection messages
|
||||
|
||||
Returns:
|
||||
Set of node names (union of all detected patterns)
|
||||
"""
|
||||
# Check file-based cache if file_path provided
|
||||
if file_path is not None:
|
||||
try:
|
||||
file_path_obj = Path(file_path) if not isinstance(file_path, Path) else file_path
|
||||
if file_path_obj.exists():
|
||||
current_mtime = file_path_obj.stat().st_mtime
|
||||
|
||||
# Check if we have cached result with matching mtime and scanner version
|
||||
if file_path_obj in _file_mtime_cache:
|
||||
cached_mtime = _file_mtime_cache[file_path_obj]
|
||||
cache_key = (str(file_path_obj), cached_mtime, SCANNER_VERSION)
|
||||
|
||||
if current_mtime == cached_mtime and cache_key in _extract_nodes_enhanced_cache:
|
||||
return _extract_nodes_enhanced_cache[cache_key].copy()
|
||||
except:
|
||||
pass # Ignore cache errors, proceed with normal execution
|
||||
|
||||
# Suppress warnings from AST parsing
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||
|
||||
# Phase 1: Original extract_nodes() - dict literal
|
||||
phase1_nodes = extract_nodes(code_text)
|
||||
|
||||
# Phase 2: Class.NAME pattern
|
||||
if visited is None:
|
||||
visited = set()
|
||||
phase2_nodes = _fallback_classname_resolver(code_text, file_path)
|
||||
|
||||
# Phase 3: Item assignment pattern
|
||||
phase3_nodes = _fallback_item_assignment(code_text)
|
||||
|
||||
# Phase 4: NODE_DISPLAY_NAME_MAPPINGS cross-reference (NEW in 2.0.11)
|
||||
# This catches nodes that are in display names but not in NODE_CLASS_MAPPINGS
|
||||
phase4_nodes = _extract_display_name_mappings(code_text)
|
||||
|
||||
# Phase 5: Class existence verification ONLY for display name candidates (NEW in 2.0.11)
|
||||
# This phase is CONSERVATIVE - only verify classes that appear in display names
|
||||
# This catches the specific Scanner 2.0.10 bug pattern:
|
||||
# - NODE_CLASS_MAPPINGS registration is commented
|
||||
# - NODE_DISPLAY_NAME_MAPPINGS still has the entry
|
||||
# - Class implementation exists
|
||||
# Example: Bjornulf_ollamaLoader in Bjornulf_custom_nodes
|
||||
phase5_nodes = set()
|
||||
for node_name in phase4_nodes:
|
||||
# Only check classes that appear in display names but not in registrations
|
||||
if node_name not in (phase1_nodes | phase2_nodes | phase3_nodes):
|
||||
exists, _, _ = _verify_class_exists(node_name, code_text, file_path)
|
||||
if exists:
|
||||
phase5_nodes.add(node_name)
|
||||
|
||||
# Union all results (FIX: Scanner 2.0.9 bug + Scanner 2.0.10 bug)
|
||||
# 2.0.9: Used early return which missed Phase 3 nodes
|
||||
# 2.0.10: Only checked registrations, missed classes referenced in display names
|
||||
all_nodes = phase1_nodes | phase2_nodes | phase3_nodes | phase4_nodes | phase5_nodes
|
||||
|
||||
# Phase 6: Empty dict detector (logging only, doesn't add nodes)
|
||||
if not all_nodes:
|
||||
_fallback_empty_dict_detector(code_text, file_path, verbose)
|
||||
|
||||
# Cache the result
|
||||
if file_path is not None:
|
||||
try:
|
||||
file_path_obj = Path(file_path) if not isinstance(file_path, Path) else file_path
|
||||
if file_path_obj.exists():
|
||||
current_mtime = file_path_obj.stat().st_mtime
|
||||
cache_key = (str(file_path_obj), current_mtime, SCANNER_VERSION)
|
||||
_extract_nodes_enhanced_cache[cache_key] = all_nodes
|
||||
_file_mtime_cache[file_path_obj] = current_mtime
|
||||
except:
|
||||
pass
|
||||
|
||||
return all_nodes
|
||||
|
||||
|
||||
def _fallback_classname_resolver(code_text: str, file_path: Optional[Path]) -> Set[str]:
|
||||
"""
|
||||
Detect Class.NAME pattern in NODE_CLASS_MAPPINGS.
|
||||
|
||||
Pattern:
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
FreeChat.NAME: FreeChat,
|
||||
PaidChat.NAME: PaidChat
|
||||
}
|
||||
"""
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||
parsed = ast.parse(code_text)
|
||||
except:
|
||||
return set()
|
||||
|
||||
nodes = set()
|
||||
|
||||
for node in parsed.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and target.id == 'NODE_CLASS_MAPPINGS':
|
||||
if isinstance(node.value, ast.Dict):
|
||||
for key in node.value.keys:
|
||||
# Detect Class.NAME pattern
|
||||
if isinstance(key, ast.Attribute):
|
||||
if isinstance(key.value, ast.Name):
|
||||
# Use class name as node name
|
||||
nodes.add(key.value.id)
|
||||
# Also handle literal strings
|
||||
elif isinstance(key, ast.Constant) and isinstance(key.value, str):
|
||||
nodes.add(key.value.strip())
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
def _fallback_item_assignment(code_text: str) -> Set[str]:
|
||||
"""
|
||||
Detect item assignment pattern.
|
||||
|
||||
Pattern:
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS["MyNode"] = MyNode
|
||||
"""
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||
parsed = ast.parse(code_text)
|
||||
except:
|
||||
return set()
|
||||
|
||||
nodes = set()
|
||||
|
||||
for node in ast.walk(parsed):
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Subscript):
|
||||
if (isinstance(target.value, ast.Name) and
|
||||
target.value.id in ['NODE_CLASS_MAPPINGS', 'NODE_CONFIG']):
|
||||
# Extract key
|
||||
if isinstance(target.slice, ast.Constant):
|
||||
if isinstance(target.slice.value, str):
|
||||
nodes.add(target.slice.value)
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
def _extract_repo_name(file_path: Path) -> str:
|
||||
"""
|
||||
Extract repository name from file path.
|
||||
|
||||
Path structure: /home/rho/.tmp/analysis/temp/{author}_{reponame}/{path/to/file.py}
|
||||
Returns: {author}_{reponame} or filename if extraction fails
|
||||
"""
|
||||
try:
|
||||
parts = file_path.parts
|
||||
# Find 'temp' directory in path
|
||||
if 'temp' in parts:
|
||||
temp_idx = parts.index('temp')
|
||||
if temp_idx + 1 < len(parts):
|
||||
# Next part after 'temp' is the repo directory
|
||||
return parts[temp_idx + 1]
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
# Fallback to filename if extraction fails
|
||||
return file_path.name if hasattr(file_path, 'name') else str(file_path)
|
||||
|
||||
|
||||
def _fallback_empty_dict_detector(code_text: str, file_path: Optional[Path], verbose: bool = False) -> None:
|
||||
"""
|
||||
Detect empty NODE_CLASS_MAPPINGS (UI-only extensions).
|
||||
Logs for documentation purposes only (when verbose=True).
|
||||
|
||||
Args:
|
||||
code_text: Python source code to analyze
|
||||
file_path: Path to the file being analyzed
|
||||
verbose: If True, print detection messages
|
||||
"""
|
||||
empty_patterns = [
|
||||
'NODE_CLASS_MAPPINGS = {}',
|
||||
'NODE_CLASS_MAPPINGS={}',
|
||||
]
|
||||
|
||||
code_normalized = code_text.replace(' ', '').replace('\n', '')
|
||||
|
||||
for pattern in empty_patterns:
|
||||
pattern_normalized = pattern.replace(' ', '')
|
||||
if pattern_normalized in code_normalized:
|
||||
if file_path and verbose:
|
||||
repo_name = _extract_repo_name(file_path)
|
||||
print(f"Info: UI-only extension (empty NODE_CLASS_MAPPINGS): {repo_name}")
|
||||
return
|
||||
|
||||
def has_comfy_node_base(class_node):
|
||||
"""Check if class inherits from io.ComfyNode or ComfyNode"""
|
||||
@ -229,6 +785,25 @@ def extract_v3_nodes(code_text):
|
||||
|
||||
|
||||
# scan
|
||||
def extract_metadata_only(filename):
|
||||
"""Extract only metadata (@author, @title, etc) without node scanning"""
|
||||
try:
|
||||
with open(filename, encoding='utf-8', errors='ignore') as file:
|
||||
code = file.read()
|
||||
|
||||
metadata = {}
|
||||
lines = code.strip().split('\n')
|
||||
for line in lines:
|
||||
if line.startswith('@'):
|
||||
if line.startswith("@author:") or line.startswith("@title:") or line.startswith("@nickname:") or line.startswith("@description:"):
|
||||
key, value = line[1:].strip().split(':', 1)
|
||||
metadata[key.strip()] = value.strip()
|
||||
|
||||
return metadata
|
||||
except:
|
||||
return {}
|
||||
|
||||
|
||||
def scan_in_file(filename, is_builtin=False):
|
||||
global builtin_nodes
|
||||
|
||||
@ -242,8 +817,8 @@ def scan_in_file(filename, is_builtin=False):
|
||||
nodes = set()
|
||||
class_dict = {}
|
||||
|
||||
# V1 nodes detection
|
||||
nodes |= extract_nodes(code)
|
||||
# V1 nodes detection (enhanced with fallback patterns)
|
||||
nodes |= extract_nodes_enhanced(code, file_path=Path(filename), visited=set())
|
||||
|
||||
# V3 nodes detection
|
||||
nodes |= extract_v3_nodes(code)
|
||||
@ -620,13 +1195,14 @@ def update_custom_nodes(scan_only_mode=False, url_list_file=None):
|
||||
return node_info
|
||||
|
||||
|
||||
def gen_json(node_info, scan_only_mode=False):
|
||||
def gen_json(node_info, scan_only_mode=False, force_rescan=False):
|
||||
"""
|
||||
Generate extension-node-map.json from scanned node information
|
||||
|
||||
Args:
|
||||
node_info (dict): Repository metadata mapping
|
||||
scan_only_mode (bool): If True, exclude metadata from output
|
||||
force_rescan (bool): If True, ignore cache and force rescan all nodes
|
||||
"""
|
||||
# scan from .py file
|
||||
node_files, node_dirs = get_nodes(temp_dir)
|
||||
@ -642,13 +1218,17 @@ def gen_json(node_info, scan_only_mode=False):
|
||||
py_files = get_py_file_paths(dirname)
|
||||
metadata = {}
|
||||
|
||||
nodes = set()
|
||||
for py in py_files:
|
||||
nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI")
|
||||
nodes.update(nodes_in_file)
|
||||
# Include metadata from .py files in both modes
|
||||
metadata.update(metadata_in_file)
|
||||
|
||||
# Use per-repo cache for node AND metadata extraction
|
||||
try:
|
||||
nodes, metadata = extract_nodes_from_repo(Path(dirname), verbose=False, force_rescan=force_rescan)
|
||||
except:
|
||||
# Fallback to file-by-file scanning if extract_nodes_from_repo fails
|
||||
nodes = set()
|
||||
for py in py_files:
|
||||
nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI")
|
||||
nodes.update(nodes_in_file)
|
||||
metadata.update(metadata_in_file)
|
||||
|
||||
dirname = os.path.basename(dirname)
|
||||
|
||||
if 'Jovimetrix' in dirname:
|
||||
@ -810,11 +1390,14 @@ if __name__ == "__main__":
|
||||
print("\n# Generating 'extension-node-map.json'...\n")
|
||||
|
||||
# Generate extension-node-map.json
|
||||
gen_json(updated_node_info, scan_only_mode)
|
||||
force_rescan = args.force_rescan if hasattr(args, 'force_rescan') else False
|
||||
if force_rescan:
|
||||
print("⚠️ Force rescan enabled - ignoring all cached results\n")
|
||||
gen_json(updated_node_info, scan_only_mode, force_rescan)
|
||||
|
||||
print("\n✅ DONE.\n")
|
||||
|
||||
if scan_only_mode:
|
||||
print("Output: extension-node-map.json (node mappings only)")
|
||||
else:
|
||||
print("Output: extension-node-map.json (full metadata)")
|
||||
print("Output: extension-node-map.json (full metadata)")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user