From ab0026c57464dd803c821f62fc4904a18f867c3e Mon Sep 17 00:00:00 2001 From: drmbt Date: Fri, 14 Mar 2025 22:42:29 -0700 Subject: [PATCH] feat: add global --verbose flag for cm-cli output This change adds a global --verbose flag that can be used to show detailed output during cm-cli command execution. The flag is propagated through the command chain and is particularly useful for debugging by returning ComfyUI-Manager operations Key changes: - Add --verbose flag to main CLI - Propagate verbose flag through command context - Update ComfyUI-Manager commands to respect verbose flag - Improve real-time output visibility for complex deployment operations --- comfy_cli/cmdline.py | 12 ++++ comfy_cli/command/custom_nodes/cm_cli_util.py | 61 ++++++++++++++---- comfy_cli/command/custom_nodes/command.py | 63 ++++++++++++++----- 3 files changed, 107 insertions(+), 29 deletions(-) diff --git a/comfy_cli/cmdline.py b/comfy_cli/cmdline.py index 247ba95a..0a9a20c6 100644 --- a/comfy_cli/cmdline.py +++ b/comfy_cli/cmdline.py @@ -115,11 +115,23 @@ def entry( help="Print version and exit", is_flag=True, ), + verbose: Annotated[ + bool, + typer.Option( + "--verbose", + help="Show detailed output during execution", + is_flag=True, + ), + ] = False, ): if version: rprint(ConfigManager().get_cli_version()) ctx.exit(0) + # Store verbose flag in context for subcommands + ctx.ensure_object(dict) + ctx.obj["verbose"] = verbose + workspace_manager.setup_workspace_manager(workspace, here, recent, skip_prompt) tracking.prompt_tracking_consent(skip_prompt, default_value=enable_telemetry) diff --git a/comfy_cli/command/custom_nodes/cm_cli_util.py b/comfy_cli/command/custom_nodes/cm_cli_util.py index 78f3370e..babaf91e 100644 --- a/comfy_cli/command/custom_nodes/cm_cli_util.py +++ b/comfy_cli/command/custom_nodes/cm_cli_util.py @@ -21,7 +21,7 @@ } -def execute_cm_cli(args, channel=None, fast_deps=False, mode=None) -> str | None: +def execute_cm_cli(args, channel=None, fast_deps=False, mode=None, verbose=False) -> str | None: _config_manager = ConfigManager() workspace_path = workspace_manager.workspace_path @@ -57,8 +57,51 @@ def execute_cm_cli(args, channel=None, fast_deps=False, mode=None) -> str | None print(f"Execute from: {workspace_path}") try: - result = subprocess.run(cmd, env=new_env, check=True, capture_output=True, text=True) - print(result.stdout) + if verbose: + # Use Popen for real-time output when verbose + process = subprocess.Popen( + cmd, + env=new_env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + universal_newlines=True + ) + + stdout_output = [] + stderr_output = [] + + while True: + stdout_line = process.stdout.readline() + stderr_line = process.stderr.readline() + + if stdout_line: + print(stdout_line.rstrip()) + stdout_output.append(stdout_line) + if stderr_line: + print(stderr_line.rstrip(), file=sys.stderr) + stderr_output.append(stderr_line) + + if process.poll() is not None and not stdout_line and not stderr_line: + break + + return_code = process.wait() + + if return_code != 0: + if return_code == 1: + print(f"\n[bold red]Execution error: {cmd}[/bold red]\n", file=sys.stderr) + return None + if return_code == 2: + return None + raise subprocess.CalledProcessError(return_code, cmd) + + output = ''.join(stdout_output) + else: + # Original behavior when not verbose + result = subprocess.run(cmd, env=new_env, check=True, capture_output=True, text=True) + print(result.stdout) + output = result.stdout if fast_deps and args[0] in _dependency_cmds: # we're using the fast_deps behavior and just ran a command that invalidated the dependencies @@ -66,15 +109,9 @@ def execute_cm_cli(args, channel=None, fast_deps=False, mode=None) -> str | None depComp.compile_deps() depComp.install_deps() - return result.stdout - except subprocess.CalledProcessError as e: - if e.returncode == 1: - print(f"\n[bold red]Execution error: {cmd}[/bold red]\n", file=sys.stderr) - return None - - if e.returncode == 2: - return None - + return output + except Exception as e: + print(f"\n[bold red]Error executing command: {str(e)}[/bold red]\n", file=sys.stderr) raise e finally: workspace_manager.set_recent_workspace(workspace_path) diff --git a/comfy_cli/command/custom_nodes/command.py b/comfy_cli/command/custom_nodes/command.py index f15d6072..5161e3f5 100644 --- a/comfy_cli/command/custom_nodes/command.py +++ b/comfy_cli/command/custom_nodes/command.py @@ -167,6 +167,15 @@ def execute_install_script(repo_path): try_install_script(repo_path, install_cmd) +@app.callback() +def app_callback(ctx: typer.Context): + """Global options for the CLI""" + if not hasattr(app_callback, "context"): + app_callback.context = {} + # Get verbose flag from parent context + app_callback.context["verbose"] = ctx.obj.get("verbose", False) + + @app.command("save-snapshot", help="Save a snapshot of the current ComfyUI environment") @tracking.track_command("node") def save_snapshot( @@ -175,11 +184,12 @@ def save_snapshot( typer.Option(show_default=False, help="Specify the output file path. (.json/.yaml)"), ] = None, ): + verbose = app_callback.context.get("verbose", False) if output is None: - execute_cm_cli(["save-snapshot"]) + execute_cm_cli(["save-snapshot"], verbose=verbose) else: output = os.path.abspath(output) # to compensate chdir - execute_cm_cli(["save-snapshot", "--output", output]) + execute_cm_cli(["save-snapshot", "--output", output], verbose=verbose) @app.command("restore-snapshot", help="Restore snapshot from snapshot file") @@ -216,32 +226,37 @@ def restore_snapshot( if pip_local_url: extras += ["--pip-local-url"] + verbose = app_callback.context.get("verbose", False) path = os.path.abspath(path) - execute_cm_cli(["restore-snapshot", path] + extras) + execute_cm_cli(["restore-snapshot", path] + extras, verbose=verbose) @app.command("restore-dependencies", help="Restore dependencies from installed custom nodes") @tracking.track_command("node") def restore_dependencies(): - execute_cm_cli(["restore-dependencies"]) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["restore-dependencies"], verbose=verbose) @manager_app.command("disable-gui", help="Disable GUI mode of ComfyUI-Manager") @tracking.track_command("node") def disable_gui(): - execute_cm_cli(["cli-only-mode", "enable"]) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["cli-only-mode", "enable"], verbose=verbose) @manager_app.command("enable-gui", help="Enable GUI mode of ComfyUI-Manager") @tracking.track_command("node") def enable_gui(): - execute_cm_cli(["cli-only-mode", "disable"]) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["cli-only-mode", "disable"], verbose=verbose) @manager_app.command(help="Clear reserved startup action in ComfyUI-Manager") @tracking.track_command("node") def clear(): - execute_cm_cli(["clear"]) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["clear"], verbose=verbose) # completers @@ -338,7 +353,8 @@ def show( validate_mode(mode) - execute_cm_cli(["show", arg], channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["show", arg], channel=channel, mode=mode, verbose=verbose) @app.command("simple-show", help="Show node list (simple mode)") @@ -377,7 +393,8 @@ def simple_show( validate_mode(mode) - execute_cm_cli(["simple-show", arg], channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["simple-show", arg], channel=channel, mode=mode, verbose=verbose) # install, reinstall, uninstall @@ -413,7 +430,8 @@ def install( validate_mode(mode) - execute_cm_cli(["install"] + nodes, channel=channel, fast_deps=fast_deps, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["install"] + nodes, channel=channel, fast_deps=fast_deps, mode=mode, verbose=verbose) @app.command(help="Reinstall custom nodes") @@ -448,7 +466,8 @@ def reinstall( validate_mode(mode) - execute_cm_cli(["reinstall"] + nodes, channel=channel, fast_deps=fast_deps, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["reinstall"] + nodes, channel=channel, fast_deps=fast_deps, mode=mode, verbose=verbose) @app.command(help="Uninstall custom nodes") @@ -475,7 +494,8 @@ def uninstall( validate_mode(mode) - execute_cm_cli(["uninstall"] + nodes, channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["uninstall"] + nodes, channel=channel, mode=mode, verbose=verbose) def update_node_id_cache(): @@ -521,7 +541,8 @@ def update( ): validate_mode(mode) - execute_cm_cli(["update"] + nodes, channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["update"] + nodes, channel=channel, mode=mode, verbose=verbose) update_node_id_cache() @@ -550,7 +571,8 @@ def disable( ): validate_mode(mode) - execute_cm_cli(["disable"] + nodes, channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["disable"] + nodes, channel=channel, mode=mode, verbose=verbose) @app.command(help="Enable custom nodes") @@ -577,7 +599,8 @@ def enable( ): validate_mode(mode) - execute_cm_cli(["enable"] + nodes, channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["enable"] + nodes, channel=channel, mode=mode, verbose=verbose) @app.command(help="Fix dependencies of custom nodes") @@ -604,7 +627,8 @@ def fix( ): validate_mode(mode) - execute_cm_cli(["fix"] + nodes, channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["fix"] + nodes, channel=channel, mode=mode, verbose=verbose) @app.command( @@ -648,17 +672,20 @@ def install_deps( os.makedirs(tmp_path) tmp_path = os.path.join(tmp_path, str(uuid.uuid4())) + ".json" + verbose = app_callback.context.get("verbose", False) execute_cm_cli( ["deps-in-workflow", "--workflow", workflow, "--output", tmp_path], channel, mode=mode, + verbose=verbose, ) deps_file = tmp_path else: deps_file = os.path.abspath(os.path.expanduser(deps)) - execute_cm_cli(["install-deps", deps_file], channel=channel, mode=mode) + verbose = app_callback.context.get("verbose", False) + execute_cm_cli(["install-deps", deps_file], channel=channel, mode=mode, verbose=verbose) if tmp_path is not None and os.path.exists(tmp_path): os.remove(tmp_path) @@ -688,10 +715,12 @@ def deps_in_workflow( workflow = os.path.abspath(os.path.expanduser(workflow)) output = os.path.abspath(os.path.expanduser(output)) + verbose = app_callback.context.get("verbose", False) execute_cm_cli( ["deps-in-workflow", "--workflow", workflow, "--output", output], channel, mode=mode, + verbose=verbose, )