comfy-cli 1.7.1__tar.gz → 1.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/PKG-INFO +24 -3
  2. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/README.md +23 -2
  3. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/cmdline.py +5 -2
  4. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/custom_nodes/command.py +12 -8
  5. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/install.py +9 -1
  6. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/models/models.py +39 -2
  7. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/constants.py +5 -0
  8. comfy_cli-1.7.3/comfy_cli/file_utils.py +481 -0
  9. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/registry/config_parser.py +19 -1
  10. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/tracking.py +3 -2
  11. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/ui.py +4 -2
  12. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/uv.py +29 -4
  13. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli.egg-info/PKG-INFO +24 -3
  14. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/pyproject.toml +1 -1
  15. comfy_cli-1.7.3/tests/test_file_utils_network.py +862 -0
  16. comfy_cli-1.7.1/comfy_cli/file_utils.py +0 -247
  17. comfy_cli-1.7.1/tests/test_file_utils_network.py +0 -164
  18. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/LICENSE +0 -0
  19. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/__init__.py +0 -0
  20. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/__main__.py +0 -0
  21. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/__init__.py +0 -0
  22. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/code_search.py +0 -0
  23. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/custom_nodes/__init__.py +0 -0
  24. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/custom_nodes/bisect_custom_nodes.py +0 -0
  25. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/custom_nodes/cm_cli_util.py +0 -0
  26. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/github/pr_info.py +0 -0
  27. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/launch.py +0 -0
  28. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/pr_command.py +0 -0
  29. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/command/run.py +0 -0
  30. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/config_manager.py +0 -0
  31. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/cuda_detect.py +0 -0
  32. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/env_checker.py +0 -0
  33. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/git_utils.py +0 -0
  34. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/logging.py +0 -0
  35. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/pr_cache.py +0 -0
  36. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/registry/__init__.py +0 -0
  37. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/registry/api.py +0 -0
  38. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/registry/types.py +0 -0
  39. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/resolve_python.py +0 -0
  40. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/standalone.py +0 -0
  41. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/typing.py +0 -0
  42. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/update.py +0 -0
  43. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/utils.py +0 -0
  44. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli/workspace_manager.py +0 -0
  45. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli.egg-info/SOURCES.txt +0 -0
  46. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli.egg-info/dependency_links.txt +0 -0
  47. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli.egg-info/entry_points.txt +0 -0
  48. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli.egg-info/requires.txt +0 -0
  49. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/comfy_cli.egg-info/top_level.txt +0 -0
  50. {comfy_cli-1.7.1 → comfy_cli-1.7.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-cli
3
- Version: 1.7.1
3
+ Version: 1.7.3
4
4
  Summary: A CLI tool for installing and using ComfyUI.
5
5
  Maintainer-email: Yoland Yan <yoland@drip.art>, James Kwon <hongilkwon316@gmail.com>, Robin Huang <robin@drip.art>, "Dr.Lt.Data" <dr.lt.data@gmail.com>
6
6
  License: GPL-3.0-only
@@ -111,6 +111,9 @@ will simply update the comfy.yaml file to reflect the local setup
111
111
  pip install comfyui-manager==4.1b8 # specific version
112
112
  ```
113
113
  - `comfy --workspace=<path> install`: Install ComfyUI into `<path>/ComfyUI`.
114
+ - `comfy install --fast-deps`: Use `uv` instead of `pip` for faster dependency resolution
115
+ during initial ComfyUI installation. comfy-cli's built-in resolver compiles all requirements (core + custom nodes)
116
+ into a single lockfile and installs from it. Also handles GPU-specific PyTorch wheel selection automatically.
114
117
  - For `comfy install`, if no path specification like `--workspace, --recent, or --here` is provided, it will be implicitly installed in `<HOME>/comfy`.
115
118
 
116
119
  #### Python environment handling
@@ -257,8 +260,9 @@ comfy node [show|simple-show] [installed|enabled|not-installed|disabled|all|snap
257
260
  #### Unified Dependency Resolution (--uv-compile)
258
261
 
259
262
  Requires ComfyUI-Manager v4.1+. Instead of installing dependencies per-node with
260
- `pip install`, `--uv-compile` batch-resolves all custom node dependencies via
261
- `uv pip compile` to avoid version conflicts.
263
+ `pip install`, `--uv-compile` delegates to ComfyUI-Manager's unified resolver which batch-resolves
264
+ all custom node dependencies via `uv pip compile` with **cross-node conflict detection** —
265
+ it can identify which node packs have incompatible dependencies and why.
262
266
 
263
267
  - Install with unified resolution:
264
268
 
@@ -280,6 +284,23 @@ Requires ComfyUI-Manager v4.1+. Instead of installing dependencies per-node with
280
284
 
281
285
  `comfy node install comfyui-impact-pack --no-uv-compile`
282
286
 
287
+ #### --fast-deps vs --uv-compile
288
+
289
+ Both flags use `uv` for faster dependency resolution, but they work differently:
290
+
291
+ | | `--fast-deps` | `--uv-compile` |
292
+ |-----------------------|-------------------------------------------------|-----------------------------------------------|
293
+ | **Resolver** | comfy-cli built-in (`DependencyCompiler`) | ComfyUI-Manager (`UnifiedDepResolver`) |
294
+ | **Scope** | `comfy install`, `comfy node install/reinstall` | Custom node commands only |
295
+ | **Conflict handling** | Interactive prompt to pick a version | Automatic detection with node attribution |
296
+ | **Config default** | No | Yes (`comfy manager uv-compile-default true`) |
297
+ | **Requires** | Only `uv` | ComfyUI-Manager v4.1+ |
298
+
299
+ **When to use which:**
300
+ - For initial ComfyUI installation with uv: `comfy install --fast-deps`
301
+ - For custom node management with Manager v4.1+: `--uv-compile` (recommended)
302
+ - For custom node management with older Manager: `--fast-deps`
303
+
283
304
  #### Bisect custom nodes
284
305
 
285
306
  If you encounter bugs only with custom nodes enabled, and want to find out which custom node(s) causes the bug,
@@ -66,6 +66,9 @@ will simply update the comfy.yaml file to reflect the local setup
66
66
  pip install comfyui-manager==4.1b8 # specific version
67
67
  ```
68
68
  - `comfy --workspace=<path> install`: Install ComfyUI into `<path>/ComfyUI`.
69
+ - `comfy install --fast-deps`: Use `uv` instead of `pip` for faster dependency resolution
70
+ during initial ComfyUI installation. comfy-cli's built-in resolver compiles all requirements (core + custom nodes)
71
+ into a single lockfile and installs from it. Also handles GPU-specific PyTorch wheel selection automatically.
69
72
  - For `comfy install`, if no path specification like `--workspace, --recent, or --here` is provided, it will be implicitly installed in `<HOME>/comfy`.
70
73
 
71
74
  #### Python environment handling
@@ -212,8 +215,9 @@ comfy node [show|simple-show] [installed|enabled|not-installed|disabled|all|snap
212
215
  #### Unified Dependency Resolution (--uv-compile)
213
216
 
214
217
  Requires ComfyUI-Manager v4.1+. Instead of installing dependencies per-node with
215
- `pip install`, `--uv-compile` batch-resolves all custom node dependencies via
216
- `uv pip compile` to avoid version conflicts.
218
+ `pip install`, `--uv-compile` delegates to ComfyUI-Manager's unified resolver which batch-resolves
219
+ all custom node dependencies via `uv pip compile` with **cross-node conflict detection** —
220
+ it can identify which node packs have incompatible dependencies and why.
217
221
 
218
222
  - Install with unified resolution:
219
223
 
@@ -235,6 +239,23 @@ Requires ComfyUI-Manager v4.1+. Instead of installing dependencies per-node with
235
239
 
236
240
  `comfy node install comfyui-impact-pack --no-uv-compile`
237
241
 
242
+ #### --fast-deps vs --uv-compile
243
+
244
+ Both flags use `uv` for faster dependency resolution, but they work differently:
245
+
246
+ | | `--fast-deps` | `--uv-compile` |
247
+ |-----------------------|-------------------------------------------------|-----------------------------------------------|
248
+ | **Resolver** | comfy-cli built-in (`DependencyCompiler`) | ComfyUI-Manager (`UnifiedDepResolver`) |
249
+ | **Scope** | `comfy install`, `comfy node install/reinstall` | Custom node commands only |
250
+ | **Conflict handling** | Interactive prompt to pick a version | Automatic detection with node attribution |
251
+ | **Config default** | No | Yes (`comfy manager uv-compile-default true`) |
252
+ | **Requires** | Only `uv` | ComfyUI-Manager v4.1+ |
253
+
254
+ **When to use which:**
255
+ - For initial ComfyUI installation with uv: `comfy install --fast-deps`
256
+ - For custom node management with Manager v4.1+: `--uv-compile` (recommended)
257
+ - For custom node management with older Manager: `--fast-deps`
258
+
238
259
  #### Bisect custom nodes
239
260
 
240
261
  If you encounter bugs only with custom nodes enabled, and want to find out which custom node(s) causes the bug,
@@ -267,7 +267,7 @@ def install(
267
267
  typer.Option(
268
268
  "--fast-deps",
269
269
  show_default=False,
270
- help="Use new fast dependency installer",
270
+ help="Use uv instead of pip for dependency resolution (comfy-cli built-in resolver)",
271
271
  ),
272
272
  ] = False,
273
273
  pr: Annotated[
@@ -416,7 +416,10 @@ def update(
416
416
  check=True,
417
417
  )
418
418
 
419
- custom_nodes.command.update_node_id_cache()
419
+ try:
420
+ custom_nodes.command.update_node_id_cache()
421
+ except (FileNotFoundError, subprocess.CalledProcessError) as e:
422
+ rprint(f"[yellow]Failed to update node id cache: {e}[/yellow]")
420
423
 
421
424
 
422
425
  @app.command(help="Run API workflow file using the ComfyUI launched by `comfy launch --background`")
@@ -18,6 +18,7 @@ from comfy_cli.command.custom_nodes.cm_cli_util import execute_cm_cli, find_cm_c
18
18
  from comfy_cli.config_manager import ConfigManager
19
19
  from comfy_cli.constants import NODE_ZIP_FILENAME
20
20
  from comfy_cli.file_utils import (
21
+ DownloadException,
21
22
  download_file,
22
23
  extract_package_as_zip,
23
24
  upload_file_to_signed_url,
@@ -170,13 +171,11 @@ def execute_install_script(repo_path):
170
171
  if os.path.exists(requirements_path):
171
172
  print("Install: pip packages")
172
173
  python = resolve_workspace_python(workspace_manager.workspace_path)
173
- with open(requirements_path, encoding="utf-8") as requirements_file:
174
- for line in requirements_file:
175
- package_name = line.strip()
176
- if package_name and not package_name.startswith("#"):
177
- install_cmd = [python, "-m", "pip", "install", package_name]
178
- if package_name.strip() != "":
179
- try_install_script(repo_path, install_cmd)
174
+ # Absolute path so pip doesn't re-resolve it against cwd=repo_path
175
+ # in try_install_script, which would double the path if repo_path
176
+ # is relative.
177
+ install_cmd = [python, "-m", "pip", "install", "-r", os.path.abspath(requirements_path)]
178
+ try_install_script(repo_path, install_cmd)
180
179
 
181
180
  if os.path.exists(install_script_path):
182
181
  print("Install: install script")
@@ -1168,7 +1167,12 @@ def registry_install(
1168
1167
 
1169
1168
  local_filename = node_specific_path / f"{node_id}-{node_version.version}.zip"
1170
1169
  logging.debug(f"Start downloading the node {node_id} version {node_version.version} to {local_filename}")
1171
- download_file(node_version.download_url, local_filename)
1170
+ try:
1171
+ download_file(node_version.download_url, local_filename)
1172
+ except DownloadException as e:
1173
+ logging.error(f"Failed to download node {node_id} version {node_version.version}: {e}")
1174
+ ui.display_error_message(f"Failed to download the custom node {node_id}: {e}")
1175
+ raise typer.Exit(code=1) from None
1172
1176
 
1173
1177
  # Extract the downloaded archive to the custom_node directory on the workspace.
1174
1178
  logging.debug(f"Start extracting the node {node_id} version {node_version.version} to {custom_nodes_path}")
@@ -476,8 +476,16 @@ def get_latest_release(repo_owner: str, repo_name: str) -> GithubRelease | None:
476
476
  """
477
477
  url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest"
478
478
 
479
+ headers = {}
480
+ if github_token := os.getenv("GITHUB_TOKEN"):
481
+ headers["Authorization"] = f"Bearer {github_token}"
482
+
479
483
  try:
480
- response = requests.get(url, timeout=5)
484
+ response = requests.get(url, headers=headers, timeout=5)
485
+
486
+ if response.status_code in (403, 429):
487
+ handle_github_rate_limit(response)
488
+
481
489
  response.raise_for_status()
482
490
 
483
491
  data = response.json()
@@ -1,12 +1,14 @@
1
1
  import contextlib
2
2
  import os
3
3
  import pathlib
4
+ import time
4
5
  from typing import Annotated
5
6
  from urllib.parse import parse_qs, unquote, urlparse
6
7
 
7
8
  import requests
8
9
  import typer
9
10
  from rich import print
11
+ from rich.markup import escape
10
12
 
11
13
  from comfy_cli import constants, tracking, ui
12
14
  from comfy_cli.config_manager import ConfigManager
@@ -19,6 +21,8 @@ app = typer.Typer()
19
21
  workspace_manager = WorkspaceManager()
20
22
  config_manager = ConfigManager()
21
23
 
24
+ _CIVITAI_SUBDOMAIN_SUFFIXES = tuple(f".{h}" for h in constants.CIVITAI_ALLOWED_HOSTS)
25
+
22
26
 
23
27
  model_path_map = {
24
28
  "lora": "loras",
@@ -33,6 +37,18 @@ def get_workspace() -> pathlib.Path:
33
37
  return pathlib.Path(workspace_manager.workspace_path)
34
38
 
35
39
 
40
+ def _format_elapsed(seconds: float) -> str:
41
+ """Format elapsed seconds into a human-readable string."""
42
+ rounded = round(seconds, 1)
43
+ if rounded < 60:
44
+ return f"{rounded:.1f}s"
45
+ minutes, secs = divmod(int(rounded), 60)
46
+ if minutes < 60:
47
+ return f"{minutes}m {secs}s"
48
+ hours, minutes = divmod(minutes, 60)
49
+ return f"{hours}h {minutes}m {secs}s"
50
+
51
+
36
52
  def potentially_strip_param_url(path_name: str) -> str:
37
53
  return path_name.split("?")[0]
38
54
 
@@ -85,7 +101,7 @@ def check_civitai_url(url: str) -> tuple[bool, bool, int | None, int | None]:
85
101
  try:
86
102
  parsed = urlparse(url)
87
103
  host = (parsed.hostname or "").lower()
88
- if host != "civitai.com" and not host.endswith(".civitai.com"):
104
+ if host not in constants.CIVITAI_ALLOWED_HOSTS and not host.endswith(_CIVITAI_SUBDOMAIN_SUFFIXES):
89
105
  return False, False, None, None
90
106
  p_parts = [p for p in parsed.path.split("/") if p]
91
107
  query = parse_qs(parsed.query)
@@ -219,6 +235,14 @@ def download(
219
235
  show_default=False,
220
236
  ),
221
237
  ] = None,
238
+ downloader: Annotated[
239
+ str | None,
240
+ typer.Option(
241
+ "--downloader",
242
+ help="Download backend: 'httpx' (default) or 'aria2' (requires aria2 RPC server).",
243
+ show_default=False,
244
+ ),
245
+ ] = None,
222
246
  ):
223
247
  if relative_path is not None:
224
248
  relative_path = os.path.expanduser(relative_path)
@@ -233,6 +257,8 @@ def download(
233
257
  constants.HF_API_TOKEN_ENV_KEY, constants.HF_API_TOKEN_KEY, set_hf_api_token
234
258
  )
235
259
 
260
+ resolved_downloader = downloader or config_manager.get(constants.CONFIG_KEY_DEFAULT_DOWNLOADER) or "httpx"
261
+
236
262
  is_civitai_model_url, is_civitai_api_url, model_id, version_id = check_civitai_url(url)
237
263
  is_huggingface_url, repo_id, hf_filename, hf_folder_name, hf_branch_name = check_huggingface_url(url)
238
264
 
@@ -297,6 +323,8 @@ def download(
297
323
  print(f"[bold red]File already exists: {local_filepath}[/bold red]")
298
324
  return
299
325
 
326
+ start_time = time.monotonic()
327
+
300
328
  if is_huggingface_url and check_unauthorized(url, headers):
301
329
  if hf_api_token is None:
302
330
  print(
@@ -329,7 +357,16 @@ def download(
329
357
  print(f"Model downloaded successfully to: {output_path}")
330
358
  else:
331
359
  print(f"Start downloading URL: {url} into {local_filepath}")
332
- download_file(url, local_filepath, headers)
360
+ try:
361
+ download_file(url, local_filepath, headers, downloader=resolved_downloader)
362
+ except DownloadException as e:
363
+ # escape() so a dynamic error message containing "[/]" or similar
364
+ # rich-markup syntax doesn't trigger MarkupError or get mis-rendered.
365
+ print(f"[bold red]{escape(str(e))}[/bold red]")
366
+ raise typer.Exit(code=1) from None
367
+
368
+ elapsed = time.monotonic() - start_time
369
+ print(f"Done in {_format_elapsed(elapsed)}")
333
370
 
334
371
 
335
372
  @app.command()
@@ -47,9 +47,14 @@ CONFIG_KEY_UV_COMPILE_DEFAULT = "uv_compile_default"
47
47
 
48
48
  CIVITAI_API_TOKEN_KEY = "civitai_api_token"
49
49
  CIVITAI_API_TOKEN_ENV_KEY = "CIVITAI_API_TOKEN"
50
+ CIVITAI_ALLOWED_HOSTS: tuple[str, ...] = ("civitai.com", "civitai.red")
50
51
  HF_API_TOKEN_KEY = "hf_api_token"
51
52
  HF_API_TOKEN_ENV_KEY = "HF_API_TOKEN"
52
53
 
54
+ ARIA2_SERVER_ENV_KEY = "COMFYUI_MANAGER_ARIA2_SERVER"
55
+ ARIA2_SECRET_ENV_KEY = "COMFYUI_MANAGER_ARIA2_SECRET"
56
+ CONFIG_KEY_DEFAULT_DOWNLOADER = "default_downloader"
57
+
53
58
  DEFAULT_TRACKING_VALUE = True
54
59
 
55
60
  COMFY_LOCK_YAML_FILE = "comfy.lock.yaml"