Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问许可证需确认审计通过

comfyui-node-migrationcomfyui 节点迁移

Agent Skill

comfyui-node-migration 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

588

周安装

25

GitHub Stars

184

下载量

206
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:comfyui-node-migration(comfyui 节点迁移)
来源仓库:https://github.com/jtydhr88/comfyui-custom-node-skills
仓库路径:skills/comfyui-node-migration
安装命令:
npx skills add https://github.com/jtydhr88/comfyui-custom-node-skills --skill comfyui-node-migration
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/jtydhr88/comfyui-custom-node-skills --skill comfyui-node-migration

简介

comfyui-node-migration 提供从 V1 到 V3 API 的迁移指南,更新节点结构。

  • 主要变更包括 base class、INPUT_TYPES 替换为 define_schema 等。
  • 需同步更新 NODE_CLASS_MAPPINGS 并使用 ComfyExtension 注册。
  • 迁移过程中应逐步测试每个节点功能,确保行为一致。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

ComfyUI V1 → V3 Migration Guide

Migrate existing V1 nodes to the modern V3 API. V3 uses classmethods, typed inputs/outputs, and ComfyExtension registration.

Migration Checklist

  1. Change base class to io.ComfyNode
  2. Replace INPUT_TYPES() with define_schema() returning io.Schema
  3. Rename execution function to execute and make it a @classmethod
  4. Replace return tuples with io.NodeOutput(...)
  5. Replace IS_CHANGED with fingerprint_inputs
  6. Replace VALIDATE_INPUTS with validate_inputs
  7. Convert check_lazy_status to @classmethod
  8. Replace NODE_CLASS_MAPPINGS with ComfyExtension + comfy_entrypoint()
  9. Access hidden inputs via cls.hidden instead of kwargs
  10. Remove __init__ methods (no instance state in V3)

Side-by-Side Comparison

V1 (Before)

import torch

class ImageInvertV1:
    CATEGORY = "image"
    FUNCTION = "invert"
    RETURN_TYPES = ("IMAGE",)
    RETURN_NAMES = ("image",)
    OUTPUT_TOOLTIPS = ("The inverted image",)
    DESCRIPTION = "Inverts image colors"

    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "image": ("IMAGE",),
                "strength": ("FLOAT", {
                    "default": 1.0,
                    "min": 0.0,
                    "max": 1.0,
                    "step": 0.01,
                }),
            },
            "optional": {
                "mask": ("MASK",),
            },
            "hidden": {
                "unique_id": "UNIQUE_ID",
            },
        }

    @classmethod
    def IS_CHANGED(s, image, strength, mask=None, unique_id=None):
        return strength

    @classmethod
    def VALIDATE_INPUTS(s, image, strength, mask=None, unique_id=None):
        if strength < 0:
            return "Strength must be non-negative"
        return True

    def invert(self, image, strength, mask=None, unique_id=None):
        inverted = 1.0 - image
        result = image * (1 - strength) + inverted * strength
        if mask is not None:
            result = image * (1 - mask.unsqueeze(-1)) + result * mask.unsqueeze(-1)
        return (result,)

NODE_CLASS_MAPPINGS = {"ImageInvertV1": ImageInvertV1}
NODE_DISPLAY_NAME_MAPPINGS = {"ImageInvertV1": "Invert Image"}

V3 (After)

import torch
from typing_extensions import override
from comfy_api.latest import ComfyExtension, io

class ImageInvertV3(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="ImageInvertV3",
            display_name="Invert Image",
            description="Inverts image colors",
            category="image",
            inputs=[
                io.Image.Input("image"),
                io.Float.Input("strength", default=1.0, min=0.0, max=1.0, step=0.01),
                io.Mask.Input("mask", optional=True),
            ],
            outputs=[
                io.Image.Output("IMAGE", tooltip="The inverted image"),
            ],
            hidden=[io.Hidden.unique_id],
        )

    @classmethod
    def fingerprint_inputs(cls, image, strength, mask=None):
        return strength

    @classmethod
    def validate_inputs(cls, image, strength, mask=None):
        if strength < 0:
            return "Strength must be non-negative"
        return True

    @classmethod
    def execute(cls, image, strength, mask=None):
        node_id = cls.hidden.unique_id  # access hidden via cls.hidden

        inverted = 1.0 - image
        result = image * (1 - strength) + inverted * strength
        if mask is not None:
            result = image * (1 - mask.unsqueeze(-1)) + result * mask.unsqueeze(-1)
        return io.NodeOutput(result)

