CoolFace
Apppublic

fred-dev/comfy_ui_ali

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
cm-cli.py1280 linesDownload Raw Back to ComfyUI-Manager
1import os2import sys3import traceback4import json5import asyncio6import concurrent7import threading8from typing import Optional9 10import typer11from rich import print12from typing_extensions import List, Annotated13import re14import git15import importlib16 17 18sys.path.append(os.path.dirname(__file__))19sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))20 21import manager_util22 23# read env vars24# COMFYUI_FOLDERS_BASE_PATH is not required in cm-cli.py25# `comfy_path` should be resolved before importing manager_core26comfy_path = os.environ.get('COMFYUI_PATH')27if comfy_path is None:28    try:29        import folder_paths30        comfy_path = os.path.join(os.path.dirname(folder_paths.__file__))31    except:32        print("\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)33        comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..'))34 35# This should be placed here36sys.path.append(comfy_path)37 38import utils.extra_config39import cm_global40import manager_core as core41from manager_core import unified_manager42import cnr_utils43 44comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))45 46cm_global.pip_blacklist = {'torch', 'torchsde', 'torchvision'}47cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']48cm_global.pip_overrides = {'numpy': 'numpy<2'}49 50if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json")):51    with open(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json"), 'r', encoding="UTF-8", errors="ignore") as json_file:52        cm_global.pip_overrides = json.load(json_file)53 54 55if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list")):56    with open(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list"), 'r', encoding="UTF-8", errors="ignore") as f:57        for x in f.readlines():58            y = x.strip()59            if y != '':60                cm_global.pip_blacklist.add(y)61 62 63def check_comfyui_hash():64    try:65        repo = git.Repo(comfy_path)66        core.comfy_ui_revision = len(list(repo.iter_commits('HEAD')))67        core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime68    except:69        print('[bold yellow]INFO: Frozen ComfyUI mode.[/bold yellow]')70        core.comfy_ui_revision = 071        core.comfy_ui_commit_datetime = 072 73    cm_global.variables['comfyui.revision'] = core.comfy_ui_revision74 75 76check_comfyui_hash()  # This is a preparation step for manager_core77core.check_invalid_nodes()78 79 80def read_downgrade_blacklist():81    try:82        import configparser83        config = configparser.ConfigParser(strict=False)84        config.read(core.manager_config.path)85        default_conf = config['default']86 87        if 'downgrade_blacklist' in default_conf:88            items = default_conf['downgrade_blacklist'].split(',')89            items = [x.strip() for x in items if x != '']90            cm_global.pip_downgrade_blacklist += items91            cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist))92    except:93        pass94 95 96read_downgrade_blacklist()  # This is a preparation step for manager_core97 98 99class Ctx:100    folder_paths = None101    102    def __init__(self):103        self.channel = 'default'104        self.no_deps = False105        self.mode = 'cache'106        self.user_directory = None107        self.custom_nodes_paths = [os.path.join(core.comfy_base_path, 'custom_nodes')]108        self.manager_files_directory = os.path.dirname(__file__)109        110        if Ctx.folder_paths is None:111            try:112                Ctx.folder_paths = importlib.import_module('folder_paths')113            except ImportError:114                print("Warning: Unable to import folder_paths module")115 116    def set_channel_mode(self, channel, mode):117        if mode is not None:118            self.mode = mode119 120        valid_modes = ["remote", "local", "cache"]121        if mode and mode.lower() not in valid_modes:122            typer.echo(123                f"Invalid mode: {mode}. Allowed modes are 'remote', 'local', 'cache'.",124                err=True,125            )126            exit(1)127 128        if channel is not None:129            self.channel = channel130 131        asyncio.run(unified_manager.reload(cache_mode=self.mode, dont_wait=False))132        asyncio.run(unified_manager.load_nightly(self.channel, self.mode))133 134    def set_no_deps(self, no_deps):135        self.no_deps = no_deps136 137    def set_user_directory(self, user_directory):138        if user_directory is None:139            return140 141        extra_model_paths_yaml = os.path.join(user_directory, 'extra_model_paths.yaml')142        if os.path.exists(extra_model_paths_yaml):143            utils.extra_config.load_extra_path_config(extra_model_paths_yaml)144 145        core.update_user_directory(user_directory)146 147        if os.path.exists(core.manager_pip_overrides_path):148            with open(core.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:149                cm_global.pip_overrides = json.load(json_file)150                cm_global.pip_overrides = {'numpy': 'numpy<2'}151 152        if os.path.exists(core.manager_pip_blacklist_path):153            with open(core.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:154                for x in f.readlines():155                    y = x.strip()156                    if y != '':157                        cm_global.pip_blacklist.add(y)158 159    def update_custom_nodes_dir(self, target_dir):160        import folder_paths161        a, b = folder_paths.folder_names_and_paths['custom_nodes']162        folder_paths.folder_names_and_paths['custom_nodes'] = [os.path.abspath(target_dir)], set()163 164    @staticmethod165    def get_startup_scripts_path():166        return os.path.join(core.manager_startup_script_path, "install-scripts.txt")167 168    @staticmethod169    def get_restore_snapshot_path():170        return os.path.join(core.manager_startup_script_path, "restore-snapshot.json")171 172    @staticmethod173    def get_snapshot_path():174        return core.manager_snapshot_path175 176    @staticmethod177    def get_custom_nodes_paths():178        if Ctx.folder_paths is None:179            print("Error: folder_paths module is not available")180            return []181        return Ctx.folder_paths.get_folder_paths('custom_nodes')182 183 184cmd_ctx = Ctx()185 186 187def install_node(node_spec_str, is_all=False, cnt_msg=''):188    if core.is_valid_url(node_spec_str):189        # install via urls190        res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps))191        if not res.result:192            print(res.msg)193            print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]")194        else:195            print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}")196    else:197        node_spec = unified_manager.resolve_node_spec(node_spec_str)198 199        if node_spec is None:200            return201 202        node_name, version_spec, is_specified = node_spec203 204        # NOTE: install node doesn't allow update if version is not specified205        if not is_specified:206            version_spec = None207 208        res = asyncio.run(unified_manager.install_by_id(node_name, version_spec, cmd_ctx.channel, cmd_ctx.mode, instant_execution=True, no_deps=cmd_ctx.no_deps))209 210        if res.action == 'skip':211            print(f"{cnt_msg} [   SKIP  ] {node_name:50} => Already installed")212        elif res.action == 'enable':213            print(f"{cnt_msg} [ ENABLED ] {node_name:50}")214        elif res.action == 'install-git' and res.target == 'nightly':215            print(f"{cnt_msg} [INSTALLED] {node_name:50}[NIGHTLY]")216        elif res.action == 'install-git' and res.target == 'unknown':217            print(f"{cnt_msg} [INSTALLED] {node_name:50}[UNKNOWN]")218        elif res.action == 'install-cnr' and res.result:219            print(f"{cnt_msg} [INSTALLED] {node_name:50}[{res.target}]")220        elif res.action == 'switch-cnr' and res.result:221            print(f"{cnt_msg} [INSTALLED] {node_name:50}[{res.target}]")222        elif (res.action == 'switch-cnr' or res.action == 'install-cnr') and not res.result and node_name in unified_manager.cnr_map:223            print(f"\nAvailable version of '{node_name}'")224            show_versions(node_name)225            print("")226        else:227            print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.\n{res.msg}[/bold red]")228 229 230def reinstall_node(node_spec_str, is_all=False, cnt_msg=''):231    node_spec = unified_manager.resolve_node_spec(node_spec_str)232 233    node_name, version_spec, _ = node_spec234 235    unified_manager.unified_uninstall(node_name, version_spec == 'unknown')236    install_node(node_name, is_all=is_all, cnt_msg=cnt_msg)237 238 239def fix_node(node_spec_str, is_all=False, cnt_msg=''):240    node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='active')241 242    if node_spec is None:243        if not is_all:244            if unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive') is not None:245                print(f"{cnt_msg} [  SKIPPED  ]: {node_spec_str:50} => Disabled")246            else:247                print(f"{cnt_msg} [  SKIPPED  ]: {node_spec_str:50} => Not installed")248 249        return250 251    node_name, version_spec, _ = node_spec252 253    print(f"{cnt_msg} [   FIXING  ]: {node_name:50}[{version_spec}]")254    res = unified_manager.unified_fix(node_name, version_spec, no_deps=cmd_ctx.no_deps)255 256    if not res.result:257        print(f"[bold red]ERROR: f{res.msg}[/bold red]")258 259 260def uninstall_node(node_spec_str: str, is_all: bool = False, cnt_msg: str = ''):261    spec = node_spec_str.split('@')262    if len(spec) == 2 and spec[1] == 'unknown':263        node_name = spec[0]264        is_unknown = True265    else:266        node_name = spec[0]267        is_unknown = False268 269    res = unified_manager.unified_uninstall(node_name, is_unknown)270    if len(spec) == 1 and res.action == 'skip' and not is_unknown:271        res = unified_manager.unified_uninstall(node_name, True)272 273    if res.action == 'skip':274        print(f"{cnt_msg} [  SKIPPED  ]: {node_name:50} => Not installed")275 276    elif res.result:277        print(f"{cnt_msg} [UNINSTALLED] {node_name:50}")278    else:279        print(f"ERROR: An error occurred while uninstalling '{node_name}'.")280 281 282def update_node(node_spec_str, is_all=False, cnt_msg=''):283    node_spec = unified_manager.resolve_node_spec(node_spec_str, 'active')284 285    if node_spec is None:286        if unified_manager.resolve_node_spec(node_spec_str, 'inactive'):287            print(f"{cnt_msg} [  SKIPPED  ]: {node_spec_str:50} => Disabled")288        else:289            print(f"{cnt_msg} [  SKIPPED  ]: {node_spec_str:50} => Not installed")290        return None291 292    node_name, version_spec, _ = node_spec293 294    res = unified_manager.unified_update(node_name, version_spec, no_deps=cmd_ctx.no_deps, return_postinstall=True)295 296    if not res.result:297        print(f"ERROR: An error occurred while updating '{node_name}'.")298    elif res.action == 'skip':299        print(f"{cnt_msg} [  SKIPPED  ]: {node_name:50} => {res.msg}")300    else:301        print(f"{cnt_msg} [  UPDATED  ]: {node_name:50} => ({version_spec} -> {res.target})")302 303    return res.with_target(f'{node_name}@{res.target}')304 305 306def update_parallel(nodes):307    is_all = False308    if 'all' in nodes:309        is_all = True310        nodes = []311        for x in unified_manager.active_nodes.keys():312            nodes.append(x)313        for x in unified_manager.unknown_active_nodes.keys():314            nodes.append(x+"@unknown")315    else:316        nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui']]317 318    total = len(nodes)319 320    lock = threading.Lock()321    processed = []322 323    i = 0324 325    def process_custom_node(x):326        nonlocal i327        nonlocal processed328 329        with lock:330            i += 1331 332        try:333            res = update_node(x, is_all=is_all, cnt_msg=f'{i}/{total}')334            with lock:335                processed.append(res)336        except Exception as e:337            print(f"ERROR: {e}")338            traceback.print_exc()339 340    with concurrent.futures.ThreadPoolExecutor(4) as executor:341        for item in nodes:342            executor.submit(process_custom_node, item)343 344    i = 1345    for res in processed:346        if res is not None:347            print(f"[{i}/{total}] Post update: {res.target}")348            if res.postinstall is not None:349                res.postinstall()350        i += 1351 352 353def update_comfyui():354    res = core.update_path(comfy_path, instant_execution=True)355    if res == 'fail':356        print("Updating ComfyUI has failed.")357    elif res == 'updated':358        print("ComfyUI is updated.")359    else:360        print("ComfyUI is already up to date.")361 362 363def enable_node(node_spec_str, is_all=False, cnt_msg=''):364    if unified_manager.resolve_node_spec(node_spec_str, guess_mode='active') is not None:365        print(f"{cnt_msg} [  SKIP ] {node_spec_str:50} => Already enabled")366        return367 368    node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive')369 370    if node_spec is None:371        print(f"{cnt_msg} [  SKIP ] {node_spec_str:50} => Not found")372        return373 374    node_name, version_spec, _ = node_spec375 376    res = unified_manager.unified_enable(node_name, version_spec)377 378    if res.action == 'skip':379        print(f"{cnt_msg} [  SKIP ] {node_name:50} => {res.msg}")380    elif res.result:381        print(f"{cnt_msg} [ENABLED] {node_name:50}")382    else:383        print(f"{cnt_msg} [  FAIL ] {node_name:50} => {res.msg}")384 385 386def disable_node(node_spec_str: str, is_all=False, cnt_msg=''):387    if 'comfyui-manager' in node_spec_str.lower():388        return389 390    node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='active')391 392    if node_spec is None:393        if unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive') is not None:394            print(f"{cnt_msg} [  SKIP  ] {node_spec_str:50} => Already disabled")395        else:396            print(f"{cnt_msg} [  SKIP  ] {node_spec_str:50} => Not found")397        return398 399    node_name, version_spec, _ = node_spec400 401    res = unified_manager.unified_disable(node_name, version_spec == 'unknown')402 403    if res.action == 'skip':404        print(f"{cnt_msg} [  SKIP  ] {node_name:50} => {res.msg}")405    elif res.result:406        print(f"{cnt_msg} [DISABLED] {node_name:50}")407    else:408        print(f"{cnt_msg} [  FAIL  ] {node_name:50} => {res.msg}")409 410 411def show_list(kind, simple=False):412    custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=cmd_ctx.channel, mode=cmd_ctx.mode))413 414    # collect not-installed unknown nodes415    not_installed_unknown_nodes = []416    repo_unknown = {}417 418    for k, v in custom_nodes.items():419        if 'cnr_latest' not in v:420            if len(v['files']) == 1:421                repo_url = v['files'][0]422                node_name = repo_url.split('/')[-1]423                if node_name not in unified_manager.unknown_inactive_nodes and node_name not in unified_manager.unknown_active_nodes:424                    not_installed_unknown_nodes.append(v)425                else:426                    repo_unknown[node_name] = v427 428    processed = {}429    unknown_processed = []430 431    flag = kind in ['all', 'cnr', 'installed', 'enabled']432    for k, v in unified_manager.active_nodes.items():433        if flag:434            cnr = unified_manager.cnr_map[k]435            processed[k] = "[    ENABLED    ] ", cnr['name'], k, cnr['publisher']['name'], v[0]436        else:437            processed[k] = None438 439    if flag and kind != 'cnr':440        for k, v in unified_manager.unknown_active_nodes.items():441            item = repo_unknown.get(k)442 443            if item is None:444                continue445 446            log_item = "[    ENABLED    ] ", item['title'], k, item['author']447            unknown_processed.append(log_item)448 449    flag = kind in ['all', 'cnr', 'installed', 'disabled']450    for k, v in unified_manager.cnr_inactive_nodes.items():451        if k in processed:452            continue453 454        if flag:455            cnr = unified_manager.cnr_map[k]456            processed[k] = "[    DISABLED   ] ", cnr['name'], k, cnr['publisher']['name'], ", ".join(list(v.keys()))457        else:458            processed[k] = None459 460    for k, v in unified_manager.nightly_inactive_nodes.items():461        if k in processed:462            continue463 464        if flag:465            cnr = unified_manager.cnr_map[k]466            processed[k] = "[    DISABLED   ] ", cnr['name'], k, cnr['publisher']['name'], 'nightly'467        else:468            processed[k] = None469 470    if flag and kind != 'cnr':471        for k, v in unified_manager.unknown_inactive_nodes.items():472            item = repo_unknown.get(k)473 474            if item is None:475                continue476 477            log_item = "[    DISABLED   ] ", item['title'], k, item['author']478            unknown_processed.append(log_item)479 480    flag = kind in ['all', 'cnr', 'not-installed']481    for k, v in unified_manager.cnr_map.items():482        if k in processed:483            continue484 485        if flag:486            cnr = unified_manager.cnr_map[k]487            ver_spec = v['latest_version']['version'] if 'latest_version' in v else '0.0.0'488            processed[k] = "[ NOT INSTALLED ] ", cnr['name'], k, cnr['publisher']['name'], ver_spec489        else:490            processed[k] = None491 492    if flag and kind != 'cnr':493        for x in not_installed_unknown_nodes:494            if len(x['files']) == 1:495                node_id = os.path.basename(x['files'][0])496                log_item = "[ NOT INSTALLED ] ", x['title'], node_id, x['author']497                unknown_processed.append(log_item)498 499    for x in processed.values():500        if x is None:501            continue502 503        prefix, title, short_id, author, ver_spec = x504        if simple:505            print(title+'@'+ver_spec)506        else:507            print(f"{prefix} {title:50} {short_id:30} (author: {author:20}) \\[{ver_spec}]")508 509    for x in unknown_processed:510        prefix, title, short_id, author = x511        if simple:512            print(title+'@unknown')513        else:514            print(f"{prefix} {title:50} {short_id:30} (author: {author:20}) [UNKNOWN]")515 516 517async def show_snapshot(simple_mode=False):518    json_obj = await core.get_current_snapshot()519 520    if simple_mode:521        print(f"[{json_obj['comfyui']}] comfyui")522        for k, v in json_obj['git_custom_nodes'].items():523            print(f"[{v['hash']}] {k}")524        for v in json_obj['file_custom_nodes']:525            print(f"[                   N/A                  ] {v['filename']}")526 527    else:528        formatted_json = json.dumps(json_obj, ensure_ascii=False, indent=4)529        print(formatted_json)530 531 532def show_snapshot_list(simple_mode=False):533    snapshot_path = cmd_ctx.get_snapshot_path()534 535    files = os.listdir(snapshot_path)536    json_files = [x for x in files if x.endswith('.json')]537    for x in sorted(json_files):538        print(x)539 540 541def cancel():542    if os.path.exists(cmd_ctx.get_startup_scripts_path()):543        os.remove(cmd_ctx.get_startup_scripts_path())544 545    if os.path.exists(cmd_ctx.get_restore_snapshot_path()):546        os.remove(cmd_ctx.get_restore_snapshot_path())547 548 549async def auto_save_snapshot():550    path = await core.save_snapshot_with_postfix('cli-autosave')551    print(f"Current snapshot is saved as `{path}`")552 553 554def get_all_installed_node_specs():555    res = []556    processed = set()557    for k, v in unified_manager.active_nodes.items():558        node_spec_str = f"{k}@{v[0]}"559        res.append(node_spec_str)560        processed.add(k)561 562    for k in unified_manager.cnr_inactive_nodes.keys():563        if k in processed:564            continue565 566        latest = unified_manager.get_from_cnr_inactive_nodes(k)567        if latest is not None:568            node_spec_str = f"{k}@{str(latest[0])}"569            res.append(node_spec_str)570 571    for k in unified_manager.nightly_inactive_nodes.keys():572        if k in processed:573            continue574 575        node_spec_str = f"{k}@nightly"576        res.append(node_spec_str)577 578    for k in unified_manager.unknown_active_nodes.keys():579        node_spec_str = f"{k}@unknown"580        res.append(node_spec_str)581 582    for k in unified_manager.unknown_inactive_nodes.keys():583        node_spec_str = f"{k}@unknown"584        res.append(node_spec_str)585 586    return res587 588 589def for_each_nodes(nodes, act, allow_all=True):590    is_all = False591    if allow_all and 'all' in nodes:592        is_all = True593        nodes = get_all_installed_node_specs()594    else:595        nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui', 'all']]596 597    total = len(nodes)598    i = 1599    for x in nodes:600        try:601            act(x, is_all=is_all, cnt_msg=f'{i}/{total}')602        except Exception as e:603            print(f"ERROR: {e}")604            traceback.print_exc()605        i += 1606 607 608app = typer.Typer()609 610 611@app.command(help="Display help for commands")612def help(ctx: typer.Context):613    print(ctx.find_root().get_help())614    ctx.exit(0)615 616 617@app.command(help="Install custom nodes")618def install(619        nodes: List[str] = typer.Argument(620            ..., help="List of custom nodes to install"621        ),622        channel: Annotated[623            str,624            typer.Option(625                show_default=False,626                help="Specify the operation mode"627            ),628        ] = None,629        mode: str = typer.Option(630            None,631            help="[remote|local|cache]"632        ),633        no_deps: Annotated[634            Optional[bool],635            typer.Option(636                "--no-deps",637                show_default=False,638                help="Skip installing any Python dependencies",639            ),640        ] = False,641        user_directory: str = typer.Option(642            None,643            help="user directory"644        ),645):646    cmd_ctx.set_user_directory(user_directory)647    cmd_ctx.set_channel_mode(channel, mode)648    cmd_ctx.set_no_deps(no_deps)649 650    pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)651    for_each_nodes(nodes, act=install_node)652    pip_fixer.fix_broken()653 654 655@app.command(help="Reinstall custom nodes")656def reinstall(657        nodes: List[str] = typer.Argument(658            ..., help="List of custom nodes to reinstall"659        ),660        channel: Annotated[661            str,662            typer.Option(663                show_default=False,664                help="Specify the operation mode"665            ),666        ] = None,667        mode: str = typer.Option(668            None,669            help="[remote|local|cache]"670        ),671        no_deps: Annotated[672            Optional[bool],673            typer.Option(674                "--no-deps",675                show_default=False,676                help="Skip installing any Python dependencies",677            ),678        ] = False,679        user_directory: str = typer.Option(680            None,681            help="user directory"682        ),683):684    cmd_ctx.set_user_directory(user_directory)685    cmd_ctx.set_channel_mode(channel, mode)686    cmd_ctx.set_no_deps(no_deps)687 688    pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)689    for_each_nodes(nodes, act=reinstall_node)690    pip_fixer.fix_broken()691 692 693@app.command(help="Uninstall custom nodes")694def uninstall(695        nodes: List[str] = typer.Argument(696            ..., help="List of custom nodes to uninstall"697        ),698        channel: Annotated[699            str,700            typer.Option(701                show_default=False,702                help="Specify the operation mode"703            ),704        ] = None,705        mode: str = typer.Option(706            None,707            help="[remote|local|cache]"708        ),709):710    cmd_ctx.set_channel_mode(channel, mode)711    for_each_nodes(nodes, act=uninstall_node)712 713 714@app.command(help="Update custom nodes")715def update(716        nodes: List[str] = typer.Argument(717            ...,718            help="[all|List of custom nodes to update]"719        ),720        channel: Annotated[721            str,722            typer.Option(723                show_default=False,724                help="Specify the operation mode"725            ),726        ] = None,727        mode: str = typer.Option(728            None,729            help="[remote|local|cache]"730        ),731        user_directory: str = typer.Option(732            None,733            help="user directory"734        ),735):736    cmd_ctx.set_user_directory(user_directory)737    cmd_ctx.set_channel_mode(channel, mode)738 739    if 'all' in nodes:740        asyncio.run(auto_save_snapshot())741 742    pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)743 744    for x in nodes:745        if x.lower() in ['comfyui', 'comfy', 'all']:746            update_comfyui()747            break748 749    update_parallel(nodes)750    pip_fixer.fix_broken()751 752 753@app.command(help="Disable custom nodes")754def disable(755        nodes: List[str] = typer.Argument(756            ...,757            help="[all|List of custom nodes to disable]"758        ),759        channel: Annotated[760            str,761            typer.Option(762                show_default=False,763                help="Specify the operation mode"764            ),765        ] = None,766        mode: str = typer.Option(767            None,768            help="[remote|local|cache]"769        ),770        user_directory: str = typer.Option(771            None,772            help="user directory"773        ),774):775    cmd_ctx.set_user_directory(user_directory)776    cmd_ctx.set_channel_mode(channel, mode)777 778    if 'all' in nodes:779        asyncio.run(auto_save_snapshot())780 781    for_each_nodes(nodes, disable_node, allow_all=True)782 783 784@app.command(help="Enable custom nodes")785def enable(786        nodes: List[str] = typer.Argument(787            ...,788            help="[all|List of custom nodes to enable]"789        ),790        channel: Annotated[791            str,792            typer.Option(793                show_default=False,794                help="Specify the operation mode"795            ),796        ] = None,797        mode: str = typer.Option(798            None,799            help="[remote|local|cache]"800        ),801        user_directory: str = typer.Option(802            None,803            help="user directory"804        ),805):806    cmd_ctx.set_user_directory(user_directory)807    cmd_ctx.set_channel_mode(channel, mode)808 809    if 'all' in nodes:810        asyncio.run(auto_save_snapshot())811 812    for_each_nodes(nodes, enable_node, allow_all=True)813 814 815@app.command(help="Fix dependencies of custom nodes")816def fix(817        nodes: List[str] = typer.Argument(818            ...,819            help="[all|List of custom nodes to fix]"820        ),821        channel: Annotated[822            str,823            typer.Option(824                show_default=False,825                help="Specify the operation mode"826            ),827        ] = None,828        mode: str = typer.Option(829            None,830            help="[remote|local|cache]"831        ),832        user_directory: str = typer.Option(833            None,834            help="user directory"835        ),836):837    cmd_ctx.set_user_directory(user_directory)838    cmd_ctx.set_channel_mode(channel, mode)839 840    if 'all' in nodes:841        asyncio.run(auto_save_snapshot())842 843    pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)844    for_each_nodes(nodes, fix_node, allow_all=True)845    pip_fixer.fix_broken()846 847 848@app.command("show-versions", help="Show all available versions of the node")849def show_versions(node_name: str):850    versions = cnr_utils.all_versions_of_node(node_name)851    if versions is None:852        print(f"Node not found in Comfy Registry: {node_name}")853 854    for x in versions:855        print(f"[{x['createdAt'][:10]}] {x['version']} -- {x['changelog']}")856 857 858@app.command("show", help="Show node list")859def show(860        arg: str = typer.Argument(861            help="[installed|enabled|not-installed|disabled|all|cnr|snapshot|snapshot-list]"862        ),863        channel: Annotated[864            str,865            typer.Option(866                show_default=False,867                help="Specify the operation mode"868            ),869        ] = None,870        mode: str = typer.Option(871            None,872            help="[remote|local|cache]"873        ),874        user_directory: str = typer.Option(875            None,876            help="user directory"877        ),878):879    valid_commands = [880        "installed",881        "enabled",882        "not-installed",883        "disabled",884        "all",885        "cnr",886        "snapshot",887        "snapshot-list",888    ]889    if arg not in valid_commands:890        typer.echo(f"Invalid command: `show {arg}`", err=True)891        exit(1)892 893    cmd_ctx.set_user_directory(user_directory)894    cmd_ctx.set_channel_mode(channel, mode)895    if arg == 'snapshot':896        show_snapshot()897    elif arg == 'snapshot-list':898        show_snapshot_list()899    else:900        show_list(arg)901 902 903@app.command("simple-show", help="Show node list (simple mode)")904def simple_show(905        arg: str = typer.Argument(906            help="[installed|enabled|not-installed|disabled|all|snapshot|snapshot-list]"907        ),908        channel: Annotated[909            str,910            typer.Option(911                show_default=False,912                help="Specify the operation mode"913            ),914        ] = None,915        mode: str = typer.Option(916            None,917            help="[remote|local|cache]"918        ),919        user_directory: str = typer.Option(920            None,921            help="user directory"922        ),923):924    valid_commands = [925        "installed",926        "enabled",927        "not-installed",928        "disabled",929        "all",930        "snapshot",931        "snapshot-list",932    ]933    if arg not in valid_commands:934        typer.echo(f"[bold red]Invalid command: `show {arg}`[/bold red]", err=True)935        exit(1)936 937    cmd_ctx.set_user_directory(user_directory)938    cmd_ctx.set_channel_mode(channel, mode)939 940    if arg == 'snapshot':941        show_snapshot(True)942    elif arg == 'snapshot-list':943        show_snapshot_list(True)944    else:945        show_list(arg, True)946 947 948@app.command('cli-only-mode', help="Set whether to use ComfyUI-Manager in CLI-only mode.")949def cli_only_mode(950        mode: str = typer.Argument(951            ..., help="[enable|disable]"952        ),953        user_directory: str = typer.Option(954            None,955            help="user directory"956        )957):958    cmd_ctx.set_user_directory(user_directory)959    cli_mode_flag = os.path.join(cmd_ctx.manager_files_directory, '.enable-cli-only-mode')960 961    if mode.lower() == 'enable':962        with open(cli_mode_flag, 'w'):963            pass964        print("\nINFO: `cli-only-mode` is enabled\n")965    elif mode.lower() == 'disable':966        if os.path.exists(cli_mode_flag):967            os.remove(cli_mode_flag)968        print("\nINFO: `cli-only-mode` is disabled\n")969    else:970        print(f"\n[bold red]Invalid value for cli-only-mode: {mode}[/bold red]\n")971        exit(1)972 973 974@app.command(975    "deps-in-workflow", help="Generate dependencies file from workflow (.json/.png)"976)977def deps_in_workflow(978        workflow: Annotated[979            str, typer.Option(show_default=False, help="Workflow file (.json/.png)")980        ],981        output: Annotated[982            str, typer.Option(show_default=False, help="Output file (.json)")983        ],984        channel: Annotated[985            str,986            typer.Option(987                show_default=False,988                help="Specify the operation mode"989            ),990        ] = None,991        mode: str = typer.Option(992            None,993            help="[remote|local|cache]"994        ),995        user_directory: str = typer.Option(996            None,997            help="user directory"998        )999):1000    cmd_ctx.set_user_directory(user_directory)1001    cmd_ctx.set_channel_mode(channel, mode)1002 1003    input_path = workflow1004    output_path = output1005 1006    if not os.path.exists(input_path):1007        print(f"[bold red]File not found: {input_path}[/bold red]")1008        exit(1)1009 1010    used_exts, unknown_nodes = asyncio.run(core.extract_nodes_from_workflow(input_path, mode=cmd_ctx.mode, channel_url=cmd_ctx.channel))1011 1012    custom_nodes = {}1013    for x in used_exts:1014        custom_nodes[x] = {'state': core.simple_check_custom_node(x),1015                           'hash': '-'1016                           }1017 1018    res = {1019        'custom_nodes': custom_nodes,1020        'unknown_nodes': list(unknown_nodes)1021    }1022 1023    with open(output_path, "w", encoding='utf-8') as output_file:1024        json.dump(res, output_file, indent=4)1025 1026    print(f"Workflow dependencies are being saved into {output_path}.")1027 1028 1029@app.command("save-snapshot", help="Save a snapshot of the current ComfyUI environment. If output path isn't provided. Save to ComfyUI-Manager/snapshots path.")1030def save_snapshot(1031        output: Annotated[1032            str,1033            typer.Option(1034                show_default=False, help="Specify the output file path. (.json/.yaml)"1035            ),1036        ] = None,1037        user_directory: str = typer.Option(1038            None,1039            help="user directory"1040        ),1041        full_snapshot: Annotated[1042            bool,1043            typer.Option(1044                show_default=False, help="If the snapshot should include custom node, ComfyUI version and pip versions (default), or only custom node details"1045            ),1046        ] = True,1047):1048    cmd_ctx.set_user_directory(user_directory)1049 1050    if output is None:1051        print("[bold red]ERROR: missing output path[/bold red]")1052        raise typer.Exit(code=1)1053        1054    if(not output.endswith('.json') and not output.endswith('.yaml')):1055        print("[bold red]ERROR: output path should be either '.json' or '.yaml' file.[/bold red]")1056        raise typer.Exit(code=1)1057    1058    dir_path = os.path.dirname(output)1059    if(dir_path != '' and not os.path.exists(dir_path)):1060        print(f"[bold red]ERROR: {output} path not exists.[/bold red]")1061        raise typer.Exit(code=1)1062        1063    path = asyncio.run(core.save_snapshot_with_postfix('snapshot', output, not full_snapshot))1064    print(f"Current snapshot is saved as `{path}`")1065 1066 1067@app.command("restore-snapshot", help="Restore snapshot from snapshot file")1068def restore_snapshot(1069        snapshot_name: str, 1070        pip_non_url: Optional[bool] = typer.Option(1071            default=None,1072            show_default=False,1073            is_flag=True,1074            help="Restore for pip packages registered on PyPI.",1075        ),1076        pip_non_local_url: Optional[bool] = typer.Option(1077            default=None,1078            show_default=False,1079            is_flag=True,1080            help="Restore for pip packages registered at web URLs.",1081        ),1082        pip_local_url: Optional[bool] = typer.Option(1083            default=None,1084            show_default=False,1085            is_flag=True,1086            help="Restore for pip packages specified by local paths.",1087        ),1088        user_directory: str = typer.Option(1089            None,1090            help="user directory"1091        ),1092        restore_to: Optional[str] = typer.Option(1093            None,1094            help="Manually specify the installation path for the custom node. Ignore user directory."1095        )1096):1097    cmd_ctx.set_user_directory(user_directory)1098 1099    if restore_to:1100        cmd_ctx.update_custom_nodes_dir(restore_to)1101 1102    extras = []1103    if pip_non_url:1104        extras.append('--pip-non-url')1105 1106    if pip_non_local_url:1107        extras.append('--pip-non-local-url')1108 1109    if pip_local_url:1110        extras.append('--pip-local-url')1111 1112    print(f"PIPs restore mode: {extras}")1113 1114    if os.path.exists(snapshot_name):1115        snapshot_path = os.path.abspath(snapshot_name)1116    else:1117        snapshot_path = os.path.join(cmd_ctx.get_snapshot_path(), snapshot_name)1118        if not os.path.exists(snapshot_path):1119            print(f"[bold red]ERROR: `{snapshot_path}` is not exists.[/bold red]")1120            exit(1)1121 1122    pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)1123    try:1124        asyncio.run(core.restore_snapshot(snapshot_path, extras))1125    except Exception:1126        print("[bold red]ERROR: Failed to restore snapshot.[/bold red]")1127        traceback.print_exc()1128        raise typer.Exit(code=1)1129    pip_fixer.fix_broken()1130 1131 1132@app.command(1133    "restore-dependencies", help="Restore dependencies from whole installed custom nodes."1134)1135def restore_dependencies(1136        user_directory: str = typer.Option(1137            None,1138            help="user directory"1139        )1140):1141    cmd_ctx.set_user_directory(user_directory)1142 1143    node_paths = []1144 1145    for base_path in cmd_ctx.get_custom_nodes_paths():1146        for name in os.listdir(base_path):1147            target = os.path.join(base_path, name)1148            if os.path.isdir(target) and not name.endswith('.disabled'):1149                node_paths.append(target)1150 1151    total = len(node_paths)1152    i = 11153 1154    pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)1155    for x in node_paths:1156        print("----------------------------------------------------------------------------------------------------")1157        print(f"Restoring [{i}/{total}]: {x}")1158        unified_manager.execute_install_script('', x, instant_execution=True)1159        i += 11160    pip_fixer.fix_broken()1161 1162 1163@app.command(1164    "post-install", help="Install dependencies and execute installation script"1165)1166def post_install(1167        path: str = typer.Argument(1168            help="path to custom node",1169        )1170):1171    path = os.path.expanduser(path)1172 1173    pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)1174    unified_manager.execute_install_script('', path, instant_execution=True)1175    pip_fixer.fix_broken()1176 1177 1178@app.command(1179    "install-deps",1180    help="Install dependencies from dependencies file(.json) or workflow(.png/.json)",1181)1182def install_deps(1183        deps: str = typer.Argument(1184            help="Dependency spec file (.json)",1185        ),1186        channel: Annotated[1187            str,1188            typer.Option(1189                show_default=False,1190                help="Specify the operation mode"1191            ),1192        ] = None,1193        mode: str = typer.Option(1194            None,1195            help="[remote|local|cache]"1196        ),1197        user_directory: str = typer.Option(1198            None,1199            help="user directory"1200        ),

Showing the first 1,200 of 1280 lines. Download the file for the rest.