comfy-cli 1.7.0__tar.gz → 1.7.2__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.
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/PKG-INFO +24 -3
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/README.md +23 -2
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/cmdline.py +57 -9
- comfy_cli-1.7.2/comfy_cli/command/code_search.py +157 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/custom_nodes/bisect_custom_nodes.py +17 -5
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/install.py +26 -5
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/models/models.py +29 -1
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/constants.py +6 -0
- comfy_cli-1.7.2/comfy_cli/cuda_detect.py +137 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/file_utils.py +220 -23
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/uv.py +22 -2
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/workspace_manager.py +46 -13
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli.egg-info/PKG-INFO +24 -3
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli.egg-info/SOURCES.txt +2 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/pyproject.toml +1 -1
- comfy_cli-1.7.2/tests/test_file_utils_network.py +530 -0
- comfy_cli-1.7.0/tests/test_file_utils_network.py +0 -164
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/LICENSE +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/__init__.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/__main__.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/__init__.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/custom_nodes/__init__.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/custom_nodes/cm_cli_util.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/custom_nodes/command.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/github/pr_info.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/launch.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/pr_command.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/command/run.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/config_manager.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/env_checker.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/git_utils.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/logging.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/pr_cache.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/registry/__init__.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/registry/api.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/registry/config_parser.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/registry/types.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/resolve_python.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/standalone.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/tracking.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/typing.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/ui.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/update.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli/utils.py +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli.egg-info/dependency_links.txt +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli.egg-info/entry_points.txt +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli.egg-info/requires.txt +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/comfy_cli.egg-info/top_level.txt +0 -0
- {comfy_cli-1.7.0 → comfy_cli-1.7.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comfy-cli
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.2
|
|
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`
|
|
261
|
-
`uv pip compile`
|
|
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`
|
|
216
|
-
`uv pip compile`
|
|
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,
|
|
@@ -10,7 +10,7 @@ from rich import print as rprint
|
|
|
10
10
|
from rich.console import Console
|
|
11
11
|
|
|
12
12
|
from comfy_cli import constants, env_checker, logging, tracking, ui, utils
|
|
13
|
-
from comfy_cli.command import custom_nodes, pr_command
|
|
13
|
+
from comfy_cli.command import code_search, custom_nodes, pr_command
|
|
14
14
|
from comfy_cli.command import install as install_inner
|
|
15
15
|
from comfy_cli.command import run as run_inner
|
|
16
16
|
from comfy_cli.command.install import validate_version
|
|
@@ -18,6 +18,7 @@ from comfy_cli.command.launch import launch as launch_command
|
|
|
18
18
|
from comfy_cli.command.models import models as models_command
|
|
19
19
|
from comfy_cli.config_manager import ConfigManager
|
|
20
20
|
from comfy_cli.constants import GPU_OPTION, CUDAVersion, ROCmVersion
|
|
21
|
+
from comfy_cli.cuda_detect import DEFAULT_CUDA_TAG, detect_cuda_driver_version, resolve_cuda_wheel
|
|
21
22
|
from comfy_cli.env_checker import EnvChecker
|
|
22
23
|
from comfy_cli.resolve_python import resolve_workspace_python
|
|
23
24
|
from comfy_cli.standalone import StandalonePython
|
|
@@ -142,6 +143,43 @@ def validate_commit_and_version(commit: str | None, ctx: typer.Context) -> str |
|
|
|
142
143
|
return commit
|
|
143
144
|
|
|
144
145
|
|
|
146
|
+
def _resolve_cuda(
|
|
147
|
+
gpu: GPU_OPTION | None,
|
|
148
|
+
cuda_version: CUDAVersion | None,
|
|
149
|
+
) -> tuple[CUDAVersion | None, str | None]:
|
|
150
|
+
"""Resolve the CUDA wheel tag for an NVIDIA install.
|
|
151
|
+
|
|
152
|
+
Returns (cuda_version_enum_or_None, cuda_tag_string_or_None).
|
|
153
|
+
When the user passed an explicit --cuda-version, that is used as-is.
|
|
154
|
+
Otherwise auto-detection is attempted.
|
|
155
|
+
"""
|
|
156
|
+
if gpu != GPU_OPTION.NVIDIA:
|
|
157
|
+
return cuda_version, None
|
|
158
|
+
|
|
159
|
+
if cuda_version is not None:
|
|
160
|
+
tag = f"cu{cuda_version.value.replace('.', '')}"
|
|
161
|
+
rprint(f"[bold]Using explicit CUDA version:[/bold] {cuda_version.value} ({tag})")
|
|
162
|
+
return cuda_version, tag
|
|
163
|
+
|
|
164
|
+
drv = detect_cuda_driver_version()
|
|
165
|
+
if drv is not None:
|
|
166
|
+
tag = resolve_cuda_wheel(drv)
|
|
167
|
+
if tag is not None:
|
|
168
|
+
rprint(f"[bold green]Detected CUDA driver version:[/bold green] {drv[0]}.{drv[1]} → using {tag}")
|
|
169
|
+
return None, tag
|
|
170
|
+
rprint(
|
|
171
|
+
f"[bold yellow]Warning:[/bold yellow] CUDA driver {drv[0]}.{drv[1]} is too old for any known PyTorch wheel. "
|
|
172
|
+
f"Falling back to {DEFAULT_CUDA_TAG}. Use `--cuda-version` to override."
|
|
173
|
+
)
|
|
174
|
+
return None, DEFAULT_CUDA_TAG
|
|
175
|
+
|
|
176
|
+
rprint(
|
|
177
|
+
f"[bold yellow]Warning:[/bold yellow] Could not detect CUDA driver version. "
|
|
178
|
+
f"Falling back to {DEFAULT_CUDA_TAG}. Use `--cuda-version` to override."
|
|
179
|
+
)
|
|
180
|
+
return None, DEFAULT_CUDA_TAG
|
|
181
|
+
|
|
182
|
+
|
|
145
183
|
@app.command(help="Download and install ComfyUI and ComfyUI-Manager")
|
|
146
184
|
@tracking.track_command()
|
|
147
185
|
def install(
|
|
@@ -186,7 +224,7 @@ def install(
|
|
|
186
224
|
callback=g_gpu_exclusivity.validate,
|
|
187
225
|
),
|
|
188
226
|
] = None,
|
|
189
|
-
cuda_version: Annotated[CUDAVersion, typer.Option(show_default=
|
|
227
|
+
cuda_version: Annotated[CUDAVersion | None, typer.Option(show_default=False)] = None,
|
|
190
228
|
rocm_version: Annotated[ROCmVersion, typer.Option(show_default=True)] = ROCmVersion.v6_3,
|
|
191
229
|
amd: Annotated[
|
|
192
230
|
bool | None,
|
|
@@ -229,7 +267,7 @@ def install(
|
|
|
229
267
|
typer.Option(
|
|
230
268
|
"--fast-deps",
|
|
231
269
|
show_default=False,
|
|
232
|
-
help="Use
|
|
270
|
+
help="Use uv instead of pip for dependency resolution (comfy-cli built-in resolver)",
|
|
233
271
|
),
|
|
234
272
|
] = False,
|
|
235
273
|
pr: Annotated[
|
|
@@ -245,7 +283,7 @@ def install(
|
|
|
245
283
|
|
|
246
284
|
comfy_path, _ = workspace_manager.get_workspace_path()
|
|
247
285
|
|
|
248
|
-
is_comfy_installed_at_path,
|
|
286
|
+
is_comfy_installed_at_path, resolved_path = check_comfy_repo(comfy_path)
|
|
249
287
|
if is_comfy_installed_at_path and not restore:
|
|
250
288
|
rprint(f"[bold red]ComfyUI is already installed at the specified path:[/bold red] {comfy_path}\n")
|
|
251
289
|
rprint(
|
|
@@ -253,8 +291,8 @@ def install(
|
|
|
253
291
|
)
|
|
254
292
|
raise typer.Exit(code=1)
|
|
255
293
|
|
|
256
|
-
if
|
|
257
|
-
comfy_path =
|
|
294
|
+
if resolved_path is not None:
|
|
295
|
+
comfy_path = resolved_path
|
|
258
296
|
|
|
259
297
|
if checker.python_version.major < 3 or checker.python_version.minor < 9:
|
|
260
298
|
rprint("[bold red]Python version 3.9 or higher is required to run ComfyUI.[/bold red]")
|
|
@@ -276,6 +314,7 @@ def install(
|
|
|
276
314
|
version=version,
|
|
277
315
|
gpu=None,
|
|
278
316
|
cuda_version=cuda_version,
|
|
317
|
+
cuda_tag=None,
|
|
279
318
|
rocm_version=rocm_version,
|
|
280
319
|
plat=platform,
|
|
281
320
|
skip_torch_or_directml=skip_torch_or_directml,
|
|
@@ -321,6 +360,8 @@ def install(
|
|
|
321
360
|
)
|
|
322
361
|
raise typer.Exit(code=1)
|
|
323
362
|
|
|
363
|
+
cuda_version, cuda_tag = _resolve_cuda(gpu, cuda_version) if not skip_torch_or_directml else (cuda_version, None)
|
|
364
|
+
|
|
324
365
|
install_inner.execute(
|
|
325
366
|
url,
|
|
326
367
|
comfy_path,
|
|
@@ -330,6 +371,7 @@ def install(
|
|
|
330
371
|
gpu=gpu,
|
|
331
372
|
version=version,
|
|
332
373
|
cuda_version=cuda_version,
|
|
374
|
+
cuda_tag=cuda_tag,
|
|
333
375
|
rocm_version=rocm_version,
|
|
334
376
|
plat=platform,
|
|
335
377
|
skip_torch_or_directml=skip_torch_or_directml,
|
|
@@ -374,7 +416,10 @@ def update(
|
|
|
374
416
|
check=True,
|
|
375
417
|
)
|
|
376
418
|
|
|
377
|
-
|
|
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]")
|
|
378
423
|
|
|
379
424
|
|
|
380
425
|
@app.command(help="Run API workflow file using the ComfyUI launched by `comfy launch --background`")
|
|
@@ -487,7 +532,7 @@ def set_default(
|
|
|
487
532
|
)
|
|
488
533
|
raise typer.Exit(code=1)
|
|
489
534
|
|
|
490
|
-
is_comfy_repo,
|
|
535
|
+
is_comfy_repo, resolved_path = check_comfy_repo(comfy_path)
|
|
491
536
|
if not is_comfy_repo:
|
|
492
537
|
rprint(
|
|
493
538
|
f"\nSpecified path is not a ComfyUI path: {comfy_path}.\n",
|
|
@@ -495,7 +540,7 @@ def set_default(
|
|
|
495
540
|
)
|
|
496
541
|
raise typer.Exit(code=1)
|
|
497
542
|
|
|
498
|
-
comfy_path =
|
|
543
|
+
comfy_path = resolved_path
|
|
499
544
|
|
|
500
545
|
rprint(f"Specified path is set as default ComfyUI path: {comfy_path} ")
|
|
501
546
|
workspace_manager.set_default_workspace(comfy_path)
|
|
@@ -643,4 +688,7 @@ app.add_typer(custom_nodes.manager_app, name="manager", help="Manage ComfyUI-Man
|
|
|
643
688
|
|
|
644
689
|
app.add_typer(pr_command.app, name="pr-cache", help="Manage PR cache.")
|
|
645
690
|
|
|
691
|
+
app.add_typer(code_search.app, name="code-search", help="Search code across ComfyUI repositories.")
|
|
692
|
+
app.add_typer(code_search.app, name="cs", hidden=True)
|
|
693
|
+
|
|
646
694
|
app.add_typer(tracking.app, name="tracking", help="Manage analytics tracking settings.")
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""CLI commands for searching code across ComfyUI repositories."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
from comfy_cli import tracking
|
|
14
|
+
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
API_URL = "https://comfy-codesearch.vercel.app/api/search/code"
|
|
19
|
+
DEFAULT_COUNT = 20
|
|
20
|
+
REQUEST_TIMEOUT = 30
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _build_query(query: str, repo: str | None, count: int) -> str:
|
|
24
|
+
parts = []
|
|
25
|
+
if repo:
|
|
26
|
+
if "/" not in repo:
|
|
27
|
+
repo = f"Comfy-Org/{repo}"
|
|
28
|
+
parts.append(f"repo:^{re.escape(repo)}$")
|
|
29
|
+
parts.append(f"count:{count}")
|
|
30
|
+
parts.append(query)
|
|
31
|
+
return " ".join(parts)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _fetch_results(query: str) -> dict:
|
|
35
|
+
response = requests.get(API_URL, params={"query": query}, timeout=REQUEST_TIMEOUT)
|
|
36
|
+
response.raise_for_status()
|
|
37
|
+
return response.json()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _format_results(search: dict) -> list[dict]:
|
|
41
|
+
raw_results = search.get("results", {}).get("results", [])
|
|
42
|
+
formatted = []
|
|
43
|
+
for result in raw_results:
|
|
44
|
+
repo_info = result.get("repository") or {}
|
|
45
|
+
repo_name = repo_info.get("name", "")
|
|
46
|
+
clean_name = repo_name.removeprefix("github.com/")
|
|
47
|
+
|
|
48
|
+
file_info = result.get("file") or {}
|
|
49
|
+
file_path = file_info.get("path", "")
|
|
50
|
+
|
|
51
|
+
if not clean_name or not file_path:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
default_branch = repo_info.get("defaultBranch") or {}
|
|
55
|
+
branch_name = default_branch.get("displayName", "main")
|
|
56
|
+
commit_hash = (default_branch.get("target") or {}).get("commit", {}).get("oid", "")
|
|
57
|
+
ref = commit_hash or branch_name
|
|
58
|
+
|
|
59
|
+
line_matches = result.get("lineMatches") or []
|
|
60
|
+
matches = []
|
|
61
|
+
if line_matches:
|
|
62
|
+
encoded_path = quote(file_path, safe="/")
|
|
63
|
+
base_url = f"https://github.com/{clean_name}/blob/{ref}/{encoded_path}"
|
|
64
|
+
for m in line_matches:
|
|
65
|
+
line = m.get("lineNumber", 0) + 1
|
|
66
|
+
preview = m.get("preview", "").rstrip()
|
|
67
|
+
matches.append({"line": line, "preview": preview, "url": f"{base_url}#L{line}"})
|
|
68
|
+
|
|
69
|
+
formatted.append(
|
|
70
|
+
{
|
|
71
|
+
"repository": clean_name,
|
|
72
|
+
"file": file_path,
|
|
73
|
+
"branch": branch_name,
|
|
74
|
+
"commit": commit_hash,
|
|
75
|
+
"matches": matches,
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return formatted
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_stats(search: dict) -> dict:
|
|
83
|
+
return {
|
|
84
|
+
"approximate_count": search.get("stats", {}).get("approximateResultCount", "0"),
|
|
85
|
+
"match_count": search.get("results", {}).get("matchCount", 0),
|
|
86
|
+
"limit_hit": search.get("results", {}).get("limitHit", False),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _print_results(results: list[dict], stats: dict, json_output: bool) -> None:
|
|
91
|
+
if json_output:
|
|
92
|
+
print(json.dumps({"stats": stats, "results": results}, indent=2))
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
if not results:
|
|
96
|
+
console.print("[yellow]No results found.[/yellow]")
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
for file_result in results:
|
|
100
|
+
header = Text()
|
|
101
|
+
header.append(file_result["repository"], style="bold cyan")
|
|
102
|
+
header.append(" / ", style="dim")
|
|
103
|
+
header.append(file_result["file"], style="bold")
|
|
104
|
+
console.print(header)
|
|
105
|
+
|
|
106
|
+
for match in file_result["matches"]:
|
|
107
|
+
line_text = Text()
|
|
108
|
+
line_text.append(f" L{match['line']:>5}", style="green")
|
|
109
|
+
line_text.append(f" {match['preview']}")
|
|
110
|
+
console.print(line_text)
|
|
111
|
+
console.print(f" [dim]{match['url']}[/dim]")
|
|
112
|
+
|
|
113
|
+
console.print()
|
|
114
|
+
|
|
115
|
+
limit_msg = " (limit hit — use --count to fetch more)" if stats.get("limit_hit") else ""
|
|
116
|
+
console.print(
|
|
117
|
+
f"[dim]{stats['approximate_count']} approximate results, {stats['match_count']} matches returned{limit_msg}[/dim]"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.callback(invoke_without_command=True)
|
|
122
|
+
@tracking.track_command()
|
|
123
|
+
def code_search(
|
|
124
|
+
query: Annotated[str, typer.Argument(help="Search query (supports Sourcegraph syntax)")],
|
|
125
|
+
repo: Annotated[
|
|
126
|
+
str | None,
|
|
127
|
+
typer.Option("--repo", "-r", help="Filter by repository (e.g. ComfyUI, Comfy-Org/ComfyUI)"),
|
|
128
|
+
] = None,
|
|
129
|
+
count: Annotated[
|
|
130
|
+
int,
|
|
131
|
+
typer.Option("--count", "-n", help="Maximum number of results"),
|
|
132
|
+
] = DEFAULT_COUNT,
|
|
133
|
+
json_output: Annotated[
|
|
134
|
+
bool,
|
|
135
|
+
typer.Option("--json", "-j", help="Output results as JSON"),
|
|
136
|
+
] = False,
|
|
137
|
+
):
|
|
138
|
+
"""Search code across ComfyUI repositories."""
|
|
139
|
+
built_query = _build_query(query, repo, count)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
data = _fetch_results(built_query)
|
|
143
|
+
except requests.ConnectionError:
|
|
144
|
+
console.print("[bold red]Error: Could not connect to the code search service.[/bold red]")
|
|
145
|
+
raise typer.Exit(code=1)
|
|
146
|
+
except requests.Timeout:
|
|
147
|
+
console.print("[bold red]Error: Request timed out.[/bold red]")
|
|
148
|
+
raise typer.Exit(code=1)
|
|
149
|
+
except requests.HTTPError as e:
|
|
150
|
+
status = e.response.status_code if e.response is not None else "unknown"
|
|
151
|
+
console.print(f"[bold red]Error: HTTP {status}[/bold red]")
|
|
152
|
+
raise typer.Exit(code=1)
|
|
153
|
+
|
|
154
|
+
search = data.get("data", {}).get("search", {})
|
|
155
|
+
results = _format_results(search)
|
|
156
|
+
stats = _get_stats(search)
|
|
157
|
+
_print_results(results, stats, json_output=json_output)
|
|
@@ -122,6 +122,22 @@ set of nodes to test: {len(self.active)}
|
|
|
122
122
|
{active_list}"""
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def parse_cm_output(cm_output: str, pinned_nodes: set[str] | None = None) -> list[str]:
|
|
126
|
+
"""Parse cm_cli simple-show output into a list of node names.
|
|
127
|
+
|
|
128
|
+
cm_cli simple-show always formats node entries as ``name@version``
|
|
129
|
+
(see ComfyUI-Manager cm_cli show_list). We whitelist on the ``@``
|
|
130
|
+
separator so any progress/status lines are ignored regardless of
|
|
131
|
+
their prefix.
|
|
132
|
+
"""
|
|
133
|
+
pinned = pinned_nodes or set()
|
|
134
|
+
return [
|
|
135
|
+
stripped
|
|
136
|
+
for line in cm_output.strip().split("\n")
|
|
137
|
+
if (stripped := line.strip()) and "@" in stripped and stripped not in pinned
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
|
|
125
141
|
@bisect_app.command(
|
|
126
142
|
help="Start a new bisect session with optionally pinned nodes to always enable, and optional ComfyUI launch args."
|
|
127
143
|
+ "?[--pinned-nodes PINNED_NODES]"
|
|
@@ -145,11 +161,7 @@ def start(
|
|
|
145
161
|
typer.echo("Failed to fetch the list of nodes.")
|
|
146
162
|
raise typer.Exit()
|
|
147
163
|
|
|
148
|
-
nodes_list =
|
|
149
|
-
line.strip()
|
|
150
|
-
for line in cm_output.strip().split("\n")
|
|
151
|
-
if not line.startswith("FETCH DATA") and line.strip() not in pinned_nodes
|
|
152
|
-
]
|
|
164
|
+
nodes_list = parse_cm_output(cm_output, pinned_nodes)
|
|
153
165
|
state = BisectState(
|
|
154
166
|
status="running",
|
|
155
167
|
all=nodes_list,
|
|
@@ -18,6 +18,7 @@ from comfy_cli import constants, ui
|
|
|
18
18
|
from comfy_cli.command.custom_nodes.command import update_node_id_cache
|
|
19
19
|
from comfy_cli.command.github.pr_info import PRInfo
|
|
20
20
|
from comfy_cli.constants import GPU_OPTION
|
|
21
|
+
from comfy_cli.cuda_detect import DEFAULT_CUDA_TAG
|
|
21
22
|
from comfy_cli.git_utils import checkout_pr, git_checkout_tag
|
|
22
23
|
from comfy_cli.resolve_python import ensure_workspace_python
|
|
23
24
|
from comfy_cli.uv import DependencyCompiler
|
|
@@ -45,11 +46,12 @@ def pip_install_comfyui_dependencies(
|
|
|
45
46
|
repo_dir,
|
|
46
47
|
gpu: GPU_OPTION,
|
|
47
48
|
plat: constants.OS,
|
|
48
|
-
cuda_version: constants.CUDAVersion,
|
|
49
|
+
cuda_version: constants.CUDAVersion | None,
|
|
49
50
|
skip_torch_or_directml: bool,
|
|
50
51
|
skip_requirement: bool,
|
|
51
52
|
python: str = sys.executable,
|
|
52
53
|
rocm_version: constants.ROCmVersion = constants.ROCmVersion.v6_3,
|
|
54
|
+
cuda_tag: str | None = None,
|
|
53
55
|
):
|
|
54
56
|
os.chdir(repo_dir)
|
|
55
57
|
|
|
@@ -63,7 +65,8 @@ def pip_install_comfyui_dependencies(
|
|
|
63
65
|
|
|
64
66
|
# install torch for NVIDIA
|
|
65
67
|
if gpu == GPU_OPTION.NVIDIA:
|
|
66
|
-
cuda_tag
|
|
68
|
+
if cuda_tag is None:
|
|
69
|
+
cuda_tag = f"cu{cuda_version.value.replace('.', '')}" if cuda_version else DEFAULT_CUDA_TAG
|
|
67
70
|
result = _pip_install_torch(python, ["--index-url", f"https://download.pytorch.org/whl/{cuda_tag}"])
|
|
68
71
|
|
|
69
72
|
# install torch for Intel Arc GPUs (upstream torch xpu)
|
|
@@ -147,7 +150,8 @@ def execute(
|
|
|
147
150
|
version: str,
|
|
148
151
|
commit: str | None = None,
|
|
149
152
|
gpu: constants.GPU_OPTION = None,
|
|
150
|
-
cuda_version: constants.CUDAVersion =
|
|
153
|
+
cuda_version: constants.CUDAVersion | None = None,
|
|
154
|
+
cuda_tag: str | None = None,
|
|
151
155
|
rocm_version: constants.ROCmVersion = constants.ROCmVersion.v6_3,
|
|
152
156
|
plat: constants.OS = None,
|
|
153
157
|
skip_torch_or_directml: bool = False,
|
|
@@ -227,6 +231,7 @@ def execute(
|
|
|
227
231
|
skip_requirement,
|
|
228
232
|
python=python,
|
|
229
233
|
rocm_version=rocm_version,
|
|
234
|
+
cuda_tag=cuda_tag,
|
|
230
235
|
)
|
|
231
236
|
|
|
232
237
|
WorkspaceManager().set_recent_workspace(repo_dir)
|
|
@@ -256,11 +261,19 @@ def execute(
|
|
|
256
261
|
# Workspace venv needs uv bootstrapped; for the global Python
|
|
257
262
|
# uv is already available as a comfy-cli dependency.
|
|
258
263
|
DependencyCompiler.Install_Build_Deps(executable=python)
|
|
264
|
+
if cuda_tag:
|
|
265
|
+
# DependencyCompiler expects a dotted version like "13.0", not a tag like "cu130"
|
|
266
|
+
digits = cuda_tag[2:]
|
|
267
|
+
resolved_cuda = f"{digits[:2]}.{digits[2:]}"
|
|
268
|
+
elif cuda_version:
|
|
269
|
+
resolved_cuda = cuda_version.value
|
|
270
|
+
else:
|
|
271
|
+
resolved_cuda = None
|
|
259
272
|
depComp = DependencyCompiler(
|
|
260
273
|
cwd=repo_dir,
|
|
261
274
|
executable=python,
|
|
262
275
|
gpu=gpu,
|
|
263
|
-
cuda_version=
|
|
276
|
+
cuda_version=resolved_cuda,
|
|
264
277
|
rocm_version=rocm_version.value,
|
|
265
278
|
skip_torch=skip_torch_or_directml,
|
|
266
279
|
)
|
|
@@ -463,8 +476,16 @@ def get_latest_release(repo_owner: str, repo_name: str) -> GithubRelease | None:
|
|
|
463
476
|
"""
|
|
464
477
|
url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest"
|
|
465
478
|
|
|
479
|
+
headers = {}
|
|
480
|
+
if github_token := os.getenv("GITHUB_TOKEN"):
|
|
481
|
+
headers["Authorization"] = f"Bearer {github_token}"
|
|
482
|
+
|
|
466
483
|
try:
|
|
467
|
-
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
|
+
|
|
468
489
|
response.raise_for_status()
|
|
469
490
|
|
|
470
491
|
data = response.json()
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
|
|
@@ -33,6 +34,18 @@ def get_workspace() -> pathlib.Path:
|
|
|
33
34
|
return pathlib.Path(workspace_manager.workspace_path)
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def _format_elapsed(seconds: float) -> str:
|
|
38
|
+
"""Format elapsed seconds into a human-readable string."""
|
|
39
|
+
rounded = round(seconds, 1)
|
|
40
|
+
if rounded < 60:
|
|
41
|
+
return f"{rounded:.1f}s"
|
|
42
|
+
minutes, secs = divmod(int(rounded), 60)
|
|
43
|
+
if minutes < 60:
|
|
44
|
+
return f"{minutes}m {secs}s"
|
|
45
|
+
hours, minutes = divmod(minutes, 60)
|
|
46
|
+
return f"{hours}h {minutes}m {secs}s"
|
|
47
|
+
|
|
48
|
+
|
|
36
49
|
def potentially_strip_param_url(path_name: str) -> str:
|
|
37
50
|
return path_name.split("?")[0]
|
|
38
51
|
|
|
@@ -219,6 +232,14 @@ def download(
|
|
|
219
232
|
show_default=False,
|
|
220
233
|
),
|
|
221
234
|
] = None,
|
|
235
|
+
downloader: Annotated[
|
|
236
|
+
str | None,
|
|
237
|
+
typer.Option(
|
|
238
|
+
"--downloader",
|
|
239
|
+
help="Download backend: 'httpx' (default) or 'aria2' (requires aria2 RPC server).",
|
|
240
|
+
show_default=False,
|
|
241
|
+
),
|
|
242
|
+
] = None,
|
|
222
243
|
):
|
|
223
244
|
if relative_path is not None:
|
|
224
245
|
relative_path = os.path.expanduser(relative_path)
|
|
@@ -233,6 +254,8 @@ def download(
|
|
|
233
254
|
constants.HF_API_TOKEN_ENV_KEY, constants.HF_API_TOKEN_KEY, set_hf_api_token
|
|
234
255
|
)
|
|
235
256
|
|
|
257
|
+
resolved_downloader = downloader or config_manager.get(constants.CONFIG_KEY_DEFAULT_DOWNLOADER) or "httpx"
|
|
258
|
+
|
|
236
259
|
is_civitai_model_url, is_civitai_api_url, model_id, version_id = check_civitai_url(url)
|
|
237
260
|
is_huggingface_url, repo_id, hf_filename, hf_folder_name, hf_branch_name = check_huggingface_url(url)
|
|
238
261
|
|
|
@@ -297,6 +320,8 @@ def download(
|
|
|
297
320
|
print(f"[bold red]File already exists: {local_filepath}[/bold red]")
|
|
298
321
|
return
|
|
299
322
|
|
|
323
|
+
start_time = time.monotonic()
|
|
324
|
+
|
|
300
325
|
if is_huggingface_url and check_unauthorized(url, headers):
|
|
301
326
|
if hf_api_token is None:
|
|
302
327
|
print(
|
|
@@ -329,7 +354,10 @@ def download(
|
|
|
329
354
|
print(f"Model downloaded successfully to: {output_path}")
|
|
330
355
|
else:
|
|
331
356
|
print(f"Start downloading URL: {url} into {local_filepath}")
|
|
332
|
-
download_file(url, local_filepath, headers)
|
|
357
|
+
download_file(url, local_filepath, headers, downloader=resolved_downloader)
|
|
358
|
+
|
|
359
|
+
elapsed = time.monotonic() - start_time
|
|
360
|
+
print(f"Done in {_format_elapsed(elapsed)}")
|
|
333
361
|
|
|
334
362
|
|
|
335
363
|
@app.command()
|
|
@@ -50,6 +50,10 @@ CIVITAI_API_TOKEN_ENV_KEY = "CIVITAI_API_TOKEN"
|
|
|
50
50
|
HF_API_TOKEN_KEY = "hf_api_token"
|
|
51
51
|
HF_API_TOKEN_ENV_KEY = "HF_API_TOKEN"
|
|
52
52
|
|
|
53
|
+
ARIA2_SERVER_ENV_KEY = "COMFYUI_MANAGER_ARIA2_SERVER"
|
|
54
|
+
ARIA2_SECRET_ENV_KEY = "COMFYUI_MANAGER_ARIA2_SECRET"
|
|
55
|
+
CONFIG_KEY_DEFAULT_DOWNLOADER = "default_downloader"
|
|
56
|
+
|
|
53
57
|
DEFAULT_TRACKING_VALUE = True
|
|
54
58
|
|
|
55
59
|
COMFY_LOCK_YAML_FILE = "comfy.lock.yaml"
|
|
@@ -72,7 +76,9 @@ COMFY_ORIGIN_URL_CHOICES = {
|
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
class CUDAVersion(str, Enum):
|
|
79
|
+
v13_0 = "13.0"
|
|
75
80
|
v12_9 = "12.9"
|
|
81
|
+
v12_8 = "12.8"
|
|
76
82
|
v12_6 = "12.6"
|
|
77
83
|
v12_4 = "12.4"
|
|
78
84
|
v12_1 = "12.1"
|