class MyExtension(ComfyExtension):
    @override
    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [ImageInvertV3]

async def comfy_entrypoint() -> MyExtension:
    return MyExtension()

Property Mapping

V1 PropertyV3 Equivalent
CATEGORY = "image"io.Schema(category="image")
FUNCTION = "my_func"Always execute (fixed name)
RETURN_TYPES = ("IMAGE",)outputs=[io.Image.Output()]
RETURN_NAMES = ("image",)outputs=[io.Image.Output(display_name="image")]
OUTPUT_TOOLTIPS = ("tip",)outputs=[io.Image.Output(tooltip="tip")]
OUTPUT_NODE = Trueio.Schema(is_output_node=True)
DEPRECATED = Trueio.Schema(is_deprecated=True)
EXPERIMENTAL = Trueio.Schema(is_experimental=True)
API_NODE = Trueio.Schema(is_api_node=True)
NOT_IDEMPOTENT = Trueio.Schema(not_idempotent=True)
DESCRIPTION = "..."io.Schema(description="...")
SEARCH_ALIASES = [...]io.Schema(search_aliases=[...])
INPUT_IS_LIST = Trueio.Schema(is_input_list=True)
OUTPUT_IS_LIST = (True,)io.Image.Output(is_output_list=True)
DEV_ONLY = Trueio.Schema(is_dev_only=True)
ESSENTIALS_CATEGORY = "Basic"io.Schema(essentials_category="Basic")

Input Type Mapping

V1 InputV3 Input
("IMAGE",)io.Image.Input("id")
("MASK",)io.Mask.Input("id")
("LATENT",)io.Latent.Input("id")
("MODEL",)io.Model.Input("id")
("CLIP",)io.Clip.Input("id")
("VAE",)io.Vae.Input("id")
("CONDITIONING",)io.Conditioning.Input("id")
("INT", {"default": 0,...})io.Int.Input("id", default=0,...)
("FLOAT", {"default": 1.0,...})io.Float.Input("id", default=1.0,...)
("STRING", {"multiline": True})io.String.Input("id", multiline=True)
("BOOLEAN", {"default": True})io.Boolean.Input("id", default=True)
(["opt1", "opt2"],)io.Combo.Input("id", options=["opt1", "opt2"])
("CONTROL_NET",)io.ControlNet.Input("id")
("CLIP_VISION",)io.ClipVision.Input("id")
("CLIP_VISION_OUTPUT",)io.ClipVisionOutput.Input("id")
("STYLE_MODEL",)io.StyleModel.Input("id")
("GLIGEN",)io.Gligen.Input("id")
("UPSCALE_MODEL",)io.UpscaleModel.Input("id")
("AUDIO",)io.Audio.Input("id")
("VIDEO",)io.Video.Input("id")
("SAMPLER",)io.Sampler.Input("id")
("SIGMAS",)io.Sigmas.Input("id")
("NOISE",)io.Noise.Input("id")
("GUIDER",)io.Guider.Input("id")
("HOOKS",)io.Hooks.Input("id")
("LORA_MODEL",)io.LoraModel.Input("id")
("MESH",)io.Mesh.Input("id")
("VOXEL",)io.Voxel.Input("id")
("FILE_3D",)io.File3DAny.Input("id")
("FILE_3D_GLB",)io.File3DGLB.Input("id")
("SVG",)io.SVG.Input("id")
("COLOR",)io.Color.Input("id")
("BOUNDING_BOX",)io.BoundingBox.Input("id")
("CURVE",)io.Curve.Input("id")
("LATENT_UPSCALE_MODEL",)io.LatentUpscaleModel.Input("id")
("MODEL_PATCH",)io.ModelPatch.Input("id")
("HOOK_KEYFRAMES",)io.HookKeyframes.Input("id")
("AUDIO_ENCODER",)io.AudioEncoder.Input("id")
("AUDIO_ENCODER_OUTPUT",)io.AudioEncoderOutput.Input("id")
("TRACKS",)io.Tracks.Input("id")
("LOSS_MAP",)io.LossMap.Input("id")
("TIMESTEPS_RANGE",)io.TimestepsRange.Input("id")
("LATENT_OPERATION",)io.LatentOperation.Input("id")
("WEBCAM",)io.Webcam.Input("id")
("PHOTOMAKER",)io.Photomaker.Input("id")
("WAN_CAMERA_EMBEDDING",)io.WanCameraEmbedding.Input("id")
("LOAD_3D",)io.Load3D.Input("id")
("LOAD_3D_ANIMATION",)io.Load3DAnimation.Input("id")
("LOAD3D_CAMERA",)io.Load3DCamera.Input("id")
("FILE_3D_GLTF",)io.File3DGLTF.Input("id")
("FILE_3D_FBX",)io.File3DFBX.Input("id")
("FILE_3D_OBJ",)io.File3DOBJ.Input("id")
("FILE_3D_STL",)io.File3DSTL.Input("id")
("FILE_3D_USDZ",)io.File3DUSDZ.Input("id")
("POINT",)io.Point.Input("id")
("FACE_ANALYSIS",)io.FaceAnalysis.Input("id")
("BBOX",)io.BBOX.Input("id")
("SEGS",)io.SEGS.Input("id")
("IMAGECOMPARE",)io.ImageCompare.Input("id")
("*",)io.AnyType.Input("id") or io.MultiType.Input("id", types=[...])

