Skip to content

Commit 88530dc

Browse files
committed
feat(manager): add ComfyUI-Manager v4 support
ComfyUI-Manager v4 is now installed as a pip package (via manager_requirements.txt) rather than being git-cloned into custom_nodes/. Breaking Changes: - Remove --manager-url and --manager-commit options from install command - validate_comfyui_manager() now uses find_cm_cli() instead of git path check Manager Subcommand Changes: - Add 'disable' command: disable manager completely (no flags passed) - Add 'enable-gui' command: enable manager with new GUI - Add 'disable-gui' command: enable manager but disable GUI only - Add 'enable-legacy-gui' command: enable manager with legacy GUI - Add 'migrate-legacy' command: migrate git-cloned manager to pip package Implementation: - find_cm_cli() uses importlib.util.find_spec("cm_cli") with @lru_cache - execute_cm_cli() uses module-based execution (python -m cm_cli) - _get_manager_flags() injects flags based on config mode - Add 34 tests covering manager v4 scenarios
1 parent d76c4a2 commit 88530dc

10 files changed

Lines changed: 1124 additions & 107 deletions

File tree

DEV_README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,23 +124,28 @@ def remove(name: str):
124124
- For progress reporting, use either [`rich.progress`](https://rich.readthedocs.io/en/stable/progress.html)
125125

126126
## Develop comfy-cli and ComfyUI-Manager (cm-cli) together
127+
128+
ComfyUI-Manager is now installed as a pip package (via `manager_requirements.txt`
129+
in the ComfyUI root) rather than being git-cloned into `custom_nodes/`.
130+
127131
### Making changes to both
128-
1. Fork your own branches of `comfy-cli` and `ComfyUI-Manager`, make changes
129-
2. Be sure to commit any changes to `ComfyUI-Manager` to a new branch, and push to remote
132+
1. Fork your own branches of `comfy-cli` and `ComfyUI-Manager`, make changes.
133+
2. Live-install `comfy-cli`:
134+
- `pip install -e /path/to/comfy-cli`
135+
3. Live-install your fork of `ComfyUI-Manager` in editable mode:
136+
- `pip install -e /path/to/ComfyUI-Manager`
137+
4. This makes the `cm-cli` entry point available and points it at your local source.
130138

131139
### Trying changes to both
132-
1. clone the changed branch of `comfy-cli`, then live install `comfy-cli`:
133-
- `pip install -e comfy-cli`
140+
1. Install both packages in editable mode as described above.
134141
2. Go to a test dir and run:
135-
- `comfy --here install --manager-url=<path-or-url-to-fork-of-ComfyUI-Manager>`
136-
3. Run:
137-
- `cd ComfyUI/custom_nodes/ComfyUI-Manager/ && git checkout <changed-branch> && cd -`
138-
4. Further changes can be pulled into these copies of the `comfy-cli` and `ComfyUI-Manager` repos
142+
- `comfy --here install`
143+
3. The `cm-cli` command will resolve to your locally installed editable package.
139144

140145
### Debugging both simultaneously
141-
1. Follow instructions above to get working install with changes
146+
1. Follow instructions above to get working install with changes.
142147
2. Add breakpoints directly to code: `import ipdb; ipdb.set_trace()`
143-
3. Execute relevant `comfy-cli` command
148+
3. Execute relevant `comfy-cli` command.
144149

145150

146151
## Contact

comfy_cli/cmdline.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,6 @@ def install(
159159
callback=validate_version,
160160
),
161161
] = "nightly",
162-
manager_url: Annotated[
163-
str,
164-
typer.Option(
165-
show_default=False,
166-
help="url or local path pointing to the ComfyUI-Manager git repo to be installed. A specific branch can optionally be specified using a setuptools-like syntax, eg https://foo.git@bar",
167-
),
168-
] = constants.COMFY_MANAGER_GITHUB_URL,
169162
restore: Annotated[
170163
bool,
171164
typer.Option(
@@ -237,10 +230,6 @@ def install(
237230
help="Use new fast dependency installer",
238231
),
239232
] = False,
240-
manager_commit: Annotated[
241-
str | None,
242-
typer.Option(help="Specify commit hash for ComfyUI-Manager"),
243-
] = None,
244233
pr: Annotated[
245234
str | None,
246235
typer.Option(
@@ -273,7 +262,6 @@ def install(
273262
rprint("[bold yellow]Installing for CPU[/bold yellow]")
274263
install_inner.execute(
275264
url,
276-
manager_url,
277265
comfy_path,
278266
restore,
279267
skip_manager,
@@ -285,7 +273,6 @@ def install(
285273
skip_torch_or_directml=skip_torch_or_directml,
286274
skip_requirement=skip_requirement,
287275
fast_deps=fast_deps,
288-
manager_commit=manager_commit,
289276
)
290277
rprint(f"ComfyUI is installed at: {comfy_path}")
291278
return None
@@ -331,7 +318,6 @@ def install(
331318

332319
install_inner.execute(
333320
url,
334-
manager_url,
335321
comfy_path,
336322
restore,
337323
skip_manager,
@@ -343,7 +329,6 @@ def install(
343329
skip_torch_or_directml=skip_torch_or_directml,
344330
skip_requirement=skip_requirement,
345331
fast_deps=fast_deps,
346-
manager_commit=manager_commit,
347332
pr=pr,
348333
)
349334

comfy_cli/command/custom_nodes/cm_cli_util.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

3+
import importlib.util
34
import os
45
import subprocess
56
import sys
67
import uuid
8+
from functools import lru_cache
79

810
import typer
911
from rich import print
@@ -21,6 +23,21 @@
2123
}
2224

2325

26+
@lru_cache(maxsize=1)
27+
def find_cm_cli() -> bool:
28+
"""Check if cm_cli module is available in the current Python environment.
29+
30+
Only checks the currently activated Python environment.
31+
Does NOT fallback to PATH lookup to avoid using cm-cli from different environments.
32+
33+
Results are cached for the session lifetime.
34+
35+
Returns:
36+
True if cm_cli module is importable, False otherwise.
37+
"""
38+
return importlib.util.find_spec("cm_cli") is not None
39+
40+
2441
def execute_cm_cli(args, channel=None, fast_deps=False, no_deps=False, mode=None, raise_on_error=False) -> str | None:
2542
_config_manager = ConfigManager()
2643

@@ -30,15 +47,14 @@ def execute_cm_cli(args, channel=None, fast_deps=False, no_deps=False, mode=None
3047
print("\n[bold red]ComfyUI path is not resolved.[/bold red]\n", file=sys.stderr)
3148
raise typer.Exit(code=1)
3249

33-
cm_cli_path = os.path.join(workspace_path, "custom_nodes", "ComfyUI-Manager", "cm-cli.py")
34-
if not os.path.exists(cm_cli_path):
50+
if not find_cm_cli():
3551
print(
36-
f"\n[bold red]ComfyUI-Manager not found: {cm_cli_path}[/bold red]\n",
52+
"\n[bold red]ComfyUI-Manager not found. 'cm-cli' command is not available.[/bold red]\n",
3753
file=sys.stderr,
3854
)
3955
raise typer.Exit(code=1)
4056

41-
cmd = [sys.executable, cm_cli_path] + args
57+
cmd = [sys.executable, "-m", "cm_cli"] + args
4258

4359
if channel is not None:
4460
cmd += ["--channel", channel]

comfy_cli/command/custom_nodes/command.py

Lines changed: 166 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import pathlib
33
import platform
4+
import shutil
45
import subprocess
56
import sys
67
import uuid
@@ -11,9 +12,9 @@
1112
from rich import print
1213
from rich.console import Console
1314

14-
from comfy_cli import logging, tracking, ui, utils
15+
from comfy_cli import constants, logging, tracking, ui, utils
1516
from comfy_cli.command.custom_nodes.bisect_custom_nodes import bisect_app
16-
from comfy_cli.command.custom_nodes.cm_cli_util import execute_cm_cli
17+
from comfy_cli.command.custom_nodes.cm_cli_util import execute_cm_cli, find_cm_cli
1718
from comfy_cli.config_manager import ConfigManager
1819
from comfy_cli.constants import NODE_ZIP_FILENAME
1920
from comfy_cli.file_utils import (
@@ -48,21 +49,9 @@ class ShowTarget(str, Enum):
4849
SNAPSHOT_LIST = "snapshot-list"
4950

5051

51-
def validate_comfyui_manager(_env_checker):
52-
manager_path = _env_checker.get_comfyui_manager_path()
53-
54-
if manager_path is None:
55-
print("[bold red]If ComfyUI is not installed, this feature cannot be used.[/bold red]")
56-
raise typer.Exit(code=1)
57-
elif not os.path.exists(manager_path):
58-
print(
59-
f"[bold red]If ComfyUI-Manager is not installed, this feature cannot be used.[/bold red] \\[{manager_path}]"
60-
)
61-
raise typer.Exit(code=1)
62-
elif not os.path.exists(os.path.join(manager_path, ".git")):
63-
print(
64-
f"[bold red]The ComfyUI-Manager installation is invalid. This feature cannot be used.[/bold red] \\[{manager_path}]"
65-
)
52+
def validate_comfyui_manager(_env_checker=None):
53+
if not find_cm_cli():
54+
print("[bold red]ComfyUI-Manager is not installed. 'cm-cli' command is not available.[/bold red]")
6655
raise typer.Exit(code=1)
6756

6857

@@ -234,16 +223,168 @@ def restore_dependencies():
234223
execute_cm_cli(["restore-dependencies"])
235224

236225

237-
@manager_app.command("disable-gui", help="Disable GUI mode of ComfyUI-Manager")
226+
@manager_app.command("disable", help="Disable ComfyUI-Manager completely")
238227
@tracking.track_command("node")
239-
def disable_gui():
240-
execute_cm_cli(["cli-only-mode", "enable"])
228+
def disable_manager():
229+
"""Disable ComfyUI-Manager. No manager flags will be passed to ComfyUI."""
230+
config_manager = ConfigManager()
231+
config_manager.set(constants.CONFIG_KEY_MANAGER_GUI_MODE, "disable")
232+
print("[bold yellow]ComfyUI-Manager has been disabled.[/bold yellow]")
233+
print("No manager flags will be passed to ComfyUI on next launch.")
241234

242235

243-
@manager_app.command("enable-gui", help="Enable GUI mode of ComfyUI-Manager")
236+
@manager_app.command("enable-gui", help="Enable ComfyUI-Manager with new GUI")
244237
@tracking.track_command("node")
245238
def enable_gui():
246-
execute_cm_cli(["cli-only-mode", "disable"])
239+
"""Enable ComfyUI-Manager with new GUI."""
240+
config_manager = ConfigManager()
241+
config_manager.set(constants.CONFIG_KEY_MANAGER_GUI_MODE, "enable-gui")
242+
print("[bold green]ComfyUI-Manager GUI has been enabled.[/bold green]")
243+
print("[dim]ComfyUI will launch with: --enable-manager[/dim]")
244+
245+
246+
@manager_app.command("disable-gui", help="Enable ComfyUI-Manager without GUI")
247+
@tracking.track_command("node")
248+
def disable_gui():
249+
"""Enable ComfyUI-Manager but disable its GUI."""
250+
config_manager = ConfigManager()
251+
config_manager.set(constants.CONFIG_KEY_MANAGER_GUI_MODE, "disable-gui")
252+
print("[bold green]ComfyUI-Manager enabled with GUI disabled.[/bold green]")
253+
print("[dim]ComfyUI will launch with: --enable-manager --disable-manager-ui[/dim]")
254+
255+
256+
@manager_app.command("enable-legacy-gui", help="Enable ComfyUI-Manager with legacy GUI")
257+
@tracking.track_command("node")
258+
def enable_legacy_gui():
259+
"""Enable ComfyUI-Manager with legacy GUI."""
260+
config_manager = ConfigManager()
261+
config_manager.set(constants.CONFIG_KEY_MANAGER_GUI_MODE, "enable-legacy-gui")
262+
print("[bold green]ComfyUI-Manager legacy GUI has been enabled.[/bold green]")
263+
print("[dim]ComfyUI will launch with: --enable-manager --enable-manager-legacy-ui[/dim]")
264+
265+
266+
@manager_app.command("migrate-legacy", help="Migrate legacy git-cloned ComfyUI-Manager to .disabled")
267+
@tracking.track_command("node")
268+
def migrate_legacy(
269+
yes: Annotated[
270+
bool,
271+
typer.Option("--yes", "-y", help="Skip confirmation prompt"),
272+
] = False,
273+
):
274+
"""
275+
Migrate legacy ComfyUI-Manager from custom_nodes/ to custom_nodes/.disabled/
276+
277+
Detects .enable-cli-only-mode file to set appropriate mode:
278+
- If .enable-cli-only-mode exists → mode = disable
279+
- Otherwise → mode = enable-gui
280+
"""
281+
if not workspace_manager.workspace_path:
282+
print("[bold red]ComfyUI workspace is not set.[/bold red]")
283+
print("[dim]Use --workspace or run from a ComfyUI directory.[/dim]")
284+
raise typer.Exit(code=1)
285+
286+
custom_nodes_path = pathlib.Path(workspace_manager.workspace_path) / "custom_nodes"
287+
288+
# Find legacy manager with case-insensitive matching (must be a real directory, not symlink)
289+
legacy_manager_path = None
290+
if custom_nodes_path.exists():
291+
for item in custom_nodes_path.iterdir():
292+
if item.is_dir() and not item.is_symlink() and item.name.lower() == "comfyui-manager":
293+
legacy_manager_path = item
294+
break
295+
296+
# Check if legacy manager exists
297+
if legacy_manager_path is None:
298+
print("[bold yellow]No legacy ComfyUI-Manager found in custom_nodes/[/bold yellow]")
299+
print("Nothing to migrate.")
300+
return
301+
302+
# Verify it's a git-cloned repository
303+
git_dir = legacy_manager_path / ".git"
304+
if not git_dir.exists():
305+
print(f"[bold yellow]Warning: {legacy_manager_path.name} does not appear to be a git repository.[/bold yellow]")
306+
print("[dim]Expected a git-cloned ComfyUI-Manager. Skipping migration.[/dim]")
307+
return
308+
309+
# Detect CLI-only mode before any changes
310+
cli_only_mode_file = legacy_manager_path / ".enable-cli-only-mode"
311+
cli_only_mode = cli_only_mode_file.exists()
312+
313+
# Show what will happen and ask for confirmation
314+
print(f"[bold]Found legacy ComfyUI-Manager:[/bold] {legacy_manager_path}")
315+
print(f"[dim]CLI-only mode: {cli_only_mode}[/dim]")
316+
print()
317+
print("[bold]This will:[/bold]")
318+
print(f" 1. Move {legacy_manager_path.name} to custom_nodes/.disabled/")
319+
print(f" 2. Set manager mode to: {'disable' if cli_only_mode else 'enable-gui'}")
320+
print(" 3. Install manager_requirements.txt (if present)")
321+
print()
322+
323+
if not yes:
324+
confirm = ui.prompt_confirm_action("Proceed with migration?", False)
325+
if not confirm:
326+
print("[dim]Migration cancelled.[/dim]")
327+
return
328+
329+
# Create .disabled directory
330+
disabled_path = custom_nodes_path / ".disabled"
331+
disabled_path.mkdir(exist_ok=True)
332+
333+
# Check if target already exists (case-insensitive)
334+
existing_target = None
335+
for item in disabled_path.iterdir():
336+
if item.is_dir() and item.name.lower() == "comfyui-manager":
337+
existing_target = item
338+
break
339+
340+
if existing_target is not None:
341+
print(f"[bold red]Target path already exists: {existing_target}[/bold red]")
342+
print("Please remove it manually and try again.")
343+
raise typer.Exit(code=1)
344+
345+
# Move legacy manager (preserve original directory name)
346+
target_path = disabled_path / legacy_manager_path.name
347+
try:
348+
shutil.move(str(legacy_manager_path), str(target_path))
349+
except OSError as e:
350+
print(f"[bold red]Failed to move legacy manager: {e}[/bold red]")
351+
raise typer.Exit(code=1)
352+
353+
# Install manager_requirements.txt if present
354+
workspace_path = pathlib.Path(workspace_manager.workspace_path)
355+
manager_req_path = workspace_path / constants.MANAGER_REQUIREMENTS_FILE
356+
install_success = False # Default to failure, set True only on success
357+
if manager_req_path.exists():
358+
print("[dim]Installing ComfyUI-Manager dependencies...[/dim]")
359+
result = subprocess.run(
360+
[sys.executable, "-m", "pip", "install", "-r", str(manager_req_path)],
361+
check=False,
362+
)
363+
if result.returncode != 0:
364+
print("[bold yellow]Warning: Failed to install ComfyUI-Manager dependencies.[/bold yellow]")
365+
print("[dim]You may need to run: pip install -r manager_requirements.txt[/dim]")
366+
else:
367+
install_success = True
368+
else:
369+
print("[bold yellow]Warning: manager_requirements.txt not found (older ComfyUI version?).[/bold yellow]")
370+
print("[dim]ComfyUI-Manager pip package not installed.[/dim]")
371+
372+
# Set config mode
373+
config_manager = ConfigManager()
374+
if cli_only_mode or not install_success:
375+
config_manager.set(constants.CONFIG_KEY_MANAGER_GUI_MODE, "disable")
376+
print("[bold green]Legacy ComfyUI-Manager migrated to .disabled/[/bold green]")
377+
if cli_only_mode:
378+
print("[dim]Detected .enable-cli-only-mode → Manager set to: disable[/dim]")
379+
else:
380+
print("[dim]Manager installation failed → Manager set to: disable[/dim]")
381+
print("[dim]After fixing installation, run: comfy manager enable-gui[/dim]")
382+
else:
383+
config_manager.set(constants.CONFIG_KEY_MANAGER_GUI_MODE, "enable-gui")
384+
print("[bold green]Legacy ComfyUI-Manager migrated to .disabled/[/bold green]")
385+
print("[dim]Manager set to: enable-gui (new GUI)[/dim]")
386+
387+
print("\n[bold]The new pip-installed ComfyUI-Manager will be used on next launch.[/bold]")
247388

248389

249390
@manager_app.command(help="Clear reserved startup action in ComfyUI-Manager")
@@ -479,14 +620,15 @@ def update_node_id_cache():
479620
config_manager = ConfigManager()
480621
workspace_path = workspace_manager.workspace_path
481622

482-
cm_cli_path = os.path.join(workspace_path, "custom_nodes", "ComfyUI-Manager", "cm-cli.py")
623+
if not find_cm_cli():
624+
raise FileNotFoundError("cm-cli not found")
483625

484626
tmp_path = os.path.join(config_manager.get_config_path(), "tmp")
485627
if not os.path.exists(tmp_path):
486628
os.makedirs(tmp_path)
487629

488630
cache_path = os.path.join(tmp_path, "node-cache.list")
489-
cmd = [sys.executable, cm_cli_path, "export-custom-node-ids", cache_path]
631+
cmd = [sys.executable, "-m", "cm_cli", "export-custom-node-ids", cache_path]
490632

491633
new_env = os.environ.copy()
492634
new_env["COMFYUI_PATH"] = workspace_path

0 commit comments

Comments
 (0)