Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions comfy_cli/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from comfy_cli.config_manager import ConfigManager
from comfy_cli.constants import GPU_OPTION, CUDAVersion, ROCmVersion
from comfy_cli.cuda_detect import DEFAULT_CUDA_TAG, detect_cuda_driver_version, resolve_cuda_wheel
from comfy_cli.deprecation import add_deprecated_alias
from comfy_cli.discovery import build_discovery
from comfy_cli.env_checker import EnvChecker
from comfy_cli.help_json import build_help_json
Expand Down Expand Up @@ -1612,12 +1613,20 @@ def standalone(


generate_command.register_with(app)
app.add_typer(models_command.app, name="model", help="Manage models.")
# The `model` noun owns BOTH the local-filesystem ops (download/remove/list) and
# the backend/cloud discovery leaves (list-folders/list-folder/search/show). The
# discovery leaves are implemented on `models_search_command.app`; surface them
# under `model` by borrowing their command registrations (same CommandInfo
# objects — no logic duplication).
models_command.app.registered_commands.extend(models_search_command.app.registered_commands)
app.add_typer(
models_search_command.app,
name="models",
help="Discover models — folders, files, and the cloud asset catalog.",
models_command.app,
name="model",
help="Manage models — local files on disk plus backend/cloud discovery.",
)
# `models` (plural) is now a hidden, deprecated alias for the discovery leaves.
# Every old `comfy models …` invocation still works but prints a warning.
add_deprecated_alias(app, models_search_command.app, old_name="models", new_name="model")
app.add_typer(custom_nodes.app, name="node", help="Manage custom nodes.")
app.add_typer(nodes_command.app, name="nodes", help="Introspect ComfyUI node classes (inputs, outputs, categories).")
app.add_typer(templates_command.app, name="templates", help="Browse the Comfy workflow-template gallery.")
Expand Down
6 changes: 5 additions & 1 deletion comfy_cli/command/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,11 @@ def list_models(path: pathlib.Path) -> list[pathlib.Path]:
return sorted(f for f in path.rglob("*") if f.is_file())


@app.command("list")
@app.command(
"list",
help="List local models on disk (files already downloaded into the current workspace). "
"For backend/cloud discovery use `comfy model list-folders` / `list-folder`.",
)
@tracking.track_command("model")
def list_command(
ctx: typer.Context,
Expand Down
8 changes: 6 additions & 2 deletions comfy_cli/command/models/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ def _emit_http_error(e: urllib.error.HTTPError, *, renderer, target, message: st

@app.command(
"list-folders",
help="List model folders available to the resolved backend (cloud: /api/experiment/models, local: /models).",
help="List model folders on the backend/cloud — the resolved server "
"(cloud: /api/experiment/models, local server: /models). For files already "
"on disk in your workspace, use `comfy model list`.",
)
@tracking.track_command("models")
def list_folders_cmd(
Expand Down Expand Up @@ -216,7 +218,9 @@ def list_folders_cmd(

@app.command(
"list-folder",
help="List model files in a specific folder. Returns name + pathIndex per entry — no enrichment.",
help="List model files in a specific folder on the backend/cloud (the resolved "
"server). Returns name + pathIndex per entry — no enrichment. For files already "
"on disk in your workspace, use `comfy model list`.",
)
@tracking.track_command("models")
def list_folder_cmd(
Expand Down
99 changes: 99 additions & 0 deletions comfy_cli/deprecation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Shared helper for wiring hidden, deprecated command aliases.

A deprecation alias keeps an old command/group spelling working while steering
users to the new one. Three things happen to an alias:

(a) it is hidden from ``--help`` (so the surface advertises only the new name);
(b) its group help is prefixed with a ``[DEPRECATED — use <new>]`` banner; and
(c) invoking any leaf under it prints a single ``[yellow]`` warning line to
stderr.

The warning is never silenced — discoverability of the rename is the whole
point, and stderr keeps it out of the one-envelope-on-stdout contract.

Introduced for the ``models`` → ``model`` merge (BE-2999); the sibling noun
consolidations reuse :func:`add_deprecated_alias`.
"""

from __future__ import annotations

import functools

import typer

# Use the output shim so the warning lands on stderr (not stdout) in JSON mode,
# preserving the one-envelope-on-stdout contract.
from comfy_cli.output import rprint


def deprecated_help(new_name: str, original_help: str | None = None) -> str:
"""Prefix ``original_help`` with the deprecation banner.

``new_name`` is the command path *without* the ``comfy`` prog prefix
(e.g. ``"model"``); the banner renders it as ``comfy <new_name>``.
"""
banner = f"[DEPRECATED — use `comfy {new_name}`]"
original = (original_help or "").strip()
return f"{banner} {original}".strip()


def warn_deprecated(old_name: str, new_name: str) -> None:
"""Emit the one-line yellow deprecation warning to stderr."""
rprint(f"[yellow]'comfy {old_name} …' is deprecated; use 'comfy {new_name} …'[/yellow]")


def add_deprecated_alias(
parent: typer.Typer,
source_app: typer.Typer,
*,
old_name: str,
new_name: str,
) -> typer.Typer:
"""Mount a hidden, deprecated alias of ``source_app`` on ``parent``.

The alias reuses ``source_app``'s command implementations verbatim (no logic
duplication) and adds a group callback that prints one yellow warning to
stderr before any leaf runs. ``source_app`` itself is left untouched — the
alias is a fresh Typer that borrows the registered commands — so the canonical
mount of the same commands stays warning-free.

Both leaf commands *and* nested sub-groups (``add_typer``) are carried over,
and if ``source_app`` declares its own group callback the alias composes it
with the deprecation warning rather than dropping it — so the helper stays
correct as the sibling noun consolidations reuse it against richer command
trees.

Returns the alias app (useful for tests).
"""
alias = typer.Typer(
no_args_is_help=True,
help=deprecated_help(new_name, source_app.info.help),
)
# Borrow the exact command + sub-group registrations — same CommandInfo /
# TyperInfo objects, so the alias and the canonical surface stay in lockstep
# with zero duplication.
alias.registered_commands.extend(source_app.registered_commands)
alias.registered_groups.extend(source_app.registered_groups)

# The alias always needs a group callback to emit the deprecation warning.
# If the source app declared one of its own (with its own options / setup),
# compose the two — warn first, then delegate — preserving the source's
# callback signature so its CLI options still resolve on the alias.
source_cb_info = source_app.registered_callback
source_cb = getattr(source_cb_info, "callback", None) if source_cb_info else None

if source_cb is None:

@alias.callback()
def _emit_deprecation_warning() -> None:
warn_deprecated(old_name, new_name)
else:

@alias.callback()
@functools.wraps(source_cb)
def _emit_deprecation_warning(*args, **kwargs):
warn_deprecated(old_name, new_name)
return source_cb(*args, **kwargs)

parent.add_typer(alias, name=old_name, hidden=True)
return alias
8 changes: 8 additions & 0 deletions comfy_cli/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@
"comfy skill show": "skill",
"comfy skill status": "skill",
# model discovery (all asset types: checkpoints, loras, controlnets, vae, ...)
# Canonical spelling under the `model` noun.
"comfy model search": "models",
"comfy model show": "models",
"comfy model list-folders": "models",
"comfy model list-folder": "models",
# `comfy models` is the hidden, deprecated plural alias; the discovery
# envelopes still carry the `models …` form in `command`, so both spellings
# register (mirrors the `skill`/`skills` alias above).
"comfy models search": "models",
"comfy models show": "models",
"comfy models list-folders": "models",
Expand Down
44 changes: 22 additions & 22 deletions comfy_cli/skills/comfy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,23 +347,23 @@ If no local server is running and you're not signed into cloud, pass

## Models — find what's installed, with metadata

On **cloud**, `comfy models search` hits the live asset catalog
On **cloud**, `comfy model search` hits the live asset catalog
(`/api/assets`) and returns enriched rows: `name`, `type`, `tags`,
`base_model`, `source_url`, `preview_url`, `size`. On **local**, the same
command falls back to `/models/<folder>` listings (filenames only).

```bash
comfy --json models list-folders # every model folder (loras, checkpoints, vae, …)
comfy --json models list-folder loras # files in a folder, with pathIndex
comfy --json models search --text "wan2.2" --type lora --limit 10
comfy --json models search --text "flux" # text search across the catalog
comfy --json models show <rows[0].name> # full Asset + projected row (cloud-only)
comfy --json model list-folders # every model folder (loras, checkpoints, vae, …)
comfy --json model list-folder loras # files in a folder, with pathIndex
comfy --json model search --text "wan2.2" --type lora --limit 10
comfy --json model search --text "flux" # text search across the catalog
comfy --json model show <rows[0].name> # full Asset + projected row (cloud-only)
```

`models search --type <X>` accepts the conventional folder names
`model search --type <X>` accepts the conventional folder names
(`lora`/`loras`, `checkpoint`/`checkpoints`, `vae`, `controlnet`,
`upscale`, `clip`, `clip_vision`, `unet`/`diffusion_models`, …). Use
`models list-folders` first if you're unsure what types the backend
`model list-folders` first if you're unsure what types the backend
exposes.

**Discover → wire loop — every asset type, never hardcoded names:**
Expand All @@ -376,12 +376,12 @@ pattern is the same regardless of type:

```bash
# 1. Discover available assets for any type
comfy --json models search --type lora --where cloud --text "detail" --limit 5
comfy --json models search --type controlnet --where cloud --limit 5
comfy --json models search --type checkpoint --where cloud --limit 5
comfy --json models search --type vae --where cloud --limit 5
comfy --json models search --type upscale --where cloud --limit 5
comfy --json models search --type embeddings --where cloud --limit 5
comfy --json model search --type lora --where cloud --text "detail" --limit 5
comfy --json model search --type controlnet --where cloud --limit 5
comfy --json model search --type checkpoint --where cloud --limit 5
comfy --json model search --type vae --where cloud --limit 5
comfy --json model search --type upscale --where cloud --limit 5
comfy --json model search --type embeddings --where cloud --limit 5

# 2. Take rows[0].name verbatim — paste it into your fragment's required param

Expand All @@ -396,8 +396,8 @@ recommendation.** On this backend, today, the survey returned the rows
sketched below; yours will differ — pick from YOUR rows:

```bash
comfy --json models search --type checkpoint --where cloud --limit 5 # → picked <ckpt> from rows
comfy --json models search --type lora --where cloud --limit 5 # → picked <lora> from rows
comfy --json model search --type checkpoint --where cloud --limit 5 # → picked <ckpt> from rows
comfy --json model search --type lora --where cloud --limit 5 # → picked <lora> from rows
# Learn the lora wiring from a real graph, not memory — fetch a matching template:
comfy --json templates ls --type image --model "<family of <ckpt>, from its row>"
comfy templates fetch <name-from-those-rows> --out ref.json # read how it wires
Expand Down Expand Up @@ -481,11 +481,11 @@ comfy --json nodes ls --produces AUDIO --limit 1 # AUDIO producer count
comfy --json nodes ls --api-only --limit 1 # partner API node count
comfy --json nodes categories --prefix "partner"# API provider categories
comfy --json nodes types # all connection types
comfy --json models list-folders # all model folders
comfy --json model list-folders # all model folders
comfy --json templates ls --limit 1 # template count
```

The `total` field in `nodes ls`, `nodes search`, and `models search`
The `total` field in `nodes ls`, `nodes search`, and `model search`
gives the full count even when `--limit` caps the returned rows.

## Workflows — what can I tweak?
Expand Down Expand Up @@ -685,8 +685,8 @@ comfy --json nodes show <ClassName>
# If error.code == "node_not_found", check details.close_matches

# Confirm a model filename is actually available on the resolved backend (cloud-only)
# On local: use `comfy models list-folder <type>` instead
comfy --json models show <filename>
# On local: use `comfy model list-folder <type>` instead
comfy --json model show <filename>
# If error.code == "model_not_found", check details.close_matches and pick one
```

Expand Down Expand Up @@ -849,11 +849,11 @@ Hard-won lessons per domain. Not a tutorial — a reference card.

## Image

- Survey first: `comfy nodes ls --produces IMAGE --api-only` (partner APIs), `comfy templates ls --type image`, `comfy models search --type checkpoint` — then choose
- Survey first: `comfy nodes ls --produces IMAGE --api-only` (partner APIs), `comfy templates ls --type image`, `comfy model search --type checkpoint` — then choose
- Batch sweeps: `comfy workflow vary` for multi-prompt/seed generation
- Text rendering: use Ideogram (IdeogramV3), NOT Flux — Flux garbles text
- Partner API escape hatch (one-shots only, via the proxy — not a workflow Job): `comfy generate flux-ultra --prompt "..."`
- Never hardcode checkpoint/LoRA names — discover via `models search`
- Never hardcode checkpoint/LoRA names — discover via `model search`

## Video

Expand Down
Loading
Loading