Method Migration

Execute Method

# V1: instance method with custom name
class V1Node:
    FUNCTION = "process"
    def process(self, image, value):
        return (result,)

# V3: classmethod named "execute", returns NodeOutput
class V3Node(io.ComfyNode):
    @classmethod
    def execute(cls, image, value):
        return io.NodeOutput(result)

IS_CHANGED → fingerprint_inputs

# V1
@classmethod
def IS_CHANGED(s, **kwargs):
    return float("NaN")  # always re-execute

# V3
@classmethod
def fingerprint_inputs(cls, **kwargs):
    import time
    return time.time()  # always re-execute

VALIDATE_INPUTS → validate_inputs

# V1
@classmethod
def VALIDATE_INPUTS(s, input_types=None, **kwargs):
    return True

# V3
@classmethod
def validate_inputs(cls, input_types=None, **kwargs):
    return True

check_lazy_status

# V1: instance method
def check_lazy_status(self, **kwargs):
    return ["input_name"]

# V3: classmethod
@classmethod
def check_lazy_status(cls, **kwargs):
    return ["input_name"]

Hidden Inputs

# V1: received as kwargs
def execute(self, image, unique_id=None, prompt=None):
    node_id = unique_id

# V3: accessed via cls.hidden
@classmethod
def execute(cls, image):
    node_id = cls.hidden.unique_id
    prompt = cls.hidden.prompt

Registration Migration

# V1
NODE_CLASS_MAPPINGS = {
    "Node1": Node1Class,
    "Node2": Node2Class,
}
NODE_DISPLAY_NAME_MAPPINGS = {
    "Node1": "Node One",
    "Node2": "Node Two",
}
WEB_DIRECTORY = "./js"

# V3
from typing_extensions import override
from comfy_api.latest import ComfyExtension, io

class MyExtension(ComfyExtension):
    @override
    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [Node1Class, Node2Class]

    @override
    async def on_load(self):
        # Optional: initialization logic
        pass

async def comfy_entrypoint() -> MyExtension:
    return MyExtension()

# WEB_DIRECTORY still works the same way for JS extensions
WEB_DIRECTORY = "./js"

Output Node Migration

# V1
class V1SaveNode:
    RETURN_TYPES = ()
    OUTPUT_NODE = True
    FUNCTION = "save"

    def save(self, images, prefix):
        # ... save logic ...
        return {"ui": {"images": results}}

# V3
from comfy_api.latest import io, ui

class V3SaveNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="V3SaveNode",
            display_name="Save",
            category="image",
            is_output_node=True,
            inputs=[
                io.Image.Input("images"),
                io.String.Input("prefix", default="output"),
            ],
            outputs=[],
            hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
        )

    @classmethod
    def execute(cls, images, prefix):
        saved = ui.ImageSaveHelper.get_save_images_ui(images, prefix, cls=cls)
        return io.NodeOutput(ui=saved)

Key Gotchas

  1. No instance state: V3 execute is a classmethod. Don't store state on self. Use external storage if needed.
  2. Fixed method name: Always execute, never custom names.
  3. Hidden access changed: Use cls.hidden.prompt not function parameters.
  4. Return type changed: io.NodeOutput(val) not (val,).
  5. Optional inputs: Use =None default in execute params, not separate "optional" dict.
  6. Async support: V3 execute can be async def execute(cls,...).

See Also

  • comfyui-node-basics - V3 node fundamentals
  • comfyui-node-packaging - Project structure
  • comfyui-node-lifecycle - Execution lifecycle differences

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

36.46%
按下载量换算75

Claude

27.75%
按下载量换算57

Cursor

18.74%
按下载量换算39

Gemini CLI

9.89%
按下载量换算20

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills