From c9b34b8167641339e46a034ea3bbbe3a4cd6fda0 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Thu, 16 Jul 2026 22:27:57 -0700 Subject: [PATCH 1/3] refactor(output): route remaining raw rich.print imports through the JSON-mode-aware rprint shim Eight production modules still imported `from rich import print`, bypassing `comfy_cli.output.rprint`. The shim is byte-identical to rich.print in pretty mode and redirects stdout->stderr in --json/agent mode so the NDJSON envelope on stdout stays parseable. Swap each remaining site per the migration policy documented in comfy_cli/output/__init__.py; cm_cli_util.py imports as `print` (shadowing the builtin) to match the models.py precedent. Function-local imports in uv.py and output/preview.py stay function-local. --- comfy_cli/command/custom_nodes/cm_cli_util.py | 2 +- comfy_cli/command/generate/app.py | 2 +- comfy_cli/command/generate/output.py | 2 +- comfy_cli/command/pr_command.py | 2 +- comfy_cli/output/preview.py | 3 ++- comfy_cli/pr_cache.py | 3 +-- comfy_cli/resolve_python.py | 2 +- comfy_cli/uv.py | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/comfy_cli/command/custom_nodes/cm_cli_util.py b/comfy_cli/command/custom_nodes/cm_cli_util.py index 43894a79..c5060093 100644 --- a/comfy_cli/command/custom_nodes/cm_cli_util.py +++ b/comfy_cli/command/custom_nodes/cm_cli_util.py @@ -9,9 +9,9 @@ from functools import lru_cache import typer -from rich import print from comfy_cli.config_manager import ConfigManager +from comfy_cli.output import rprint as print # noqa: A001 - context-aware: stderr in JSON mode from comfy_cli.resolve_python import resolve_workspace_python from comfy_cli.uv import DependencyCompiler from comfy_cli.workspace_manager import WorkspaceManager, check_comfy_repo diff --git a/comfy_cli/command/generate/app.py b/comfy_cli/command/generate/app.py index ab6ab7fb..a7071e55 100644 --- a/comfy_cli/command/generate/app.py +++ b/comfy_cli/command/generate/app.py @@ -21,11 +21,11 @@ import httpx import typer -from rich import print as rprint from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn from comfy_cli import tracking, ui from comfy_cli.command.generate import adapters, client, emit, output, poll, schema, spec, upload +from comfy_cli.output import rprint from comfy_cli.output.renderer import get_renderer _HELP = "Generate images via ComfyUI partner nodes (Flux, Ideogram, DALL·E, Recraft, Stability, …)." diff --git a/comfy_cli/command/generate/output.py b/comfy_cli/command/generate/output.py index 66eaedae..7af69f9d 100644 --- a/comfy_cli/command/generate/output.py +++ b/comfy_cli/command/generate/output.py @@ -11,9 +11,9 @@ from pathlib import Path import httpx -from rich import print as rprint from comfy_cli.command.generate import client +from comfy_cli.output import rprint _EXT_FROM_MIME = { "image/png": "png", diff --git a/comfy_cli/command/pr_command.py b/comfy_cli/command/pr_command.py index 3105ce90..4aa6b33c 100644 --- a/comfy_cli/command/pr_command.py +++ b/comfy_cli/command/pr_command.py @@ -7,11 +7,11 @@ """ import typer -from rich import print as rprint from rich.console import Console from rich.table import Table from comfy_cli import tracking +from comfy_cli.output import rprint from comfy_cli.pr_cache import PRCache app = typer.Typer(help="Manage PR cache") diff --git a/comfy_cli/output/preview.py b/comfy_cli/output/preview.py index 80c8028b..6f6a5e72 100644 --- a/comfy_cli/output/preview.py +++ b/comfy_cli/output/preview.py @@ -141,9 +141,10 @@ def _show_video_info(path: Path) -> None: size_bytes = int(fmt.get("size", 0)) size_mb = size_bytes / 1048576 - from rich import print as rprint from rich.panel import Panel + from comfy_cli.output import rprint + rprint( Panel( f"🎬 [bold]{path.name}[/bold]\n {w}×{h} · {fps:.0f}fps · {codec} · {duration:.1f}s · {size_mb:.1f}MB", diff --git a/comfy_cli/pr_cache.py b/comfy_cli/pr_cache.py index e0b63a6e..f35e637b 100644 --- a/comfy_cli/pr_cache.py +++ b/comfy_cli/pr_cache.py @@ -11,9 +11,8 @@ from datetime import datetime, timedelta from pathlib import Path -from rich import print as rprint - from comfy_cli.config_manager import ConfigManager +from comfy_cli.output import rprint class PRCache: diff --git a/comfy_cli/resolve_python.py b/comfy_cli/resolve_python.py index 2fbe5bd1..b0affcf3 100644 --- a/comfy_cli/resolve_python.py +++ b/comfy_cli/resolve_python.py @@ -6,7 +6,7 @@ import sys import sysconfig -from rich import print as rprint +from comfy_cli.output import rprint def _get_python_binary(env_path: str) -> str: diff --git a/comfy_cli/uv.py b/comfy_cli/uv.py index f1d89ae2..cb495a5b 100644 --- a/comfy_cli/uv.py +++ b/comfy_cli/uv.py @@ -23,7 +23,7 @@ def _check_call(cmd: list[str], cwd: PathLike | None = None): subprocess.check_call(cmd, cwd=cwd) except subprocess.CalledProcessError: if len(cmd) >= 5 and cmd[1:4] == ["-m", "uv", "pip"] and cmd[4] in ("install", "sync"): - from rich import print as rprint + from comfy_cli.output import rprint rprint( "\n[bold yellow]Hint:[/bold yellow] If you are on a network filesystem " From e365aa3aac922b250f10b17c76fd6305513b3b13 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Thu, 16 Jul 2026 23:04:14 -0700 Subject: [PATCH 2/3] fix(output): don't route result-producing commands through the rprint shim The shim sends human text to stderr whenever the renderer is in JSON mode, and Renderer.resolve() auto-selects JSON mode when stdout isn't a TTY. That is only correct for call sites whose text is a *log*, because JSON mode reserves stdout for the envelope emitted via renderer.emit(). `generate` and `pr` never emit an envelope on their main paths -- `generate` has its own local --json flag (_emit_result -> output.print_json) and `pr list` renders through a plain module-level Console. Migrating them therefore moved their primary result off stdout and put nothing back. Verified on the branch before this commit (CliRunner, non-TTY): comfy generate flux-pro --prompt x --async exit=0 stdout='' stderr='Submitted: flux-pro\n job id: job-xyz\n ...' stdout was empty, so `comfy generate ... > out.txt` wrote an empty file. This was the root cause of the 25 failing tests in tests/comfy_cli/command/generate/. Revert those three modules to `from rich import print`, each with a comment explaining why, so the mechanical migration isn't simply redone. The five log/hint modules (cm_cli_util, preview, pr_cache, resolve_python, uv) stay on the shim -- stderr in JSON mode is exactly right for them. Giving `generate`/`pr` real envelopes is tracked as a follow-up; it needs a call on how `generate`'s existing --json reconciles with the envelope contract. Co-Authored-By: Claude Opus 4.8 --- comfy_cli/command/generate/app.py | 10 +++++++++- comfy_cli/command/generate/output.py | 6 +++++- comfy_cli/command/pr_command.py | 9 ++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/comfy_cli/command/generate/app.py b/comfy_cli/command/generate/app.py index a7071e55..5f59d6ae 100644 --- a/comfy_cli/command/generate/app.py +++ b/comfy_cli/command/generate/app.py @@ -21,11 +21,19 @@ import httpx import typer + +# NOT migrated to `comfy_cli.output.rprint` on purpose. `generate` carries its own +# `--json` flag (see `_emit_result` / `output.print_json`) and never emits a +# renderer envelope on its submit/sync/resume paths. Routing these calls through +# the shim would send the command's *primary result* (job ids, image URLs) to +# stderr whenever stdout isn't a TTY, leaving stdout empty -- so `comfy generate +# ... > out.txt` would write an empty file. Migrate only once `generate` emits +# envelopes via the renderer. +from rich import print as rprint from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn from comfy_cli import tracking, ui from comfy_cli.command.generate import adapters, client, emit, output, poll, schema, spec, upload -from comfy_cli.output import rprint from comfy_cli.output.renderer import get_renderer _HELP = "Generate images via ComfyUI partner nodes (Flux, Ideogram, DALL·E, Recraft, Stability, …)." diff --git a/comfy_cli/command/generate/output.py b/comfy_cli/command/generate/output.py index 7af69f9d..2d5a7e2b 100644 --- a/comfy_cli/command/generate/output.py +++ b/comfy_cli/command/generate/output.py @@ -12,8 +12,12 @@ import httpx +# NOT migrated to `comfy_cli.output.rprint` on purpose: `print_urls` / `print_saved` +# are `generate`'s primary result, and `generate` emits no renderer envelope. See +# the note in `comfy_cli/command/generate/app.py`. +from rich import print as rprint + from comfy_cli.command.generate import client -from comfy_cli.output import rprint _EXT_FROM_MIME = { "image/png": "png", diff --git a/comfy_cli/command/pr_command.py b/comfy_cli/command/pr_command.py index 4aa6b33c..f21cbdbb 100644 --- a/comfy_cli/command/pr_command.py +++ b/comfy_cli/command/pr_command.py @@ -7,11 +7,18 @@ """ import typer + +# NOT migrated to `comfy_cli.output.rprint` on purpose. `pr list` renders its +# result through the module-level `console` below (plain Rich -> stdout) and emits +# no renderer envelope. Routing only these calls through the shim would split one +# command's output across two streams in JSON mode -- table on stdout, "no cached +# PR builds found" / cache settings on stderr. Migrate `console` and these calls +# together, once `pr` emits envelopes via the renderer. +from rich import print as rprint from rich.console import Console from rich.table import Table from comfy_cli import tracking -from comfy_cli.output import rprint from comfy_cli.pr_cache import PRCache app = typer.Typer(help="Manage PR cache") From 73bd0663c152e1d43a3d2614c406aadbb31c4e3c Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Fri, 17 Jul 2026 00:00:24 -0700 Subject: [PATCH 3/3] docs(output): record the result-path exception in the migration policy The Phase 1 policy said "Every existing `from rich import print as rprint` becomes `from comfy_cli.output import rprint`", which is the instruction that motivated migrating generate/pr in the first place and would motivate redoing it later. `Renderer.resolve()` picks JSON mode whenever stdout is not a TTY, so migrating a call site that prints a command's primary result routes that result to stderr and leaves stdout empty. Scope the rule to log/hint output, state the result-path carve-out, and name the three intentionally un-migrated modules so the in-file comments have a policy to point back to. Co-Authored-By: Claude Opus 4.8 --- comfy_cli/output/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/comfy_cli/output/__init__.py b/comfy_cli/output/__init__.py index c8b6e7fe..d78ce1d5 100644 --- a/comfy_cli/output/__init__.py +++ b/comfy_cli/output/__init__.py @@ -5,10 +5,18 @@ `Renderer.resolve(...)` and stored as a process-wide singleton via `set_renderer`. Migration policy (Phase 1): -- Every existing ``from rich import print as rprint`` becomes +- A ``from rich import print as rprint`` whose output is a *log or hint* becomes ``from comfy_cli.output import rprint``. The shim suppresses stdout in JSON mode (redirecting to stderr so logs are still visible) and is byte-identical to rprint in pretty mode. +- A call site that prints a command's *primary result* must NOT be migrated + until that command emits a ``renderer.emit(...)`` envelope. ``resolve()`` + selects JSON mode whenever stdout is not a TTY, so migrating a result path + first would route the result to stderr and leave stdout empty -- e.g. + ``comfy generate ... > out.txt`` would write an empty file. The known + un-migrated result paths are ``command/generate/app.py``, + ``command/generate/output.py`` and ``command/pr_command.py``; each carries an + in-file comment saying so. - Commands that produce a structured result emit a final envelope with ``renderer.emit(data)``. This is the *only* thing on stdout in JSON mode. - Streaming commands emit ``renderer.event(type, **fields)`` events; one