diff --git a/comfy_cli/cmdline.py b/comfy_cli/cmdline.py index cb9f5f4..7d3a32e 100644 --- a/comfy_cli/cmdline.py +++ b/comfy_cli/cmdline.py @@ -19,7 +19,7 @@ from comfy_cli.command.models import models as models_command from comfy_cli.config_manager import ConfigManager from comfy_cli.env_checker import EnvChecker, check_comfy_server_running -from comfy_cli.workspace_manager import WorkspaceManager +from comfy_cli.workspace_manager import WorkspaceManager, check_comfy_repo logging.setup_logging() app = typer.Typer() @@ -90,7 +90,6 @@ def entry( @app.command(help="Download and install ComfyUI and ComfyUI-Manager") @tracking.track_command() def install( - ctx: typer.Context, url: Annotated[str, typer.Option(show_default=False)] = constants.COMFY_GITHUB_URL, manager_url: Annotated[ str, typer.Option(show_default=False) @@ -110,10 +109,22 @@ def install( ): checker = EnvChecker() - if workspace_manager.workspace_path is None: - workspace_path = utils.get_not_user_set_default_workspace() - else: - workspace_path = workspace_manager.workspace_path + comfy_path = workspace_manager.get_specified_workspace() + + if comfy_path is None: + comfy_path = utils.get_not_user_set_default_workspace() + + is_comfy_path, repo_dir = check_comfy_repo(comfy_path) + if is_comfy_path and not restore: + typer.echo( + f"[bold red]ComfyUI is already installed at the specified path:[/bold red] {comfy_path}\n" + f"[bold yellow]If you want to restore dependencies, add the '--restore' option.[/bold yellow]", + err=True, + ) + raise typer.Exit(code=1) + + if repo_dir is not None: + comfy_path = repo_dir.working_dir if checker.python_version.major < 3: print( @@ -130,23 +141,39 @@ def install( install_inner.execute( url, manager_url, - workspace_path, + comfy_path, restore, skip_manager, torch_mode, commit=commit, ) - workspace_manager.set_recent_workspace(workspace_path) + + print(f"ComfyUI is installed at: {comfy_path}") -def update(self): +@app.command(help="Stop background ComfyUI") +@tracking.track_command() +def update(target: str = typer.Argument("comfy", help="[all|comfy]")): + if target not in ["all", "comfy"]: + typer.echo( + f"Invalid target: {target}. Allowed targets are 'all', 'comfy'.", + err=True, + ) + raise typer.Exit(code=1) + _env_checker = EnvChecker() - print(f"Updating ComfyUI in {self.workspace}...") - os.chdir(self.workspace) - subprocess.run(["git", "pull"], check=True) - subprocess.run( - [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], check=True - ) + comfy_path = workspace_manager.workspace_path + + if "all" == target: + custom_nodes.command.execute_cm_cli(["update", "all"]) + else: + print(f"Updating ComfyUI in {comfy_path}...") + os.chdir(comfy_path) + subprocess.run(["git", "pull"], check=True) + subprocess.run( + [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], + check=True, + ) # @app.command(help="Run workflow file") @@ -165,12 +192,10 @@ def validate_comfyui(_env_checker): raise typer.Exit(code=1) -def launch_comfyui(_env_checker, _config_manager, extra, background=False): - validate_comfyui(_env_checker) - +def launch_comfyui(extra, background=False): if background: - if _config_manager.background is not None and utils.is_running( - _config_manager.background[2] + if ConfigManager().background is not None and utils.is_running( + ConfigManager().background[2] ): print( f"[bold red]ComfyUI is already running in background.\nYou cannot start more than one background service.[/bold red]\n" @@ -200,7 +225,7 @@ def launch_comfyui(_env_checker, _config_manager, extra, background=False): cmd = [ "comfy", - f'--workspace={os.path.join(os.getcwd(), "..")}', + f"--workspace={os.path.abspath(os.getcwd())}", "launch", ] + extra @@ -210,20 +235,20 @@ def launch_comfyui(_env_checker, _config_manager, extra, background=False): print( f"[bold yellow]Run ComfyUI in the background.[/bold yellow] ({listen}:{port})" ) - _config_manager.config["DEFAULT"][ + ConfigManager().config["DEFAULT"][ constants.CONFIG_KEY_BACKGROUND ] = f"{(listen, port, process.pid)}" - _config_manager.write_config() + ConfigManager().write_config() return - env_path = _env_checker.get_isolated_env() + env_path = EnvChecker().get_isolated_env() reboot_path = None new_env = os.environ.copy() if env_path is not None: session_path = os.path.join( - _config_manager.get_config_path(), "tmp", str(uuid.uuid4()) + ConfigManager().get_config_path(), "tmp", str(uuid.uuid4()) ) new_env["__COMFY_CLI_SESSION__"] = session_path @@ -242,35 +267,30 @@ def launch_comfyui(_env_checker, _config_manager, extra, background=False): @app.command(help="Stop background ComfyUI") +@tracking.track_command() def stop(): - _config_manager = ConfigManager() - - if constants.CONFIG_KEY_BACKGROUND not in _config_manager.config["DEFAULT"]: + if constants.CONFIG_KEY_BACKGROUND not in ConfigManager().config["DEFAULT"]: print(f"[bold red]No ComfyUI is running in the background.[/bold red]\n") raise typer.Exit(code=1) - bg_info = _config_manager.background + bg_info = ConfigManager().background is_killed = utils.kill_all(bg_info[2]) print( f"[bold yellow]Background ComfyUI is stopped.[/bold yellow] ({bg_info[0]}:{bg_info[1]})" ) - _config_manager.remove_background() + ConfigManager().remove_background() @app.command(help="Launch ComfyUI: ?[--background] ?[-- ]") @tracking.track_command() def launch( - ctx: typer.Context, background: Annotated[ bool, typer.Option(help="Launch ComfyUI in background") ] = False, extra: List[str] = typer.Argument(None), ): - _env_checker = EnvChecker() - _config_manager = ConfigManager() - resolved_workspace = workspace_manager.get_workspace_path() if not resolved_workspace: print( @@ -281,32 +301,33 @@ def launch( print(f"\nLaunching ComfyUI from: {resolved_workspace}\n") - os.chdir(resolved_workspace + "/ComfyUI") - _env_checker.check() # update environment checks - # Update the recent workspace workspace_manager.set_recent_workspace(resolved_workspace) - launch_comfyui(_env_checker, _config_manager, extra, background=background) + os.chdir(resolved_workspace) + launch_comfyui(extra, background=background) -@app.command("set-default", help="Set default workspace") +@app.command("set-default", help="Set default ComfyUI path") @tracking.track_command() def set_default(workspace_path: str): - workspace_path = os.path.expanduser(workspace_path) - comfy_path = os.path.join(workspace_path, "ComfyUI") + comfy_path = os.path.expanduser(workspace_path) + if not os.path.exists(comfy_path): - print( - f"Invalid workspace path: {workspace_path}\nThe workspace path must contain 'ComfyUI'." - ) + print(f"Path not found: {comfy_path}.") + raise typer.Exit(code=1) + + if not check_comfy_repo(comfy_path)[0]: + print(f"Specified path is not a ComfyUI path: {comfy_path}.") raise typer.Exit(code=1) - workspace_manager.set_default_workspace(workspace_path) + print(f"Specified path is set as default ComfyUI path: {comfy_path} ") + workspace_manager.set_default_workspace(comfy_path) @app.command(help="Show which ComfyUI is selected.") @tracking.track_command() -def which(ctx: typer.Context): +def which(): comfy_path = workspace_manager.workspace_path if comfy_path is None: print( diff --git a/comfy_cli/command/custom_nodes/command.py b/comfy_cli/command/custom_nodes/command.py index c99cb19..5fb3198 100644 --- a/comfy_cli/command/custom_nodes/command.py +++ b/comfy_cli/command/custom_nodes/command.py @@ -23,8 +23,7 @@ workspace_manager = WorkspaceManager() -# TODO: remove ctx as an args -def execute_cm_cli(_ctx: typer.Context, args, channel=None, mode=None): +def execute_cm_cli(args, channel=None, mode=None): _config_manager = ConfigManager() workspace_path = workspace_manager.workspace_path @@ -83,7 +82,6 @@ def validate_comfyui_manager(_env_checker): @app.command("save-snapshot", help="Save a snapshot of the current ComfyUI environment") @tracking.track_command("node") def save_snapshot( - ctx: typer.Context, output: Annotated[ str, "--output", @@ -93,48 +91,47 @@ def save_snapshot( ] = None, ): if output is None: - execute_cm_cli(ctx, ["save-snapshot"]) + execute_cm_cli(["save-snapshot"]) else: output = os.path.abspath(output) # to compensate chdir - execute_cm_cli(ctx, ["save-snapshot", "--output", output]) + execute_cm_cli(["save-snapshot", "--output", output]) @app.command("restore-snapshot") @tracking.track_command("node") -def restore_snapshot(ctx: typer.Context, path: str): +def restore_snapshot(path: str): path = os.path.abspath(path) - execute_cm_cli(ctx, ["restore-snapshot", path]) + execute_cm_cli(["restore-snapshot", path]) @app.command("restore-dependencies") @tracking.track_command("node") -def restore_dependencies(ctx: typer.Context): - execute_cm_cli(ctx, ["restore-dependencies"]) +def restore_dependencies(): + execute_cm_cli(["restore-dependencies"]) @manager_app.command("disable-gui") @tracking.track_command("node") -def disable_gui(ctx: typer.Context): - execute_cm_cli(ctx, ["cli-only-mode", "enable"]) +def disable_gui(): + execute_cm_cli(["cli-only-mode", "enable"]) @manager_app.command("enable-gui") @tracking.track_command("node") -def enable_gui(ctx: typer.Context): - execute_cm_cli(ctx, ["cli-only-mode", "disable"]) +def enable_gui(): + execute_cm_cli(["cli-only-mode", "disable"]) @manager_app.command() @tracking.track_command("node") -def clear(ctx: typer.Context, path: str): +def clear(path: str): path = os.path.abspath(path) - execute_cm_cli(ctx, ["clear", path]) + execute_cm_cli(["clear", path]) @app.command() @tracking.track_command("node") def show( - ctx: typer.Context, args: List[str] = typer.Argument( ..., help="[installed|enabled|not-installed|disabled|all|snapshot|snapshot-list]", @@ -169,13 +166,12 @@ def show( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["show"] + args, channel, mode) + execute_cm_cli(["show"] + args, channel, mode) @app.command("simple-show") @tracking.track_command("node") def simple_show( - ctx: typer.Context, args: List[str] = typer.Argument( ..., help="[installed|enabled|not-installed|disabled|all|snapshot|snapshot-list]", @@ -210,14 +206,13 @@ def simple_show( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["simple-show"] + args, channel, mode) + execute_cm_cli(["simple-show"] + args, channel, mode) # install, reinstall, uninstall @app.command() @tracking.track_command("node") def install( - ctx: typer.Context, args: List[str] = typer.Argument(..., help="install custom nodes"), channel: Annotated[ str, @@ -240,13 +235,12 @@ def install( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["install"] + args, channel, mode) + execute_cm_cli(["install"] + args, channel, mode) @app.command() @tracking.track_command("node") def reinstall( - ctx: typer.Context, args: List[str] = typer.Argument(..., help="reinstall custom nodes"), channel: Annotated[ str, @@ -269,13 +263,12 @@ def reinstall( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["reinstall"] + args, channel, mode) + execute_cm_cli(["reinstall"] + args, channel, mode) @app.command() @tracking.track_command("node") def uninstall( - ctx: typer.Context, args: List[str] = typer.Argument(..., help="uninstall custom nodes"), channel: Annotated[ str, @@ -298,7 +291,7 @@ def uninstall( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["uninstall"] + args, channel, mode) + execute_cm_cli(["uninstall"] + args, channel, mode) # `update, disable, enable, fix` allows `all` param @@ -307,7 +300,6 @@ def uninstall( @app.command() @tracking.track_command("node") def update( - ctx: typer.Context, args: List[str] = typer.Argument(..., help="update custom nodes"), channel: Annotated[ str, @@ -326,13 +318,12 @@ def update( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["update"] + args, channel, mode) + execute_cm_cli(["update"] + args, channel, mode) @app.command() @tracking.track_command("node") def disable( - ctx: typer.Context, args: List[str] = typer.Argument(..., help="disable custom nodes"), channel: Annotated[ str, @@ -351,13 +342,12 @@ def disable( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["disable"] + args, channel, mode) + execute_cm_cli(["disable"] + args, channel, mode) @app.command() @tracking.track_command("node") def enable( - ctx: typer.Context, args: List[str] = typer.Argument(..., help="enable custom nodes"), channel: Annotated[ str, @@ -376,13 +366,12 @@ def enable( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["enable"] + args, channel, mode) + execute_cm_cli(["enable"] + args, channel, mode) @app.command() @tracking.track_command("node") def fix( - ctx: typer.Context, args: List[str] = typer.Argument( ..., help="fix dependencies for specified custom nodes" ), @@ -403,14 +392,12 @@ def fix( ) raise typer.Exit(code=1) - execute_cm_cli(ctx, ["fix"] + args, channel, mode) + execute_cm_cli(["fix"] + args, channel, mode) @app.command("publish", help="Publish node to registry") @tracking.track_command("node") -def publish( - ctx: typer.Context, -): +def publish(): """ Publish a node with optional validation. """ diff --git a/comfy_cli/command/install.py b/comfy_cli/command/install.py index ef7c462..67e2fb0 100644 --- a/comfy_cli/command/install.py +++ b/comfy_cli/command/install.py @@ -2,6 +2,7 @@ import subprocess from rich import print import sys +from comfy_cli.workspace_manager import WorkspaceManager def install_comfyui_dependencies(repo_dir, torch_mode): @@ -45,7 +46,7 @@ def install_manager_dependencies(repo_dir): def execute( url: str, manager_url: str, - comfy_workspace: str, + comfy_path: str, restore: bool, skip_manager: bool, torch_mode=None, @@ -53,13 +54,11 @@ def execute( *args, **kwargs, ): - print(f"Installing from {url}") + print(f"Installing from '{url}' to '{comfy_path}'") - # install ComfyUI - working_dir = os.path.expanduser(comfy_workspace) - repo_dir = os.path.join(working_dir, os.path.basename(url).replace(".git", "")) - repo_dir = os.path.abspath(repo_dir) + repo_dir = comfy_path + # install ComfyUI if os.path.exists(os.path.join(repo_dir, ".git")): if restore or commit is not None: if commit is not None: @@ -73,10 +72,11 @@ def execute( ) else: print("\nInstalling ComfyUI..") - os.makedirs(working_dir, exist_ok=True) + parent_path = os.path.join(repo_dir, "..") + + if not os.path.exists(parent_path): + os.makedirs(parent_path, exist_ok=True) - repo_dir = os.path.join(working_dir, os.path.basename(url).replace(".git", "")) - repo_dir = os.path.abspath(repo_dir) subprocess.run(["git", "clone", url, repo_dir]) # checkout specified commit @@ -86,6 +86,8 @@ def execute( install_comfyui_dependencies(repo_dir, torch_mode) + WorkspaceManager().set_recent_workspace(repo_dir) + print("") # install ComfyUI-Manager diff --git a/comfy_cli/registry/zip.py b/comfy_cli/registry/zip.py index b57ef92..285381c 100644 --- a/comfy_cli/registry/zip.py +++ b/comfy_cli/registry/zip.py @@ -26,4 +26,5 @@ def zip_files(zip_filename): ) -zip_files("node.tar.gz") +# TODO: check this code. this make slow down comfy-cli extremely +# zip_files("node.tar.gz") diff --git a/comfy_cli/workspace_manager.py b/comfy_cli/workspace_manager.py index 201dbcc..bc7d214 100644 --- a/comfy_cli/workspace_manager.py +++ b/comfy_cli/workspace_manager.py @@ -162,6 +162,12 @@ def set_default_workspace(self, path: str): constants.CONFIG_KEY_DEFAULT_WORKSPACE, os.path.abspath(path) ) + def get_specified_workspace(self): + if self.specified_workspace is None: + return None + + return os.path.abspath(os.path.expanduser(self.specified_workspace)) + def get_workspace_path(self) -> str: """ Retrieves the workspace path based on the following precedence: @@ -176,9 +182,10 @@ def get_workspace_path(self) -> str: FileNotFoundError: If no valid workspace is found. """ # Check for explicitly specified workspace first + specified_workspace = self.get_specified_workspace() if self.specified_workspace: - if check_comfy_repo(os.path.expanduser(self.specified_workspace)): - return self.specified_workspace + if check_comfy_repo(specified_workspace): + return specified_workspace print( "[bold red]warn: The specified workspace is not ComfyUI directory.[/bold red]"