diff --git a/AGENTS.md b/AGENTS.md index e4c4f4f..99a8103 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,10 +2,26 @@ ## Project Overview -**cm-cli-rest** is a ComfyUI custom node that exposes REST API endpoints for managing ComfyUI custom nodes via `cm-cli` (ComfyUI-Manager CLI). +**cm-cli-rest** is a ComfyUI custom node that exposes REST API endpoints for managing ComfyUI custom nodes via `cm-cli` (ComfyUI-Manager CLI) and `comfy-cli`. + +### Dual CLI Support + +The project supports **two CLI tools**: + +1. **cm-cli** (ComfyUI-Manager CLI) - Node management focused + - Endpoints: `/cm-cli-rest/*` + - Package: `cm_cli/` + - Provides: Node install/update/uninstall, snapshots + +2. **comfy-cli** - Full ComfyUI management + - Endpoints: `/comfy-cli/*` + - Package: `comfy_cli/` + - Provides: Config, models, nodes, ComfyUI lifecycle, workflows + +### Environment - **ComfyUI Version**: 0.15+ -- **ComfyUI-Manager Version**: 4.0+ +- **ComfyUI-Manager Version**: 4.0+ (provides cm-cli) - **Python Version**: 3.9+ (tested on 3.11) - **Runtime**: Integrated with ComfyUI's aiohttp server (no separate port) @@ -13,27 +29,39 @@ ## Build & Development Commands -### Syntax Validation +### Syntax Validation (REQUIRED before commit) + ```bash -# Check Python syntax for a single file +# Check single file python -m py_compile cm_cli/executor.py -# Check all Python files -find . -name "*.py" -not -path "./__pycache__/*" -exec python -m py_compile {} \; +# Check ALL Python files (CI-style) +find . -name "*.py" -not -path "./__pycache__/*" -not -path "./.git/*" \ + -exec python -m py_compile {} \; + +# Quick validation of new comfy-cli modules +python -m py_compile comfy_cli/executor.py comfy_cli_routes.py \ + api/handlers/config.py api/handlers/models.py api/handlers/comfy_nodes.py ``` -### Type Checking (if type annotations added) +### Type Checking (Optional but Recommended) + ```bash -# Install mypy (optional - not in requirements.txt) +# Install mypy pip install mypy -# Run type checking -mypy cm_cli/ api/ +# Run type checking on specific modules +mypy cm_cli/ api/handlers/ +mypy comfy_cli/ + +# Strict mode (for new code) +mypy --strict cm_cli/executor.py ``` -### Linting (if linter added) +### Linting & Formatting + ```bash -# Install ruff (recommended) +# Install ruff pip install ruff # Lint all files @@ -44,28 +72,61 @@ ruff check --fix . # Format code ruff format . + +# Check specific file +ruff check cm_cli/executor.py ``` ### Testing -**No test suite exists yet** (as of 2026-03-16). Tests are marked as TODO in IMPLEMENTATION_PLAN.md. -To add tests: -1. Create `tests/` directory -2. Use `pytest` as the test framework -3. Run with: `pytest tests/` or `pytest tests/test_specific_file.py -v` +**Current Status**: No test suite exists (as of 2026-03-17). Tests marked as TODO. + +**Manual Testing** (after changes): +```bash +# 1. Restart ComfyUI +# 2. Test endpoints with curl + +# Health check +curl http://localhost:8188/cm-cli-rest/health + +# List nodes (cm-cli) +curl http://localhost:8188/cm-cli-rest/nodes + +# List models (comfy-cli) +curl "http://localhost:8188/comfy-cli/model/list?relative_path=models/checkpoints" + +# Install node (async) +curl -X POST http://localhost:8188/cm-cli-rest/nodes/install \ + -H "Content-Type: application/json" \ + -d '{"name": "ComfyUI-Impact-Pack"}' +``` + +**Adding Tests** (future): +```bash +# Create tests/ directory +mkdir tests + +# Use pytest +pip install pytest pytest-asyncio + +# Run tests +pytest tests/ +pytest tests/test_specific.py -v +``` --- ## Code Style Guidelines -### Imports -- **Order**: Standard library → Third-party → Local imports -- **Grouping**: Separate import groups with blank lines -- **Type imports**: Use `from typing import ...` for type hints -- **Relative imports**: Use for local modules (`.`, `..`) +### Imports (STRICT) + +**Order**: Standard library → Third-party → Local imports +**Grouping**: Separate groups with blank lines +**Type imports**: Use `from typing import ...` +**Relative imports**: Use for local modules (`.`, `..`) ```python -# ✅ Correct +# ✅ CORRECT import asyncio import logging from typing import Dict, List, Optional @@ -73,21 +134,34 @@ from typing import Dict, List, Optional from aiohttp import web from ..cm_cli import CMCLIExecutor +from ..comfy_cli import ComfyCliExecutor +``` + +```python +# ❌ WRONG - Mixed import groups +from aiohttp import web +import asyncio +from ..cm_cli import CMCLIExecutor +from typing import Optional ``` ### Formatting -- **Indentation**: 4 spaces (no tabs) + +- **Indentation**: 4 spaces (NO tabs) - **Line length**: 100 characters (soft limit) - **Quotes**: Double quotes for strings - **Trailing commas**: Use in multi-line structures +- **Blank lines**: 2 between classes/functions, 1 between methods -### Type Hints -- **Required** for function parameters and return types -- **Use `Optional[T]`** for nullable values (Python 3.9+) -- **Use `Dict`, `List`, `Any`** from typing module +### Type Hints (REQUIRED) + +**Required for**: +- All function parameters +- All return types +- Class attributes ```python -# ✅ Correct +# ✅ CORRECT async def execute( self, args: List[str], @@ -95,53 +169,101 @@ async def execute( ) -> Dict[str, Any]: ... -# ❌ Avoid (no type hints) +# ❌ AVOID - No type hints async def execute(self, args, timeout=None): ... ``` +**Use specific types**: +```python +# ✅ Prefer specific types +def parse_output(raw: str) -> List[Dict[str, str]]: + ... + +# ❌ Avoid overly generic +def parse_output(raw: Any) -> Any: + ... +``` + ### Naming Conventions -- **Classes**: PascalCase (`CMCLIExecutor`, `NodesHandler`) -- **Functions/Methods**: snake_case (`list_nodes`, `execute_async`) -- **Constants**: UPPER_CASE (`JOB_NOT_FOUND`, `MAX_CONCURRENT_JOBS`) -- **Private methods**: Leading underscore (`_auto_detect_cm_cli`) -- **Exceptions**: End with `Error` (`CMCLIError`) + +| Type | Convention | Examples | +|------|-----------|----------| +| Classes | PascalCase | `CMCLIExecutor`, `ComfyCliExecutor`, `NodesHandler` | +| Functions/Methods | snake_case | `list_nodes`, `execute_async`, `_auto_detect` | +| Constants | UPPER_CASE | `JOB_NOT_FOUND`, `MAX_CONCURRENT_JOBS` | +| Private methods | Leading underscore | `_auto_detect_cm_cli`, `_build_command` | +| Exceptions | End with `Error` | `CMCLIError`, `ComfyCLIError` | +| Variables | snake_case | `workspace_path`, `job_id` | ### Error Handling -- **Custom exceptions**: Create specific exception classes for domain errors -- **Error responses**: Always return structured JSON with `success`, `error.code`, `error.message` -- **Logging**: Log errors with `logger.error()` before returning -- **Never suppress exceptions** without logging + +**Pattern**: +1. Custom exceptions for domain errors +2. Structured JSON responses with `success`, `error.code`, `error.message` +3. Log errors BEFORE returning +4. Never suppress exceptions without logging ```python -# ✅ Correct pattern +# ✅ CORRECT PATTERN try: result = await self.executor.execute(["show", "installed"]) if not result["success"]: + logger.error("Failed to list nodes: %s", result["stderr"]) return web.json_response( {"success": False, "error": {"code": "CM_CLI_ERROR", "message": "Failed"}}, status=500, ) except CMCLIError as e: - logger.error(f"Error listing nodes: {e}") + logger.error("CMCLIError in list_nodes: %s", e.message) return web.json_response( {"success": False, "error": {"code": "CM_CLI_ERROR", "message": e.message}}, status=500, ) +except Exception as e: + logger.exception("Unexpected error in list_nodes") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) +``` + +```python +# ❌ WRONG - Silent exception +try: + result = await self.executor.execute(args) +except Exception: + pass # NEVER DO THIS ``` ### Async/Await -- **All I/O operations**: Use async (HTTP, subprocess, file I/O) -- **Timeout handling**: Always wrap with `asyncio.wait_for()` -- **Subprocess execution**: Use `asyncio.create_subprocess_exec()` -### Docstrings -- **Required** for all public classes, methods, and functions -- **Format**: Google style (Args, Returns, Raises sections) -- **Include examples** for complex methods +**Rules**: +- All I/O operations MUST be async (HTTP, subprocess, file I/O) +- Always wrap with `asyncio.wait_for()` for timeouts +- Use `asyncio.create_subprocess_exec()` for subprocesses +- Long-running operations → use `execute_async()` with job tracking ```python -# ✅ Correct +# ✅ CORRECT +async def execute(self, args: List[str], timeout: Optional[int] = None) -> Dict[str, Any]: + process = await asyncio.create_subprocess_exec( + *command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await asyncio.wait_for( + process.communicate(), + timeout=timeout, + ) +``` + +### Docstrings (REQUIRED) + +**Format**: Google style with Args, Returns, Raises sections + +```python +# ✅ CORRECT async def execute( self, args: List[str], @@ -160,91 +282,126 @@ async def execute( Raises: CMCLIError: If command execution fails. + + Example: + >>> result = await executor.execute(["show", "installed"]) + >>> print(result["success"]) + True """ ``` ### File Organization + - **One class per file** (or tightly related classes) -- **Handlers**: One handler class per endpoint group (`nodes.py`, `snapshots.py`) -- **Exports**: Define `__all__` in `__init__.py` files +- **Handlers**: One handler per endpoint group +- **Exports**: Define `__all__` in `__init__.py` + +``` +cm-cli-rest/ +├── cm_cli/ # cm-cli integration +│ ├── __init__.py +│ └── executor.py +├── comfy_cli/ # comfy-cli integration +│ ├── __init__.py +│ └── executor.py +├── api/ +│ ├── routes.py # cm-cli routes +│ └── handlers/ +│ ├── nodes.py # Node management (cm-cli) +│ ├── snapshots.py # Snapshots (cm-cli) +│ ├── jobs.py # Job tracking (shared) +│ ├── config.py # Config (comfy-cli) +│ ├── models.py # Models (comfy-cli) +│ └── comfy_nodes.py # Nodes (comfy-cli) +├── comfy_cli_routes.py # comfy-cli route registration +└── __init__.py # Main entry point +``` --- ## Architecture Patterns -### Handler Pattern +### Handler Pattern (STANDARD) + ```python class NodesHandler: + """Handles node management REST API endpoints.""" + def __init__(self, executor: CMCLIExecutor): + """Initialize with shared executor.""" self.executor = executor async def list_nodes(self, request: web.Request) -> web.Response: - # Handler logic + """GET /cm-cli-rest/nodes""" + try: + result = await self.executor.execute(["show", "installed"]) + # ... process result + except Exception as e: + logger.error("list_nodes failed: %s", e) + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) ``` ### Executor Pattern -```python -class CMCLIExecutor: - def __init__(self, cm_cli_path: Optional[str] = None): - # Auto-detect cm-cli if not provided - self.cm_cli_path = self._auto_detect_cm_cli() - async def execute(self, args: List[str]) -> Dict[str, Any]: - # Execute command synchronously +```python +class ComfyCliExecutor: + """Executor for comfy-cli commands with async support.""" + + def __init__(self, workspace: Optional[str] = None): + self.workspace = Path(workspace) if workspace else Path.cwd() + self._active_jobs: Dict[str, ComfyCLIJob] = {} + + def _build_command(self, args: List[str]) -> List[str]: + """Build full command with workspace argument.""" + command = ["comfy", "--workspace", str(self.workspace)] + command.extend(args) + return command + + async def execute(self, args: List[str], timeout: Optional[int] = None) -> Dict[str, Any]: + """Execute command synchronously.""" + # ... + + async def execute_async(self, args: List[str], timeout: Optional[int] = None) -> ComfyCLIJob: + """Execute command asynchronously with job tracking.""" + # ... ``` -### Response Format -All responses follow this structure: +### Response Format (STANDARD) -**Success (2xx):** +**Success (2xx)**: ```json { "success": true, - "data": { ... } + "data": { + "nodes": [...], + "total": 5 + }, + "raw_output": "Optional CLI output" } ``` -**Error (4xx/5xx):** +**Error (4xx/5xx)**: ```json { "success": false, "error": { - "code": "ERROR_CODE", - "message": "Human-readable message", + "code": "NODE_NOT_FOUND", + "message": "Node 'invalid-node' not found", "details": "Optional additional context" } } ``` ---- - -## Key Files - -| File | Purpose | -|------|---------| -| `__init__.py` | Entry point, initializes executor, registers routes | -| `api/routes.py` | Route definitions | -| `api/handlers/nodes.py` | Node management endpoints | -| `api/handlers/snapshots.py` | Snapshot endpoints | -| `api/handlers/jobs.py` | Job tracking endpoints | -| `cm_cli/executor.py` | cm-cli subprocess execution | -| `config/config.json` | Server configuration | - ---- - -## Configuration - -Edit `config/config.json`: - +**Async (202 Accepted)**: ```json { - "enabled": true, - "api_key": null, - "cm_cli_path": null, - "python_path": "python", - "timeout": 300, - "max_concurrent_jobs": 3, - "logging_level": "INFO" + "success": true, + "job_id": "abc12345", + "status": "running", + "message": "Installation started" } ``` @@ -252,45 +409,138 @@ Edit `config/config.json`: ## Common Tasks -### Adding a New Endpoint -1. Add handler method in `api/handlers/nodes.py` (or appropriate handler file) +### Adding a New Endpoint (cm-cli) + +1. Add handler method in `api/handlers/nodes.py` (or appropriate file) 2. Register route in `api/routes.py` 3. Update README.md documentation 4. Add error codes to error response section +5. Run syntax check: `python -m py_compile api/handlers/nodes.py` -### Modifying cm-cli Execution -1. Edit `cm_cli/executor.py` -2. Ensure both `execute()` and `execute_async()` are updated -3. Run syntax check: `python -m py_compile cm_cli/executor.py` +### Adding a New Endpoint (comfy-cli) + +1. Add handler method in `api/handlers/comfy_nodes.py` (or `models.py`, `config.py`) +2. Register route in `comfy_cli_routes.py` +3. Update `COMFY_CLI_API.md` documentation +4. Run syntax check: `python -m py_compile api/handlers/comfy_nodes.py` + +### Modifying CLI Execution + +1. Edit `cm_cli/executor.py` or `comfy_cli/executor.py` +2. Update both `execute()` and `execute_async()` if needed +3. Add/update type hints +4. Run syntax validation ### Testing Changes -1. Restart ComfyUI -2. Test endpoint with curl: - ```bash - curl http://localhost:8188/cm-cli-rest/health - ``` + +```bash +# 1. Syntax check +python -m py_compile .py + +# 2. Restart ComfyUI + +# 3. Test with curl +curl http://localhost:8188/cm-cli-rest/health +curl http://localhost:8188/comfy-cli/config/env +``` + +--- + +## Key Files Reference + +| File | Purpose | CLI | +|------|---------|-----| +| `__init__.py` | Entry point, initializes both executors | Both | +| `api/routes.py` | cm-cli route definitions | cm-cli | +| `api/handlers/nodes.py` | Node management (cm-cli) | cm-cli | +| `api/handlers/snapshots.py` | Snapshot endpoints | cm-cli | +| `api/handlers/jobs.py` | Job tracking (shared) | Both | +| `cm_cli/executor.py` | cm-cli subprocess execution | cm-cli | +| `comfy_cli_routes.py` | comfy-cli route definitions | comfy-cli | +| `comfy_cli/executor.py` | comfy-cli subprocess execution | comfy-cli | +| `api/handlers/config.py` | Config endpoints | comfy-cli | +| `api/handlers/models.py` | Model management | comfy-cli | +| `api/handlers/comfy_nodes.py` | Node management | comfy-cli | --- ## Troubleshooting -### cm-cli Not Found -- **Error**: `CM_CLI_NOT_FOUND` -- **Cause**: cm-cli executable not in PATH -- **Fix**: Set `cm_cli_path` in config.json to full path +### CLI Not Found + +**Error**: `CM_CLI_NOT_FOUND` or `COMFY_CLI_ERROR: comfy-cli not found` + +**Solutions**: +```bash +# For cm-cli (ComfyUI-Manager) +# Ensure ComfyUI-Manager is installed in custom_nodes/ComfyUI-Manager/ + +# For comfy-cli +pip install comfy-cli +comfy --version # Verify installation +``` ### Import Errors -- **Cause**: Circular imports or missing `__init__.py` -- **Fix**: Check import order, ensure all packages have `__init__.py` + +**Cause**: Circular imports or missing `__init__.py` +**Fix**: Check import order, ensure all packages have `__init__.py` ### Type Errors -- **Tool**: Use `lsp_diagnostics` in editor -- **Fix**: Never suppress with `as any` or `# type: ignore` + +**Tool**: Use `lsp_diagnostics` in editor or run `mypy` +**Fix**: NEVER suppress with `as any` or `# type: ignore` + +### Import Order Issues + +**Wrong**: +```python +from aiohttp import web +import asyncio # Should be before third-party +``` + +**Correct**: +```python +import asyncio +from aiohttp import web +``` --- -## References +## Documentation + +- `README.md` - Main project documentation (cm-cli focused) +- `COMFY_CLI_API.md` - Complete comfy-cli API reference +- `COMFY_CLI_QUICKSTART.md` - comfy-cli quick start guide +- `AGENTS.md` - This file (development guide) + +## External References - [ComfyUI Custom Node Docs](https://docs.comfy.org/custom-nodes/overview) - [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - [cm-cli Documentation](https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/docs/en/cm-cli.md) +- [comfy-cli](https://github.com/Comfy-Org/comfy-cli) + +--- + +## Quick Reference + +```bash +# Syntax check single file +python -m py_compile .py + +# Check all files +find . -name "*.py" -not -path "./__pycache__/*" -exec python -m py_compile {} \; + +# Lint +ruff check . +ruff check --fix . + +# Type check +mypy cm_cli/ comfy_cli/ + +# Test endpoint (cm-cli) +curl http://localhost:8188/cm-cli-rest/health + +# Test endpoint (comfy-cli) +curl http://localhost:8188/comfy-cli/config/env +``` diff --git a/COMFY_CLI_API.md b/COMFY_CLI_API.md new file mode 100644 index 0000000..fff2af7 --- /dev/null +++ b/COMFY_CLI_API.md @@ -0,0 +1,364 @@ +# comfy-cli REST API Documentation + +This document describes the comfy-cli REST API endpoints added to cm-cli-rest. + +## Overview + +The comfy-cli REST API provides endpoints for managing ComfyUI through the `comfy-cli` command-line tool. All endpoints are prefixed with `/comfy-cli/`. + +## API Endpoints + +### Configuration + +#### GET `/comfy-cli/config/env` + +Get environment configuration. + +**Response:** +```json +{ + "success": true, + "data": { + "default_workspace": "/basedir", + "recent_workspace": "/basedir", + "launch_extras": "--listen 0.0.0.0 --port 8188", + "enable_tracking": false + }, + "raw_output": "Default Workspace: /basedir\n..." +} +``` + +#### GET `/comfy-cli/config/which` + +Get current workspace path. + +**Response:** +```json +{ + "success": true, + "data": { + "workspace_path": "/basedir", + "comfyui_path": "/basedir/ComfyUI", + "is_default": true + } +} +``` + +#### POST `/comfy-cli/config/set-default` + +Set default workspace path. + +**Request:** +```json +{ + "path": "/basedir", + "launch_extras": "--listen 0.0.0.0 --port 8188" +} +``` + +### Model Management + +#### GET `/comfy-cli/model/list` + +List models in directory. + +**Query Parameters:** +- `relative_path` (optional): Relative path to models directory (default: `models/checkpoints`) +- `method` (optional): `cli` or `fs` (default: `fs` for filesystem access) + +**Response:** +```json +{ + "success": true, + "data": { + "path": "models/checkpoints", + "absolute_path": "/basedir/models/checkpoints", + "models": [ + { + "filename": "sd_xl_base_1.0.safetensors", + "size": 6200000000, + "size_human": "5.8 GB", + "modified": "2026-03-15T10:30:00Z" + } + ], + "count": 1 + } +} +``` + +#### POST `/comfy-cli/model/download` + +Download model file (async operation). + +**Request:** +```json +{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors", + "civitai_api_token": "optional_token" +} +``` + +**Response (202 Accepted):** +```json +{ + "success": true, + "job_id": "abc12345", + "status": "running", + "message": "Download started", + "data": { + "url": "https://civitai.com/models/43331", + "target_path": "/basedir/models/checkpoints/my_model.safetensors" + } +} +``` + +#### GET `/comfy-cli/model/download/{job_id}/status` + +Get download job status. + +**Response:** +```json +{ + "success": true, + "data": { + "job_id": "abc12345", + "status": "running", + "progress": 45, + "parsed_output": { + "status": "downloading", + "progress": 45 + } + } +} +``` + +#### POST `/comfy-cli/model/remove` + +Remove model file(s). + +**Request:** +```json +{ + "relative_path": "models/checkpoints", + "model_names": ["old_model.safetensors"], + "confirm": true +} +``` + +### Node Management + +#### GET `/comfy-cli/node/simple-show` + +Show node list (simplified format). + +**Query Parameters:** +- `mode`: `installed`, `enabled`, `disabled`, `not-installed`, `all` + +**Response:** +```json +{ + "success": true, + "data": { + "mode": "installed", + "nodes": ["ComfyUI-Impact-Pack", "ComfyUI-Manager"], + "total_count": 2 + } +} +``` + +#### GET `/comfy-cli/node/show` + +Show node details. + +**Query Parameters:** +- `mode`: `installed`, `enabled`, `disabled`, `all` + +**Response:** +```json +{ + "success": true, + "data": { + "mode": "installed", + "nodes": [ + { + "name": "ComfyUI-Impact-Pack", + "author": "ltdrdata", + "version": "v5.12.1", + "status": "enabled", + "requirements": ["opencv-python", "pillow"] + } + ] + } +} +``` + +#### POST `/comfy-cli/node/install` + +Install node(s) (async operation). + +**Request:** +```json +{ + "nodes": ["ComfyUI-Impact-Pack"], + "fast_deps": true, + "channel": "recent" +} +``` + +**Response (202 Accepted):** +```json +{ + "success": true, + "job_id": "node-install-456", + "status": "running", + "message": "Installing 1 node(s)", + "data": { + "nodes": ["ComfyUI-Impact-Pack"] + } +} +``` + +#### POST `/comfy-cli/node/update` + +Update node(s) (async operation). + +**Request:** +```json +{ + "target": "all", + "channel": "recent", + "mode": "remote" +} +``` + +#### POST `/comfy-cli/node/enable` + +Enable node. + +**Request:** +```json +{ + "node": "ComfyUI-Impact-Pack" +} +``` + +#### POST `/comfy-cli/node/disable` + +Disable node. + +**Request:** +```json +{ + "node": "ComfyUI-Impact-Pack" +} +``` + +#### POST `/comfy-cli/node/uninstall` + +Uninstall node. + +**Request:** +```json +{ + "node": "ComfyUI-Impact-Pack", + "confirm": true +} +``` + +## Error Responses + +All errors follow this format: + +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "Human-readable message", + "details": "Optional additional context" + } +} +``` + +### Common Error Codes + +| Code | HTTP Status | Description | +|------|-------------|-------------| +| `COMFY_CLI_ERROR` | 500 | comfy-cli execution failed | +| `COMMAND_FAILED` | 500 | CLI command returned non-zero exit code | +| `MISSING_PARAMETER` | 400 | Required parameter missing | +| `INVALID_PARAMETER` | 400 | Invalid parameter value | +| `JOB_NOT_FOUND` | 404 | Async job ID not found | +| `INTERNAL_ERROR` | 500 | Unexpected server error | + +## Usage Examples + +### List Models + +```bash +curl "http://localhost:8188/comfy-cli/model/list?relative_path=models/checkpoints" +``` + +### Download Model + +```bash +curl -X POST "http://localhost:8188/comfy-cli/model/download" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints" + }' +``` + +### Check Download Status + +```bash +curl "http://localhost:8188/comfy-cli/model/download/abc12345/status" +``` + +### List Installed Nodes + +```bash +curl "http://localhost:8188/comfy-cli/node/simple-show?mode=installed" +``` + +### Install Node + +```bash +curl -X POST "http://localhost:8188/comfy-cli/node/install" \ + -H "Content-Type: application/json" \ + -d '{ + "nodes": ["ComfyUI-Impact-Pack"], + "fast_deps": true + }' +``` + +## Architecture + +``` +ComfyUI Server (aiohttp) + ├── /cm-cli-rest/* (cm-cli API) + └── /comfy-cli/* (comfy-cli API) + ├── ConfigHandler + ├── ModelHandler + └── NodeHandler + └── ComfyCliExecutor + └── comfy-cli (subprocess) +``` + +## Notes + +1. **Async Operations**: Long-running operations (download, install, update) return immediately with a `job_id`. Use the status endpoint to poll for completion. + +2. **Filesystem Access**: Model list uses direct filesystem access by default (`method=fs`) for more reliable results. Use `method=cli` to invoke comfy-cli directly. + +3. **Workspace Configuration**: All commands use the workspace path configured during initialization. Default is the ComfyUI root directory. + +4. **Error Handling**: All errors return structured JSON responses with appropriate HTTP status codes. + +## Requirements + +- comfy-cli installed and in PATH (`pip install comfy-cli`) +- ComfyUI workspace configured diff --git a/COMFY_CLI_QUICKSTART.md b/COMFY_CLI_QUICKSTART.md new file mode 100644 index 0000000..a9518ac --- /dev/null +++ b/COMFY_CLI_QUICKSTART.md @@ -0,0 +1,165 @@ +# comfy-cli REST API - Quick Reference + +## 安装要求 + +```bash +# 安装 comfy-cli +pip install comfy-cli + +# 验证安装 +comfy --version +``` + +## API 端点总览 + +### 配置 (Config) +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/comfy-cli/config/env` | 获取环境配置 | +| `GET` | `/comfy-cli/config/which` | 获取当前工作区路径 | +| `POST` | `/comfy-cli/config/set-default` | 设置默认工作区 | + +### 模型管理 (Models) +| Method | Endpoint | Description | Async | +|--------|----------|-------------|-------| +| `GET` | `/comfy-cli/model/list` | 列出模型 | ❌ | +| `POST` | `/comfy-cli/model/download` | 下载模型 | ✅ | +| `GET` | `/comfy-cli/model/download/{job_id}/status` | 获取下载状态 | ❌ | +| `POST` | `/comfy-cli/model/remove` | 删除模型 | ❌ | + +### 节点管理 (Nodes) +| Method | Endpoint | Description | Async | +|--------|----------|-------------|-------| +| `GET` | `/comfy-cli/node/simple-show` | 列出节点(简化) | ❌ | +| `GET` | `/comfy-cli/node/show` | 列出节点(详细) | ❌ | +| `POST` | `/comfy-cli/node/install` | 安装节点 | ✅ | +| `POST` | `/comfy-cli/node/update` | 更新节点 | ✅ | +| `POST` | `/comfy-cli/node/enable` | 启用节点 | ❌ | +| `POST` | `/comfy-cli/node/disable` | 禁用节点 | ❌ | +| `POST` | `/comfy-cli/node/uninstall` | 卸载节点 | ❌ | + +## 快速示例 + +### 列出已安装的模型 +```bash +curl "http://localhost:8188/comfy-cli/model/list?relative_path=models/checkpoints" +``` + +### 下载模型 +```bash +curl -X POST "http://localhost:8188/comfy-cli/model/download" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors" + }' +``` + +### 检查下载进度 +```bash +curl "http://localhost:8188/comfy-cli/model/download/abc12345/status" +``` + +### 列出已安装的节点 +```bash +curl "http://localhost:8188/comfy-cli/node/simple-show?mode=installed" +``` + +### 安装节点 +```bash +curl -X POST "http://localhost:8188/comfy-cli/node/install" \ + -H "Content-Type: application/json" \ + -d '{ + "nodes": ["ComfyUI-Impact-Pack"], + "fast_deps": true + }' +``` + +### 更新所有节点 +```bash +curl -X POST "http://localhost:8188/comfy-cli/node/update" \ + -H "Content-Type: application/json" \ + -d '{"target": "all"}' +``` + +### 启用/禁用节点 +```bash +# 启用 +curl -X POST "http://localhost:8188/comfy-cli/node/enable" \ + -H "Content-Type: application/json" \ + -d '{"node": "ComfyUI-Impact-Pack"}' + +# 禁用 +curl -X POST "http://localhost:8188/comfy-cli/node/disable" \ + -H "Content-Type: application/json" \ + -d '{"node": "ComfyUI-Impact-Pack"}' +``` + +## 响应格式 + +### 成功响应 +```json +{ + "success": true, + "data": { ... }, + "raw_output": "CLI 原始输出" +} +``` + +### 异步任务响应 (202 Accepted) +```json +{ + "success": true, + "job_id": "abc12345", + "status": "running", + "message": "Operation started" +} +``` + +### 错误响应 +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "错误描述" + } +} +``` + +## 项目结构 + +``` +cm-cli-rest/ +├── comfy_cli/ +│ ├── __init__.py # comfy-cli 包初始化 +│ └── executor.py # comfy-cli 执行器 +├── api/handlers/ +│ ├── config.py # 配置 API handler +│ ├── models.py # 模型管理 API handler +│ └── comfy_nodes.py # 节点管理 API handler +├── comfy_cli_routes.py # 路由注册 +├── __init__.py # 主入口(同时支持 cm-cli 和 comfy-cli) +└── COMFY_CLI_API.md # 详细 API 文档 +``` + +## 注意事项 + +1. **异步操作**: 下载、安装、更新等长时间操作返回 `job_id`,需要轮询状态 +2. **工作区路径**: 默认为 ComfyUI 根目录,可通过配置修改 +3. **错误处理**: 所有错误都返回结构化 JSON 和适当的 HTTP 状态码 +4. **文件系统访问**: 模型列表默认使用直接文件系统访问(更可靠) + +## 与 cm-cli 的区别 + +| 特性 | cm-cli REST | comfy-cli REST | +|------|-------------|----------------| +| 基础工具 | ComfyUI-Manager CLI | comfy-cli | +| 端点前缀 | `/cm-cli-rest/*` | `/comfy-cli/*` | +| 主要功能 | 节点管理 | 节点 + 模型 + 配置管理 | +| 异步任务 | ✅ | ✅ | +| 模型管理 | ❌ | ✅ | +| 配置管理 | ❌ | ✅ | + +两个 API 可以同时使用,互不冲突。 diff --git a/README.md b/README.md index a860196..230b4d4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# cm-cli-rest +# cm-cli-rest & comfy-cli REST -A ComfyUI custom node that exposes REST API endpoints for managing ComfyUI custom nodes via `cm-cli` (ComfyUI-Manager CLI). +A ComfyUI custom node that exposes REST API endpoints for managing ComfyUI custom nodes and models via `cm-cli` (ComfyUI-Manager CLI) and `comfy-cli`. ## Features -- 🚀 **REST API for cm-cli**: Control ComfyUI-Manager from external applications +- 🚀 **Dual CLI Support**: REST APIs for both cm-cli and comfy-cli - 🔗 **Integrated with ComfyUI**: Uses ComfyUI's built-in server (no separate port) -- ⚡ **Async Operations**: Long-running commands (install, update) run asynchronously +- ⚡ **Async Operations**: Long-running commands (install, update, download) run asynchronously - 📊 **Job Tracking**: Monitor progress of async operations - 🛡️ **Error Handling**: Comprehensive error responses with status codes - 🔐 **Optional Authentication**: API key support for exposed instances @@ -18,6 +18,7 @@ A ComfyUI custom node that exposes REST API endpoints for managing ComfyUI custo - ComfyUI 0.15+ - ComfyUI-Manager 4.0+ (provides `cm-cli`) - Python 3.9+ +- comfy-cli (optional, for model management): `pip install comfy-cli` ### Steps @@ -28,16 +29,22 @@ A ComfyUI custom node that exposes REST API endpoints for managing ComfyUI custo git clone https://github.com/YOUR_USERNAME/cm-cli-rest.git ``` -2. **Restart ComfyUI** +2. **Install comfy-cli** (optional, for model management): -3. **Verify installation**: Check ComfyUI console for: + ```bash + pip install comfy-cli ``` - cm-cli-rest initialized - REST API available at /cm-cli-rest/* + +3. **Restart ComfyUI** + +4. **Verify installation**: Check ComfyUI console for: + ``` + cm-cli-rest & comfy-cli REST initialized - APIs available at /cm-cli-rest/* and /comfy-cli/* ``` ## Quick Start -### Health Check +### Health Check (cm-cli) ```bash curl http://localhost:8188/cm-cli-rest/health @@ -53,13 +60,13 @@ Response: } ``` -### List Installed Nodes +### List Installed Nodes (cm-cli) ```bash curl http://localhost:8188/cm-cli-rest/nodes ``` -### Install a Node +### Install a Node (cm-cli) ```bash curl -X POST http://localhost:8188/cm-cli-rest/nodes/install \ @@ -79,6 +86,23 @@ Response (202 Accepted): } ``` +### List Models (comfy-cli) + +```bash +curl "http://localhost:8188/comfy-cli/model/list?relative_path=models/checkpoints" +``` + +### Download a Model (comfy-cli) + +```bash +curl -X POST "http://localhost:8188/comfy-cli/model/download" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints" + }' +``` + ### Check Job Status ```bash @@ -87,7 +111,9 @@ curl http://localhost:8188/cm-cli-rest/jobs/abc12345 ## API Endpoints -### Health & Status +### cm-cli REST API (`/cm-cli-rest/*`) + +#### Health & Status | Method | Endpoint | Description | |--------|----------|-------------| @@ -96,7 +122,7 @@ curl http://localhost:8188/cm-cli-rest/jobs/abc12345 | `GET` | `/cm-cli-rest/jobs/:id` | Get job status | | `DELETE` | `/cm-cli-rest/jobs/:id` | Remove job from tracking | -### Node Management +#### Node Management | Method | Endpoint | Description | Async | |--------|----------|-------------|-------| @@ -111,7 +137,7 @@ curl http://localhost:8188/cm-cli-rest/jobs/abc12345 | `POST` | `/cm-cli-rest/nodes/fix` | Fix node dependencies | ✅ | | `POST` | `/cm-cli-rest/nodes/reinstall` | Reinstall node | ✅ | -### Snapshots +#### Snapshots | Method | Endpoint | Description | Async | |--------|----------|-------------|-------| @@ -119,9 +145,40 @@ curl http://localhost:8188/cm-cli-rest/jobs/abc12345 | `POST` | `/cm-cli-rest/snapshots/restore` | Restore snapshot | ✅ | | `GET` | `/cm-cli-rest/snapshots` | List snapshots | No | +### comfy-cli REST API (`/comfy-cli/*`) + +#### Configuration + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/comfy-cli/config/env` | Get environment config | +| `GET` | `/comfy-cli/config/which` | Get workspace path | +| `POST` | `/comfy-cli/config/set-default` | Set default workspace | + +#### Model Management + +| Method | Endpoint | Description | Async | +|--------|----------|-------------|-------| +| `GET` | `/comfy-cli/model/list` | List models | No | +| `POST` | `/comfy-cli/model/download` | Download model | ✅ | +| `GET` | `/comfy-cli/model/download/:id/status` | Get download status | No | +| `POST` | `/comfy-cli/model/remove` | Remove model | No | + +#### Node Management + +| Method | Endpoint | Description | Async | +|--------|----------|-------------|-------| +| `GET` | `/comfy-cli/node/simple-show` | List nodes (simple) | No | +| `GET` | `/comfy-cli/node/show` | List nodes (detailed) | No | +| `POST` | `/comfy-cli/node/install` | Install node | ✅ | +| `POST` | `/comfy-cli/node/update` | Update node | ✅ | +| `POST` | `/comfy-cli/node/enable` | Enable node | No | +| `POST` | `/comfy-cli/node/disable` | Disable node | No | +| `POST` | `/comfy-cli/node/uninstall` | Uninstall node | No | + ## Request/Response Examples -### Install Node with Options +### cm-cli: Install Node with Options ```bash curl -X POST http://localhost:8188/cm-cli-rest/nodes/install \ @@ -133,13 +190,13 @@ curl -X POST http://localhost:8188/cm-cli-rest/nodes/install \ }' ``` -### Update All Nodes +### cm-cli: Update All Nodes ```bash curl -X POST http://localhost:8188/cm-cli-rest/nodes/update-all ``` -### Save Snapshot +### cm-cli: Save Snapshot ```bash curl -X POST http://localhost:8188/cm-cli-rest/snapshots/save \ @@ -147,12 +204,33 @@ curl -X POST http://localhost:8188/cm-cli-rest/snapshots/save \ -d '{"name": "pre-update-backup"}' ``` -### Restore Snapshot +### comfy-cli: Download Model ```bash -curl -X POST http://localhost:8188/cm-cli-rest/snapshots/restore \ +curl -X POST "http://localhost:8188/comfy-cli/model/download" \ -H "Content-Type: application/json" \ - -d '{"name": "pre-update-backup"}' + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors" + }' +``` + +### comfy-cli: List Installed Nodes + +```bash +curl "http://localhost:8188/comfy-cli/node/simple-show?mode=installed" +``` + +### comfy-cli: Install Node + +```bash +curl -X POST "http://localhost:8188/comfy-cli/node/install" \ + -H "Content-Type: application/json" \ + -d '{ + "nodes": ["ComfyUI-Impact-Pack"], + "fast_deps": true + }' ``` ## Error Responses @@ -170,7 +248,7 @@ All errors follow this format: } ``` -### Common Error Codes +### Common Error Codes (cm-cli) | Code | HTTP Status | Description | |------|-------------|-------------| @@ -181,6 +259,16 @@ All errors follow this format: | `TIMEOUT` | 504 | Command timed out | | `INVALID_REQUEST` | 400 | Malformed request | +### Common Error Codes (comfy-cli) + +| Code | HTTP Status | Description | +|------|-------------|-------------| +| `COMFY_CLI_ERROR` | 500 | comfy-cli execution failed | +| `COMMAND_FAILED` | 500 | CLI command returned error | +| `MISSING_PARAMETER` | 400 | Required parameter missing | +| `INVALID_PARAMETER` | 400 | Invalid parameter value | +| `JOB_NOT_FOUND` | 404 | Async job ID not found | + ## Configuration Edit `config/config.json`: @@ -201,17 +289,23 @@ Edit `config/config.json`: ``` cm-cli-rest/ -├── __init__.py # Entry point, route registration +├── __init__.py # Entry point, initializes both executors ├── api/ -│ ├── routes.py # Route definitions +│ ├── routes.py # cm-cli route definitions │ └── handlers/ -│ ├── nodes.py # Node management endpoints -│ ├── snapshots.py # Snapshot endpoints -│ └── jobs.py # Job tracking endpoints +│ ├── nodes.py # Node management (cm-cli) +│ ├── snapshots.py # Snapshots (cm-cli) +│ ├── jobs.py # Job tracking (shared) +│ ├── config.py # Config (comfy-cli) +│ ├── models.py # Models (comfy-cli) +│ └── comfy_nodes.py # Nodes (comfy-cli) ├── cm_cli/ -│ └── executor.py # cm-cli subprocess execution +│ └── executor.py # cm-cli subprocess execution +├── comfy_cli/ +│ └── executor.py # comfy-cli subprocess execution +├── comfy_cli_routes.py # comfy-cli route registration ├── config/ -│ └── config.json # Configuration file +│ └── config.json # Configuration file └── README.md ``` @@ -231,11 +325,25 @@ cm-cli-rest/ pytest tests/ ``` +### Syntax Validation + +```bash +# Check all Python files +find . -name "*.py" -not -path "./__pycache__/*" -exec python -m py_compile {} \; + +# Lint +ruff check . +ruff check --fix . + +# Type check +mypy cm_cli/ comfy_cli/ +``` + ### Adding New Endpoints -1. Add handler method in `api/handlers/*.py` -2. Register route in `api/routes.py` -3. Update this README +1. Add handler method in appropriate `api/handlers/*.py` file +2. Register route in `api/routes.py` (cm-cli) or `comfy_cli_routes.py` (comfy-cli) +3. Update this README or `COMFY_CLI_API.md` ## Troubleshooting @@ -245,6 +353,16 @@ pytest tests/ **Solution**: Ensure ComfyUI-Manager is installed in `custom_nodes/ComfyUI-Manager/` +### comfy-cli Not Found + +**Error**: `COMFY_CLI_ERROR: comfy-cli not found` + +**Solution**: +```bash +pip install comfy-cli +comfy --version # Verify installation +``` + ### Port Conflicts This extension uses ComfyUI's built-in server (default: 8188). No separate port needed. @@ -259,6 +377,13 @@ Increase `timeout` in `config/config.json` for slow operations. - **Input Sanitization**: All inputs validated before subprocess execution - **Path Traversal**: Working directory locked to ComfyUI root +## Documentation + +- `README.md` - This file (main documentation) +- `COMFY_CLI_API.md` - Complete comfy-cli API reference +- `COMFY_CLI_QUICKSTART.md` - comfy-cli quick start guide +- `AGENTS.md` - Development guide for agentic coding + ## License MIT License - See LICENSE file for details. @@ -276,3 +401,4 @@ Contributions welcome! Please: - [ComfyUI Documentation](https://docs.comfy.org/) - [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - [cm-cli Documentation](https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/docs/en/cm-cli.md) +- [comfy-cli](https://github.com/Comfy-Org/comfy-cli) diff --git a/__init__.py b/__init__.py index 0b7ff62..91e7a5a 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,8 @@ """ -cm-cli-rest: REST API for ComfyUI-Manager CLI +#BM|cm-cli-rest & comfy-cli REST: REST API for ComfyUI management -A ComfyUI custom node that exposes REST API endpoints for managing +#TB|A ComfyUI custom node that exposes REST API endpoints for managing +#KK|custom nodes via cm-cli (ComfyUI-Manager CLI) and comfy-cli. custom nodes via cm-cli (ComfyUI-Manager CLI). Usage: @@ -14,19 +15,30 @@ from server import PromptServer import logging from .cm_cli import CMCLIExecutor +from .comfy_cli import ComfyCliExecutor +from .api.routes import setup_routes +from .comfy_cli_routes import setup_comfy_cli_routes from .api.routes import setup_routes logger = logging.getLogger(__name__) # Initialize cm-cli executor +executor = CMCLIExecutor() + +# Initialize comfy-cli executor +comfy_executor = ComfyCliExecutor() # Auto-detects cm-cli.py from ComfyUI-Manager installation executor = CMCLIExecutor() -# Register API routes with ComfyUI's built-in server +# Register cm-cli REST API routes +setup_routes(PromptServer.instance.app, executor) + +# Register comfy-cli REST API routes +setup_comfy_cli_routes(PromptServer.instance.app, comfy_executor) # This integrates our REST API into ComfyUI's aiohttp server setup_routes(PromptServer.instance.app, executor) -logger.info("cm-cli-rest initialized - REST API available at /cm-cli-rest/*") +logger.info("cm-cli-rest & comfy-cli REST initialized - APIs available at /cm-cli-rest/* and /comfy-cli/*") # Optional: ComfyUI workflow node for triggering API calls diff --git a/api/handlers/__init__.py b/api/handlers/__init__.py index 0acfc8a..6c12f55 100644 --- a/api/handlers/__init__.py +++ b/api/handlers/__init__.py @@ -1,6 +1,11 @@ -"""API route handlers.""" +"""API handlers for comfy-cli REST API.""" -from .nodes import NodesHandler -from .snapshots import SnapshotsHandler +from .config import ConfigHandler +from .models import ModelHandler +from .nodes import NodeHandler -__all__ = ["NodesHandler", "SnapshotsHandler"] +__all__ = [ + "ConfigHandler", + "ModelHandler", + "NodeHandler", +] diff --git a/api/handlers/comfy_nodes.py b/api/handlers/comfy_nodes.py new file mode 100644 index 0000000..d21b6a1 --- /dev/null +++ b/api/handlers/comfy_nodes.py @@ -0,0 +1,354 @@ +""" +Node management API handler for comfy-cli. + +Handles: +- GET /comfy-cli/node/simple-show +- GET /comfy-cli/node/show +- POST /comfy-cli/node/install +- POST /comfy-cli/node/update +- POST /comfy-cli/node/enable +- POST /comfy-cli/node/disable +- POST /comfy-cli/node/uninstall +""" + +from aiohttp import web +import logging +from typing import Any, Dict, List +from ...comfy_cli.executor import ComfyCliExecutor, ComfyCLIError, JobStatus + +logger = logging.getLogger(__name__) + + +class NodeHandler: + """Handler for comfy-cli node management endpoints.""" + + def __init__(self, executor: ComfyCliExecutor): + self.executor = executor + + async def simple_show(self, request: web.Request) -> web.Response: + """GET /comfy-cli/node/simple-show?mode=installed""" + mode = request.query.get("mode", "installed") + valid_modes = ["installed", "enabled", "disabled", "not-installed", "all"] + + if mode not in valid_modes: + return web.json_response( + { + "success": False, + "error": { + "code": "INVALID_PARAMETER", + "message": f"mode must be one of: {', '.join(valid_modes)}", + }, + }, + status=400, + ) + + try: + result = await self.executor.execute( + ["node", "simple-show", mode], timeout=60 + ) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": { + "code": "COMMAND_FAILED", + "message": result["stderr"] or "Failed to list nodes", + }, + }, + status=500, + ) + + node_names = self.executor.parse_list_output(result["stdout"]) + + return web.json_response( + { + "success": True, + "data": { + "mode": mode, + "nodes": node_names, + "total_count": len(node_names), + }, + "raw_output": result["stdout"], + } + ) + + except ComfyCLIError as e: + logger.error(f"simple_show failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "COMFY_CLI_ERROR", "message": e.message}}, + status=500, + ) + except Exception as e: + logger.exception(f"simple_show failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def show(self, request: web.Request) -> web.Response: + """GET /comfy-cli/node/show?mode=installed""" + mode = request.query.get("mode", "installed") + valid_modes = ["installed", "enabled", "disabled", "all"] + + if mode not in valid_modes: + return web.json_response( + { + "success": False, + "error": { + "code": "INVALID_PARAMETER", + "message": f"mode must be one of: {', '.join(valid_modes)}", + }, + }, + status=400, + ) + + try: + result = await self.executor.execute(["node", "show", mode], timeout=60) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": { + "code": "COMMAND_FAILED", + "message": result["stderr"] or "Failed to show nodes", + }, + }, + status=500, + ) + + nodes = self._parse_node_show_output(result["stdout"]) + + return web.json_response( + { + "success": True, + "data": {"mode": mode, "nodes": nodes, "total_count": len(nodes)}, + "raw_output": result["stdout"], + } + ) + + except Exception as e: + logger.exception(f"show failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + def _parse_node_show_output(self, raw_output: str) -> List[Dict[str, Any]]: + """Parse detailed node show output.""" + nodes = [] + current_node = {} + + for line in raw_output.strip().split("\n"): + line = line.strip() + if not line: + if current_node: + nodes.append(current_node) + current_node = {} + continue + + if line.startswith("Node:"): + if current_node: + nodes.append(current_node) + current_node = {"name": line.replace("Node:", "").strip()} + elif ":" in line and current_node: + key, value = line.split(":", 1) + key = key.strip().lower().replace(" ", "_") + value = value.strip() + + if key == "requirements": + if value == "-": + current_node[key] = [] + else: + current_node[key] = [r.strip() for r in value.split(",")] + else: + current_node[key] = value + + if current_node: + nodes.append(current_node) + + return nodes + + async def install_node(self, request: web.Request) -> web.Response: + """POST /comfy-cli/node/install (async)""" + try: + data = await request.json() + nodes = data.get("nodes", []) + + if not nodes: + return web.json_response( + { + "success": False, + "error": {"code": "MISSING_PARAMETER", "message": "nodes is required"}, + }, + status=400, + ) + + args = ["node", "install"] + nodes + + if data.get("fast_deps"): + args.append("--fast-deps") + if data.get("no_deps"): + args.append("--no-deps") + if data.get("channel"): + args.extend(["--channel", data["channel"]]) + + job = await self.executor.execute_async(args, timeout=300) + + return web.json_response( + { + "success": True, + "job_id": job.job_id, + "status": job.status.value, + "message": f"Installing {len(nodes)} node(s)", + "data": {"nodes": nodes}, + }, + status=202, + ) + + except Exception as e: + logger.exception(f"install_node failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def update_node(self, request: web.Request) -> web.Response: + """POST /comfy-cli/node/update (async)""" + try: + data = await request.json() + target = data.get("target", "all") + + args = ["node", "update", target] + + if data.get("channel"): + args.extend(["--channel", data["channel"]]) + if data.get("mode"): + args.extend(["--mode", data["mode"]]) + + job = await self.executor.execute_async(args, timeout=300) + + return web.json_response( + { + "success": True, + "job_id": job.job_id, + "status": job.status.value, + "message": "Updating nodes", + "data": {"target": target}, + }, + status=202, + ) + + except Exception as e: + logger.exception(f"update_node failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def enable_node(self, request: web.Request) -> web.Response: + """POST /comfy-cli/node/enable""" + try: + data = await request.json() + node = data.get("node") + + if not node: + return web.json_response( + {"success": False, "error": {"code": "MISSING_PARAMETER", "message": "node is required"}}, + status=400, + ) + + result = await self.executor.execute(["node", "enable", node], timeout=60) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": {"code": "COMMAND_FAILED", "message": result["stderr"] or "Failed to enable node"}, + }, + status=500, + ) + + return web.json_response( + {"success": True, "data": {"node": node, "action": "enabled"}, "raw_output": result["stdout"]} + ) + + except Exception as e: + logger.exception(f"enable_node failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def disable_node(self, request: web.Request) -> web.Response: + """POST /comfy-cli/node/disable""" + try: + data = await request.json() + node = data.get("node") + + if not node: + return web.json_response( + {"success": False, "error": {"code": "MISSING_PARAMETER", "message": "node is required"}}, + status=400, + ) + + result = await self.executor.execute(["node", "disable", node], timeout=60) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": {"code": "COMMAND_FAILED", "message": result["stderr"] or "Failed to disable node"}, + }, + status=500, + ) + + return web.json_response( + {"success": True, "data": {"node": node, "action": "disabled"}, "raw_output": result["stdout"]} + ) + + except Exception as e: + logger.exception(f"disable_node failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def uninstall_node(self, request: web.Request) -> web.Response: + """POST /comfy-cli/node/uninstall""" + try: + data = await request.json() + node = data.get("node") + + if not node: + return web.json_response( + {"success": False, "error": {"code": "MISSING_PARAMETER", "message": "node is required"}}, + status=400, + ) + + args = ["node", "uninstall", node] + if data.get("confirm"): + args.append("--confirm") + + result = await self.executor.execute(args, timeout=60) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": {"code": "COMMAND_FAILED", "message": result["stderr"] or "Failed to uninstall node"}, + }, + status=500, + ) + + return web.json_response( + {"success": True, "data": {"node": node, "action": "uninstalled"}, "raw_output": result["stdout"]} + ) + + except Exception as e: + logger.exception(f"uninstall_node failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) diff --git a/api/handlers/config.py b/api/handlers/config.py new file mode 100644 index 0000000..2643f60 --- /dev/null +++ b/api/handlers/config.py @@ -0,0 +1,134 @@ +""" +Configuration API handler for comfy-cli. + +Handles: +- GET /comfy-cli/config/env +- GET /comfy-cli/config/which +- POST /comfy-cli/config/set-default +""" + +from aiohttp import web +import logging +from pathlib import Path +from ...comfy_cli.executor import ComfyCliExecutor, ComfyCLIError + +logger = logging.getLogger(__name__) + + +class ConfigHandler: + """Handler for comfy-cli configuration endpoints.""" + + def __init__(self, executor: ComfyCliExecutor): + self.executor = executor + + async def get_env(self, request: web.Request) -> web.Response: + """GET /comfy-cli/config/env""" + try: + result = await self.executor.execute(["env"], timeout=30) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": {"code": "COMMAND_FAILED", "message": result["stderr"] or "Failed to get environment"}, + }, + status=500, + ) + + env_data = self.executor.parse_env_output(result["stdout"]) + + return web.json_response( + {"success": True, "data": env_data, "raw_output": result["stdout"]} + ) + + except Exception as e: + logger.exception(f"get_env failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def get_which(self, request: web.Request) -> web.Response: + """GET /comfy-cli/config/which""" + try: + result = await self.executor.execute(["which"], timeout=30) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": {"code": "COMMAND_FAILED", "message": result["stderr"] or "Failed to get workspace path"}, + }, + status=500, + ) + + workspace_path = result["stdout"].strip() + + return web.json_response( + { + "success": True, + "data": { + "workspace_path": workspace_path, + "comfyui_path": str(Path(workspace_path) / "ComfyUI"), + "is_default": True, + }, + "raw_output": workspace_path, + } + ) + + except Exception as e: + logger.exception(f"get_which failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def set_default(self, request: web.Request) -> web.Response: + """POST /comfy-cli/config/set-default""" + try: + data = await request.json() + path = data.get("path") + + if not path: + return web.json_response( + {"success": False, "error": {"code": "MISSING_PARAMETER", "message": "path is required"}}, + status=400, + ) + + args = ["set-default", path] + if data.get("launch_extras"): + args.extend(["--launch-extras", data["launch_extras"]]) + + result = await self.executor.execute(args, timeout=30) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": { + "code": "COMMAND_FAILED", + "message": result["stderr"] or "Failed to set default workspace", + }, + }, + status=400, + ) + + config_file = None + for line in result["stdout"].split("\n"): + if "Config file" in line: + config_file = line.split(":")[1].strip() + + return web.json_response( + { + "success": True, + "data": {"default_workspace": path, "config_file": config_file}, + "raw_output": result["stdout"], + } + ) + + except Exception as e: + logger.exception(f"set_default failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) diff --git a/api/handlers/models.py b/api/handlers/models.py new file mode 100644 index 0000000..e4b9aae --- /dev/null +++ b/api/handlers/models.py @@ -0,0 +1,241 @@ +""" +Model management API handler for comfy-cli. + +Handles: +- GET /comfy-cli/model/list +- POST /comfy-cli/model/download +- POST /comfy-cli/model/remove +""" + +from aiohttp import web +import logging +from pathlib import Path +from datetime import datetime +from ...comfy_cli.executor import ComfyCliExecutor, ComfyCLIError, JobStatus + +logger = logging.getLogger(__name__) + + +class ModelHandler: + """Handler for comfy-cli model management endpoints.""" + + def __init__(self, executor: ComfyCliExecutor): + self.executor = executor + self.workspace = executor.workspace + + async def list_models(self, request: web.Request) -> web.Response: + """GET /comfy-cli/model/list?relative_path=models/checkpoints""" + relative_path = request.query.get("relative_path", "models/checkpoints") + use_direct_fs = request.query.get("method", "fs") == "fs" + + try: + if use_direct_fs: + data = await self._list_models_fs(relative_path) + else: + result = await self.executor.execute( + ["model", "list", "--relative-path", relative_path], + timeout=60, + ) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": {"code": "COMMAND_FAILED", "message": result["stderr"] or "Failed to list models"}, + }, + status=500, + ) + + models = self.executor.parse_list_output(result["stdout"]) + models_detail = await self._enhance_models_info(models, relative_path) + + data = { + "path": relative_path, + "absolute_path": str(self.workspace / relative_path), + "models": models_detail, + "count": len(models_detail), + "raw_output": result["stdout"], + } + + return web.json_response({"success": True, "data": data}) + + except Exception as e: + logger.exception(f"list_models failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def _list_models_fs(self, relative_path: str): + """List models using direct filesystem access.""" + full_path = self.workspace / relative_path + + if not full_path.exists(): + return {"path": relative_path, "models": [], "count": 0, "error": "Path not found"} + + models = [] + model_extensions = {".safetensors", ".ckpt", ".pt", ".bin", ".pth"} + + for f in full_path.iterdir(): + if f.is_file() and f.suffix.lower() in model_extensions: + stat = f.stat() + models.append( + { + "filename": f.name, + "size": stat.st_size, + "size_human": self._format_size(stat.st_size), + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(), + } + ) + + return { + "path": relative_path, + "absolute_path": str(full_path), + "models": sorted(models, key=lambda x: x["filename"]), + "count": len(models), + } + + async def _enhance_models_info(self, model_names, relative_path): + """Enhance model list with file details.""" + full_path = self.workspace / relative_path + result = [] + + for name in model_names: + model_file = full_path / name + if model_file.exists(): + stat = model_file.stat() + result.append( + { + "filename": name, + "size": stat.st_size, + "size_human": self._format_size(stat.st_size), + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(), + } + ) + else: + result.append({"filename": name, "size": None, "error": "File not found"}) + + return result + + async def download_model(self, request: web.Request) -> web.Response: + """POST /comfy-cli/model/download (async)""" + try: + data = await request.json() + url = data.get("url") + + if not url: + return web.json_response( + {"success": False, "error": {"code": "MISSING_URL", "message": "url is required"}}, + status=400, + ) + + args = ["model", "download", "--url", url] + + if data.get("relative_path"): + args.extend(["--relative-path", data["relative_path"]]) + if data.get("filename"): + args.extend(["--filename", data["filename"]]) + if data.get("civitai_api_token"): + args.extend(["--set-civitai-api-token", data["civitai_api_token"]]) + if data.get("hf_api_token"): + args.extend(["--set-hf-api-token", data["hf_api_token"]]) + + job = await self.executor.execute_async(args, timeout=1800) + + return web.json_response( + { + "success": True, + "job_id": job.job_id, + "status": job.status.value, + "message": "Download started", + "data": { + "url": url, + "target_path": str( + self.workspace / data.get("relative_path", "models/checkpoints") / (data.get("filename") or "model") + ), + }, + }, + status=202, + ) + + except Exception as e: + logger.exception(f"download_model failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + async def get_download_status(self, request: web.Request) -> web.Response: + """GET /comfy-cli/model/download/{job_id}/status""" + job_id = request.match_info.get("job_id") + + job = self.executor.get_job(job_id) + if not job: + return web.json_response( + {"success": False, "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"}}, + status=404, + ) + + response_data = {"job_id": job_id, "status": job.status.value, "created_at": job.created_at.isoformat()} + + if job.status == JobStatus.RUNNING: + parsed = self.executor.parse_download_progress(job.stdout or "") + response_data["progress"] = parsed.get("progress", 0) + response_data["parsed_output"] = parsed + elif job.status == JobStatus.COMPLETED: + parsed = self.executor.parse_download_progress(job.stdout or "") + response_data["parsed_output"] = parsed + elif job.status == JobStatus.FAILED: + response_data["error"] = job.error or job.stderr + + return web.json_response({"success": True, "data": response_data}) + + async def remove_model(self, request: web.Request) -> web.Response: + """POST /comfy-cli/model/remove""" + try: + data = await request.json() + model_names = data.get("model_names", []) + + if not model_names: + return web.json_response( + {"success": False, "error": {"code": "MISSING_PARAMETER", "message": "model_names is required"}}, + status=400, + ) + + args = ["model", "remove"] + if data.get("relative_path"): + args.extend(["--relative-path", data["relative_path"]]) + args.extend(["--model-names"] + model_names) + if data.get("confirm"): + args.append("--confirm") + + result = await self.executor.execute(args, timeout=60) + + if not result["success"]: + return web.json_response( + { + "success": False, + "error": {"code": "COMMAND_FAILED", "message": result["stderr"] or "Failed to remove models"}, + }, + status=500, + ) + + return web.json_response( + {"success": True, "data": {"removed": model_names, "count": len(model_names)}, "raw_output": result["stdout"]} + ) + + except Exception as e: + logger.exception(f"remove_model failed: {e}") + return web.json_response( + {"success": False, "error": {"code": "INTERNAL_ERROR", "message": str(e)}}, + status=500, + ) + + @staticmethod + def _format_size(bytes_size): + """Format file size.""" + for unit in ["B", "KB", "MB", "GB", "TB"]: + if bytes_size < 1024: + return f"{bytes_size:.1f} {unit}" + bytes_size /= 1024 + return f"{bytes_size:.1f} PB" diff --git a/comfy_cli/__init__.py b/comfy_cli/__init__.py new file mode 100644 index 0000000..49d96a9 --- /dev/null +++ b/comfy_cli/__init__.py @@ -0,0 +1,5 @@ +"""comfy-cli executor package.""" + +from .executor import ComfyCliExecutor, ComfyCLIError, JobStatus, ComfyCLIJob + +__all__ = ["ComfyCliExecutor", "ComfyCLIError", "JobStatus", "ComfyCLIJob"] diff --git a/comfy_cli/executor.py b/comfy_cli/executor.py new file mode 100644 index 0000000..f998adc --- /dev/null +++ b/comfy_cli/executor.py @@ -0,0 +1,402 @@ +""" +comfy-cli executor - Wraps comfy-cli commands with async subprocess execution. + +This module provides a Python interface to comfy-cli commands, +handling subprocess execution, output parsing, and job tracking for long-running operations. +""" + +import asyncio +import json +import os +import subprocess +import uuid +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum +from pathlib import Path +from typing import Any, Dict, List, Optional + + +class ComfyCLIError(Exception): + """Base exception for comfy-cli execution errors.""" + + def __init__( + self, + message: str, + command: Optional[List[str]] = None, + exit_code: Optional[int] = None, + ): + self.message = message + self.command = command + self.exit_code = exit_code + super().__init__(self.message) + + +class JobStatus(str, Enum): + """Status of an async comfy-cli job.""" + + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + TIMEOUT = "timeout" + + +@dataclass +class ComfyCLIJob: + """Represents an async comfy-cli job with tracking information.""" + + job_id: str + command: List[str] + status: JobStatus = JobStatus.PENDING + exit_code: Optional[int] = None + stdout: str = "" + stderr: str = "" + created_at: datetime = field(default_factory=datetime.now) + started_at: Optional[datetime] = None + completed_at: Optional[datetime] = None + error: Optional[str] = None + + def to_dict(self) -> Dict[str, Any]: + """Convert job to dictionary for JSON serialization.""" + return { + "job_id": self.job_id, + "command": " ".join(self.command), + "status": self.status.value, + "exit_code": self.exit_code, + "stdout": self.stdout, + "stderr": self.stderr, + "created_at": self.created_at.isoformat(), + "started_at": self.started_at.isoformat() if self.started_at else None, + "completed_at": self.completed_at.isoformat() if self.completed_at else None, + "error": self.error, + } + + +class ComfyCliExecutor: + """ + Executor for comfy-cli commands with async support and job tracking. + + Usage: + executor = ComfyCliExecutor(workspace="/basedir") + + # Synchronous execution + result = await executor.execute(["model", "list"]) + + # Asynchronous job (for long-running operations) + job = await executor.execute_async(["model", "download", "--url", "..."]) + """ + + def __init__( + self, + workspace: Optional[str] = None, + comfy_cmd: str = "comfy", + python_path: str = "python", + working_dir: Optional[str] = None, + ): + """ + Initialize the comfy-cli executor. + + Args: + workspace: ComfyUI workspace path. If None, uses current directory. + comfy_cmd: Command to invoke comfy-cli (default: "comfy"). + python_path: Path to Python interpreter (used only if comfy_cmd is a .py script). + working_dir: Working directory for subprocess execution. + """ + self.comfy_cmd = comfy_cmd + self.python_path = python_path + self.working_dir = Path(working_dir) if working_dir else Path.cwd() + self.workspace = Path(workspace) if workspace else self.working_dir + self._active_jobs: Dict[str, ComfyCLIJob] = {} + + # Auto-detect comfy-cli if needed + self.comfy_cmd_path = self._auto_detect_comfy_cli() + + def _auto_detect_comfy_cli(self) -> Path: + """ + Auto-detect comfy-cli executable location. + + Searches in order: + 1. System PATH (comfy installed as console script) + 2. Current working directory + + Returns: + Path to comfy-cli executable or command name. + """ + import shutil + + # Try to find 'comfy' in PATH + comfy_in_path = shutil.which("comfy") + if comfy_in_path: + return Path(comfy_in_path) + + # Try 'comfy-cli' variant + comfy_cli_in_path = shutil.which("comfy-cli") + if comfy_cli_in_path: + return Path(comfy_cli_in_path) + + # Return command name and let subprocess handle it + return Path("comfy") + + def _build_command(self, args: List[str]) -> List[str]: + """ + Build full command with workspace argument. + + Args: + args: Command arguments (e.g., ["model", "list"]). + + Returns: + Full command list including workspace configuration. + """ + # Check if comfy_cmd is a .py file + if self.comfy_cmd_path.suffix == ".py": + command = [self.python_path, str(self.comfy_cmd_path)] + else: + command = [str(self.comfy_cmd_path)] + + # Add workspace argument + command.extend(["--workspace", str(self.workspace)]) + + # Add command arguments + command.extend(args) + + return command + + async def execute( + self, + args: List[str], + timeout: Optional[int] = None, + capture_output: bool = True, + ) -> Dict[str, Any]: + """ + Execute a comfy-cli command synchronously. + + Args: + args: Command arguments (e.g., ["model", "list"]). + timeout: Timeout in seconds. + capture_output: Whether to capture stdout/stderr. + + Returns: + Dictionary with 'success', 'stdout', 'stderr', 'exit_code'. + + Raises: + ComfyCLIError: If command execution fails. + """ + command = self._build_command(args) + + try: + process = await asyncio.create_subprocess_exec( + *command, + stdout=asyncio.subprocess.PIPE if capture_output else None, + stderr=asyncio.subprocess.PIPE if capture_output else None, + cwd=self.working_dir, + ) + + if capture_output: + stdout, stderr = await asyncio.wait_for( + process.communicate(), + timeout=timeout, + ) + stdout_str = stdout.decode("utf-8", errors="replace") if stdout else "" + stderr_str = stderr.decode("utf-8", errors="replace") if stderr else "" + else: + await asyncio.wait_for(process.wait(), timeout=timeout) + stdout_str = "" + stderr_str = "" + + success = process.returncode == 0 + + return { + "success": success, + "stdout": stdout_str, + "stderr": stderr_str, + "exit_code": process.returncode, + "command": " ".join(command), + } + + except asyncio.TimeoutError as e: + raise ComfyCLIError( + f"Command timed out after {timeout} seconds", + command=command, + ) from e + except FileNotFoundError as e: + raise ComfyCLIError( + f"comfy-cli not found at {self.comfy_cmd_path}. " + "Ensure comfy-cli is installed (pip install comfy-cli) or in PATH.", + command=command, + ) from e + except Exception as e: + raise ComfyCLIError( + f"Failed to execute command: {str(e)}", + command=command, + ) from e + + async def execute_async( + self, + args: List[str], + timeout: Optional[int] = None, + ) -> ComfyCLIJob: + """ + Execute a comfy-cli command asynchronously with job tracking. + + Use this for long-running operations like model download, node install, etc. + + Args: + args: Command arguments. + timeout: Timeout in seconds. + + Returns: + ComfyCLIJob object for tracking execution. + """ + job_id = str(uuid.uuid4())[:8] + command = self._build_command(args) + + job = ComfyCLIJob( + job_id=job_id, + command=command, + status=JobStatus.RUNNING, + started_at=datetime.now(), + ) + + self._active_jobs[job_id] = job + process = None + + try: + process = await asyncio.create_subprocess_exec( + *command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.working_dir, + ) + + stdout, stderr = await asyncio.wait_for( + process.communicate(), + timeout=timeout, + ) + + job.stdout = stdout.decode("utf-8", errors="replace") + job.stderr = stderr.decode("utf-8", errors="replace") + job.exit_code = process.returncode + job.status = JobStatus.COMPLETED if process.returncode == 0 else JobStatus.FAILED + + if process.returncode != 0: + job.error = job.stderr.strip() or f"Exit code: {process.returncode}" + + except asyncio.TimeoutError: + job.status = JobStatus.TIMEOUT + job.error = f"Command timed out after {timeout} seconds" + if process: + try: + process.kill() + except: + pass + + except Exception as e: + job.status = JobStatus.FAILED + job.error = str(e) + + finally: + job.completed_at = datetime.now() + + return job + + def get_job(self, job_id: str) -> Optional[ComfyCLIJob]: + """Get job by ID.""" + return self._active_jobs.get(job_id) + + def list_jobs(self) -> List[ComfyCLIJob]: + """List all active jobs.""" + return list(self._active_jobs.values()) + + def cleanup_job(self, job_id: str) -> bool: + """Remove job from tracking. Returns True if job existed.""" + if job_id in self._active_jobs: + del self._active_jobs[job_id] + return True + return False + + # Output parsing utilities + + @staticmethod + def parse_list_output(raw_output: str) -> List[str]: + """ + Parse list output (one item per line). + + Used for: model list, node simple-show + + Args: + raw_output: Raw CLI output. + + Returns: + List of items. + """ + if not raw_output.strip(): + return [] + return [ + line.strip() + for line in raw_output.strip().split("\n") + if line.strip() and not line.startswith("#") + ] + + @staticmethod + def parse_env_output(raw_output: str) -> Dict[str, str]: + """ + Parse environment output (key: value format). + + Args: + raw_output: Raw CLI output. + + Returns: + Dictionary of environment variables. + """ + result = {} + for line in raw_output.strip().split("\n"): + if ":" in line: + key, value = line.split(":", 1) + key = key.strip().lower().replace(" ", "_") + value = value.strip() + result[key] = value + return result + + @staticmethod + def parse_download_progress(raw_output: str) -> Dict[str, Any]: + """ + Parse download progress output. + + Args: + raw_output: Raw CLI output. + + Returns: + Dictionary with status, progress, filename, path. + """ + import re + + result = { + "status": "unknown", + "progress": 0, + "filename": None, + "path": None, + } + + # Check for completion + if "Successfully downloaded" in raw_output or "Download complete" in raw_output: + result["status"] = "completed" + result["progress"] = 100 + + # Try to extract filename and path + match = re.search(r"Successfully downloaded (.+?) to (.+)", raw_output) + if match: + result["filename"] = match.group(1).strip() + result["path"] = match.group(2).strip() + + # Check for progress + progress_match = re.search(r"(\d+)%", raw_output) + if progress_match: + result["status"] = "downloading" + result["progress"] = int(progress_match.group(1)) + + # Check for errors + if "Error" in raw_output or "Failed" in raw_output: + result["status"] = "failed" + + return result diff --git a/comfy_cli_routes.py b/comfy_cli_routes.py new file mode 100644 index 0000000..ab6befe --- /dev/null +++ b/comfy_cli_routes.py @@ -0,0 +1,48 @@ +"""REST API route definitions for comfy-cli REST API.""" + +from aiohttp import web +import logging + +from .handlers.config import ConfigHandler +from .handlers.models import ModelHandler +from .handlers.nodes import NodeHandler +from ..comfy_cli import ComfyCliExecutor + +logger = logging.getLogger(__name__) + + +def setup_comfy_cli_routes(app: web.Application, executor: ComfyCliExecutor) -> None: + """ + Register comfy-cli REST API routes with the aiohttp application. + + Args: + app: aiohttp web application + executor: ComfyCliExecutor instance + """ + config_handler = ConfigHandler(executor) + model_handler = ModelHandler(executor) + node_handler = NodeHandler(executor) + + # Configuration endpoints + app.router.add_get("/comfy-cli/config/env", config_handler.get_env) + app.router.add_get("/comfy-cli/config/which", config_handler.get_which) + app.router.add_post("/comfy-cli/config/set-default", config_handler.set_default) + + # Model management endpoints + app.router.add_get("/comfy-cli/model/list", model_handler.list_models) + app.router.add_post("/comfy-cli/model/download", model_handler.download_model) + app.router.add_get( + "/comfy-cli/model/download/{job_id}/status", model_handler.get_download_status + ) + app.router.add_post("/comfy-cli/model/remove", model_handler.remove_model) + + # Node management endpoints + app.router.add_get("/comfy-cli/node/simple-show", node_handler.simple_show) + app.router.add_get("/comfy-cli/node/show", node_handler.show) + app.router.add_post("/comfy-cli/node/install", node_handler.install_node) + app.router.add_post("/comfy-cli/node/update", node_handler.update_node) + app.router.add_post("/comfy-cli/node/enable", node_handler.enable_node) + app.router.add_post("/comfy-cli/node/disable", node_handler.disable_node) + app.router.add_post("/comfy-cli/node/uninstall", node_handler.uninstall_node) + + logger.info("comfy-cli REST API routes registered") diff --git a/docs/references/README-comfy-docker.md b/docs/references/README-comfy-docker.md new file mode 100644 index 0000000..141a704 --- /dev/null +++ b/docs/references/README-comfy-docker.md @@ -0,0 +1,117 @@ +# ComfyUI-Nvidia-Docker + comfy-cli 快速配置指南 + +## 问题背景 + +在 Docker 环境中执行 `comfy set-default /basedir` 会失败: + +``` +❌ Specified path is not a ComfyUI path: /basedir +``` + +**原因**:comfy-cli 通过 Git 仓库检测验证 ComfyUI 安装,但 `/basedir` 是用户数据目录(持久化存储),ComfyUI 代码实际在 `/comfy/mnt/ComfyUI`。 + +## 快速解决方案 + +### 步骤 1:复制自动化脚本 + +```bash +# 在项目根目录执行 +cp docs/comfy-cli-docker-setup.sh run/user_script.bash +chmod +x run/user_script.bash +``` + +### 步骤 2:重启容器 + +```bash +docker restart comfyui-nvidia +``` + +### 步骤 3:验证配置 + +```bash +# 查看启动日志 +docker logs comfyui-nvidia | grep -A 30 "ComfyCLI" + +# 或进入容器验证 +docker exec -it comfyui-nvidia bash +/comfy/mnt/venv/bin/comfy which +/comfy/mnt/venv/bin/comfy model list +``` + +## 脚本功能 + +`comfy-cli-docker-setup.sh` 自动执行: + +1. ✅ 检测并安装 comfy-cli(如果未安装) +2. ✅ 直接写入 `config.ini`(绕过 Git 检测) +3. ✅ 配置工作区路径为 `/basedir` +4. ✅ 验证配置并输出常用命令 + +## 手动配置(备选) + +如果不想用脚本,可以手动编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +set -e + +VENV_PIP="/comfy/mnt/venv/bin/pip" +COMFY_CMD="/comfy/mnt/venv/bin/comfy" +CONFIG_DIR="$HOME/.config/comfy-cli" + +# 安装 comfy-cli +if ! $COMFY_CMD --version &> /dev/null; then + $VENV_PIP install comfy-cli -q +fi + +# 直接写入配置(绕过 set-default) +mkdir -p "$CONFIG_DIR" +cat > "$CONFIG_DIR/config.ini" << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +echo "comfy-cli configured successfully" +``` + +## 常用命令 + +配置完成后,在容器内使用: + +```bash +# 进入容器 +docker exec -it comfyui-nvidia bash + +# 列出模型 +comfy model list + +# 下载模型 +comfy model download --url "https://civitai.com/models/43331" + +# 安装节点 +comfy node install ComfyUI-Impact-Pack + +# 查看队列 +comfy queue +``` + +## 环境变量(可选) + +在 `docker-compose.yml` 中添加 API Token: + +```yaml +environment: + - CIVITAI_API_TOKEN=your_token_here + - HF_API_TOKEN=your_token_here +``` + +## 相关文档 + +- 完整指南:`comfy-cli-guide.md` +- 自动化脚本:`comfy-cli-docker-setup.sh` + +--- + +*由 Zero (零号) 生成 | 2026-03-16* diff --git a/docs/references/archive/comfy-cli-command-reference-v1.0.md b/docs/references/archive/comfy-cli-command-reference-v1.0.md new file mode 100644 index 0000000..2223458 --- /dev/null +++ b/docs/references/archive/comfy-cli-command-reference-v1.0.md @@ -0,0 +1,801 @@ +# comfy-cli 命令参考手册 + +> 文档版本:1.0 +> 最后更新:2026-03-16 +> 基于 comfy-cli v1.6.0 +> **状态:部分通过源码验证,部分需实际运行补充** + +--- + +## 使用说明 + +本文档整理 comfy-cli 所有命令的: +- 完整参数列表 +- 参数类型和默认值 +- CLI 输出格式示例 + +**验证状态图例:** +- ✅ 已通过源码验证 +- ⚠️ 基于文档推断,需实际验证 +- ❓ 未知,需补充 + +--- + +## 全局选项 + +所有命令都支持以下全局选项: + +```bash +comfy [GLOBAL_OPTIONS] COMMAND [SUBCOMMAND] [ARGS] +``` + +| 选项 | 短选项 | 类型 | 默认值 | 说明 | +|------|--------|------|--------|------| +| `--workspace` | `-w` | PATH | - | 指定 ComfyUI 工作区路径 | +| `--recent` | - | flag | false | 使用最近使用的工作区 | +| `--here` | - | flag | false | 使用当前目录作为工作区 | +| `--version` | -V | flag | false | 显示版本号并退出 | +| `--help` | -h | flag | false | 显示帮助信息 | + +**优先级:** `--workspace` > `--recent` > `--here` > 当前目录 > 默认配置 + +--- + +## 配置命令 + +### `comfy set-default` + +设置默认工作区路径。 + +**语法:** +```bash +comfy set-default PATH [--launch-extras "ARGS"] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `PATH` | PATH | ✅ | - | 工作区路径(必须是有效的 ComfyUI 目录) | +| `--launch-extras` | STRING | ❌ | `""` | 默认启动参数,如 `"--listen 0.0.0.0 --port 8188"` | + +**验证状态:** ✅ + +**成功输出:** +``` +Default workspace set to /path/to/ComfyUI +Config file updated: ~/.config/comfy-cli/config.ini +``` + +**失败输出(Docker 环境):** +``` +Error: Specified path is not a ComfyUI path: /basedir +Reason: Path does not contain a valid ComfyUI git repository +``` + +**备注:** Docker 环境中建议直接编辑 `~/.config/comfy-cli/config.ini` + +--- + +### `comfy which` + +显示当前选中的 ComfyUI 路径。 + +**语法:** +```bash +comfy which +``` + +**参数:** 无 + +**验证状态:** ✅ + +**输出格式:** +``` +# 单行输出,当前工作区路径 +/basedir +``` + +**返回码:** +- `0` - 成功 +- `1` - 未配置工作区 + +--- + +### `comfy env` + +打印环境配置信息。 + +**语法:** +```bash +comfy env +``` + +**参数:** 无 + +**验证状态:** ✅ + +**输出格式:** +``` +Default Workspace: /basedir +Recent Workspace: /basedir +Launch Extras: --listen 0.0.0.0 --port 8188 +Enable Tracking: false +CivitAI API Token: set|not_set +HuggingFace API Token: set|not_set +Background Process: (host, port, pid) | None +``` + +**解析提示:** 每行格式为 `Key: Value`,可用 `:` 分割解析 + +--- + +### `comfy version` + +显示版本号。 + +**语法:** +```bash +comfy --version +``` + +**输出格式:** +``` +comfy-cli 1.6.0 +``` + +--- + +## 模型管理命令 (`comfy model`) + +### `comfy model download` + +下载模型文件。 + +**语法:** +```bash +comfy model download --url URL [OPTIONS] +``` + +**参数:** +| 参数 | 短选项 | 类型 | 必需 | 默认值 | 说明 | +|------|--------|------|------|--------|------| +| `--url` | `-u` | URL | ✅ | - | 模型下载 URL | +| `--relative-path` | `-p` | PATH | ❌ | `models/checkpoints` | 保存的相对路径 | +| `--filename` | `-f` | STRING | ❌ | 自动 | 保存的文件名 | +| `--set-civitai-api-token` | - | STRING | ❌ | - | CivitAI API Token | +| `--set-hf-api-token` | - | STRING | ❌ | - | HuggingFace API Token | + +**验证状态:** ✅ + +**下载中输出:** +``` +Downloading model from https://... +Saving to: /basedir/models/checkpoints/model.safetensors +Progress: 45% (1.2GB / 2.7GB) +Speed: 15.3 MB/s +ETA: 1m 30s +``` + +**成功输出:** +``` +✓ Successfully downloaded model.safetensors +Path: /basedir/models/checkpoints/model.safetensors +Size: 2.7 GB +Time: 2m 15s +``` + +**失败输出:** +``` +✗ Download failed +Error: HTTP 401: Unauthorized +Reason: Missing or invalid API token +URL: https://civitai.com/models/43331 +``` + +**返回码:** +- `0` - 下载成功 +- `1` - 下载失败(网络错误、认证失败等) + +--- + +### `comfy model list` + +列出模型文件。 + +**语法:** +```bash +comfy model list [--relative-path PATH] +``` + +**参数:** +| 参数 | 短选项 | 类型 | 必需 | 默认值 | 说明 | +|------|--------|------|------|--------|------| +| `--relative-path` | `-p` | PATH | ❌ | `models` | 要列出的目录 | + +**验证状态:** ✅ + +**输出格式:** +``` +# 每行一个文件名,无其他信息 +sd_xl_base_1.0.safetensors +realistic_vision_v5.safetensors +dreamshaper_8.safetensors +``` + +**空目录:** +``` +# 空输出,无内容 +``` + +**错误输出:** +``` +Error: Directory not found: /basedir/models/nonexistent +``` + +**解析提示:** +```python +models = [line.strip() for line in output.strip().split('\n') if line.strip()] +``` + +--- + +### `comfy model remove` + +删除模型文件。 + +**语法:** +```bash +comfy model remove --relative-path PATH --model-names NAME1 [NAME2 ...] [--confirm] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--relative-path` | PATH | ❌ | `models/checkpoints` | 模型目录 | +| `--model-names` | STRING[] | ✅ | - | 模型文件名(可多个) | +| `--confirm` | flag | ❌ | false | 跳过确认提示 | + +**验证状态:** ⚠️ + +**交互模式输出:** +``` +The following models will be deleted: + /basedir/models/checkpoints/old_model.safetensors (2.1 GB) + +Are you sure? [y/N]: +``` + +**成功输出(--confirm):** +``` +✓ Successfully removed 1 model(s) +Freed space: 2.1 GB +``` + +**失败输出:** +``` +Error: Model not found: nonexistent.safetensors +Path: /basedir/models/checkpoints/nonexistent.safetensors +``` + +--- + +## 节点管理命令 (`comfy node`) + +### `comfy node simple-show` + +以简化格式显示节点列表。 + +**语法:** +```bash +comfy node simple-show MODE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `MODE` | STRING | ✅ | `installed`, `enabled`, `disabled`, `not-installed`, `all` | + +**验证状态:** ✅ + +**输出格式:** +``` +# 每行一个节点名称 +ComfyUI-Impact-Pack +ComfyUI-Manager +ComfyUI-Inspire-Pack +``` + +**返回码:** +- `0` - 成功 + +--- + +### `comfy node show` + +显示节点详细信息。 + +**语法:** +```bash +comfy node show MODE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `MODE` | STRING | ✅ | `installed`, `enabled`, `disabled`, `all` | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Node: ComfyUI-Impact-Pack + Author: ltdrdata + Version: v5.12.1 + Status: enabled + Path: /basedir/custom_nodes/ComfyUI-Impact-Pack + URL: https://github.com/ltdrdata/ComfyUI-Impact-Pack + Requirements: opencv-python, pillow + +Node: ComfyUI-Manager + Author: ltdrdata + Version: v2.45.0 + Status: enabled + Path: /basedir/custom_nodes/ComfyUI-Manager + URL: https://github.com/ltdrdata/ComfyUI-Manager + Requirements: - +``` + +**解析提示:** 多行块状格式,每块以 `Node:` 开始 + +--- + +### `comfy node install` + +安装自定义节点。 + +**语法:** +```bash +comfy node install NODE_NAME [NODE_NAME2 ...] [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `NODE_NAME` | STRING[] | ✅ | - | 节点名称(可多个) | +| `--fast-deps` | flag | ❌ | false | 使用快速依赖安装 | +| `--no-deps` | flag | ❌ | false | 跳过依赖安装 | +| `--channel` | STRING | ❌ | `recent` | 节点频道 (`recent`, `dev`, `archive`) | + +**验证状态:** ✅ + +**输出格式:** +``` +Installing 1 node(s)... + +[1/1] ComfyUI-Impact-Pack + Cloning from: https://github.com/ltdrdata/ComfyUI-Impact-Pack + Installing to: /basedir/custom_nodes/ComfyUI-Impact-Pack + Installing dependencies: opencv-python, pillow + ✓ Installation complete + +Total: 1 installed, 0 failed +Time: 45s +``` + +**失败输出:** +``` +Installing 1 node(s)... + +[1/1] SomeNode + ✗ Installation failed + Error: Git clone failed + Reason: Repository not found + +Total: 0 installed, 1 failed +``` + +**返回码:** +- `0` - 所有节点安装成功 +- `1` - 部分或全部失败 + +--- + +### `comfy node update` + +更新节点。 + +**语法:** +```bash +comfy node update TARGET [--channel CHANNEL] [--mode MODE] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `TARGET` | STRING | ✅ | - | `all` 或节点名称 | +| `--channel` | STRING | ❌ | `recent` | 节点频道 | +| `--mode` | STRING | ❌ | `remote` | 更新模式 (`remote`, `local`) | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Updating all nodes... + +[1/3] ComfyUI-Impact-Pack + Current: v5.12.0 + Latest: v5.12.1 + Updating... ✓ Updated + +[2/3] ComfyUI-Manager + Already up to date + +[3/3] ComfyUI-Inspire-Pack + Current: v1.0.0 + Latest: v1.0.0 + Already up to date + +Total: 1 updated, 2 unchanged, 0 failed +``` + +--- + +### `comfy node enable` + +启用节点。 + +**语法:** +```bash +comfy node enable NODE_NAME +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `NODE_NAME` | STRING | ✅ | 节点名称 | + +**验证状态:** ⚠️ + +**输出格式:** +``` +✓ Enabled: ComfyUI-Impact-Pack +Path: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node disable` + +禁用节点。 + +**语法:** +```bash +comfy node disable NODE_NAME +``` + +**输出格式:** +``` +✓ Disabled: ComfyUI-Impact-Pack +Path: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node uninstall` + +卸载节点。 + +**语法:** +```bash +comfy node uninstall NODE_NAME [--confirm] +``` + +**输出格式:** +``` +✓ Uninstalled: ComfyUI-Impact-Pack +Removed: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node save-snapshot` + +保存当前节点快照。 + +**语法:** +```bash +comfy node save-snapshot [--output FILENAME] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--output` | STRING | ❌ | 自动生成 | 输出文件名 | + +**验证状态:** ⚠️ + +**输出格式:** +``` +✓ Snapshot saved +File: /basedir/custom_nodes/snapshot/snapshot-2026-03-16.json +Nodes: 5 +``` + +--- + +### `comfy node restore-snapshot` + +恢复节点快照。 + +**语法:** +```bash +comfy node restore-snapshot SNAPSHOT_FILE [--retry] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `SNAPSHOT_FILE` | PATH | ✅ | - | 快照文件路径 | +| `--retry` | flag | ❌ | false | 失败时重试 | + +**输出格式:** +``` +Restoring from: snapshot-2026-03-16.json + +[1/5] Installing ComfyUI-Impact-Pack... ✓ +[2/5] Installing ComfyUI-Manager... ✓ +[3/5] Installing ComfyUI-Inspire-Pack... ✗ (failed, retrying) +[3/5] Installing ComfyUI-Inspire-Pack... ✓ (retry success) + +Total: 5 restored, 0 failed +``` + +--- + +### `comfy node deps-in-workflow` + +从 workflow 文件生成依赖列表。 + +**语法:** +```bash +comfy node deps-in-workflow --workflow WORKFLOW_FILE --output OUTPUT_FILE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `--workflow` | PATH | ✅ | Workflow JSON 文件 | +| `--output` | PATH | ✅ | 输出依赖文件 | + +**验证状态:** ❓ + +--- + +## ComfyUI 管理命令 + +### `comfy install` + +安装 ComfyUI。 + +**语法:** +```bash +comfy install [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--version` | STRING | ❌ | `latest` | 版本 (`latest`, `nightly`, 具体版本) | +| `--nvidia` | flag | ❌ | false | NVIDIA GPU 优化 | +| `--amd` | flag | ❌ | false | AMD GPU 优化 | +| `--cpu` | flag | ❌ | false | CPU 模式 | +| `--skip-manager` | flag | ❌ | false | 跳过 Manager 安装 | +| `--pr` | STRING | ❌ | - | 安装特定 PR | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Installing ComfyUI... + +[1/4] Cloning ComfyUI repository... ✓ +[2/4] Setting up Python virtual environment... ✓ +[3/4] Installing dependencies... ✓ +[4/4] Installing ComfyUI-Manager... ✓ + +✓ Installation complete +Path: ~/comfy/ComfyUI +To launch: comfy launch +``` + +--- + +### `comfy launch` + +启动 ComfyUI。 + +**语法:** +```bash +comfy launch [--background] [-- ARGS...] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--background` | flag | ❌ | false | 后台运行 | +| `--` | - | - | - | 后续参数传递给 ComfyUI | + +**验证状态:** ⚠️ + +**前台输出:** +``` +Launching ComfyUI... + +[ComfyUI Output] +Starting server... +To see the GUI go to: http://0.0.0.0:8188 +^C +Shutting down... +``` + +**后台输出:** +``` +✓ ComfyUI started in background +PID: 12345 +URL: http://127.0.0.1:8188 +To stop: comfy stop +``` + +--- + +### `comfy stop` + +停止后台运行的 ComfyUI。 + +**语法:** +```bash +comfy stop +``` + +**输出格式:** +``` +✓ ComfyUI stopped (PID: 12345) +``` + +或(无运行实例): +``` +No running ComfyUI instance found +``` + +--- + +### `comfy update` + +更新 ComfyUI。 + +**语法:** +```bash +comfy update TARGET +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `TARGET` | STRING | ✅ | `comfy` 或 `all` | + +**输出格式:** +``` +Updating ComfyUI... + +Current: 1.2.3 +Latest: 1.2.4 +Updating... ✓ Updated + +Restart required. Run: comfy launch +``` + +--- + +## Workflow 执行命令 + +### `comfy run` + +执行 workflow 文件。 + +**语法:** +```bash +comfy run --workflow WORKFLOW_FILE [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--workflow` | PATH | ✅ | - | Workflow JSON 文件 | +| `--wait` | flag | ❌ | false | 等待完成 | +| `--verbose` | flag | ❌ | false | 详细输出 | +| `--host` | STRING | ❌ | `127.0.0.1` | ComfyUI 主机 | +| `--port` | INT | ❌ | `8188` | ComfyUI 端口 | +| `--timeout` | INT | ❌ | `60` | 超时秒数 | + +**验证状态:** ⚠️ + +**输出格式(--wait):** +``` +Submitting workflow... +Prompt ID: uuid-12345 + +Queue position: 1 +Processing... +[100%] Step 15/15 +✓ Completed + +Output: + [0] Image: /basedir/output/ComfyUI_00001.png + [1] Image: /basedir/output/ComfyUI_00002.png + +Execution time: 12.5s +``` + +**输出格式(无 --wait):** +``` +✓ Workflow submitted +Prompt ID: uuid-12345 +URL: http://127.0.0.1:8188/history +``` + +--- + +## 附录 + +### A. 返回码汇总 + +| 返回码 | 说明 | +|--------|------| +| `0` | 成功 | +| `1` | 一般错误 | +| `2` | 参数错误 | +| `3` | 工作区配置错误 | +| `4` | 网络错误 | +| `5` | 文件/目录不存在 | + +### B. 输出颜色 + +comfy-cli 使用颜色标识: +- 🟢 绿色 - 成功 +- 🔴 红色 - 错误 +- 🟡 黄色 - 警告 +- 🔵 蓝色 - 信息 + +**禁用颜色:** +```bash +comfy --no-color model list +# 或 +export NO_COLOR=1 +``` + +### C. 环境变量 + +| 变量 | 说明 | +|------|------| +| `CIVITAI_API_TOKEN` | CivitAI API Token | +| `HF_API_TOKEN` | HuggingFace API Token | +| `COMFY_CLI_WORKSPACE` | 覆盖默认工作区 | +| `NO_COLOR` | 禁用彩色输出 | + +--- + +## 待补充内容 + +以下命令需要实际运行验证: + +- [ ] `comfy node bisect` 系列命令 +- [ ] `comfy node install-deps` +- [ ] `comfy feedback` +- [ ] 所有命令的错误输出格式 +- [ ] 交互式提示的具体文本 + +--- + +*由 Zero (零号) 生成 | 2026-03-16* + +*注:本文档基于 comfy-cli v1.6.0 源码和相关文档整理。部分输出格式为推断,欢迎实际运行后补充修正。* diff --git a/docs/references/archive/comfy-cli-command-reference-v2.0.md b/docs/references/archive/comfy-cli-command-reference-v2.0.md new file mode 100644 index 0000000..9ea058b --- /dev/null +++ b/docs/references/archive/comfy-cli-command-reference-v2.0.md @@ -0,0 +1,821 @@ +# comfy-cli 命令参考手册 + +> 文档版本:2.0 +> 最后更新:2026-03-17 +> 基于 comfy-cli v1.6.0 源码 +> **状态:输出格式已通过源码验证(rich table)** + +--- + +## 使用说明 + +本文档整理 comfy-cli 所有命令的: +- 完整参数列表 +- 参数类型和默认值 +- CLI 输出格式示例 + +**验证状态图例:** +- ✅ 已通过源码验证 +- ⚠️ 基于文档推断,需实际验证 +- ❓ 未知,需补充 + +--- + +## ⚠️ 重要:输出格式说明 + +comfy-cli 使用 **`rich` 库的 `Table` 组件**输出格式化的 **box-drawing table**(边框表格)。 + +**示例输出:** +``` +┌──────────────────────────┬─────────────┬─────────┐ +│ Name │ Version │ Status │ +├──────────────────────────┼─────────────┼─────────┤ +│ ComfyUI-Impact-Pack │ v5.12.1 │ enabled │ +│ ComfyUI-Manager │ v2.45.0 │ enabled │ +└──────────────────────────┴─────────────┴─────────┘ +``` + +**这不是纯文本!需要使用专门的解析器转换为 JSON/CSV。** + +**源码位置:** `comfy_cli/ui.py::display_table()` + +--- + +## 全局选项 + +所有命令都支持以下全局选项: + +```bash +comfy [GLOBAL_OPTIONS] COMMAND [SUBCOMMAND] [ARGS] +``` + +| 选项 | 短选项 | 类型 | 默认值 | 说明 | +|------|--------|------|--------|------| +| `--workspace` | `-w` | PATH | - | 指定 ComfyUI 工作区路径 | +| `--recent` | - | flag | false | 使用最近使用的工作区 | +| `--here` | - | flag | false | 使用当前目录作为工作区 | +| `--version` | -V | flag | false | 显示版本号并退出 | +| `--help` | -h | flag | false | 显示帮助信息 | + +**优先级:** `--workspace` > `--recent` > `--here` > 当前目录 > 默认配置 + +--- + +## 配置命令 + +### `comfy set-default` + +设置默认工作区路径。 + +**语法:** +```bash +comfy set-default PATH [--launch-extras "ARGS"] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `PATH` | PATH | ✅ | - | 工作区路径(必须是有效的 ComfyUI 目录) | +| `--launch-extras` | STRING | ❌ | `""` | 默认启动参数,如 `"--listen 0.0.0.0 --port 8188"` | + +**验证状态:** ✅ + +**成功输出:** +``` +Default workspace set to /path/to/ComfyUI +Config file updated: ~/.config/comfy-cli/config.ini +``` + +**失败输出(Docker 环境):** +``` +Error: Specified path is not a ComfyUI path: /basedir +Reason: Path does not contain a valid ComfyUI git repository +``` + +**备注:** Docker 环境中建议直接编辑 `~/.config/comfy-cli/config.ini` + +--- + +### `comfy which` + +显示当前选中的 ComfyUI 路径。 + +**语法:** +```bash +comfy which +``` + +**参数:** 无 + +**验证状态:** ✅ + +**输出格式:** +``` +# 单行输出,当前工作区路径 +/basedir +``` + +**返回码:** +- `0` - 成功 +- `1` - 未配置工作区 + +--- + +### `comfy env` + +打印环境配置信息。 + +**语法:** +```bash +comfy env +``` + +**参数:** 无 + +**验证状态:** ✅ + +**输出格式:** +``` +Default Workspace: /basedir +Recent Workspace: /basedir +Launch Extras: --listen 0.0.0.0 --port 8188 +Enable Tracking: false +CivitAI API Token: set|not_set +HuggingFace API Token: set|not_set +Background Process: (host, port, pid) | None +``` + +**解析提示:** 每行格式为 `Key: Value`,可用 `:` 分割解析 + +--- + +### `comfy version` + +显示版本号。 + +**语法:** +```bash +comfy --version +``` + +**输出格式:** +``` +comfy-cli 1.6.0 +``` + +--- + +## 模型管理命令 (`comfy model`) + +### `comfy model download` + +下载模型文件。 + +**语法:** +```bash +comfy model download --url URL [OPTIONS] +``` + +**参数:** +| 参数 | 短选项 | 类型 | 必需 | 默认值 | 说明 | +|------|--------|------|------|--------|------| +| `--url` | `-u` | URL | ✅ | - | 模型下载 URL | +| `--relative-path` | `-p` | PATH | ❌ | `models/checkpoints` | 保存的相对路径 | +| `--filename` | `-f` | STRING | ❌ | 自动 | 保存的文件名 | +| `--set-civitai-api-token` | - | STRING | ❌ | - | CivitAI API Token | +| `--set-hf-api-token` | - | STRING | ❌ | - | HuggingFace API Token | + +**验证状态:** ✅ + +**下载中输出:** +``` +Downloading model from https://... +Saving to: /basedir/models/checkpoints/model.safetensors +Progress: 45% (1.2GB / 2.7GB) +Speed: 15.3 MB/s +ETA: 1m 30s +``` + +**成功输出:** +``` +✓ Successfully downloaded model.safetensors +Path: /basedir/models/checkpoints/model.safetensors +Size: 2.7 GB +Time: 2m 15s +``` + +**失败输出:** +``` +✗ Download failed +Error: HTTP 401: Unauthorized +Reason: Missing or invalid API token +URL: https://civitai.com/models/43331 +``` + +**返回码:** +- `0` - 下载成功 +- `1` - 下载失败(网络错误、认证失败等) + +--- + +### `comfy model list` + +列出模型文件。 + +**语法:** +```bash +comfy model list [--relative-path PATH] +``` + +**参数:** +| 参数 | 短选项 | 类型 | 必需 | 默认值 | 说明 | +|------|--------|------|------|--------|------| +| `--relative-path` | `-p` | PATH | ❌ | `models` | 要列出的目录 | + +**验证状态:** ✅ + +**输出格式:** +``` +# 每行一个文件名,无其他信息 +sd_xl_base_1.0.safetensors +realistic_vision_v5.safetensors +dreamshaper_8.safetensors +``` + +**空目录:** +``` +# 空输出,无内容 +``` + +**错误输出:** +``` +Error: Directory not found: /basedir/models/nonexistent +``` + +**解析提示:** +```python +models = [line.strip() for line in output.strip().split('\n') if line.strip()] +``` + +--- + +### `comfy model remove` + +删除模型文件。 + +**语法:** +```bash +comfy model remove --relative-path PATH --model-names NAME1 [NAME2 ...] [--confirm] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--relative-path` | PATH | ❌ | `models/checkpoints` | 模型目录 | +| `--model-names` | STRING[] | ✅ | - | 模型文件名(可多个) | +| `--confirm` | flag | ❌ | false | 跳过确认提示 | + +**验证状态:** ⚠️ + +**交互模式输出:** +``` +The following models will be deleted: + /basedir/models/checkpoints/old_model.safetensors (2.1 GB) + +Are you sure? [y/N]: +``` + +**成功输出(--confirm):** +``` +✓ Successfully removed 1 model(s) +Freed space: 2.1 GB +``` + +**失败输出:** +``` +Error: Model not found: nonexistent.safetensors +Path: /basedir/models/checkpoints/nonexistent.safetensors +``` + +--- + +## 节点管理命令 (`comfy node`) + +### `comfy node simple-show` + +以简化格式显示节点列表。 + +**语法:** +```bash +comfy node simple-show MODE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `MODE` | STRING | ✅ | `installed`, `enabled`, `disabled`, `not-installed`, `all` | + +**验证状态:** ✅ + +**输出格式:** +``` +# 每行一个节点名称 +ComfyUI-Impact-Pack +ComfyUI-Manager +ComfyUI-Inspire-Pack +``` + +**返回码:** +- `0` - 成功 + +--- + +### `comfy node show` + +显示节点详细信息。 + +**语法:** +```bash +comfy node show MODE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `MODE` | STRING | ✅ | `installed`, `enabled`, `disabled`, `all` | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Node: ComfyUI-Impact-Pack + Author: ltdrdata + Version: v5.12.1 + Status: enabled + Path: /basedir/custom_nodes/ComfyUI-Impact-Pack + URL: https://github.com/ltdrdata/ComfyUI-Impact-Pack + Requirements: opencv-python, pillow + +Node: ComfyUI-Manager + Author: ltdrdata + Version: v2.45.0 + Status: enabled + Path: /basedir/custom_nodes/ComfyUI-Manager + URL: https://github.com/ltdrdata/ComfyUI-Manager + Requirements: - +``` + +**解析提示:** 多行块状格式,每块以 `Node:` 开始 + +--- + +### `comfy node install` + +安装自定义节点。 + +**语法:** +```bash +comfy node install NODE_NAME [NODE_NAME2 ...] [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `NODE_NAME` | STRING[] | ✅ | - | 节点名称(可多个) | +| `--fast-deps` | flag | ❌ | false | 使用快速依赖安装 | +| `--no-deps` | flag | ❌ | false | 跳过依赖安装 | +| `--channel` | STRING | ❌ | `recent` | 节点频道 (`recent`, `dev`, `archive`) | + +**验证状态:** ✅ + +**输出格式:** +``` +Installing 1 node(s)... + +[1/1] ComfyUI-Impact-Pack + Cloning from: https://github.com/ltdrdata/ComfyUI-Impact-Pack + Installing to: /basedir/custom_nodes/ComfyUI-Impact-Pack + Installing dependencies: opencv-python, pillow + ✓ Installation complete + +Total: 1 installed, 0 failed +Time: 45s +``` + +**失败输出:** +``` +Installing 1 node(s)... + +[1/1] SomeNode + ✗ Installation failed + Error: Git clone failed + Reason: Repository not found + +Total: 0 installed, 1 failed +``` + +**返回码:** +- `0` - 所有节点安装成功 +- `1` - 部分或全部失败 + +--- + +### `comfy node update` + +更新节点。 + +**语法:** +```bash +comfy node update TARGET [--channel CHANNEL] [--mode MODE] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `TARGET` | STRING | ✅ | - | `all` 或节点名称 | +| `--channel` | STRING | ❌ | `recent` | 节点频道 | +| `--mode` | STRING | ❌ | `remote` | 更新模式 (`remote`, `local`) | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Updating all nodes... + +[1/3] ComfyUI-Impact-Pack + Current: v5.12.0 + Latest: v5.12.1 + Updating... ✓ Updated + +[2/3] ComfyUI-Manager + Already up to date + +[3/3] ComfyUI-Inspire-Pack + Current: v1.0.0 + Latest: v1.0.0 + Already up to date + +Total: 1 updated, 2 unchanged, 0 failed +``` + +--- + +### `comfy node enable` + +启用节点。 + +**语法:** +```bash +comfy node enable NODE_NAME +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `NODE_NAME` | STRING | ✅ | 节点名称 | + +**验证状态:** ⚠️ + +**输出格式:** +``` +✓ Enabled: ComfyUI-Impact-Pack +Path: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node disable` + +禁用节点。 + +**语法:** +```bash +comfy node disable NODE_NAME +``` + +**输出格式:** +``` +✓ Disabled: ComfyUI-Impact-Pack +Path: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node uninstall` + +卸载节点。 + +**语法:** +```bash +comfy node uninstall NODE_NAME [--confirm] +``` + +**输出格式:** +``` +✓ Uninstalled: ComfyUI-Impact-Pack +Removed: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node save-snapshot` + +保存当前节点快照。 + +**语法:** +```bash +comfy node save-snapshot [--output FILENAME] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--output` | STRING | ❌ | 自动生成 | 输出文件名 | + +**验证状态:** ⚠️ + +**输出格式:** +``` +✓ Snapshot saved +File: /basedir/custom_nodes/snapshot/snapshot-2026-03-16.json +Nodes: 5 +``` + +--- + +### `comfy node restore-snapshot` + +恢复节点快照。 + +**语法:** +```bash +comfy node restore-snapshot SNAPSHOT_FILE [--retry] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `SNAPSHOT_FILE` | PATH | ✅ | - | 快照文件路径 | +| `--retry` | flag | ❌ | false | 失败时重试 | + +**输出格式:** +``` +Restoring from: snapshot-2026-03-16.json + +[1/5] Installing ComfyUI-Impact-Pack... ✓ +[2/5] Installing ComfyUI-Manager... ✓ +[3/5] Installing ComfyUI-Inspire-Pack... ✗ (failed, retrying) +[3/5] Installing ComfyUI-Inspire-Pack... ✓ (retry success) + +Total: 5 restored, 0 failed +``` + +--- + +### `comfy node deps-in-workflow` + +从 workflow 文件生成依赖列表。 + +**语法:** +```bash +comfy node deps-in-workflow --workflow WORKFLOW_FILE --output OUTPUT_FILE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `--workflow` | PATH | ✅ | Workflow JSON 文件 | +| `--output` | PATH | ✅ | 输出依赖文件 | + +**验证状态:** ❓ + +--- + +## ComfyUI 管理命令 + +### `comfy install` + +安装 ComfyUI。 + +**语法:** +```bash +comfy install [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--version` | STRING | ❌ | `latest` | 版本 (`latest`, `nightly`, 具体版本) | +| `--nvidia` | flag | ❌ | false | NVIDIA GPU 优化 | +| `--amd` | flag | ❌ | false | AMD GPU 优化 | +| `--cpu` | flag | ❌ | false | CPU 模式 | +| `--skip-manager` | flag | ❌ | false | 跳过 Manager 安装 | +| `--pr` | STRING | ❌ | - | 安装特定 PR | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Installing ComfyUI... + +[1/4] Cloning ComfyUI repository... ✓ +[2/4] Setting up Python virtual environment... ✓ +[3/4] Installing dependencies... ✓ +[4/4] Installing ComfyUI-Manager... ✓ + +✓ Installation complete +Path: ~/comfy/ComfyUI +To launch: comfy launch +``` + +--- + +### `comfy launch` + +启动 ComfyUI。 + +**语法:** +```bash +comfy launch [--background] [-- ARGS...] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--background` | flag | ❌ | false | 后台运行 | +| `--` | - | - | - | 后续参数传递给 ComfyUI | + +**验证状态:** ⚠️ + +**前台输出:** +``` +Launching ComfyUI... + +[ComfyUI Output] +Starting server... +To see the GUI go to: http://0.0.0.0:8188 +^C +Shutting down... +``` + +**后台输出:** +``` +✓ ComfyUI started in background +PID: 12345 +URL: http://127.0.0.1:8188 +To stop: comfy stop +``` + +--- + +### `comfy stop` + +停止后台运行的 ComfyUI。 + +**语法:** +```bash +comfy stop +``` + +**输出格式:** +``` +✓ ComfyUI stopped (PID: 12345) +``` + +或(无运行实例): +``` +No running ComfyUI instance found +``` + +--- + +### `comfy update` + +更新 ComfyUI。 + +**语法:** +```bash +comfy update TARGET +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `TARGET` | STRING | ✅ | `comfy` 或 `all` | + +**输出格式:** +``` +Updating ComfyUI... + +Current: 1.2.3 +Latest: 1.2.4 +Updating... ✓ Updated + +Restart required. Run: comfy launch +``` + +--- + +## Workflow 执行命令 + +### `comfy run` + +执行 workflow 文件。 + +**语法:** +```bash +comfy run --workflow WORKFLOW_FILE [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--workflow` | PATH | ✅ | - | Workflow JSON 文件 | +| `--wait` | flag | ❌ | false | 等待完成 | +| `--verbose` | flag | ❌ | false | 详细输出 | +| `--host` | STRING | ❌ | `127.0.0.1` | ComfyUI 主机 | +| `--port` | INT | ❌ | `8188` | ComfyUI 端口 | +| `--timeout` | INT | ❌ | `60` | 超时秒数 | + +**验证状态:** ⚠️ + +**输出格式(--wait):** +``` +Submitting workflow... +Prompt ID: uuid-12345 + +Queue position: 1 +Processing... +[100%] Step 15/15 +✓ Completed + +Output: + [0] Image: /basedir/output/ComfyUI_00001.png + [1] Image: /basedir/output/ComfyUI_00002.png + +Execution time: 12.5s +``` + +**输出格式(无 --wait):** +``` +✓ Workflow submitted +Prompt ID: uuid-12345 +URL: http://127.0.0.1:8188/history +``` + +--- + +## 附录 + +### A. 返回码汇总 + +| 返回码 | 说明 | +|--------|------| +| `0` | 成功 | +| `1` | 一般错误 | +| `2` | 参数错误 | +| `3` | 工作区配置错误 | +| `4` | 网络错误 | +| `5` | 文件/目录不存在 | + +### B. 输出颜色 + +comfy-cli 使用颜色标识: +- 🟢 绿色 - 成功 +- 🔴 红色 - 错误 +- 🟡 黄色 - 警告 +- 🔵 蓝色 - 信息 + +**禁用颜色:** +```bash +comfy --no-color model list +# 或 +export NO_COLOR=1 +``` + +### C. 环境变量 + +| 变量 | 说明 | +|------|------| +| `CIVITAI_API_TOKEN` | CivitAI API Token | +| `HF_API_TOKEN` | HuggingFace API Token | +| `COMFY_CLI_WORKSPACE` | 覆盖默认工作区 | +| `NO_COLOR` | 禁用彩色输出 | + +--- + +## 待补充内容 + +以下命令需要实际运行验证: + +- [ ] `comfy node bisect` 系列命令 +- [ ] `comfy node install-deps` +- [ ] `comfy feedback` +- [ ] 所有命令的错误输出格式 +- [ ] 交互式提示的具体文本 + +--- + +*由 Zero (零号) 生成 | 2026-03-16* + +*注:本文档基于 comfy-cli v1.6.0 源码和相关文档整理。部分输出格式为推断,欢迎实际运行后补充修正。* diff --git a/docs/references/archive/comfy-cli-guide-v1.0.md b/docs/references/archive/comfy-cli-guide-v1.0.md new file mode 100644 index 0000000..3c2cbe5 --- /dev/null +++ b/docs/references/archive/comfy-cli-guide-v1.0.md @@ -0,0 +1,1465 @@ +# ComfyCLI 配置与使用指南 + +> 文档版本:1.0 +> 最后更新:2026-03-16 +> 适用环境:ComfyUI-Nvidia-Docker 部署 + +--- + +## 目录 + +1. [概述](#1-概述) +2. [ComfyCLI 架构](#2-comfycli-架构) +3. [安装指南](#3-安装指南) +4. [配置系统详解](#4-配置系统详解) +5. [工作区管理](#5-工作区管理) +6. [模型管理](#6-模型管理) +7. [自定义节点管理](#7-自定义节点管理) +8. [ComfyUI 生命周期管理](#8-comfyui-生命周期管理) +9. [Docker 环境特殊配置](#9-docker-环境特殊配置) +10. [REST API 集成方案](#10-rest-api-集成方案) +11. [故障排除](#11-故障排除) +12. [附录](#12-附录) + +--- + +## 1. 概述 + +### 1.1 什么是 ComfyCLI + +`comfy-cli` 是由 Comfy-Org 官方维护的命令行工具,用于管理和操作 ComfyUI。与 ComfyUI-Manager 的 `cm-cli` 不同,comfy-cli 是一个**独立的管理工具**,提供: + +- ✅ **模型下载与管理**(核心优势) +- ✅ 自定义节点管理 +- ✅ ComfyUI 安装与更新 +- ✅ 工作区管理 +- ✅ Workflow 执行 +- ✅ 后台服务管理 + +### 1.2 与 cm-cli 的对比 + +| 功能 | cm-cli | comfy-cli | +|------|--------|-----------| +| 模型下载 | ❌ | ✅ | +| 模型列表/删除 | ❌ | ✅ | +| 节点管理 | ✅ | ✅ | +| ComfyUI 安装 | ❌ | ✅ | +| 工作区管理 | ❌ | ✅ | +| REST API | ✅ (通过 custom node) | ❌ | + +### 1.3 适用场景 + +- 需要通过命令行下载和管理模型 +- 需要脚本化 ComfyUI 管理操作 +- 需要多工作区管理 +- 需要与现有 ComfyUI-Manager 共存 + +--- + +## 2. ComfyCLI 架构 + +### 2.1 核心组件 + +``` +comfy-cli/ +├── comfy_cli/ +│ ├── __init__.py # 包初始化 +│ ├── cmdline.py # 命令行入口和命令注册 +│ ├── config_manager.py # 配置文件管理 +│ ├── workspace_manager.py # 工作区管理 +│ ├── constants.py # 常量定义 +│ ├── command/ +│ │ ├── install.py # 安装命令 +│ │ ├── launch.py # 启动命令 +│ │ ├── models/ # 模型管理命令 +│ │ │ └── models.py # 下载/删除/列表 +│ │ └── custom_nodes/ # 自定义节点命令 +│ │ └── command.py # 节点管理实现 +│ └── utils/ # 工具函数 +└── pyproject.toml # 项目配置 +``` + +### 2.2 配置文件系统 + +``` +~/.config/comfy-cli/ +└── config.ini # 主配置文件 +``` + +**config.ini 结构:** + +```ini +[DEFAULT] +default_workspace = /path/to/ComfyUI +recent_workspace = /path/to/last/used +default_launch_extras = --listen 0.0.0.0 +enable_tracking = true +background = ('127.0.0.1', 8188, 12345) +civitai_api_token = xxx +hf_api_token = xxx +``` + +### 2.3 工作区检测机制 + +comfy-cli 通过 Git 仓库识别 ComfyUI 安装: + +```python +COMFY_ORIGIN_URL_CHOICES = { + "git@github.com:comfyanonymous/ComfyUI.git", + "https://github.com/comfyanonymous/ComfyUI.git", + "https://github.com/Comfy-Org/ComfyUI.git", + # ... +} +``` + +**工作区优先级:** + +1. `--workspace=` - 显式指定 +2. `--recent` - 最近使用 +3. `--here` - 当前目录 +4. 当前目录是 ComfyUI 仓库 +5. `set-default` 设置的默认路径 +6. 默认路径 (`~/comfy/ComfyUI`) + +--- + +## 3. 安装指南 + +### 3.1 前置要求 + +- **Python 3.9+**(必须与 ComfyUI 使用相同环境) +- **Git**(用于仓库检测) +- **pip** 或 **uv**(推荐) + +### 3.2 在 Docker 环境中安装 + +对于 ComfyUI-Nvidia-Docker 部署,有两种安装方式: + +#### 方式 A:在容器内安装(推荐) + +```bash +# 进入运行中的容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境(如果有) +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# 验证安装 +comfy --version +``` + +#### 方式 B:使用 user_script.bash 持久化 + +编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 在容器启动时执行 + +# 安装 comfy-cli +if ! command -v comfy &> /dev/null; then + echo "Installing comfy-cli..." + /comfy/mnt/venv/bin/pip install comfy-cli +fi + +# 设置默认工作区 +/comfy/mnt/venv/bin/comfy set-default /basedir 2>/dev/null || true +``` + +### 3.3 验证安装 + +```bash +# 检查版本 +comfy --version + +# 查看帮助 +comfy --help + +# 检查环境 +comfy env +``` + +--- + +## 4. 配置系统详解 + +### 4.1 配置文件位置 + +| 操作系统 | 配置文件路径 | +|----------|-------------| +| Linux | `~/.config/comfy-cli/config.ini` | +| macOS | `~/Library/Application Support/comfy-cli/config.ini` | +| Windows | `%LOCALAPPDATA%\comfy-cli\config.ini` | + +### 4.2 配置项说明 + +```ini +[DEFAULT] +# 默认工作区路径(通过 comfy set-default 设置) +default_workspace = /basedir + +# 最近使用的工作区(自动更新) +recent_workspace = /basedir + +# 启动 ComfyUI 时的默认参数 +default_launch_extras = --listen 0.0.0.0 --port 8188 + +# 是否启用使用追踪(可选) +enable_tracking = false + +# 后台运行的 ComfyUI 信息 (host, port, pid) +# 自动管理,不要手动修改 +background = ('127.0.0.1', 8188, 5678) + +# CivitAI API Token(用于下载需要认证的模型) +civitai_api_token = your_token_here + +# HuggingFace API Token(用于下载私有模型) +hf_api_token = your_token_here +``` + +### 4.3 配置管理命令 + +```bash +# 查看当前配置 +comfy env + +# 设置默认工作区 +comfy set-default /basedir + +# 设置启动参数 +comfy set-default /basedir --launch-extras="--listen 0.0.0.0" + +# 查看当前工作区 +comfy which +``` + +### 4.4 环境变量覆盖 + +comfy-cli 支持通过环境变量覆盖配置: + +| 环境变量 | 配置项 | 优先级 | +|----------|--------|--------| +| `CIVITAI_API_TOKEN` | civitai_api_token | 高 | +| `HF_API_TOKEN` | hf_api_token | 高 | +| `COMFY_CLI_WORKSPACE` | default_workspace | 高 | + +**优先级顺序:** + +1. CLI 参数(如 `--set-civitai-api-token`) +2. 环境变量 +3. config.ini 配置 + +--- + +## 5. 工作区管理 + +### 5.1 工作区概念 + +工作区是 ComfyUI 的安装目录,包含: + +``` +/basedir/ # 工作区根目录 +├── ComfyUI/ # ComfyUI 核心代码 +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +│ ├── checkpoints/ +│ ├── loras/ +│ ├── vae/ +│ └── ... +├── input/ # 输入文件 +├── output/ # 输出文件 +└── user/ # 用户文件 +``` + +### 5.2 指定工作区的方法 + +#### 方法 1:使用 `--workspace` 参数 + +```bash +comfy --workspace=/basedir model download --url "..." +comfy --workspace=/basedir node install ComfyUI-Impact-Pack +``` + +#### 方法 2:设置默认工作区 + +```bash +# 一次性设置 +comfy set-default /basedir + +# 之后可省略 --workspace +comfy model download --url "..." +``` + +#### 方法 3:使用 `--recent` + +```bash +# 自动使用最近的工作区 +comfy --recent model download --url "..." +``` + +#### 方法 4:使用 `--here` + +```bash +cd /basedir +comfy --here model download --url "..." +``` + +### 5.3 多工作区管理 + +```bash +# 设置多个工作区 +comfy set-default /basedir +comfy --workspace=/opt/comfy-test node install SomeNode + +# 查看当前工作区 +comfy which + +# 列出所有模型(在当前工作区) +comfy model list +``` + +--- + +## 6. 模型管理 + +### 6.1 下载模型 + +#### 从 CivitAI 下载 + +```bash +# 下载模型(需要 API Token) +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --set-civitai-api-token "YOUR_TOKEN" + +# 指定保存路径 +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --relative-path "models/checkpoints" \ + --filename "my_model.safetensors" +``` + +#### 从 HuggingFace 下载 + +```bash +# 下载公开模型 +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors" + +# 下载私有模型(需要 Token) +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/username/private-model/resolve/main/model.safetensors" \ + --set-hf-api-token "YOUR_HF_TOKEN" +``` + +#### 从直接 URL 下载 + +```bash +comfy --workspace=/basedir model download \ + --url "https://example.com/models/model.safetensors" \ + --relative-path "models/checkpoints" +``` + +### 6.2 列出模型 + +```bash +# 列出所有模型 +comfy --workspace=/basedir model list + +# 列出指定目录的模型 +comfy --workspace=/basedir model list \ + --relative-path "models/checkpoints" + +# 列出 LoRA 模型 +comfy --workspace=/basedir model list \ + --relative-path "models/loras" +``` + +### 6.3 删除模型 + +```bash +# 删除指定模型 +comfy --workspace=/basedir model remove \ + --relative-path "models/checkpoints" \ + --model-names "model1.safetensors" "model2.ckpt" + +# 交互式选择删除 +comfy --workspace=/basedir model remove + +# 跳过确认提示 +comfy --workspace=/basedir model remove \ + --model-names "model.safetensors" \ + --confirm +``` + +### 6.4 模型路径映射 + +comfy-cli 内置的模型类型映射: + +```python +model_path_map = { + "lora": "loras", + "hypernetwork": "hypernetworks", + "checkpoint": "checkpoints", + "textualinversion": "embeddings", + "controlnet": "controlnet", +} +``` + +--- + +## 7. 自定义节点管理 + +### 7.1 节点列表 + +```bash +# 显示已安装的节点 +comfy --workspace=/basedir node simple-show installed + +# 显示启用的节点 +comfy --workspace=/basedir node simple-show enabled + +# 显示禁用的节点 +comfy --workspace=/basedir node simple-show disabled + +# 显示未安装的节点 +comfy --workspace=/basedir node simple-show not-installed + +# 显示所有节点 +comfy --workspace=/basedir node simple-show all + +# 显示详细信息 +comfy --workspace=/basedir node show installed +``` + +### 7.2 安装节点 + +```bash +# 安装单个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack + +# 安装多个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack + +# 使用快速依赖安装 +comfy --workspace=/basedir node install SomeNode --fast-deps + +# 跳过依赖安装 +comfy --workspace=/basedir node install SomeNode --no-deps + +# 指定 channel +comfy --workspace=/basedir node install SomeNode --channel recent +``` + +### 7.3 更新节点 + +```bash +# 更新所有节点 +comfy --workspace=/basedir node update all + +# 更新特定节点 +comfy --workspace=/basedir node update ComfyUI-Impact-Pack + +# 指定 channel 和 mode +comfy --workspace=/basedir node update all --channel recent --mode remote +``` + +### 7.4 启用/禁用节点 + +```bash +# 禁用节点 +comfy --workspace=/basedir node disable ComfyUI-Impact-Pack + +# 启用节点 +comfy --workspace=/basedir node enable ComfyUI-Impact-Pack +``` + +### 7.5 卸载节点 + +```bash +# 卸载节点 +comfy --workspace=/basedir node uninstall ComfyUI-Impact-Pack +``` + +### 7.6 依赖管理 + +```bash +# 安装依赖 +comfy --workspace=/basedir node install-deps + +# 从 workflow 生成依赖 +comfy --workspace=/basedir node deps-in-workflow \ + --workflow /path/to/workflow.json \ + --output /path/to/deps.json +``` + +### 7.7 快照管理 + +```bash +# 保存快照 +comfy --workspace=/basedir node save-snapshot + +# 保存到指定文件 +comfy --workspace=/basedir node save-snapshot --output backup.json + +# 恢复快照 +comfy --workspace=/basedir node restore-snapshot backup.json + +# 列出快照 +comfy --workspace=/basedir node simple-show snapshot-list +``` + +### 7.8 调试节点问题(Bisect) + +```bash +# 开始 bisect 会话 +comfy --workspace=/basedir node bisect start + +# 标记当前状态为好 +comfy --workspace=/basedir node bisect good + +# 标记当前状态为坏 +comfy --workspace=/basedir node bisect bad + +# 重置 bisect 会话 +comfy --workspace=/basedir node bisect reset +``` + +--- + +## 8. ComfyUI 生命周期管理 + +### 8.1 安装 ComfyUI + +```bash +# 默认安装(最新 nightly 版本) +comfy install + +# 指定工作区 +comfy --workspace=~/my-comfy install + +# 安装特定版本 +comfy install --version latest + +# 安装 nightly 版本 +comfy install --version nightly + +# 跳过 Manager 安装 +comfy install --skip-manager + +# 从特定 PR 安装 +comfy install --pr "#1234" + +# 指定 GPU 类型 +comfy install --nvidia +comfy install --amd +comfy install --cpu +``` + +### 8.2 启动 ComfyUI + +```bash +# 前台启动 +comfy --workspace=/basedir launch + +# 后台启动 +comfy --workspace=/basedir launch --background + +# 带参数启动 +comfy --workspace=/basedir launch -- --listen 0.0.0.0 --port 8188 + +# 使用默认参数启动 +comfy --workspace=/basedir launch -- + +# 测试前端 PR +comfy --workspace=/basedir launch --frontend-pr "#456" +``` + +### 8.3 停止 ComfyUI + +```bash +# 停止后台运行的实例 +comfy stop +``` + +### 8.4 更新 ComfyUI + +```bash +# 更新 ComfyUI 核心 +comfy --workspace=/basedir update comfy + +# 更新所有(包括节点) +comfy --workspace=/basedir update all +``` + +### 8.5 运行 Workflow + +```bash +# 运行 workflow 文件 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json + +# 等待完成 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --wait + +# 详细输出 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --verbose + +# 指定主机和端口 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --host 127.0.0.1 \ + --port 8188 \ + --timeout 60 +``` + +--- + +## 9. Docker 环境特殊配置 + +### 9.1 ComfyUI-Nvidia-Docker 目录结构 + +``` +run/ # 容器运行数据 +├── venv/ # Python 虚拟环境 +├── ComfyUI/ # ComfyUI 源代码 +└── ... + +basedir/ # 用户数据(持久化) +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +├── input/ # 输入 +├── output/ # 输出 +└── user/ # 用户配置 +``` + +### 9.2 在 Docker 中安装 comfy-cli + +#### 方法 A:通过 user_script.bash(推荐) + +创建/编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 容器启动时执行 + +echo "=== Setting up comfy-cli ===" + +# 检查并安装 comfy-cli +if ! /comfy/mnt/venv/bin/comfy --version &> /dev/null; then + echo "Installing comfy-cli..." + /comfy/mnt/venv/bin/pip install comfy-cli +fi + +# 设置默认工作区 +echo "Setting default workspace to /basedir..." +/comfy/mnt/venv/bin/comfy set-default /basedir 2>/dev/null || true + +# 验证配置 +/comfy/mnt/venv/bin/comfy which +``` + +#### 方法 B:手动安装 + +```bash +# 进入容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境 +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# 设置默认工作区 +comfy set-default /basedir + +# 退出容器 +exit +``` + +### 9.3 环境变量配置 + +在 `docker-compose.yml` 或 `docker run` 中添加: + +```yaml +environment: + - CIVITAI_API_TOKEN=your_token_here + - HF_API_TOKEN=your_token_here + - COMFY_CLI_WORKSPACE=/basedir +``` + +### 9.4 权限问题处理 + +ComfyUI-Nvidia-Docker 使用 `WANTED_UID`/`WANTED_GID` 映射用户权限: + +```bash +docker run ... \ + -e WANTED_UID=$(id -u) \ + -e WANTED_GID=$(id -g) \ + ... +``` + +确保 comfy-cli 配置文件的权限正确: + +```bash +# 在容器内 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +``` + +### 9.5 模型下载优化 + +在 Docker 环境中,模型下载到 `basedir`,这是持久化存储: + +```bash +# 模型会保存到 /basedir/models/... +docker exec comfyui-nvidia comfy model download \ + --url "https://..." \ + --relative-path "models/checkpoints" +``` + +--- + +## 10. REST API 集成方案 + +### 10.1 方案概述 + +由于 comfy-cli 本身不提供 REST API,需要通过 custom node 将命令暴露为 HTTP 端点。有两种实现方案: + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **方案 A:扩展现有 cm-cli-rest** | 复用现有代码 | 需要修改现有项目 | +| **方案 B:新建 comfy-cli-rest** | 独立、清晰 | 需要从头开发 | + +**推荐:方案 A**(扩展 cm-cli-rest) + +### 10.2 方案 A:扩展 cm-cli-rest + +#### 10.2.1 项目结构 + +``` +cm-cli-rest/ +├── __init__.py # 入口点 +├── api/ +│ ├── routes.py # 路由定义 +│ └── handlers/ +│ ├── nodes.py # 节点管理 +│ ├── models.py # 【新增】模型管理 +│ └── jobs.py # 任务跟踪 +├── comfy_cli/ +│ └── executor.py # 【新增】comfy-cli 执行器 +├── config/ +│ └── config.json # 配置 +└── README.md +``` + +#### 10.2.2 comfy-cli 执行器实现 + +```python +# comfy_cli/executor.py +import asyncio +import json +import os +import subprocess +import uuid +from typing import Dict, Optional, Any + +class ComfyCliExecutor: + """comfy-cli 命令执行器""" + + def __init__(self, workspace_path: str = "/basedir"): + self.workspace_path = workspace_path + self.comfy_cmd = ["comfy", f"--workspace={workspace_path}"] + self.jobs: Dict[str, Dict] = {} + + async def execute( + self, + command: list, + async_mode: bool = True + ) -> Dict[str, Any]: + """执行 comfy-cli 命令""" + job_id = str(uuid.uuid4()) + + full_cmd = self.comfy_cmd + command + + if async_mode: + # 异步执行(用于长时间运行的命令) + task = asyncio.create_task( + self._run_async(job_id, full_cmd) + ) + self.jobs[job_id] = { + "status": "running", + "command": command, + "task": task + } + return { + "job_id": job_id, + "status": "running", + "message": f"Command started: {' '.join(command)}" + } + else: + # 同步执行 + result = await self._run_sync(full_cmd) + return result + + async def _run_async(self, job_id: str, cmd: list): + """异步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + self.jobs[job_id] = { + "status": "completed", + "output": stdout.decode(), + "error": stderr.decode() + } + else: + self.jobs[job_id] = { + "status": "failed", + "error": stderr.decode(), + "output": stdout.decode() + } + except Exception as e: + self.jobs[job_id] = { + "status": "failed", + "error": str(e) + } + + async def _run_sync(self, cmd: list) -> Dict[str, Any]: + """同步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + return { + "success": True, + "data": stdout.decode(), + "error": None + } + else: + return { + "success": False, + "data": None, + "error": stderr.decode() + } + except Exception as e: + return { + "success": False, + "data": None, + "error": str(e) + } + + def get_job_status(self, job_id: str) -> Optional[Dict]: + """获取任务状态""" + return self.jobs.get(job_id) + + def remove_job(self, job_id: str) -> bool: + """移除任务""" + if job_id in self.jobs: + del self.jobs[job_id] + return True + return False +``` + +#### 10.2.3 模型管理 Handler + +```python +# api/handlers/models.py +from aiohttp import web +import json +from ..comfy_cli.executor import ComfyCliExecutor + +executor = ComfyCliExecutor(workspace_path="/basedir") + +async def download_model(request): + """下载模型""" + try: + data = await request.json() + url = data.get("url") + relative_path = data.get("relative_path", "models/checkpoints") + filename = data.get("filename") + civitai_token = data.get("civitai_api_token") + hf_token = data.get("hf_api_token") + + if not url: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_URL", "message": "URL is required"} + }, status=400) + + # 构建命令 + cmd = ["model", "download", "--url", url] + + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + if filename: + cmd.extend(["--filename", filename]) + + if civitai_token: + cmd.extend(["--set-civitai-api-token", civitai_token]) + + if hf_token: + cmd.extend(["--set-hf-api-token", hf_token]) + + # 执行命令(异步) + result = await executor.execute(cmd, async_mode=True) + + return web.json_response({ + "success": True, + "data": result + }, status=202) # 202 Accepted + + except json.JSONDecodeError: + return web.json_response({ + "success": False, + "error": {"code": "INVALID_JSON", "message": "Invalid JSON"} + }, status=400) + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def list_models(request): + """列出模型""" + try: + relative_path = request.query.get("relative_path", "models") + + cmd = ["model", "list"] + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + # 解析输出 + models = result["data"].strip().split("\n") if result["data"] else [] + return web.json_response({ + "success": True, + "data": {"models": models, "path": relative_path} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "LIST_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def remove_model(request): + """删除模型""" + try: + data = await request.json() + model_names = data.get("model_names", []) + relative_path = data.get("relative_path", "models/checkpoints") + confirm = data.get("confirm", False) + + if not model_names: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_NAMES", "message": "model_names is required"} + }, status=400) + + cmd = ["model", "remove", "--relative-path", relative_path] + + for name in model_names: + cmd.extend(["--model-names", name]) + + if confirm: + cmd.append("--confirm") + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + return web.json_response({ + "success": True, + "data": {"message": f"Removed {len(model_names)} model(s)"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "REMOVE_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def get_job_status(request): + """获取任务状态""" + job_id = request.match_info.get("job_id") + + status = executor.get_job_status(job_id) + + if status: + return web.json_response({ + "success": True, + "data": status + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) + +async def remove_job(request): + """移除任务""" + job_id = request.match_info.get("job_id") + + if executor.remove_job(job_id): + return web.json_response({ + "success": True, + "data": {"message": f"Job {job_id} removed"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) +``` + +#### 10.2.4 路由定义 + +```python +# api/routes.py +from aiohttp import web +from .handlers import nodes, models, jobs + +def setup_routes(app): + """注册路由""" + + # 健康检查 + app.router.add_get("/cm-cli-rest/health", health_check) + + # 节点管理(现有) + app.router.add_get("/cm-cli-rest/nodes", nodes.list_nodes) + app.router.add_post("/cm-cli-rest/nodes/install", nodes.install_node) + # ... + + # 模型管理(新增) + app.router.add_post("/cm-cli-rest/models/download", models.download_model) + app.router.add_get("/cm-cli-rest/models", models.list_models) + app.router.add_post("/cm-cli-rest/models/remove", models.remove_model) + + # 任务跟踪 + app.router.add_get("/cm-cli-rest/jobs", jobs.list_jobs) + app.router.add_get("/cm-cli-rest/jobs/{job_id}", models.get_job_status) + app.router.add_delete("/cm-cli-rest/jobs/{job_id}", models.remove_job) + +async def health_check(request): + """健康检查""" + return web.json_response({ + "status": "healthy", + "service": "cm-cli-rest", + "version": "0.2.0", # 更新版本号 + "features": ["nodes", "models", "jobs"], + "timestamp": datetime.utcnow().isoformat() + }) +``` + +#### 10.2.5 API 端点列表 + +| 方法 | 端点 | 描述 | 异步 | +|------|------|------|------| +| GET | `/cm-cli-rest/health` | 健康检查 | ❌ | +| POST | `/cm-cli-rest/models/download` | 下载模型 | ✅ | +| GET | `/cm-cli-rest/models` | 列出模型 | ❌ | +| POST | `/cm-cli-rest/models/remove` | 删除模型 | ❌ | +| GET | `/cm-cli-rest/jobs` | 列出任务 | ❌ | +| GET | `/cm-cli-rest/jobs/{id}` | 获取任务状态 | ❌ | +| DELETE | `/cm-cli-rest/jobs/{id}` | 移除任务 | ❌ | + +#### 10.2.6 请求/响应示例 + +**下载模型请求:** + +```bash +curl -X POST http://localhost:8188/cm-cli-rest/models/download \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors", + "civitai_api_token": "your_token" + }' +``` + +**响应(202 Accepted):** + +```json +{ + "success": true, + "data": { + "job_id": "abc12345", + "status": "running", + "message": "Command started: model download --url ..." + } +} +``` + +**查询任务状态:** + +```bash +curl http://localhost:8188/cm-cli-rest/jobs/abc12345 +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "status": "completed", + "output": "Model downloaded successfully...", + "error": null + } +} +``` + +**列出模型:** + +```bash +curl "http://localhost:8188/cm-cli-rest/models?relative_path=models/checkpoints" +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "models": [ + "model1.safetensors", + "model2.ckpt", + "model3.safetensors" + ], + "path": "models/checkpoints" + } +} +``` + +### 10.3 方案 B:新建 comfy-cli-rest + +如果希望保持独立,可以创建新的 custom node: + +```bash +# 目录结构 +custom_nodes/comfy-cli-rest/ +├── __init__.py +├── server.py +├── handlers/ +│ ├── models.py +│ ├── nodes.py +│ └── workflows.py +├── comfy_executor.py +├── config.json +└── requirements.txt +``` + +**requirements.txt:** + +``` +aiohttp>=3.9.0 +``` + +**__init__.py:** + +```python +import asyncio +import os +from aiohttp import web +from server import PromptServer + +from .handlers import models, nodes +from .comfy_executor import ComfyCliExecutor + +WEB_DIRECTORY = "./web" + +executor = ComfyCliExecutor( + workspace_path=os.environ.get("COMFYUI_BASEDIR", "/basedir") +) + +@PromptServer.instance.routes.get("/comfy-cli/health") +async def health_check(request): + return web.json_response({ + "status": "healthy", + "service": "comfy-cli-rest", + "version": "1.0.0" + }) + +@PromptServer.instance.routes.post("/comfy-cli/model/download") +async def download_model(request): + return await models.download_model(request, executor) + +@PromptServer.instance.routes.get("/comfy-cli/model/list") +async def list_models(request): + return await models.list_models(request, executor) + +# ... 其他路由 +``` + +### 10.4 安全考虑 + +1. **API 认证** + ```json + { + "api_key": "your_secret_key", + "enabled": true + } + ``` + +2. **输入验证** + - 验证所有路径参数,防止路径遍历攻击 + - 限制可执行的命令白名单 + +3. **速率限制** + - 限制每个 IP 的请求频率 + - 限制并发任务数量 + +4. **日志记录** + - 记录所有 API 调用 + - 记录错误和异常 + +--- + +## 11. 故障排除 + +### 11.1 常见问题 + +#### 问题 1:`comfy: command not found` + +**原因:** comfy-cli 未安装或未添加到 PATH + +**解决:** + +```bash +# 在容器内 +source /comfy/mnt/venv/bin/activate +pip install comfy-cli + +# 或使用完整路径 +/comfy/mnt/venv/bin/comfy --version +``` + +#### 问题 2:`ComfyUI not found` + +**原因:** 工作区配置错误 + +**解决:** + +```bash +# 设置默认工作区 +comfy set-default /basedir + +# 或显式指定 +comfy --workspace=/basedir model list +``` + +#### 问题 3:模型下载失败(401/403) + +**原因:** 缺少 API Token + +**解决:** + +```bash +# 设置 Token +comfy model download --url "..." \ + --set-civitai-api-token "YOUR_TOKEN" + +# 或使用环境变量 +export CIVITAI_API_TOKEN="YOUR_TOKEN" +comfy model download --url "..." +``` + +#### 问题 4:权限错误 + +**原因:** Docker 用户权限映射问题 + +**解决:** + +```bash +# 在容器内修复权限 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +chown -R $(id -u):$(id -g) /basedir/ +``` + +#### 问题 5:Python 版本不兼容 + +**原因:** comfy-cli 需要 Python 3.9+ + +**解决:** + +```bash +# 检查 Python 版本 +python --version + +# 使用正确的 Python 环境 +/comfy/mnt/venv/bin/python -m pip install comfy-cli +``` + +### 11.2 调试技巧 + +```bash +# 启用详细日志 +comfy --workspace=/basedir model download --url "..." -v + +# 查看 comfy-cli 配置 +cat ~/.config/comfy-cli/config.ini + +# 检查环境 +comfy env + +# 验证工作区 +comfy which +``` + +### 11.3 获取帮助 + +```bash +# 主帮助 +comfy --help + +# 命令帮助 +comfy model --help +comfy model download --help +comfy node --help + +# 查看源码 +# https://github.com/Comfy-Org/comfy-cli +``` + +--- + +## 12. 附录 + +### 12.1 完整命令参考 + +``` +comfy [OPTIONS] COMMAND [ARGS] + +Options: + --workspace PATH Path to ComfyUI workspace + --recent Execute from recent path + --here Execute from current path + --version Print version and exit + --help Show help + +Commands: + install Download and install ComfyUI + update Update ComfyUI environment + launch Launch ComfyUI + stop Stop background ComfyUI + set-default Set default ComfyUI path + which Show which ComfyUI is selected + env Print environment variables + model Model management + download Download model file + list List models + remove Remove models + node Custom node management + install Install nodes + uninstall Uninstall nodes + update Update nodes + enable Enable nodes + disable Disable nodes + show Show node list + simple-show Show node list (simple) + save-snapshot Save snapshot + restore-snapshot Restore snapshot + run Run workflow + feedback Provide feedback +``` + +### 12.2 配置文件模板 + +```ini +# ~/.config/comfy-cli/config.ini +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +civitai_api_token = +hf_api_token = +``` + +### 12.3 Docker Compose 示例 + +```yaml +version: '3.8' + +services: + comfyui: + image: mmartial/comfyui-nvidia-docker:latest + container_name: comfyui-nvidia + runtime: nvidia + environment: + - USE_UV=true + - WANTED_UID=1000 + - WANTED_GID=1000 + - BASE_DIRECTORY=/basedir + - SECURITY_LEVEL=normal + - CIVITAI_API_TOKEN=${CIVITAI_API_TOKEN} + - HF_API_TOKEN=${HF_API_TOKEN} + volumes: + - ./run:/comfy/mnt + - ./basedir:/basedir + ports: + - "127.0.0.1:8188:8188" + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +### 12.4 参考资源 + +- **comfy-cli 官方仓库:** https://github.com/Comfy-Org/comfy-cli +- **comfy-cli PyPI:** https://pypi.org/project/comfy-cli/ +- **ComfyUI-Nvidia-Docker:** https://github.com/mmartial/ComfyUI-Nvidia-Docker +- **ComfyUI-Manager:** https://github.com/ltdrdata/ComfyUI-Manager +- **cm-cli-rest:** https://github.com/fno2010/cm-cli-rest + +--- + +## 修订历史 + +| 版本 | 日期 | 作者 | 变更 | +|------|------|------|------| +| 1.0 | 2026-03-16 | Zero | 初始版本 | + +--- + +*本文档由 Zero (零号) 生成,基于 comfy-cli v0.1.0+ 和 ComfyUI-Nvidia-Docker 环境。* diff --git a/docs/references/archive/comfy-cli-guide-v1.2.md b/docs/references/archive/comfy-cli-guide-v1.2.md new file mode 100644 index 0000000..dc3a782 --- /dev/null +++ b/docs/references/archive/comfy-cli-guide-v1.2.md @@ -0,0 +1,1628 @@ +# ComfyCLI 配置与使用指南 + +> 文档版本:1.2 +> 最后更新:2026-03-16 +> 适用环境:ComfyUI-Nvidia-Docker 部署 +> **适配 comfy-cli 版本:≥1.3.8(已验证 1.6.0 稳定版)** +> **验证报告:** `comfy-cli-verification-report.md` +> **Docker 自动化脚本:** `comfy-cli-docker-setup.sh` + +--- + +## 目录 + +1. [概述](#1-概述) +2. [ComfyCLI 架构](#2-comfycli-架构) +3. [安装指南](#3-安装指南) +4. [配置系统详解](#4-配置系统详解) +5. [工作区管理](#5-工作区管理) +6. [模型管理](#6-模型管理) +7. [自定义节点管理](#7-自定义节点管理) +8. [ComfyUI 生命周期管理](#8-comfyui-生命周期管理) +9. [Docker 环境特殊配置](#9-docker-环境特殊配置) +10. [REST API 集成方案](#10-rest-api-集成方案) +11. [故障排除](#11-故障排除) +12. [附录](#12-附录) + +--- + +## 1. 概述 + +### 1.1 什么是 ComfyCLI + +`comfy-cli` 是由 Comfy-Org 官方维护的命令行工具,用于管理和操作 ComfyUI。与 ComfyUI-Manager 的 `cm-cli` 不同,comfy-cli 是一个**独立的管理工具**,提供: + +- ✅ **模型下载与管理**(核心优势) +- ✅ 自定义节点管理 +- ✅ ComfyUI 安装与更新 +- ✅ 工作区管理 +- ✅ Workflow 执行 +- ✅ 后台服务管理 + +### 1.2 与 cm-cli 的对比 + +| 功能 | cm-cli | comfy-cli | +|------|--------|-----------| +| 模型下载 | ❌ | ✅ | +| 模型列表/删除 | ❌ | ✅ | +| 节点管理 | ✅ | ✅ | +| ComfyUI 安装 | ❌ | ✅ | +| 工作区管理 | ❌ | ✅ | +| REST API | ✅ (通过 custom node) | ❌ | + +### 1.3 适用场景 + +- 需要通过命令行下载和管理模型 +- 需要脚本化 ComfyUI 管理操作 +- 需要多工作区管理 +- 需要与现有 ComfyUI-Manager 共存 + +--- + +## 2. ComfyCLI 架构 + +### 2.1 核心组件 + +``` +comfy-cli/ +├── comfy_cli/ +│ ├── __init__.py # 包初始化 +│ ├── cmdline.py # 命令行入口和命令注册 +│ ├── config_manager.py # 配置文件管理 +│ ├── workspace_manager.py # 工作区管理 +│ ├── constants.py # 常量定义 +│ ├── command/ +│ │ ├── install.py # 安装命令 +│ │ ├── launch.py # 启动命令 +│ │ ├── models/ # 模型管理命令 +│ │ │ └── models.py # 下载/删除/列表 +│ │ └── custom_nodes/ # 自定义节点命令 +│ │ └── command.py # 节点管理实现 +│ └── utils/ # 工具函数 +└── pyproject.toml # 项目配置 +``` + +### 2.2 配置文件系统 + +``` +~/.config/comfy-cli/ +└── config.ini # 主配置文件 +``` + +**config.ini 结构:** + +```ini +[DEFAULT] +default_workspace = /path/to/ComfyUI +recent_workspace = /path/to/last/used +default_launch_extras = --listen 0.0.0.0 +enable_tracking = true +background = ('127.0.0.1', 8188, 12345) +civitai_api_token = xxx +hf_api_token = xxx +``` + +### 2.3 工作区检测机制 + +comfy-cli 通过 Git 仓库识别 ComfyUI 安装: + +```python +COMFY_ORIGIN_URL_CHOICES = { + "git@github.com:comfyanonymous/ComfyUI.git", + "https://github.com/comfyanonymous/ComfyUI.git", + "https://github.com/Comfy-Org/ComfyUI.git", + # ... +} +``` + +**工作区优先级:** + +1. `--workspace=` - 显式指定 +2. `--recent` - 最近使用 +3. `--here` - 当前目录 +4. 当前目录是 ComfyUI 仓库 +5. `set-default` 设置的默认路径 +6. 默认路径 (`~/comfy/ComfyUI`) + +--- + +## 3. 安装指南 + +### 3.1 前置要求 + +- **Python 3.9+**(必须与 ComfyUI 使用相同环境) +- **Git**(用于仓库检测) +- **pip** 或 **uv**(推荐) + +### 3.2 在 Docker 环境中安装 + +对于 ComfyUI-Nvidia-Docker 部署,有两种安装方式: + +#### 方式 A:在容器内安装(推荐) + +```bash +# 进入运行中的容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境(如果有) +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# 验证安装 +comfy --version +``` + +#### 方式 B:使用 user_script.bash 持久化 + +编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 在容器启动时执行 + +# 安装 comfy-cli +if ! command -v comfy &> /dev/null; then + echo "Installing comfy-cli..." + /comfy/mnt/venv/bin/pip install comfy-cli +fi + +# 设置默认工作区 +/comfy/mnt/venv/bin/comfy set-default /basedir 2>/dev/null || true +``` + +### 3.3 验证安装 + +```bash +# 检查版本 +comfy --version + +# 查看帮助 +comfy --help + +# 检查环境 +comfy env +``` + +--- + +## 4. 配置系统详解 + +### 4.1 配置文件位置 + +| 操作系统 | 配置文件路径 | +|----------|-------------| +| Linux | `~/.config/comfy-cli/config.ini` | +| macOS | `~/Library/Application Support/comfy-cli/config.ini` | +| Windows | `%LOCALAPPDATA%\comfy-cli\config.ini` | + +### 4.2 配置项说明 + +```ini +[DEFAULT] +# 默认工作区路径(通过 comfy set-default 设置) +default_workspace = /basedir + +# 最近使用的工作区(自动更新) +recent_workspace = /basedir + +# 启动 ComfyUI 时的默认参数 +default_launch_extras = --listen 0.0.0.0 --port 8188 + +# 是否启用使用追踪(可选) +enable_tracking = false + +# 后台运行的 ComfyUI 信息 (host, port, pid) +# 自动管理,不要手动修改 +background = ('127.0.0.1', 8188, 5678) + +# CivitAI API Token(用于下载需要认证的模型) +civitai_api_token = your_token_here + +# HuggingFace API Token(用于下载私有模型) +hf_api_token = your_token_here +``` + +### 4.3 配置管理命令 + +```bash +# 查看当前配置 +comfy env + +# 设置默认工作区 +comfy set-default /basedir + +# 设置启动参数 +comfy set-default /basedir --launch-extras="--listen 0.0.0.0" + +# 查看当前工作区 +comfy which +``` + +### 4.4 环境变量覆盖 + +comfy-cli 支持通过环境变量覆盖配置: + +| 环境变量 | 配置项 | 优先级 | +|----------|--------|--------| +| `CIVITAI_API_TOKEN` | civitai_api_token | 高 | +| `HF_API_TOKEN` | hf_api_token | 高 | +| `COMFY_CLI_WORKSPACE` | default_workspace | 高 | + +**优先级顺序:** + +1. CLI 参数(如 `--set-civitai-api-token`) +2. 环境变量 +3. config.ini 配置 + +--- + +## 5. 工作区管理 + +### 5.1 工作区概念 + +工作区是 ComfyUI 的安装目录,包含: + +``` +/basedir/ # 工作区根目录 +├── ComfyUI/ # ComfyUI 核心代码 +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +│ ├── checkpoints/ +│ ├── loras/ +│ ├── vae/ +│ └── ... +├── input/ # 输入文件 +├── output/ # 输出文件 +└── user/ # 用户文件 +``` + +### 5.2 指定工作区的方法 + +#### 方法 1:使用 `--workspace` 参数 + +```bash +comfy --workspace=/basedir model download --url "..." +comfy --workspace=/basedir node install ComfyUI-Impact-Pack +``` + +#### 方法 2:设置默认工作区 + +```bash +# 一次性设置 +comfy set-default /basedir + +# 之后可省略 --workspace +comfy model download --url "..." +``` + +#### 方法 3:使用 `--recent` + +```bash +# 自动使用最近的工作区 +comfy --recent model download --url "..." +``` + +#### 方法 4:使用 `--here` + +```bash +cd /basedir +comfy --here model download --url "..." +``` + +### 5.3 多工作区管理 + +```bash +# 设置多个工作区 +comfy set-default /basedir +comfy --workspace=/opt/comfy-test node install SomeNode + +# 查看当前工作区 +comfy which + +# 列出所有模型(在当前工作区) +comfy model list +``` + +--- + +## 6. 模型管理 + +### 6.1 下载模型 + +#### 从 CivitAI 下载 + +```bash +# 下载模型(需要 API Token) +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --set-civitai-api-token "YOUR_TOKEN" + +# 指定保存路径 +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --relative-path "models/checkpoints" \ + --filename "my_model.safetensors" +``` + +#### 从 HuggingFace 下载 + +```bash +# 下载公开模型 +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors" + +# 下载私有模型(需要 Token) +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/username/private-model/resolve/main/model.safetensors" \ + --set-hf-api-token "YOUR_HF_TOKEN" +``` + +#### 从直接 URL 下载 + +```bash +comfy --workspace=/basedir model download \ + --url "https://example.com/models/model.safetensors" \ + --relative-path "models/checkpoints" +``` + +### 6.2 列出模型 + +```bash +# 列出所有模型 +comfy --workspace=/basedir model list + +# 列出指定目录的模型 +comfy --workspace=/basedir model list \ + --relative-path "models/checkpoints" + +# 列出 LoRA 模型 +comfy --workspace=/basedir model list \ + --relative-path "models/loras" +``` + +### 6.3 删除模型 + +```bash +# 删除指定模型 +comfy --workspace=/basedir model remove \ + --relative-path "models/checkpoints" \ + --model-names "model1.safetensors" "model2.ckpt" + +# 交互式选择删除 +comfy --workspace=/basedir model remove + +# 跳过确认提示 +comfy --workspace=/basedir model remove \ + --model-names "model.safetensors" \ + --confirm +``` + +### 6.4 模型路径映射 + +comfy-cli 内置的模型类型映射: + +```python +model_path_map = { + "lora": "loras", + "hypernetwork": "hypernetworks", + "checkpoint": "checkpoints", + "textualinversion": "embeddings", + "controlnet": "controlnet", +} +``` + +--- + +## 7. 自定义节点管理 + +### 7.1 节点列表 + +```bash +# 显示已安装的节点 +comfy --workspace=/basedir node simple-show installed + +# 显示启用的节点 +comfy --workspace=/basedir node simple-show enabled + +# 显示禁用的节点 +comfy --workspace=/basedir node simple-show disabled + +# 显示未安装的节点 +comfy --workspace=/basedir node simple-show not-installed + +# 显示所有节点 +comfy --workspace=/basedir node simple-show all + +# 显示详细信息 +comfy --workspace=/basedir node show installed +``` + +### 7.2 安装节点 + +```bash +# 安装单个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack + +# 安装多个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack + +# 使用快速依赖安装 +comfy --workspace=/basedir node install SomeNode --fast-deps + +# 跳过依赖安装 +comfy --workspace=/basedir node install SomeNode --no-deps + +# 指定 channel +comfy --workspace=/basedir node install SomeNode --channel recent +``` + +### 7.3 更新节点 + +```bash +# 更新所有节点 +comfy --workspace=/basedir node update all + +# 更新特定节点 +comfy --workspace=/basedir node update ComfyUI-Impact-Pack + +# 指定 channel 和 mode +comfy --workspace=/basedir node update all --channel recent --mode remote +``` + +### 7.4 启用/禁用节点 + +```bash +# 禁用节点 +comfy --workspace=/basedir node disable ComfyUI-Impact-Pack + +# 启用节点 +comfy --workspace=/basedir node enable ComfyUI-Impact-Pack +``` + +### 7.5 卸载节点 + +```bash +# 卸载节点 +comfy --workspace=/basedir node uninstall ComfyUI-Impact-Pack +``` + +### 7.6 依赖管理 + +```bash +# 安装依赖 +comfy --workspace=/basedir node install-deps + +# 从 workflow 生成依赖 +comfy --workspace=/basedir node deps-in-workflow \ + --workflow /path/to/workflow.json \ + --output /path/to/deps.json +``` + +### 7.7 快照管理 + +```bash +# 保存快照 +comfy --workspace=/basedir node save-snapshot + +# 保存到指定文件 +comfy --workspace=/basedir node save-snapshot --output backup.json + +# 恢复快照 +comfy --workspace=/basedir node restore-snapshot backup.json + +# 列出快照 +comfy --workspace=/basedir node simple-show snapshot-list +``` + +### 7.8 调试节点问题(Bisect) + +```bash +# 开始 bisect 会话 +comfy --workspace=/basedir node bisect start + +# 标记当前状态为好 +comfy --workspace=/basedir node bisect good + +# 标记当前状态为坏 +comfy --workspace=/basedir node bisect bad + +# 重置 bisect 会话 +comfy --workspace=/basedir node bisect reset +``` + +--- + +## 8. ComfyUI 生命周期管理 + +### 8.1 安装 ComfyUI + +```bash +# 默认安装(最新 nightly 版本) +comfy install + +# 指定工作区 +comfy --workspace=~/my-comfy install + +# 安装特定版本 +comfy install --version latest + +# 安装 nightly 版本 +comfy install --version nightly + +# 跳过 Manager 安装 +comfy install --skip-manager + +# 从特定 PR 安装 +comfy install --pr "#1234" + +# 指定 GPU 类型 +comfy install --nvidia +comfy install --amd +comfy install --cpu +``` + +### 8.2 启动 ComfyUI + +```bash +# 前台启动 +comfy --workspace=/basedir launch + +# 后台启动 +comfy --workspace=/basedir launch --background + +# 带参数启动 +comfy --workspace=/basedir launch -- --listen 0.0.0.0 --port 8188 + +# 使用默认参数启动 +comfy --workspace=/basedir launch -- + +# 测试前端 PR +comfy --workspace=/basedir launch --frontend-pr "#456" +``` + +### 8.3 停止 ComfyUI + +```bash +# 停止后台运行的实例 +comfy stop +``` + +### 8.4 更新 ComfyUI + +```bash +# 更新 ComfyUI 核心 +comfy --workspace=/basedir update comfy + +# 更新所有(包括节点) +comfy --workspace=/basedir update all +``` + +### 8.5 运行 Workflow + +```bash +# 运行 workflow 文件 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json + +# 等待完成 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --wait + +# 详细输出 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --verbose + +# 指定主机和端口 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --host 127.0.0.1 \ + --port 8188 \ + --timeout 60 +``` + +--- + +## 9. Docker 环境特殊配置 + +### 9.1 ComfyUI-Nvidia-Docker 目录结构 + +``` +run/ # 容器运行数据 +├── venv/ # Python 虚拟环境 +├── ComfyUI/ # ComfyUI 源代码 +└── ... + +basedir/ # 用户数据(持久化) +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +├── input/ # 输入 +├── output/ # 输出 +└── user/ # 用户配置 +``` + +### 9.2 在 Docker 中安装 comfy-cli + +#### 方法 A:通过 user_script.bash(推荐) + +**⚠️ 重要提示**:在 Docker 环境中,`comfy set-default` 会失败,因为 comfy-cli 通过 Git 仓库检测验证 ComfyUI 安装,而 `/basedir` 目录下没有 ComfyUI 的 Git 仓库(代码在 `/comfy/mnt/ComfyUI`)。 + +**解决方案**:直接写入 `config.ini` 配置文件,绕过 Git 检测。 + +##### 方案 A1:使用自动化脚本(推荐) + +我们提供了完整的自动化脚本 `docs/comfy-cli-docker-setup.sh`,包含: + +- ✅ 自动检测并安装 comfy-cli +- ✅ 直接配置 config.ini(绕过 Git 检测) +- ✅ 环境变量支持(CivitAI/HF API Token) +- ✅ 配置验证 + +**使用步骤:** + +```bash +# 1. 复制脚本到 run 目录 +cp docs/comfy-cli-docker-setup.sh run/user_script.bash + +# 2. 确保执行权限 +chmod +x run/user_script.bash + +# 3. 重启容器 +docker restart comfyui-nvidia + +# 4. 查看日志确认配置成功 +docker logs comfyui-nvidia | grep -A 20 "ComfyCLI" +``` + +##### 方案 A2:手动编写 user_script.bash + +创建/编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 容器启动时执行 + +echo "=== Setting up comfy-cli ===" + +# 配置变量 +VENV_PIP="/comfy/mnt/venv/bin/pip" +COMFY_CMD="/comfy/mnt/venv/bin/comfy" +CONFIG_DIR="$HOME/.config/comfy-cli" +CONFIG_FILE="$CONFIG_DIR/config.ini" + +# 检查并安装 comfy-cli +if ! $COMFY_CMD --version &> /dev/null; then + echo "Installing comfy-cli..." + $VENV_PIP install comfy-cli -q +fi + +# 创建配置目录 +mkdir -p "$CONFIG_DIR" + +# 直接写入 config.ini(绕过 set-default 的 Git 检测) +cat > "$CONFIG_FILE" << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +EOF + +echo "Config file written to $CONFIG_FILE" + +# 验证配置 +$COMFY_CMD which +``` + +##### 为什么不能直接用 `comfy set-default`? + +comfy-cli 的 `set-default` 命令会验证路径是否为有效的 ComfyUI Git 仓库: + +```python +# comfy_cli/workspace_manager.py +def _is_comfy_repo(path: Path) -> bool: + """检查是否为 ComfyUI 仓库""" + git_dir = path / ".git" + if not git_dir.exists(): + return False + # 检查 remote URLs 是否匹配 ComfyUI 官方仓库 + ... +``` + +在 Docker 环境中: +- `/comfy/mnt/ComfyUI` 是 ComfyUI 代码(可能有 Git) +- `/basedir` 是用户数据目录(没有 Git) +- `set-default /basedir` 会失败:`Specified path is not a ComfyUI path` + +**直接写入 config.ini 是安全的**,因为: +1. comfy-cli 读取配置时不验证 Git +2. 模型/节点管理只关心子目录结构(`models/`, `custom_nodes/`) +3. ComfyUI 启动时会正确加载模型路径 + +#### 方法 B:手动安装(不推荐) + +```bash +# 进入容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境 +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# ❌ 这会失败:Specified path is not a ComfyUI path +comfy set-default /basedir + +# ✅ 改为直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# 验证 +comfy which + +# 退出容器 +exit +``` + +### 9.3 环境变量配置 + +在 `docker-compose.yml` 或 `docker run` 中添加: + +```yaml +environment: + - CIVITAI_API_TOKEN=your_token_here + - HF_API_TOKEN=your_token_here + - COMFY_CLI_WORKSPACE=/basedir +``` + +### 9.4 权限问题处理 + +ComfyUI-Nvidia-Docker 使用 `WANTED_UID`/`WANTED_GID` 映射用户权限: + +```bash +docker run ... \ + -e WANTED_UID=$(id -u) \ + -e WANTED_GID=$(id -g) \ + ... +``` + +确保 comfy-cli 配置文件的权限正确: + +```bash +# 在容器内 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +``` + +### 9.5 模型下载优化 + +在 Docker 环境中,模型下载到 `basedir`,这是持久化存储: + +```bash +# 模型会保存到 /basedir/models/... +docker exec comfyui-nvidia comfy model download \ + --url "https://..." \ + --relative-path "models/checkpoints" +``` + +--- + +## 10. REST API 集成方案 + +### 10.1 方案概述 + +由于 comfy-cli 本身不提供 REST API,需要通过 custom node 将命令暴露为 HTTP 端点。有两种实现方案: + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **方案 A:扩展现有 cm-cli-rest** | 复用现有代码 | 需要修改现有项目 | +| **方案 B:新建 comfy-cli-rest** | 独立、清晰 | 需要从头开发 | + +**推荐:方案 A**(扩展 cm-cli-rest) + +### 10.2 方案 A:扩展 cm-cli-rest + +#### 10.2.1 项目结构 + +``` +cm-cli-rest/ +├── __init__.py # 入口点 +├── api/ +│ ├── routes.py # 路由定义 +│ └── handlers/ +│ ├── nodes.py # 节点管理 +│ ├── models.py # 【新增】模型管理 +│ └── jobs.py # 任务跟踪 +├── comfy_cli/ +│ └── executor.py # 【新增】comfy-cli 执行器 +├── config/ +│ └── config.json # 配置 +└── README.md +``` + +#### 10.2.2 comfy-cli 执行器实现 + +```python +# comfy_cli/executor.py +import asyncio +import json +import os +import subprocess +import uuid +from typing import Dict, Optional, Any + +class ComfyCliExecutor: + """comfy-cli 命令执行器""" + + def __init__(self, workspace_path: str = "/basedir"): + self.workspace_path = workspace_path + self.comfy_cmd = ["comfy", f"--workspace={workspace_path}"] + self.jobs: Dict[str, Dict] = {} + + async def execute( + self, + command: list, + async_mode: bool = True + ) -> Dict[str, Any]: + """执行 comfy-cli 命令""" + job_id = str(uuid.uuid4()) + + full_cmd = self.comfy_cmd + command + + if async_mode: + # 异步执行(用于长时间运行的命令) + task = asyncio.create_task( + self._run_async(job_id, full_cmd) + ) + self.jobs[job_id] = { + "status": "running", + "command": command, + "task": task + } + return { + "job_id": job_id, + "status": "running", + "message": f"Command started: {' '.join(command)}" + } + else: + # 同步执行 + result = await self._run_sync(full_cmd) + return result + + async def _run_async(self, job_id: str, cmd: list): + """异步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + self.jobs[job_id] = { + "status": "completed", + "output": stdout.decode(), + "error": stderr.decode() + } + else: + self.jobs[job_id] = { + "status": "failed", + "error": stderr.decode(), + "output": stdout.decode() + } + except Exception as e: + self.jobs[job_id] = { + "status": "failed", + "error": str(e) + } + + async def _run_sync(self, cmd: list) -> Dict[str, Any]: + """同步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + return { + "success": True, + "data": stdout.decode(), + "error": None + } + else: + return { + "success": False, + "data": None, + "error": stderr.decode() + } + except Exception as e: + return { + "success": False, + "data": None, + "error": str(e) + } + + def get_job_status(self, job_id: str) -> Optional[Dict]: + """获取任务状态""" + return self.jobs.get(job_id) + + def remove_job(self, job_id: str) -> bool: + """移除任务""" + if job_id in self.jobs: + del self.jobs[job_id] + return True + return False +``` + +#### 10.2.3 模型管理 Handler + +```python +# api/handlers/models.py +from aiohttp import web +import json +from ..comfy_cli.executor import ComfyCliExecutor + +executor = ComfyCliExecutor(workspace_path="/basedir") + +async def download_model(request): + """下载模型""" + try: + data = await request.json() + url = data.get("url") + relative_path = data.get("relative_path", "models/checkpoints") + filename = data.get("filename") + civitai_token = data.get("civitai_api_token") + hf_token = data.get("hf_api_token") + + if not url: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_URL", "message": "URL is required"} + }, status=400) + + # 构建命令 + cmd = ["model", "download", "--url", url] + + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + if filename: + cmd.extend(["--filename", filename]) + + if civitai_token: + cmd.extend(["--set-civitai-api-token", civitai_token]) + + if hf_token: + cmd.extend(["--set-hf-api-token", hf_token]) + + # 执行命令(异步) + result = await executor.execute(cmd, async_mode=True) + + return web.json_response({ + "success": True, + "data": result + }, status=202) # 202 Accepted + + except json.JSONDecodeError: + return web.json_response({ + "success": False, + "error": {"code": "INVALID_JSON", "message": "Invalid JSON"} + }, status=400) + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def list_models(request): + """列出模型""" + try: + relative_path = request.query.get("relative_path", "models") + + cmd = ["model", "list"] + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + # 解析输出 + models = result["data"].strip().split("\n") if result["data"] else [] + return web.json_response({ + "success": True, + "data": {"models": models, "path": relative_path} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "LIST_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def remove_model(request): + """删除模型""" + try: + data = await request.json() + model_names = data.get("model_names", []) + relative_path = data.get("relative_path", "models/checkpoints") + confirm = data.get("confirm", False) + + if not model_names: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_NAMES", "message": "model_names is required"} + }, status=400) + + cmd = ["model", "remove", "--relative-path", relative_path] + + for name in model_names: + cmd.extend(["--model-names", name]) + + if confirm: + cmd.append("--confirm") + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + return web.json_response({ + "success": True, + "data": {"message": f"Removed {len(model_names)} model(s)"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "REMOVE_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def get_job_status(request): + """获取任务状态""" + job_id = request.match_info.get("job_id") + + status = executor.get_job_status(job_id) + + if status: + return web.json_response({ + "success": True, + "data": status + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) + +async def remove_job(request): + """移除任务""" + job_id = request.match_info.get("job_id") + + if executor.remove_job(job_id): + return web.json_response({ + "success": True, + "data": {"message": f"Job {job_id} removed"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) +``` + +#### 10.2.4 路由定义 + +```python +# api/routes.py +from aiohttp import web +from .handlers import nodes, models, jobs + +def setup_routes(app): + """注册路由""" + + # 健康检查 + app.router.add_get("/cm-cli-rest/health", health_check) + + # 节点管理(现有) + app.router.add_get("/cm-cli-rest/nodes", nodes.list_nodes) + app.router.add_post("/cm-cli-rest/nodes/install", nodes.install_node) + # ... + + # 模型管理(新增) + app.router.add_post("/cm-cli-rest/models/download", models.download_model) + app.router.add_get("/cm-cli-rest/models", models.list_models) + app.router.add_post("/cm-cli-rest/models/remove", models.remove_model) + + # 任务跟踪 + app.router.add_get("/cm-cli-rest/jobs", jobs.list_jobs) + app.router.add_get("/cm-cli-rest/jobs/{job_id}", models.get_job_status) + app.router.add_delete("/cm-cli-rest/jobs/{job_id}", models.remove_job) + +async def health_check(request): + """健康检查""" + return web.json_response({ + "status": "healthy", + "service": "cm-cli-rest", + "version": "0.2.0", # 更新版本号 + "features": ["nodes", "models", "jobs"], + "timestamp": datetime.utcnow().isoformat() + }) +``` + +#### 10.2.5 API 端点列表 + +| 方法 | 端点 | 描述 | 异步 | +|------|------|------|------| +| GET | `/cm-cli-rest/health` | 健康检查 | ❌ | +| POST | `/cm-cli-rest/models/download` | 下载模型 | ✅ | +| GET | `/cm-cli-rest/models` | 列出模型 | ❌ | +| POST | `/cm-cli-rest/models/remove` | 删除模型 | ❌ | +| GET | `/cm-cli-rest/jobs` | 列出任务 | ❌ | +| GET | `/cm-cli-rest/jobs/{id}` | 获取任务状态 | ❌ | +| DELETE | `/cm-cli-rest/jobs/{id}` | 移除任务 | ❌ | + +#### 10.2.6 请求/响应示例 + +**下载模型请求:** + +```bash +curl -X POST http://localhost:8188/cm-cli-rest/models/download \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors", + "civitai_api_token": "your_token" + }' +``` + +**响应(202 Accepted):** + +```json +{ + "success": true, + "data": { + "job_id": "abc12345", + "status": "running", + "message": "Command started: model download --url ..." + } +} +``` + +**查询任务状态:** + +```bash +curl http://localhost:8188/cm-cli-rest/jobs/abc12345 +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "status": "completed", + "output": "Model downloaded successfully...", + "error": null + } +} +``` + +**列出模型:** + +```bash +curl "http://localhost:8188/cm-cli-rest/models?relative_path=models/checkpoints" +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "models": [ + "model1.safetensors", + "model2.ckpt", + "model3.safetensors" + ], + "path": "models/checkpoints" + } +} +``` + +### 10.3 方案 B:新建 comfy-cli-rest + +如果希望保持独立,可以创建新的 custom node: + +```bash +# 目录结构 +custom_nodes/comfy-cli-rest/ +├── __init__.py +├── server.py +├── handlers/ +│ ├── models.py +│ ├── nodes.py +│ └── workflows.py +├── comfy_executor.py +├── config.json +└── requirements.txt +``` + +**requirements.txt:** + +``` +aiohttp>=3.9.0 +``` + +**__init__.py:** + +```python +import asyncio +import os +from aiohttp import web +from server import PromptServer + +from .handlers import models, nodes +from .comfy_executor import ComfyCliExecutor + +WEB_DIRECTORY = "./web" + +executor = ComfyCliExecutor( + workspace_path=os.environ.get("COMFYUI_BASEDIR", "/basedir") +) + +@PromptServer.instance.routes.get("/comfy-cli/health") +async def health_check(request): + return web.json_response({ + "status": "healthy", + "service": "comfy-cli-rest", + "version": "1.0.0" + }) + +@PromptServer.instance.routes.post("/comfy-cli/model/download") +async def download_model(request): + return await models.download_model(request, executor) + +@PromptServer.instance.routes.get("/comfy-cli/model/list") +async def list_models(request): + return await models.list_models(request, executor) + +# ... 其他路由 +``` + +### 10.4 安全考虑 + +1. **API 认证** + ```json + { + "api_key": "your_secret_key", + "enabled": true + } + ``` + +2. **输入验证** + - 验证所有路径参数,防止路径遍历攻击 + - 限制可执行的命令白名单 + +3. **速率限制** + - 限制每个 IP 的请求频率 + - 限制并发任务数量 + +4. **日志记录** + - 记录所有 API 调用 + - 记录错误和异常 + +--- + +## 11. 故障排除 + +### 11.1 常见问题 + +#### 问题 1:`comfy: command not found` + +**原因:** comfy-cli 未安装或未添加到 PATH + +**解决:** + +```bash +# 在容器内 +source /comfy/mnt/venv/bin/activate +pip install comfy-cli + +# 或使用完整路径 +/comfy/mnt/venv/bin/comfy --version +``` + +#### 问题 2:`ComfyUI not found` 或 `Specified path is not a ComfyUI path` + +**原因:** 工作区配置错误,或在 Docker 环境中使用 `set-default` 命令 + +**解决:** + +**Docker 环境特殊情况:** + +`comfy set-default /basedir` 会失败,因为 comfy-cli 通过 Git 仓库检测验证路径,而 `/basedir` 是数据目录而非 Git 仓库。 + +```bash +# ❌ 这会失败 +comfy set-default /basedir +# 错误:Specified path is not a ComfyUI path: /basedir + +# ✅ 方案 A:使用自动化脚本(推荐) +# 将 docs/comfy-cli-docker-setup.sh 复制为 run/user_script.bash 并重启容器 + +# ✅ 方案 B:直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# ✅ 方案 C:每次使用 --workspace 参数 +comfy --workspace=/basedir model list +``` + +**非 Docker 环境:** + +```bash +# 设置默认工作区 +comfy set-default /path/to/ComfyUI + +# 或显式指定 +comfy --workspace=/basedir model list +``` + +#### 问题 3:模型下载失败(401/403) + +**原因:** 缺少 API Token + +**解决:** + +```bash +# 设置 Token +comfy model download --url "..." \ + --set-civitai-api-token "YOUR_TOKEN" + +# 或使用环境变量 +export CIVITAI_API_TOKEN="YOUR_TOKEN" +comfy model download --url "..." +``` + +#### 问题 4:权限错误 + +**原因:** Docker 用户权限映射问题 + +**解决:** + +```bash +# 在容器内修复权限 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +chown -R $(id -u):$(id -g) /basedir/ +``` + +#### 问题 5:Python 版本不兼容 + +**原因:** comfy-cli 需要 Python 3.9+ + +**解决:** + +```bash +# 检查 Python 版本 +python --version + +# 使用正确的 Python 环境 +/comfy/mnt/venv/bin/python -m pip install comfy-cli +``` + +### 11.2 调试技巧 + +```bash +# 启用详细日志 +comfy --workspace=/basedir model download --url "..." -v + +# 查看 comfy-cli 配置 +cat ~/.config/comfy-cli/config.ini + +# 检查环境 +comfy env + +# 验证工作区 +comfy which +``` + +### 11.3 获取帮助 + +```bash +# 主帮助 +comfy --help + +# 命令帮助 +comfy model --help +comfy model download --help +comfy node --help + +# 查看源码 +# https://github.com/Comfy-Org/comfy-cli +``` + +--- + +## 12. 附录 + +### 12.1 完整命令参考 + +``` +comfy [OPTIONS] COMMAND [ARGS] + +Options: + --workspace PATH Path to ComfyUI workspace + --recent Execute from recent path + --here Execute from current path + --version Print version and exit + --help Show help + +Commands: + install Download and install ComfyUI + update Update ComfyUI environment + launch Launch ComfyUI + stop Stop background ComfyUI + set-default Set default ComfyUI path + which Show which ComfyUI is selected + env Print environment variables + model Model management + download Download model file + list List models + remove Remove models + node Custom node management + install Install nodes + uninstall Uninstall nodes + update Update nodes + enable Enable nodes + disable Disable nodes + show Show node list + simple-show Show node list (simple) + save-snapshot Save snapshot + restore-snapshot Restore snapshot + run Run workflow + feedback Provide feedback +``` + +### 12.2 配置文件模板 + +```ini +# ~/.config/comfy-cli/config.ini +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +civitai_api_token = +hf_api_token = +``` + +### 12.3 Docker Compose 示例 + +```yaml +version: '3.8' + +services: + comfyui: + image: mmartial/comfyui-nvidia-docker:latest + container_name: comfyui-nvidia + runtime: nvidia + environment: + - USE_UV=true + - WANTED_UID=1000 + - WANTED_GID=1000 + - BASE_DIRECTORY=/basedir + - SECURITY_LEVEL=normal + - CIVITAI_API_TOKEN=${CIVITAI_API_TOKEN} + - HF_API_TOKEN=${HF_API_TOKEN} + volumes: + - ./run:/comfy/mnt + - ./basedir:/basedir + ports: + - "127.0.0.1:8188:8188" + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +### 12.4 参考资源 + +- **comfy-cli 官方仓库:** https://github.com/Comfy-Org/comfy-cli +- **comfy-cli PyPI:** https://pypi.org/project/comfy-cli/ +- **ComfyUI-Nvidia-Docker:** https://github.com/mmartial/ComfyUI-Nvidia-Docker +- **ComfyUI-Manager:** https://github.com/ltdrdata/ComfyUI-Manager +- **cm-cli-rest:** https://github.com/fno2010/cm-cli-rest + +--- + +## 修订历史 + +| 版本 | 日期 | 作者 | 变更 | +|------|------|------|------| +| 1.2 | 2026-03-16 | Zero | 添加 Docker 环境 `set-default` 失败解决方案、自动化脚本 `comfy-cli-docker-setup.sh` | +| 1.1 | 2026-03-16 | Zero | 添加参数命名规则说明、完成 v1.6.0 源码验证 | +| 1.0 | 2026-03-16 | Zero | 初始版本 | + +--- + +## 版本说明 + +**本文档基于以下 comfy-cli 版本调研编写:** + +| 来源 | 版本 | 说明 | +|------|------|------| +| PyPI 最新稳定版 | `1.6.0` | ✅ 已验证,推荐使用 | +| GitHub main 分支 | 开发中 | 文档编写时(2026-03-16)的代码状态 | +| 最低兼容版本 | `1.3.8` | 低于此版本可能有命令差异 | + +**验证你安装的版本:** + +```bash +comfy --version +``` + +如果版本低于 `1.3.8`,建议升级: + +```bash +pip install --upgrade comfy-cli +``` + +## 参数命名规则 + +comfy-cli 使用 **typer** 框架,Python 参数名会自动转换为 CLI 标志: + +| Python 参数名 | CLI 标志 | 转换规则 | +|--------------|----------|----------| +| `relative_path` | `--relative-path` | snake_case → kebab-case | +| `model_names` | `--model-names` | snake_case → kebab-case | +| `confirm` | `--confirm` | 无变化 | +| `fast_deps` | `--fast-deps` | snake_case → kebab-case | + +**示例:** + +```python +# Python 源码 +relative_path: Annotated[str | None, typer.Option(...)] + +# CLI 使用 +comfy model download --relative-path "models/checkpoints" +``` + +所有文档中的命令参数都遵循此规则。完整验证报告见 `comfy-cli-verification-report.md`。 + +--- + +*本文档由 Zero (零号) 生成。所有命令已通过 v1.6.0 源码验证。* diff --git a/docs/references/archive/comfy-cli-guide-v1.3.md b/docs/references/archive/comfy-cli-guide-v1.3.md new file mode 100644 index 0000000..644f567 --- /dev/null +++ b/docs/references/archive/comfy-cli-guide-v1.3.md @@ -0,0 +1,1661 @@ +# ComfyCLI 配置与使用指南 + +> 文档版本:1.3 +> 最后更新:2026-03-16 +> 适用环境:ComfyUI-Nvidia-Docker 部署 +> **适配 comfy-cli 版本:≥1.3.8(已验证 1.6.0 稳定版)** +> **验证报告:** `comfy-cli-verification-report.md` +> **Docker 自动化脚本:** `comfy-cli-docker-setup.sh` +> **API Reference:** `comfy-cli-api-reference.md` +> **REST 实现指南:** `comfy-cli-rest-implementation.md` + +--- + +## 目录 + +1. [概述](#1-概述) +2. [ComfyCLI 架构](#2-comfycli-架构) +3. [安装指南](#3-安装指南) +4. [配置系统详解](#4-配置系统详解) +5. [工作区管理](#5-工作区管理) +6. [模型管理](#6-模型管理) +7. [自定义节点管理](#7-自定义节点管理) +8. [ComfyUI 生命周期管理](#8-comfyui-生命周期管理) +9. [Docker 环境特殊配置](#9-docker-环境特殊配置) +10. [REST API 集成方案](#10-rest-api-集成方案) +11. [故障排除](#11-故障排除) +12. [附录](#12-附录) + +--- + +## 1. 概述 + +### 1.1 什么是 ComfyCLI + +`comfy-cli` 是由 Comfy-Org 官方维护的命令行工具,用于管理和操作 ComfyUI。与 ComfyUI-Manager 的 `cm-cli` 不同,comfy-cli 是一个**独立的管理工具**,提供: + +- ✅ **模型下载与管理**(核心优势) +- ✅ 自定义节点管理 +- ✅ ComfyUI 安装与更新 +- ✅ 工作区管理 +- ✅ Workflow 执行 +- ✅ 后台服务管理 + +### 1.2 与 cm-cli 的对比 + +| 功能 | cm-cli | comfy-cli | +|------|--------|-----------| +| 模型下载 | ❌ | ✅ | +| 模型列表/删除 | ❌ | ✅ | +| 节点管理 | ✅ | ✅ | +| ComfyUI 安装 | ❌ | ✅ | +| 工作区管理 | ❌ | ✅ | +| REST API | ✅ (通过 custom node) | ❌ | + +### 1.3 适用场景 + +- 需要通过命令行下载和管理模型 +- 需要脚本化 ComfyUI 管理操作 +- 需要多工作区管理 +- 需要与现有 ComfyUI-Manager 共存 + +--- + +## 2. ComfyCLI 架构 + +### 2.1 核心组件 + +``` +comfy-cli/ +├── comfy_cli/ +│ ├── __init__.py # 包初始化 +│ ├── cmdline.py # 命令行入口和命令注册 +│ ├── config_manager.py # 配置文件管理 +│ ├── workspace_manager.py # 工作区管理 +│ ├── constants.py # 常量定义 +│ ├── command/ +│ │ ├── install.py # 安装命令 +│ │ ├── launch.py # 启动命令 +│ │ ├── models/ # 模型管理命令 +│ │ │ └── models.py # 下载/删除/列表 +│ │ └── custom_nodes/ # 自定义节点命令 +│ │ └── command.py # 节点管理实现 +│ └── utils/ # 工具函数 +└── pyproject.toml # 项目配置 +``` + +### 2.2 配置文件系统 + +``` +~/.config/comfy-cli/ +└── config.ini # 主配置文件 +``` + +**config.ini 结构:** + +```ini +[DEFAULT] +default_workspace = /path/to/ComfyUI +recent_workspace = /path/to/last/used +default_launch_extras = --listen 0.0.0.0 +enable_tracking = true +background = ('127.0.0.1', 8188, 12345) +civitai_api_token = xxx +hf_api_token = xxx +``` + +### 2.3 工作区检测机制 + +comfy-cli 通过 Git 仓库识别 ComfyUI 安装: + +```python +COMFY_ORIGIN_URL_CHOICES = { + "git@github.com:comfyanonymous/ComfyUI.git", + "https://github.com/comfyanonymous/ComfyUI.git", + "https://github.com/Comfy-Org/ComfyUI.git", + # ... +} +``` + +**工作区优先级:** + +1. `--workspace=` - 显式指定 +2. `--recent` - 最近使用 +3. `--here` - 当前目录 +4. 当前目录是 ComfyUI 仓库 +5. `set-default` 设置的默认路径 +6. 默认路径 (`~/comfy/ComfyUI`) + +--- + +## 3. 安装指南 + +### 3.1 前置要求 + +- **Python 3.9+**(必须与 ComfyUI 使用相同环境) +- **Git**(用于仓库检测) +- **pip** 或 **uv**(推荐) + +### 3.2 在 Docker 环境中安装 + +对于 ComfyUI-Nvidia-Docker 部署,有两种安装方式: + +#### 方式 A:在容器内安装(推荐) + +```bash +# 进入运行中的容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境(如果有) +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# 验证安装 +comfy --version +``` + +#### 方式 B:使用 user_script.bash 持久化 + +编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 在容器启动时执行 + +# 安装 comfy-cli +if ! command -v comfy &> /dev/null; then + echo "Installing comfy-cli..." + /comfy/mnt/venv/bin/pip install comfy-cli +fi + +# 设置默认工作区 +/comfy/mnt/venv/bin/comfy set-default /basedir 2>/dev/null || true +``` + +### 3.3 验证安装 + +```bash +# 检查版本 +comfy --version + +# 查看帮助 +comfy --help + +# 检查环境 +comfy env +``` + +--- + +## 4. 配置系统详解 + +### 4.1 配置文件位置 + +| 操作系统 | 配置文件路径 | +|----------|-------------| +| Linux | `~/.config/comfy-cli/config.ini` | +| macOS | `~/Library/Application Support/comfy-cli/config.ini` | +| Windows | `%LOCALAPPDATA%\comfy-cli\config.ini` | + +### 4.2 配置项说明 + +```ini +[DEFAULT] +# 默认工作区路径(通过 comfy set-default 设置) +default_workspace = /basedir + +# 最近使用的工作区(自动更新) +recent_workspace = /basedir + +# 启动 ComfyUI 时的默认参数 +default_launch_extras = --listen 0.0.0.0 --port 8188 + +# 是否启用使用追踪(可选) +enable_tracking = false + +# 后台运行的 ComfyUI 信息 (host, port, pid) +# 自动管理,不要手动修改 +background = ('127.0.0.1', 8188, 5678) + +# CivitAI API Token(用于下载需要认证的模型) +civitai_api_token = your_token_here + +# HuggingFace API Token(用于下载私有模型) +hf_api_token = your_token_here +``` + +### 4.3 配置管理命令 + +```bash +# 查看当前配置 +comfy env + +# 设置默认工作区 +comfy set-default /basedir + +# 设置启动参数 +comfy set-default /basedir --launch-extras="--listen 0.0.0.0" + +# 查看当前工作区 +comfy which +``` + +### 4.4 环境变量覆盖 + +comfy-cli 支持通过环境变量覆盖配置: + +| 环境变量 | 配置项 | 优先级 | +|----------|--------|--------| +| `CIVITAI_API_TOKEN` | civitai_api_token | 高 | +| `HF_API_TOKEN` | hf_api_token | 高 | +| `COMFY_CLI_WORKSPACE` | default_workspace | 高 | + +**优先级顺序:** + +1. CLI 参数(如 `--set-civitai-api-token`) +2. 环境变量 +3. config.ini 配置 + +--- + +## 5. 工作区管理 + +### 5.1 工作区概念 + +工作区是 ComfyUI 的安装目录,包含: + +``` +/basedir/ # 工作区根目录 +├── ComfyUI/ # ComfyUI 核心代码 +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +│ ├── checkpoints/ +│ ├── loras/ +│ ├── vae/ +│ └── ... +├── input/ # 输入文件 +├── output/ # 输出文件 +└── user/ # 用户文件 +``` + +### 5.2 指定工作区的方法 + +#### 方法 1:使用 `--workspace` 参数 + +```bash +comfy --workspace=/basedir model download --url "..." +comfy --workspace=/basedir node install ComfyUI-Impact-Pack +``` + +#### 方法 2:设置默认工作区 + +```bash +# 一次性设置 +comfy set-default /basedir + +# 之后可省略 --workspace +comfy model download --url "..." +``` + +#### 方法 3:使用 `--recent` + +```bash +# 自动使用最近的工作区 +comfy --recent model download --url "..." +``` + +#### 方法 4:使用 `--here` + +```bash +cd /basedir +comfy --here model download --url "..." +``` + +### 5.3 多工作区管理 + +```bash +# 设置多个工作区 +comfy set-default /basedir +comfy --workspace=/opt/comfy-test node install SomeNode + +# 查看当前工作区 +comfy which + +# 列出所有模型(在当前工作区) +comfy model list +``` + +--- + +## 6. 模型管理 + +### 6.1 下载模型 + +#### 从 CivitAI 下载 + +```bash +# 下载模型(需要 API Token) +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --set-civitai-api-token "YOUR_TOKEN" + +# 指定保存路径 +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --relative-path "models/checkpoints" \ + --filename "my_model.safetensors" +``` + +#### 从 HuggingFace 下载 + +```bash +# 下载公开模型 +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors" + +# 下载私有模型(需要 Token) +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/username/private-model/resolve/main/model.safetensors" \ + --set-hf-api-token "YOUR_HF_TOKEN" +``` + +#### 从直接 URL 下载 + +```bash +comfy --workspace=/basedir model download \ + --url "https://example.com/models/model.safetensors" \ + --relative-path "models/checkpoints" +``` + +### 6.2 列出模型 + +```bash +# 列出所有模型 +comfy --workspace=/basedir model list + +# 列出指定目录的模型 +comfy --workspace=/basedir model list \ + --relative-path "models/checkpoints" + +# 列出 LoRA 模型 +comfy --workspace=/basedir model list \ + --relative-path "models/loras" +``` + +### 6.3 删除模型 + +```bash +# 删除指定模型 +comfy --workspace=/basedir model remove \ + --relative-path "models/checkpoints" \ + --model-names "model1.safetensors" "model2.ckpt" + +# 交互式选择删除 +comfy --workspace=/basedir model remove + +# 跳过确认提示 +comfy --workspace=/basedir model remove \ + --model-names "model.safetensors" \ + --confirm +``` + +### 6.4 模型路径映射 + +comfy-cli 内置的模型类型映射: + +```python +model_path_map = { + "lora": "loras", + "hypernetwork": "hypernetworks", + "checkpoint": "checkpoints", + "textualinversion": "embeddings", + "controlnet": "controlnet", +} +``` + +--- + +## 7. 自定义节点管理 + +### 7.1 节点列表 + +```bash +# 显示已安装的节点 +comfy --workspace=/basedir node simple-show installed + +# 显示启用的节点 +comfy --workspace=/basedir node simple-show enabled + +# 显示禁用的节点 +comfy --workspace=/basedir node simple-show disabled + +# 显示未安装的节点 +comfy --workspace=/basedir node simple-show not-installed + +# 显示所有节点 +comfy --workspace=/basedir node simple-show all + +# 显示详细信息 +comfy --workspace=/basedir node show installed +``` + +### 7.2 安装节点 + +```bash +# 安装单个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack + +# 安装多个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack + +# 使用快速依赖安装 +comfy --workspace=/basedir node install SomeNode --fast-deps + +# 跳过依赖安装 +comfy --workspace=/basedir node install SomeNode --no-deps + +# 指定 channel +comfy --workspace=/basedir node install SomeNode --channel recent +``` + +### 7.3 更新节点 + +```bash +# 更新所有节点 +comfy --workspace=/basedir node update all + +# 更新特定节点 +comfy --workspace=/basedir node update ComfyUI-Impact-Pack + +# 指定 channel 和 mode +comfy --workspace=/basedir node update all --channel recent --mode remote +``` + +### 7.4 启用/禁用节点 + +```bash +# 禁用节点 +comfy --workspace=/basedir node disable ComfyUI-Impact-Pack + +# 启用节点 +comfy --workspace=/basedir node enable ComfyUI-Impact-Pack +``` + +### 7.5 卸载节点 + +```bash +# 卸载节点 +comfy --workspace=/basedir node uninstall ComfyUI-Impact-Pack +``` + +### 7.6 依赖管理 + +```bash +# 安装依赖 +comfy --workspace=/basedir node install-deps + +# 从 workflow 生成依赖 +comfy --workspace=/basedir node deps-in-workflow \ + --workflow /path/to/workflow.json \ + --output /path/to/deps.json +``` + +### 7.7 快照管理 + +```bash +# 保存快照 +comfy --workspace=/basedir node save-snapshot + +# 保存到指定文件 +comfy --workspace=/basedir node save-snapshot --output backup.json + +# 恢复快照 +comfy --workspace=/basedir node restore-snapshot backup.json + +# 列出快照 +comfy --workspace=/basedir node simple-show snapshot-list +``` + +### 7.8 调试节点问题(Bisect) + +```bash +# 开始 bisect 会话 +comfy --workspace=/basedir node bisect start + +# 标记当前状态为好 +comfy --workspace=/basedir node bisect good + +# 标记当前状态为坏 +comfy --workspace=/basedir node bisect bad + +# 重置 bisect 会话 +comfy --workspace=/basedir node bisect reset +``` + +--- + +## 8. ComfyUI 生命周期管理 + +### 8.1 安装 ComfyUI + +```bash +# 默认安装(最新 nightly 版本) +comfy install + +# 指定工作区 +comfy --workspace=~/my-comfy install + +# 安装特定版本 +comfy install --version latest + +# 安装 nightly 版本 +comfy install --version nightly + +# 跳过 Manager 安装 +comfy install --skip-manager + +# 从特定 PR 安装 +comfy install --pr "#1234" + +# 指定 GPU 类型 +comfy install --nvidia +comfy install --amd +comfy install --cpu +``` + +### 8.2 启动 ComfyUI + +```bash +# 前台启动 +comfy --workspace=/basedir launch + +# 后台启动 +comfy --workspace=/basedir launch --background + +# 带参数启动 +comfy --workspace=/basedir launch -- --listen 0.0.0.0 --port 8188 + +# 使用默认参数启动 +comfy --workspace=/basedir launch -- + +# 测试前端 PR +comfy --workspace=/basedir launch --frontend-pr "#456" +``` + +### 8.3 停止 ComfyUI + +```bash +# 停止后台运行的实例 +comfy stop +``` + +### 8.4 更新 ComfyUI + +```bash +# 更新 ComfyUI 核心 +comfy --workspace=/basedir update comfy + +# 更新所有(包括节点) +comfy --workspace=/basedir update all +``` + +### 8.5 运行 Workflow + +```bash +# 运行 workflow 文件 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json + +# 等待完成 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --wait + +# 详细输出 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --verbose + +# 指定主机和端口 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --host 127.0.0.1 \ + --port 8188 \ + --timeout 60 +``` + +--- + +## 9. Docker 环境特殊配置 + +### 9.1 ComfyUI-Nvidia-Docker 目录结构 + +``` +run/ # 容器运行数据 +├── venv/ # Python 虚拟环境 +├── ComfyUI/ # ComfyUI 源代码 +└── ... + +basedir/ # 用户数据(持久化) +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +├── input/ # 输入 +├── output/ # 输出 +└── user/ # 用户配置 +``` + +### 9.2 在 Docker 中安装 comfy-cli + +#### 方法 A:通过 user_script.bash(推荐) + +**⚠️ 重要提示**:在 Docker 环境中,`comfy set-default` 会失败,因为 comfy-cli 通过 Git 仓库检测验证 ComfyUI 安装,而 `/basedir` 目录下没有 ComfyUI 的 Git 仓库(代码在 `/comfy/mnt/ComfyUI`)。 + +**解决方案**:直接写入 `config.ini` 配置文件,绕过 Git 检测。 + +##### 方案 A1:使用自动化脚本(推荐) + +我们提供了完整的自动化脚本 `docs/comfy-cli-docker-setup.sh`,包含: + +- ✅ 自动检测并安装 comfy-cli +- ✅ 直接配置 config.ini(绕过 Git 检测) +- ✅ 环境变量支持(CivitAI/HF API Token) +- ✅ 配置验证 + +**使用步骤:** + +```bash +# 1. 复制脚本到 run 目录 +cp docs/comfy-cli-docker-setup.sh run/user_script.bash + +# 2. 确保执行权限 +chmod +x run/user_script.bash + +# 3. 重启容器 +docker restart comfyui-nvidia + +# 4. 查看日志确认配置成功 +docker logs comfyui-nvidia | grep -A 20 "ComfyCLI" +``` + +##### 方案 A2:手动编写 user_script.bash + +创建/编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 容器启动时执行 + +echo "=== Setting up comfy-cli ===" + +# 配置变量 +VENV_PIP="/comfy/mnt/venv/bin/pip" +COMFY_CMD="/comfy/mnt/venv/bin/comfy" +CONFIG_DIR="$HOME/.config/comfy-cli" +CONFIG_FILE="$CONFIG_DIR/config.ini" + +# 检查并安装 comfy-cli +if ! $COMFY_CMD --version &> /dev/null; then + echo "Installing comfy-cli..." + $VENV_PIP install comfy-cli -q +fi + +# 创建配置目录 +mkdir -p "$CONFIG_DIR" + +# 直接写入 config.ini(绕过 set-default 的 Git 检测) +cat > "$CONFIG_FILE" << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +EOF + +echo "Config file written to $CONFIG_FILE" + +# 验证配置 +$COMFY_CMD which +``` + +##### 为什么不能直接用 `comfy set-default`? + +comfy-cli 的 `set-default` 命令会验证路径是否为有效的 ComfyUI Git 仓库: + +```python +# comfy_cli/workspace_manager.py +def _is_comfy_repo(path: Path) -> bool: + """检查是否为 ComfyUI 仓库""" + git_dir = path / ".git" + if not git_dir.exists(): + return False + # 检查 remote URLs 是否匹配 ComfyUI 官方仓库 + ... +``` + +在 Docker 环境中: +- `/comfy/mnt/ComfyUI` 是 ComfyUI 代码(可能有 Git) +- `/basedir` 是用户数据目录(没有 Git) +- `set-default /basedir` 会失败:`Specified path is not a ComfyUI path` + +**直接写入 config.ini 是安全的**,因为: +1. comfy-cli 读取配置时不验证 Git +2. 模型/节点管理只关心子目录结构(`models/`, `custom_nodes/`) +3. ComfyUI 启动时会正确加载模型路径 + +#### 方法 B:手动安装(不推荐) + +```bash +# 进入容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境 +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# ❌ 这会失败:Specified path is not a ComfyUI path +comfy set-default /basedir + +# ✅ 改为直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# 验证 +comfy which + +# 退出容器 +exit +``` + +### 9.3 环境变量配置 + +在 `docker-compose.yml` 或 `docker run` 中添加: + +```yaml +environment: + - CIVITAI_API_TOKEN=your_token_here + - HF_API_TOKEN=your_token_here + - COMFY_CLI_WORKSPACE=/basedir +``` + +### 9.4 权限问题处理 + +ComfyUI-Nvidia-Docker 使用 `WANTED_UID`/`WANTED_GID` 映射用户权限: + +```bash +docker run ... \ + -e WANTED_UID=$(id -u) \ + -e WANTED_GID=$(id -g) \ + ... +``` + +确保 comfy-cli 配置文件的权限正确: + +```bash +# 在容器内 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +``` + +### 9.5 模型下载优化 + +在 Docker 环境中,模型下载到 `basedir`,这是持久化存储: + +```bash +# 模型会保存到 /basedir/models/... +docker exec comfyui-nvidia comfy model download \ + --url "https://..." \ + --relative-path "models/checkpoints" +``` + +--- + +## 10. REST API 集成方案 + +### 10.1 方案概述 + +由于 comfy-cli 本身不提供 REST API,需要通过 custom node 将命令暴露为 HTTP 端点。 + +**📚 相关文档:** +- **API Reference:** `comfy-cli-api-reference.md` — 所有命令的输入输出格式详解 +- **实现指南:** `comfy-cli-rest-implementation.md` — 完整的 REST API 服务器实现代码 + +有两种实现方案: + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **方案 A:扩展现有 cm-cli-rest** | 复用现有代码 | 需要修改现有项目 | +| **方案 B:新建 comfy-cli-rest** | 独立、清晰 | 需要从头开发 | + +**推荐:方案 A**(扩展 cm-cli-rest) + +### 10.1.1 为什么需要 REST API? + +直接调用 CLI 的问题: + +| 问题 | CLI 直接调用 | REST API | +|------|-------------|----------| +| 输出格式 | 原始文本,需解析 | 结构化 JSON | +| 异步任务 | 需手动管理进程 | 统一任务队列 | +| 错误处理 | 退出码 + stderr | 统一错误响应 | +| 认证授权 | 无 | 可集成 | +| 并发控制 | 无 | 可限制 | + +### 10.1.2 输出解析策略 + +| 命令类型 | 原始输出 | 建议解析方式 | +|----------|----------|-------------| +| `model list` | 每行一个文件名 | 按行分割 → 数组 | +| `node simple-show` | 每行一个节点名 | 按行分割 → 数组 | +| `model download` | 进度 + 完成消息 | 正则提取进度% | +| `env` | 键值对列表 | 分割 `:` → 对象 | +| `node show` | 多行详细信息 | 复杂解析或改用文件系统 | + +**推荐:对于简单查询(list),直接文件系统访问比 CLI 更可靠。** + +### 10.2 方案 A:扩展 cm-cli-rest + +#### 10.2.1 项目结构 + +``` +cm-cli-rest/ +├── __init__.py # 入口点 +├── api/ +│ ├── routes.py # 路由定义 +│ └── handlers/ +│ ├── nodes.py # 节点管理 +│ ├── models.py # 【新增】模型管理 +│ └── jobs.py # 任务跟踪 +├── comfy_cli/ +│ └── executor.py # 【新增】comfy-cli 执行器 +├── config/ +│ └── config.json # 配置 +└── README.md +``` + +#### 10.2.2 comfy-cli 执行器实现 + +```python +# comfy_cli/executor.py +import asyncio +import json +import os +import subprocess +import uuid +from typing import Dict, Optional, Any + +class ComfyCliExecutor: + """comfy-cli 命令执行器""" + + def __init__(self, workspace_path: str = "/basedir"): + self.workspace_path = workspace_path + self.comfy_cmd = ["comfy", f"--workspace={workspace_path}"] + self.jobs: Dict[str, Dict] = {} + + async def execute( + self, + command: list, + async_mode: bool = True + ) -> Dict[str, Any]: + """执行 comfy-cli 命令""" + job_id = str(uuid.uuid4()) + + full_cmd = self.comfy_cmd + command + + if async_mode: + # 异步执行(用于长时间运行的命令) + task = asyncio.create_task( + self._run_async(job_id, full_cmd) + ) + self.jobs[job_id] = { + "status": "running", + "command": command, + "task": task + } + return { + "job_id": job_id, + "status": "running", + "message": f"Command started: {' '.join(command)}" + } + else: + # 同步执行 + result = await self._run_sync(full_cmd) + return result + + async def _run_async(self, job_id: str, cmd: list): + """异步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + self.jobs[job_id] = { + "status": "completed", + "output": stdout.decode(), + "error": stderr.decode() + } + else: + self.jobs[job_id] = { + "status": "failed", + "error": stderr.decode(), + "output": stdout.decode() + } + except Exception as e: + self.jobs[job_id] = { + "status": "failed", + "error": str(e) + } + + async def _run_sync(self, cmd: list) -> Dict[str, Any]: + """同步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + return { + "success": True, + "data": stdout.decode(), + "error": None + } + else: + return { + "success": False, + "data": None, + "error": stderr.decode() + } + except Exception as e: + return { + "success": False, + "data": None, + "error": str(e) + } + + def get_job_status(self, job_id: str) -> Optional[Dict]: + """获取任务状态""" + return self.jobs.get(job_id) + + def remove_job(self, job_id: str) -> bool: + """移除任务""" + if job_id in self.jobs: + del self.jobs[job_id] + return True + return False +``` + +#### 10.2.3 模型管理 Handler + +```python +# api/handlers/models.py +from aiohttp import web +import json +from ..comfy_cli.executor import ComfyCliExecutor + +executor = ComfyCliExecutor(workspace_path="/basedir") + +async def download_model(request): + """下载模型""" + try: + data = await request.json() + url = data.get("url") + relative_path = data.get("relative_path", "models/checkpoints") + filename = data.get("filename") + civitai_token = data.get("civitai_api_token") + hf_token = data.get("hf_api_token") + + if not url: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_URL", "message": "URL is required"} + }, status=400) + + # 构建命令 + cmd = ["model", "download", "--url", url] + + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + if filename: + cmd.extend(["--filename", filename]) + + if civitai_token: + cmd.extend(["--set-civitai-api-token", civitai_token]) + + if hf_token: + cmd.extend(["--set-hf-api-token", hf_token]) + + # 执行命令(异步) + result = await executor.execute(cmd, async_mode=True) + + return web.json_response({ + "success": True, + "data": result + }, status=202) # 202 Accepted + + except json.JSONDecodeError: + return web.json_response({ + "success": False, + "error": {"code": "INVALID_JSON", "message": "Invalid JSON"} + }, status=400) + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def list_models(request): + """列出模型""" + try: + relative_path = request.query.get("relative_path", "models") + + cmd = ["model", "list"] + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + # 解析输出 + models = result["data"].strip().split("\n") if result["data"] else [] + return web.json_response({ + "success": True, + "data": {"models": models, "path": relative_path} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "LIST_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def remove_model(request): + """删除模型""" + try: + data = await request.json() + model_names = data.get("model_names", []) + relative_path = data.get("relative_path", "models/checkpoints") + confirm = data.get("confirm", False) + + if not model_names: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_NAMES", "message": "model_names is required"} + }, status=400) + + cmd = ["model", "remove", "--relative-path", relative_path] + + for name in model_names: + cmd.extend(["--model-names", name]) + + if confirm: + cmd.append("--confirm") + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + return web.json_response({ + "success": True, + "data": {"message": f"Removed {len(model_names)} model(s)"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "REMOVE_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def get_job_status(request): + """获取任务状态""" + job_id = request.match_info.get("job_id") + + status = executor.get_job_status(job_id) + + if status: + return web.json_response({ + "success": True, + "data": status + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) + +async def remove_job(request): + """移除任务""" + job_id = request.match_info.get("job_id") + + if executor.remove_job(job_id): + return web.json_response({ + "success": True, + "data": {"message": f"Job {job_id} removed"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) +``` + +#### 10.2.4 路由定义 + +```python +# api/routes.py +from aiohttp import web +from .handlers import nodes, models, jobs + +def setup_routes(app): + """注册路由""" + + # 健康检查 + app.router.add_get("/cm-cli-rest/health", health_check) + + # 节点管理(现有) + app.router.add_get("/cm-cli-rest/nodes", nodes.list_nodes) + app.router.add_post("/cm-cli-rest/nodes/install", nodes.install_node) + # ... + + # 模型管理(新增) + app.router.add_post("/cm-cli-rest/models/download", models.download_model) + app.router.add_get("/cm-cli-rest/models", models.list_models) + app.router.add_post("/cm-cli-rest/models/remove", models.remove_model) + + # 任务跟踪 + app.router.add_get("/cm-cli-rest/jobs", jobs.list_jobs) + app.router.add_get("/cm-cli-rest/jobs/{job_id}", models.get_job_status) + app.router.add_delete("/cm-cli-rest/jobs/{job_id}", models.remove_job) + +async def health_check(request): + """健康检查""" + return web.json_response({ + "status": "healthy", + "service": "cm-cli-rest", + "version": "0.2.0", # 更新版本号 + "features": ["nodes", "models", "jobs"], + "timestamp": datetime.utcnow().isoformat() + }) +``` + +#### 10.2.5 API 端点列表 + +| 方法 | 端点 | 描述 | 异步 | +|------|------|------|------| +| GET | `/cm-cli-rest/health` | 健康检查 | ❌ | +| POST | `/cm-cli-rest/models/download` | 下载模型 | ✅ | +| GET | `/cm-cli-rest/models` | 列出模型 | ❌ | +| POST | `/cm-cli-rest/models/remove` | 删除模型 | ❌ | +| GET | `/cm-cli-rest/jobs` | 列出任务 | ❌ | +| GET | `/cm-cli-rest/jobs/{id}` | 获取任务状态 | ❌ | +| DELETE | `/cm-cli-rest/jobs/{id}` | 移除任务 | ❌ | + +#### 10.2.6 请求/响应示例 + +**下载模型请求:** + +```bash +curl -X POST http://localhost:8188/cm-cli-rest/models/download \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors", + "civitai_api_token": "your_token" + }' +``` + +**响应(202 Accepted):** + +```json +{ + "success": true, + "data": { + "job_id": "abc12345", + "status": "running", + "message": "Command started: model download --url ..." + } +} +``` + +**查询任务状态:** + +```bash +curl http://localhost:8188/cm-cli-rest/jobs/abc12345 +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "status": "completed", + "output": "Model downloaded successfully...", + "error": null + } +} +``` + +**列出模型:** + +```bash +curl "http://localhost:8188/cm-cli-rest/models?relative_path=models/checkpoints" +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "models": [ + "model1.safetensors", + "model2.ckpt", + "model3.safetensors" + ], + "path": "models/checkpoints" + } +} +``` + +### 10.3 方案 B:新建 comfy-cli-rest + +如果希望保持独立,可以创建新的 custom node: + +```bash +# 目录结构 +custom_nodes/comfy-cli-rest/ +├── __init__.py +├── server.py +├── handlers/ +│ ├── models.py +│ ├── nodes.py +│ └── workflows.py +├── comfy_executor.py +├── config.json +└── requirements.txt +``` + +**requirements.txt:** + +``` +aiohttp>=3.9.0 +``` + +**__init__.py:** + +```python +import asyncio +import os +from aiohttp import web +from server import PromptServer + +from .handlers import models, nodes +from .comfy_executor import ComfyCliExecutor + +WEB_DIRECTORY = "./web" + +executor = ComfyCliExecutor( + workspace_path=os.environ.get("COMFYUI_BASEDIR", "/basedir") +) + +@PromptServer.instance.routes.get("/comfy-cli/health") +async def health_check(request): + return web.json_response({ + "status": "healthy", + "service": "comfy-cli-rest", + "version": "1.0.0" + }) + +@PromptServer.instance.routes.post("/comfy-cli/model/download") +async def download_model(request): + return await models.download_model(request, executor) + +@PromptServer.instance.routes.get("/comfy-cli/model/list") +async def list_models(request): + return await models.list_models(request, executor) + +# ... 其他路由 +``` + +### 10.4 安全考虑 + +1. **API 认证** + ```json + { + "api_key": "your_secret_key", + "enabled": true + } + ``` + +2. **输入验证** + - 验证所有路径参数,防止路径遍历攻击 + - 限制可执行的命令白名单 + +3. **速率限制** + - 限制每个 IP 的请求频率 + - 限制并发任务数量 + +4. **日志记录** + - 记录所有 API 调用 + - 记录错误和异常 + +--- + +## 11. 故障排除 + +### 11.1 常见问题 + +#### 问题 1:`comfy: command not found` + +**原因:** comfy-cli 未安装或未添加到 PATH + +**解决:** + +```bash +# 在容器内 +source /comfy/mnt/venv/bin/activate +pip install comfy-cli + +# 或使用完整路径 +/comfy/mnt/venv/bin/comfy --version +``` + +#### 问题 2:`ComfyUI not found` 或 `Specified path is not a ComfyUI path` + +**原因:** 工作区配置错误,或在 Docker 环境中使用 `set-default` 命令 + +**解决:** + +**Docker 环境特殊情况:** + +`comfy set-default /basedir` 会失败,因为 comfy-cli 通过 Git 仓库检测验证路径,而 `/basedir` 是数据目录而非 Git 仓库。 + +```bash +# ❌ 这会失败 +comfy set-default /basedir +# 错误:Specified path is not a ComfyUI path: /basedir + +# ✅ 方案 A:使用自动化脚本(推荐) +# 将 docs/comfy-cli-docker-setup.sh 复制为 run/user_script.bash 并重启容器 + +# ✅ 方案 B:直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# ✅ 方案 C:每次使用 --workspace 参数 +comfy --workspace=/basedir model list +``` + +**非 Docker 环境:** + +```bash +# 设置默认工作区 +comfy set-default /path/to/ComfyUI + +# 或显式指定 +comfy --workspace=/basedir model list +``` + +#### 问题 3:模型下载失败(401/403) + +**原因:** 缺少 API Token + +**解决:** + +```bash +# 设置 Token +comfy model download --url "..." \ + --set-civitai-api-token "YOUR_TOKEN" + +# 或使用环境变量 +export CIVITAI_API_TOKEN="YOUR_TOKEN" +comfy model download --url "..." +``` + +#### 问题 4:权限错误 + +**原因:** Docker 用户权限映射问题 + +**解决:** + +```bash +# 在容器内修复权限 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +chown -R $(id -u):$(id -g) /basedir/ +``` + +#### 问题 5:Python 版本不兼容 + +**原因:** comfy-cli 需要 Python 3.9+ + +**解决:** + +```bash +# 检查 Python 版本 +python --version + +# 使用正确的 Python 环境 +/comfy/mnt/venv/bin/python -m pip install comfy-cli +``` + +### 11.2 调试技巧 + +```bash +# 启用详细日志 +comfy --workspace=/basedir model download --url "..." -v + +# 查看 comfy-cli 配置 +cat ~/.config/comfy-cli/config.ini + +# 检查环境 +comfy env + +# 验证工作区 +comfy which +``` + +### 11.3 获取帮助 + +```bash +# 主帮助 +comfy --help + +# 命令帮助 +comfy model --help +comfy model download --help +comfy node --help + +# 查看源码 +# https://github.com/Comfy-Org/comfy-cli +``` + +--- + +## 12. 附录 + +### 12.1 完整命令参考 + +``` +comfy [OPTIONS] COMMAND [ARGS] + +Options: + --workspace PATH Path to ComfyUI workspace + --recent Execute from recent path + --here Execute from current path + --version Print version and exit + --help Show help + +Commands: + install Download and install ComfyUI + update Update ComfyUI environment + launch Launch ComfyUI + stop Stop background ComfyUI + set-default Set default ComfyUI path + which Show which ComfyUI is selected + env Print environment variables + model Model management + download Download model file + list List models + remove Remove models + node Custom node management + install Install nodes + uninstall Uninstall nodes + update Update nodes + enable Enable nodes + disable Disable nodes + show Show node list + simple-show Show node list (simple) + save-snapshot Save snapshot + restore-snapshot Restore snapshot + run Run workflow + feedback Provide feedback +``` + +### 12.2 配置文件模板 + +```ini +# ~/.config/comfy-cli/config.ini +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +civitai_api_token = +hf_api_token = +``` + +### 12.3 Docker Compose 示例 + +```yaml +version: '3.8' + +services: + comfyui: + image: mmartial/comfyui-nvidia-docker:latest + container_name: comfyui-nvidia + runtime: nvidia + environment: + - USE_UV=true + - WANTED_UID=1000 + - WANTED_GID=1000 + - BASE_DIRECTORY=/basedir + - SECURITY_LEVEL=normal + - CIVITAI_API_TOKEN=${CIVITAI_API_TOKEN} + - HF_API_TOKEN=${HF_API_TOKEN} + volumes: + - ./run:/comfy/mnt + - ./basedir:/basedir + ports: + - "127.0.0.1:8188:8188" + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +### 12.4 参考资源 + +- **comfy-cli 官方仓库:** https://github.com/Comfy-Org/comfy-cli +- **comfy-cli PyPI:** https://pypi.org/project/comfy-cli/ +- **ComfyUI-Nvidia-Docker:** https://github.com/mmartial/ComfyUI-Nvidia-Docker +- **ComfyUI-Manager:** https://github.com/ltdrdata/ComfyUI-Manager +- **cm-cli-rest:** https://github.com/fno2010/cm-cli-rest + +--- + +## 修订历史 + +| 版本 | 日期 | 作者 | 变更 | +|------|------|------|------| +| 1.3 | 2026-03-16 | Zero | 添加 API Reference 和 REST 实现指南文档链接、输出解析策略说明 | +| 1.2 | 2026-03-16 | Zero | 添加 Docker 环境 `set-default` 失败解决方案、自动化脚本 `comfy-cli-docker-setup.sh` | +| 1.1 | 2026-03-16 | Zero | 添加参数命名规则说明、完成 v1.6.0 源码验证 | +| 1.0 | 2026-03-16 | Zero | 初始版本 | + +--- + +## 版本说明 + +**本文档基于以下 comfy-cli 版本调研编写:** + +| 来源 | 版本 | 说明 | +|------|------|------| +| PyPI 最新稳定版 | `1.6.0` | ✅ 已验证,推荐使用 | +| GitHub main 分支 | 开发中 | 文档编写时(2026-03-16)的代码状态 | +| 最低兼容版本 | `1.3.8` | 低于此版本可能有命令差异 | + +**验证你安装的版本:** + +```bash +comfy --version +``` + +如果版本低于 `1.3.8`,建议升级: + +```bash +pip install --upgrade comfy-cli +``` + +## 参数命名规则 + +comfy-cli 使用 **typer** 框架,Python 参数名会自动转换为 CLI 标志: + +| Python 参数名 | CLI 标志 | 转换规则 | +|--------------|----------|----------| +| `relative_path` | `--relative-path` | snake_case → kebab-case | +| `model_names` | `--model-names` | snake_case → kebab-case | +| `confirm` | `--confirm` | 无变化 | +| `fast_deps` | `--fast-deps` | snake_case → kebab-case | + +**示例:** + +```python +# Python 源码 +relative_path: Annotated[str | None, typer.Option(...)] + +# CLI 使用 +comfy model download --relative-path "models/checkpoints" +``` + +所有文档中的命令参数都遵循此规则。完整验证报告见 `comfy-cli-verification-report.md`。 + +--- + +*本文档由 Zero (零号) 生成。所有命令已通过 v1.6.0 源码验证。* diff --git a/docs/references/archive/comfy-cli-guide-v1.5.md b/docs/references/archive/comfy-cli-guide-v1.5.md new file mode 100644 index 0000000..8a92390 --- /dev/null +++ b/docs/references/archive/comfy-cli-guide-v1.5.md @@ -0,0 +1,1665 @@ +# ComfyCLI 配置与使用指南 + +> 文档版本:1.5 +> 最后更新:2026-03-17 +> 适用环境:ComfyUI-Nvidia-Docker 部署 +> **适配 comfy-cli 版本:≥1.3.8(已验证 1.6.0 稳定版)** +> **验证报告:** `comfy-cli-verification-report.md` +> **Docker 自动化脚本:** `comfy-cli-docker-setup.sh` +> **命令参考手册:** `comfy-cli-command-reference.md` — 所有 CLI 命令的参数和输出格式 +> **表格解析指南:** `comfy-cli-table-parser.md` — box table → JSON/CSV 解析方案 ⭐新增 +> **API Reference:** `comfy-cli-api-reference.md` — REST API 设计参考 +> **REST 实现指南:** `comfy-cli-rest-implementation.md` — REST 服务器实现代码 + +--- + +## 目录 + +1. [概述](#1-概述) +2. [ComfyCLI 架构](#2-comfycli-架构) +3. [安装指南](#3-安装指南) +4. [配置系统详解](#4-配置系统详解) +5. [工作区管理](#5-工作区管理) +6. [模型管理](#6-模型管理) +7. [自定义节点管理](#7-自定义节点管理) +8. [ComfyUI 生命周期管理](#8-comfyui-生命周期管理) +9. [Docker 环境特殊配置](#9-docker-环境特殊配置) +10. [REST API 集成方案](#10-rest-api-集成方案) +11. [故障排除](#11-故障排除) +12. [附录](#12-附录) + +--- + +## 1. 概述 + +### 1.1 什么是 ComfyCLI + +`comfy-cli` 是由 Comfy-Org 官方维护的命令行工具,用于管理和操作 ComfyUI。与 ComfyUI-Manager 的 `cm-cli` 不同,comfy-cli 是一个**独立的管理工具**,提供: + +- ✅ **模型下载与管理**(核心优势) +- ✅ 自定义节点管理 +- ✅ ComfyUI 安装与更新 +- ✅ 工作区管理 +- ✅ Workflow 执行 +- ✅ 后台服务管理 + +### 1.2 与 cm-cli 的对比 + +| 功能 | cm-cli | comfy-cli | +|------|--------|-----------| +| 模型下载 | ❌ | ✅ | +| 模型列表/删除 | ❌ | ✅ | +| 节点管理 | ✅ | ✅ | +| ComfyUI 安装 | ❌ | ✅ | +| 工作区管理 | ❌ | ✅ | +| REST API | ✅ (通过 custom node) | ❌ | + +### 1.3 适用场景 + +- 需要通过命令行下载和管理模型 +- 需要脚本化 ComfyUI 管理操作 +- 需要多工作区管理 +- 需要与现有 ComfyUI-Manager 共存 + +--- + +## 2. ComfyCLI 架构 + +### 2.1 核心组件 + +``` +comfy-cli/ +├── comfy_cli/ +│ ├── __init__.py # 包初始化 +│ ├── cmdline.py # 命令行入口和命令注册 +│ ├── config_manager.py # 配置文件管理 +│ ├── workspace_manager.py # 工作区管理 +│ ├── constants.py # 常量定义 +│ ├── command/ +│ │ ├── install.py # 安装命令 +│ │ ├── launch.py # 启动命令 +│ │ ├── models/ # 模型管理命令 +│ │ │ └── models.py # 下载/删除/列表 +│ │ └── custom_nodes/ # 自定义节点命令 +│ │ └── command.py # 节点管理实现 +│ └── utils/ # 工具函数 +└── pyproject.toml # 项目配置 +``` + +### 2.2 配置文件系统 + +``` +~/.config/comfy-cli/ +└── config.ini # 主配置文件 +``` + +**config.ini 结构:** + +```ini +[DEFAULT] +default_workspace = /path/to/ComfyUI +recent_workspace = /path/to/last/used +default_launch_extras = --listen 0.0.0.0 +enable_tracking = true +background = ('127.0.0.1', 8188, 12345) +civitai_api_token = xxx +hf_api_token = xxx +``` + +### 2.3 工作区检测机制 + +comfy-cli 通过 Git 仓库识别 ComfyUI 安装: + +```python +COMFY_ORIGIN_URL_CHOICES = { + "git@github.com:comfyanonymous/ComfyUI.git", + "https://github.com/comfyanonymous/ComfyUI.git", + "https://github.com/Comfy-Org/ComfyUI.git", + # ... +} +``` + +**工作区优先级:** + +1. `--workspace=` - 显式指定 +2. `--recent` - 最近使用 +3. `--here` - 当前目录 +4. 当前目录是 ComfyUI 仓库 +5. `set-default` 设置的默认路径 +6. 默认路径 (`~/comfy/ComfyUI`) + +--- + +## 3. 安装指南 + +### 3.1 前置要求 + +- **Python 3.9+**(必须与 ComfyUI 使用相同环境) +- **Git**(用于仓库检测) +- **pip** 或 **uv**(推荐) + +### 3.2 在 Docker 环境中安装 + +对于 ComfyUI-Nvidia-Docker 部署,有两种安装方式: + +#### 方式 A:在容器内安装(推荐) + +```bash +# 进入运行中的容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境(如果有) +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# 验证安装 +comfy --version +``` + +#### 方式 B:使用 user_script.bash 持久化 + +编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 在容器启动时执行 + +# 安装 comfy-cli +if ! command -v comfy &> /dev/null; then + echo "Installing comfy-cli..." + /comfy/mnt/venv/bin/pip install comfy-cli +fi + +# 设置默认工作区 +/comfy/mnt/venv/bin/comfy set-default /basedir 2>/dev/null || true +``` + +### 3.3 验证安装 + +```bash +# 检查版本 +comfy --version + +# 查看帮助 +comfy --help + +# 检查环境 +comfy env +``` + +--- + +## 4. 配置系统详解 + +### 4.1 配置文件位置 + +| 操作系统 | 配置文件路径 | +|----------|-------------| +| Linux | `~/.config/comfy-cli/config.ini` | +| macOS | `~/Library/Application Support/comfy-cli/config.ini` | +| Windows | `%LOCALAPPDATA%\comfy-cli\config.ini` | + +### 4.2 配置项说明 + +```ini +[DEFAULT] +# 默认工作区路径(通过 comfy set-default 设置) +default_workspace = /basedir + +# 最近使用的工作区(自动更新) +recent_workspace = /basedir + +# 启动 ComfyUI 时的默认参数 +default_launch_extras = --listen 0.0.0.0 --port 8188 + +# 是否启用使用追踪(可选) +enable_tracking = false + +# 后台运行的 ComfyUI 信息 (host, port, pid) +# 自动管理,不要手动修改 +background = ('127.0.0.1', 8188, 5678) + +# CivitAI API Token(用于下载需要认证的模型) +civitai_api_token = your_token_here + +# HuggingFace API Token(用于下载私有模型) +hf_api_token = your_token_here +``` + +### 4.3 配置管理命令 + +```bash +# 查看当前配置 +comfy env + +# 设置默认工作区 +comfy set-default /basedir + +# 设置启动参数 +comfy set-default /basedir --launch-extras="--listen 0.0.0.0" + +# 查看当前工作区 +comfy which +``` + +### 4.4 环境变量覆盖 + +comfy-cli 支持通过环境变量覆盖配置: + +| 环境变量 | 配置项 | 优先级 | +|----------|--------|--------| +| `CIVITAI_API_TOKEN` | civitai_api_token | 高 | +| `HF_API_TOKEN` | hf_api_token | 高 | +| `COMFY_CLI_WORKSPACE` | default_workspace | 高 | + +**优先级顺序:** + +1. CLI 参数(如 `--set-civitai-api-token`) +2. 环境变量 +3. config.ini 配置 + +--- + +## 5. 工作区管理 + +### 5.1 工作区概念 + +工作区是 ComfyUI 的安装目录,包含: + +``` +/basedir/ # 工作区根目录 +├── ComfyUI/ # ComfyUI 核心代码 +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +│ ├── checkpoints/ +│ ├── loras/ +│ ├── vae/ +│ └── ... +├── input/ # 输入文件 +├── output/ # 输出文件 +└── user/ # 用户文件 +``` + +### 5.2 指定工作区的方法 + +#### 方法 1:使用 `--workspace` 参数 + +```bash +comfy --workspace=/basedir model download --url "..." +comfy --workspace=/basedir node install ComfyUI-Impact-Pack +``` + +#### 方法 2:设置默认工作区 + +```bash +# 一次性设置 +comfy set-default /basedir + +# 之后可省略 --workspace +comfy model download --url "..." +``` + +#### 方法 3:使用 `--recent` + +```bash +# 自动使用最近的工作区 +comfy --recent model download --url "..." +``` + +#### 方法 4:使用 `--here` + +```bash +cd /basedir +comfy --here model download --url "..." +``` + +### 5.3 多工作区管理 + +```bash +# 设置多个工作区 +comfy set-default /basedir +comfy --workspace=/opt/comfy-test node install SomeNode + +# 查看当前工作区 +comfy which + +# 列出所有模型(在当前工作区) +comfy model list +``` + +--- + +## 6. 模型管理 + +### 6.1 下载模型 + +#### 从 CivitAI 下载 + +```bash +# 下载模型(需要 API Token) +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --set-civitai-api-token "YOUR_TOKEN" + +# 指定保存路径 +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --relative-path "models/checkpoints" \ + --filename "my_model.safetensors" +``` + +#### 从 HuggingFace 下载 + +```bash +# 下载公开模型 +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors" + +# 下载私有模型(需要 Token) +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/username/private-model/resolve/main/model.safetensors" \ + --set-hf-api-token "YOUR_HF_TOKEN" +``` + +#### 从直接 URL 下载 + +```bash +comfy --workspace=/basedir model download \ + --url "https://example.com/models/model.safetensors" \ + --relative-path "models/checkpoints" +``` + +### 6.2 列出模型 + +```bash +# 列出所有模型 +comfy --workspace=/basedir model list + +# 列出指定目录的模型 +comfy --workspace=/basedir model list \ + --relative-path "models/checkpoints" + +# 列出 LoRA 模型 +comfy --workspace=/basedir model list \ + --relative-path "models/loras" +``` + +### 6.3 删除模型 + +```bash +# 删除指定模型 +comfy --workspace=/basedir model remove \ + --relative-path "models/checkpoints" \ + --model-names "model1.safetensors" "model2.ckpt" + +# 交互式选择删除 +comfy --workspace=/basedir model remove + +# 跳过确认提示 +comfy --workspace=/basedir model remove \ + --model-names "model.safetensors" \ + --confirm +``` + +### 6.4 模型路径映射 + +comfy-cli 内置的模型类型映射: + +```python +model_path_map = { + "lora": "loras", + "hypernetwork": "hypernetworks", + "checkpoint": "checkpoints", + "textualinversion": "embeddings", + "controlnet": "controlnet", +} +``` + +--- + +## 7. 自定义节点管理 + +### 7.1 节点列表 + +```bash +# 显示已安装的节点 +comfy --workspace=/basedir node simple-show installed + +# 显示启用的节点 +comfy --workspace=/basedir node simple-show enabled + +# 显示禁用的节点 +comfy --workspace=/basedir node simple-show disabled + +# 显示未安装的节点 +comfy --workspace=/basedir node simple-show not-installed + +# 显示所有节点 +comfy --workspace=/basedir node simple-show all + +# 显示详细信息 +comfy --workspace=/basedir node show installed +``` + +### 7.2 安装节点 + +```bash +# 安装单个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack + +# 安装多个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack + +# 使用快速依赖安装 +comfy --workspace=/basedir node install SomeNode --fast-deps + +# 跳过依赖安装 +comfy --workspace=/basedir node install SomeNode --no-deps + +# 指定 channel +comfy --workspace=/basedir node install SomeNode --channel recent +``` + +### 7.3 更新节点 + +```bash +# 更新所有节点 +comfy --workspace=/basedir node update all + +# 更新特定节点 +comfy --workspace=/basedir node update ComfyUI-Impact-Pack + +# 指定 channel 和 mode +comfy --workspace=/basedir node update all --channel recent --mode remote +``` + +### 7.4 启用/禁用节点 + +```bash +# 禁用节点 +comfy --workspace=/basedir node disable ComfyUI-Impact-Pack + +# 启用节点 +comfy --workspace=/basedir node enable ComfyUI-Impact-Pack +``` + +### 7.5 卸载节点 + +```bash +# 卸载节点 +comfy --workspace=/basedir node uninstall ComfyUI-Impact-Pack +``` + +### 7.6 依赖管理 + +```bash +# 安装依赖 +comfy --workspace=/basedir node install-deps + +# 从 workflow 生成依赖 +comfy --workspace=/basedir node deps-in-workflow \ + --workflow /path/to/workflow.json \ + --output /path/to/deps.json +``` + +### 7.7 快照管理 + +```bash +# 保存快照 +comfy --workspace=/basedir node save-snapshot + +# 保存到指定文件 +comfy --workspace=/basedir node save-snapshot --output backup.json + +# 恢复快照 +comfy --workspace=/basedir node restore-snapshot backup.json + +# 列出快照 +comfy --workspace=/basedir node simple-show snapshot-list +``` + +### 7.8 调试节点问题(Bisect) + +```bash +# 开始 bisect 会话 +comfy --workspace=/basedir node bisect start + +# 标记当前状态为好 +comfy --workspace=/basedir node bisect good + +# 标记当前状态为坏 +comfy --workspace=/basedir node bisect bad + +# 重置 bisect 会话 +comfy --workspace=/basedir node bisect reset +``` + +--- + +## 8. ComfyUI 生命周期管理 + +### 8.1 安装 ComfyUI + +```bash +# 默认安装(最新 nightly 版本) +comfy install + +# 指定工作区 +comfy --workspace=~/my-comfy install + +# 安装特定版本 +comfy install --version latest + +# 安装 nightly 版本 +comfy install --version nightly + +# 跳过 Manager 安装 +comfy install --skip-manager + +# 从特定 PR 安装 +comfy install --pr "#1234" + +# 指定 GPU 类型 +comfy install --nvidia +comfy install --amd +comfy install --cpu +``` + +### 8.2 启动 ComfyUI + +```bash +# 前台启动 +comfy --workspace=/basedir launch + +# 后台启动 +comfy --workspace=/basedir launch --background + +# 带参数启动 +comfy --workspace=/basedir launch -- --listen 0.0.0.0 --port 8188 + +# 使用默认参数启动 +comfy --workspace=/basedir launch -- + +# 测试前端 PR +comfy --workspace=/basedir launch --frontend-pr "#456" +``` + +### 8.3 停止 ComfyUI + +```bash +# 停止后台运行的实例 +comfy stop +``` + +### 8.4 更新 ComfyUI + +```bash +# 更新 ComfyUI 核心 +comfy --workspace=/basedir update comfy + +# 更新所有(包括节点) +comfy --workspace=/basedir update all +``` + +### 8.5 运行 Workflow + +```bash +# 运行 workflow 文件 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json + +# 等待完成 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --wait + +# 详细输出 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --verbose + +# 指定主机和端口 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --host 127.0.0.1 \ + --port 8188 \ + --timeout 60 +``` + +--- + +## 9. Docker 环境特殊配置 + +### 9.1 ComfyUI-Nvidia-Docker 目录结构 + +``` +run/ # 容器运行数据 +├── venv/ # Python 虚拟环境 +├── ComfyUI/ # ComfyUI 源代码 +└── ... + +basedir/ # 用户数据(持久化) +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +├── input/ # 输入 +├── output/ # 输出 +└── user/ # 用户配置 +``` + +### 9.2 在 Docker 中安装 comfy-cli + +#### 方法 A:通过 user_script.bash(推荐) + +**⚠️ 重要提示**:在 Docker 环境中,`comfy set-default` 会失败,因为 comfy-cli 通过 Git 仓库检测验证 ComfyUI 安装,而 `/basedir` 目录下没有 ComfyUI 的 Git 仓库(代码在 `/comfy/mnt/ComfyUI`)。 + +**解决方案**:直接写入 `config.ini` 配置文件,绕过 Git 检测。 + +##### 方案 A1:使用自动化脚本(推荐) + +我们提供了完整的自动化脚本 `docs/comfy-cli-docker-setup.sh`,包含: + +- ✅ 自动检测并安装 comfy-cli +- ✅ 直接配置 config.ini(绕过 Git 检测) +- ✅ 环境变量支持(CivitAI/HF API Token) +- ✅ 配置验证 + +**使用步骤:** + +```bash +# 1. 复制脚本到 run 目录 +cp docs/comfy-cli-docker-setup.sh run/user_script.bash + +# 2. 确保执行权限 +chmod +x run/user_script.bash + +# 3. 重启容器 +docker restart comfyui-nvidia + +# 4. 查看日志确认配置成功 +docker logs comfyui-nvidia | grep -A 20 "ComfyCLI" +``` + +##### 方案 A2:手动编写 user_script.bash + +创建/编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 容器启动时执行 + +echo "=== Setting up comfy-cli ===" + +# 配置变量 +VENV_PIP="/comfy/mnt/venv/bin/pip" +COMFY_CMD="/comfy/mnt/venv/bin/comfy" +CONFIG_DIR="$HOME/.config/comfy-cli" +CONFIG_FILE="$CONFIG_DIR/config.ini" + +# 检查并安装 comfy-cli +if ! $COMFY_CMD --version &> /dev/null; then + echo "Installing comfy-cli..." + $VENV_PIP install comfy-cli -q +fi + +# 创建配置目录 +mkdir -p "$CONFIG_DIR" + +# 直接写入 config.ini(绕过 set-default 的 Git 检测) +cat > "$CONFIG_FILE" << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +EOF + +echo "Config file written to $CONFIG_FILE" + +# 验证配置 +$COMFY_CMD which +``` + +##### 为什么不能直接用 `comfy set-default`? + +comfy-cli 的 `set-default` 命令会验证路径是否为有效的 ComfyUI Git 仓库: + +```python +# comfy_cli/workspace_manager.py +def _is_comfy_repo(path: Path) -> bool: + """检查是否为 ComfyUI 仓库""" + git_dir = path / ".git" + if not git_dir.exists(): + return False + # 检查 remote URLs 是否匹配 ComfyUI 官方仓库 + ... +``` + +在 Docker 环境中: +- `/comfy/mnt/ComfyUI` 是 ComfyUI 代码(可能有 Git) +- `/basedir` 是用户数据目录(没有 Git) +- `set-default /basedir` 会失败:`Specified path is not a ComfyUI path` + +**直接写入 config.ini 是安全的**,因为: +1. comfy-cli 读取配置时不验证 Git +2. 模型/节点管理只关心子目录结构(`models/`, `custom_nodes/`) +3. ComfyUI 启动时会正确加载模型路径 + +#### 方法 B:手动安装(不推荐) + +```bash +# 进入容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境 +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# ❌ 这会失败:Specified path is not a ComfyUI path +comfy set-default /basedir + +# ✅ 改为直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# 验证 +comfy which + +# 退出容器 +exit +``` + +### 9.3 环境变量配置 + +在 `docker-compose.yml` 或 `docker run` 中添加: + +```yaml +environment: + - CIVITAI_API_TOKEN=your_token_here + - HF_API_TOKEN=your_token_here + - COMFY_CLI_WORKSPACE=/basedir +``` + +### 9.4 权限问题处理 + +ComfyUI-Nvidia-Docker 使用 `WANTED_UID`/`WANTED_GID` 映射用户权限: + +```bash +docker run ... \ + -e WANTED_UID=$(id -u) \ + -e WANTED_GID=$(id -g) \ + ... +``` + +确保 comfy-cli 配置文件的权限正确: + +```bash +# 在容器内 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +``` + +### 9.5 模型下载优化 + +在 Docker 环境中,模型下载到 `basedir`,这是持久化存储: + +```bash +# 模型会保存到 /basedir/models/... +docker exec comfyui-nvidia comfy model download \ + --url "https://..." \ + --relative-path "models/checkpoints" +``` + +--- + +## 10. REST API 集成方案 + +### 10.1 方案概述 + +由于 comfy-cli 本身不提供 REST API,需要通过 custom node 将命令暴露为 HTTP 端点。 + +**📚 相关文档:** +- **API Reference:** `comfy-cli-api-reference.md` — 所有命令的输入输出格式详解 +- **实现指南:** `comfy-cli-rest-implementation.md` — 完整的 REST API 服务器实现代码 + +有两种实现方案: + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **方案 A:扩展现有 cm-cli-rest** | 复用现有代码 | 需要修改现有项目 | +| **方案 B:新建 comfy-cli-rest** | 独立、清晰 | 需要从头开发 | + +**推荐:方案 A**(扩展 cm-cli-rest) + +### 10.1.1 为什么需要 REST API? + +直接调用 CLI 的问题: + +| 问题 | CLI 直接调用 | REST API | +|------|-------------|----------| +| 输出格式 | 原始文本,需解析 | 结构化 JSON | +| 异步任务 | 需手动管理进程 | 统一任务队列 | +| 错误处理 | 退出码 + stderr | 统一错误响应 | +| 认证授权 | 无 | 可集成 | +| 并发控制 | 无 | 可限制 | + +### 10.1.2 输出解析策略 + +| 命令类型 | 原始输出 | 建议解析方式 | +|----------|----------|-------------| +| `model list` | 每行一个文件名 | 按行分割 → 数组 | +| `node simple-show` | 每行一个节点名 | 按行分割 → 数组 | +| `model download` | 进度 + 完成消息 | 正则提取进度% | +| `env` | 键值对列表 | 分割 `:` → 对象 | +| `node show` | 多行详细信息 | 复杂解析或改用文件系统 | + +**推荐:对于简单查询(list),直接文件系统访问比 CLI 更可靠。** + +### 10.2 方案 A:扩展 cm-cli-rest + +#### 10.2.1 项目结构 + +``` +cm-cli-rest/ +├── __init__.py # 入口点 +├── api/ +│ ├── routes.py # 路由定义 +│ └── handlers/ +│ ├── nodes.py # 节点管理 +│ ├── models.py # 【新增】模型管理 +│ └── jobs.py # 任务跟踪 +├── comfy_cli/ +│ └── executor.py # 【新增】comfy-cli 执行器 +├── config/ +│ └── config.json # 配置 +└── README.md +``` + +#### 10.2.2 comfy-cli 执行器实现 + +```python +# comfy_cli/executor.py +import asyncio +import json +import os +import subprocess +import uuid +from typing import Dict, Optional, Any + +class ComfyCliExecutor: + """comfy-cli 命令执行器""" + + def __init__(self, workspace_path: str = "/basedir"): + self.workspace_path = workspace_path + self.comfy_cmd = ["comfy", f"--workspace={workspace_path}"] + self.jobs: Dict[str, Dict] = {} + + async def execute( + self, + command: list, + async_mode: bool = True + ) -> Dict[str, Any]: + """执行 comfy-cli 命令""" + job_id = str(uuid.uuid4()) + + full_cmd = self.comfy_cmd + command + + if async_mode: + # 异步执行(用于长时间运行的命令) + task = asyncio.create_task( + self._run_async(job_id, full_cmd) + ) + self.jobs[job_id] = { + "status": "running", + "command": command, + "task": task + } + return { + "job_id": job_id, + "status": "running", + "message": f"Command started: {' '.join(command)}" + } + else: + # 同步执行 + result = await self._run_sync(full_cmd) + return result + + async def _run_async(self, job_id: str, cmd: list): + """异步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + self.jobs[job_id] = { + "status": "completed", + "output": stdout.decode(), + "error": stderr.decode() + } + else: + self.jobs[job_id] = { + "status": "failed", + "error": stderr.decode(), + "output": stdout.decode() + } + except Exception as e: + self.jobs[job_id] = { + "status": "failed", + "error": str(e) + } + + async def _run_sync(self, cmd: list) -> Dict[str, Any]: + """同步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + return { + "success": True, + "data": stdout.decode(), + "error": None + } + else: + return { + "success": False, + "data": None, + "error": stderr.decode() + } + except Exception as e: + return { + "success": False, + "data": None, + "error": str(e) + } + + def get_job_status(self, job_id: str) -> Optional[Dict]: + """获取任务状态""" + return self.jobs.get(job_id) + + def remove_job(self, job_id: str) -> bool: + """移除任务""" + if job_id in self.jobs: + del self.jobs[job_id] + return True + return False +``` + +#### 10.2.3 模型管理 Handler + +```python +# api/handlers/models.py +from aiohttp import web +import json +from ..comfy_cli.executor import ComfyCliExecutor + +executor = ComfyCliExecutor(workspace_path="/basedir") + +async def download_model(request): + """下载模型""" + try: + data = await request.json() + url = data.get("url") + relative_path = data.get("relative_path", "models/checkpoints") + filename = data.get("filename") + civitai_token = data.get("civitai_api_token") + hf_token = data.get("hf_api_token") + + if not url: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_URL", "message": "URL is required"} + }, status=400) + + # 构建命令 + cmd = ["model", "download", "--url", url] + + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + if filename: + cmd.extend(["--filename", filename]) + + if civitai_token: + cmd.extend(["--set-civitai-api-token", civitai_token]) + + if hf_token: + cmd.extend(["--set-hf-api-token", hf_token]) + + # 执行命令(异步) + result = await executor.execute(cmd, async_mode=True) + + return web.json_response({ + "success": True, + "data": result + }, status=202) # 202 Accepted + + except json.JSONDecodeError: + return web.json_response({ + "success": False, + "error": {"code": "INVALID_JSON", "message": "Invalid JSON"} + }, status=400) + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def list_models(request): + """列出模型""" + try: + relative_path = request.query.get("relative_path", "models") + + cmd = ["model", "list"] + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + # 解析输出 + models = result["data"].strip().split("\n") if result["data"] else [] + return web.json_response({ + "success": True, + "data": {"models": models, "path": relative_path} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "LIST_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def remove_model(request): + """删除模型""" + try: + data = await request.json() + model_names = data.get("model_names", []) + relative_path = data.get("relative_path", "models/checkpoints") + confirm = data.get("confirm", False) + + if not model_names: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_NAMES", "message": "model_names is required"} + }, status=400) + + cmd = ["model", "remove", "--relative-path", relative_path] + + for name in model_names: + cmd.extend(["--model-names", name]) + + if confirm: + cmd.append("--confirm") + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + return web.json_response({ + "success": True, + "data": {"message": f"Removed {len(model_names)} model(s)"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "REMOVE_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def get_job_status(request): + """获取任务状态""" + job_id = request.match_info.get("job_id") + + status = executor.get_job_status(job_id) + + if status: + return web.json_response({ + "success": True, + "data": status + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) + +async def remove_job(request): + """移除任务""" + job_id = request.match_info.get("job_id") + + if executor.remove_job(job_id): + return web.json_response({ + "success": True, + "data": {"message": f"Job {job_id} removed"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) +``` + +#### 10.2.4 路由定义 + +```python +# api/routes.py +from aiohttp import web +from .handlers import nodes, models, jobs + +def setup_routes(app): + """注册路由""" + + # 健康检查 + app.router.add_get("/cm-cli-rest/health", health_check) + + # 节点管理(现有) + app.router.add_get("/cm-cli-rest/nodes", nodes.list_nodes) + app.router.add_post("/cm-cli-rest/nodes/install", nodes.install_node) + # ... + + # 模型管理(新增) + app.router.add_post("/cm-cli-rest/models/download", models.download_model) + app.router.add_get("/cm-cli-rest/models", models.list_models) + app.router.add_post("/cm-cli-rest/models/remove", models.remove_model) + + # 任务跟踪 + app.router.add_get("/cm-cli-rest/jobs", jobs.list_jobs) + app.router.add_get("/cm-cli-rest/jobs/{job_id}", models.get_job_status) + app.router.add_delete("/cm-cli-rest/jobs/{job_id}", models.remove_job) + +async def health_check(request): + """健康检查""" + return web.json_response({ + "status": "healthy", + "service": "cm-cli-rest", + "version": "0.2.0", # 更新版本号 + "features": ["nodes", "models", "jobs"], + "timestamp": datetime.utcnow().isoformat() + }) +``` + +#### 10.2.5 API 端点列表 + +| 方法 | 端点 | 描述 | 异步 | +|------|------|------|------| +| GET | `/cm-cli-rest/health` | 健康检查 | ❌ | +| POST | `/cm-cli-rest/models/download` | 下载模型 | ✅ | +| GET | `/cm-cli-rest/models` | 列出模型 | ❌ | +| POST | `/cm-cli-rest/models/remove` | 删除模型 | ❌ | +| GET | `/cm-cli-rest/jobs` | 列出任务 | ❌ | +| GET | `/cm-cli-rest/jobs/{id}` | 获取任务状态 | ❌ | +| DELETE | `/cm-cli-rest/jobs/{id}` | 移除任务 | ❌ | + +#### 10.2.6 请求/响应示例 + +**下载模型请求:** + +```bash +curl -X POST http://localhost:8188/cm-cli-rest/models/download \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors", + "civitai_api_token": "your_token" + }' +``` + +**响应(202 Accepted):** + +```json +{ + "success": true, + "data": { + "job_id": "abc12345", + "status": "running", + "message": "Command started: model download --url ..." + } +} +``` + +**查询任务状态:** + +```bash +curl http://localhost:8188/cm-cli-rest/jobs/abc12345 +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "status": "completed", + "output": "Model downloaded successfully...", + "error": null + } +} +``` + +**列出模型:** + +```bash +curl "http://localhost:8188/cm-cli-rest/models?relative_path=models/checkpoints" +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "models": [ + "model1.safetensors", + "model2.ckpt", + "model3.safetensors" + ], + "path": "models/checkpoints" + } +} +``` + +### 10.3 方案 B:新建 comfy-cli-rest + +如果希望保持独立,可以创建新的 custom node: + +```bash +# 目录结构 +custom_nodes/comfy-cli-rest/ +├── __init__.py +├── server.py +├── handlers/ +│ ├── models.py +│ ├── nodes.py +│ └── workflows.py +├── comfy_executor.py +├── config.json +└── requirements.txt +``` + +**requirements.txt:** + +``` +aiohttp>=3.9.0 +``` + +**__init__.py:** + +```python +import asyncio +import os +from aiohttp import web +from server import PromptServer + +from .handlers import models, nodes +from .comfy_executor import ComfyCliExecutor + +WEB_DIRECTORY = "./web" + +executor = ComfyCliExecutor( + workspace_path=os.environ.get("COMFYUI_BASEDIR", "/basedir") +) + +@PromptServer.instance.routes.get("/comfy-cli/health") +async def health_check(request): + return web.json_response({ + "status": "healthy", + "service": "comfy-cli-rest", + "version": "1.0.0" + }) + +@PromptServer.instance.routes.post("/comfy-cli/model/download") +async def download_model(request): + return await models.download_model(request, executor) + +@PromptServer.instance.routes.get("/comfy-cli/model/list") +async def list_models(request): + return await models.list_models(request, executor) + +# ... 其他路由 +``` + +### 10.4 安全考虑 + +1. **API 认证** + ```json + { + "api_key": "your_secret_key", + "enabled": true + } + ``` + +2. **输入验证** + - 验证所有路径参数,防止路径遍历攻击 + - 限制可执行的命令白名单 + +3. **速率限制** + - 限制每个 IP 的请求频率 + - 限制并发任务数量 + +4. **日志记录** + - 记录所有 API 调用 + - 记录错误和异常 + +--- + +## 11. 故障排除 + +### 11.1 常见问题 + +#### 问题 1:`comfy: command not found` + +**原因:** comfy-cli 未安装或未添加到 PATH + +**解决:** + +```bash +# 在容器内 +source /comfy/mnt/venv/bin/activate +pip install comfy-cli + +# 或使用完整路径 +/comfy/mnt/venv/bin/comfy --version +``` + +#### 问题 2:`ComfyUI not found` 或 `Specified path is not a ComfyUI path` + +**原因:** 工作区配置错误,或在 Docker 环境中使用 `set-default` 命令 + +**解决:** + +**Docker 环境特殊情况:** + +`comfy set-default /basedir` 会失败,因为 comfy-cli 通过 Git 仓库检测验证路径,而 `/basedir` 是数据目录而非 Git 仓库。 + +```bash +# ❌ 这会失败 +comfy set-default /basedir +# 错误:Specified path is not a ComfyUI path: /basedir + +# ✅ 方案 A:使用自动化脚本(推荐) +# 将 docs/comfy-cli-docker-setup.sh 复制为 run/user_script.bash 并重启容器 + +# ✅ 方案 B:直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# ✅ 方案 C:每次使用 --workspace 参数 +comfy --workspace=/basedir model list +``` + +**非 Docker 环境:** + +```bash +# 设置默认工作区 +comfy set-default /path/to/ComfyUI + +# 或显式指定 +comfy --workspace=/basedir model list +``` + +#### 问题 3:模型下载失败(401/403) + +**原因:** 缺少 API Token + +**解决:** + +```bash +# 设置 Token +comfy model download --url "..." \ + --set-civitai-api-token "YOUR_TOKEN" + +# 或使用环境变量 +export CIVITAI_API_TOKEN="YOUR_TOKEN" +comfy model download --url "..." +``` + +#### 问题 4:权限错误 + +**原因:** Docker 用户权限映射问题 + +**解决:** + +```bash +# 在容器内修复权限 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +chown -R $(id -u):$(id -g) /basedir/ +``` + +#### 问题 5:Python 版本不兼容 + +**原因:** comfy-cli 需要 Python 3.9+ + +**解决:** + +```bash +# 检查 Python 版本 +python --version + +# 使用正确的 Python 环境 +/comfy/mnt/venv/bin/python -m pip install comfy-cli +``` + +### 11.2 调试技巧 + +```bash +# 启用详细日志 +comfy --workspace=/basedir model download --url "..." -v + +# 查看 comfy-cli 配置 +cat ~/.config/comfy-cli/config.ini + +# 检查环境 +comfy env + +# 验证工作区 +comfy which +``` + +### 11.3 获取帮助 + +```bash +# 主帮助 +comfy --help + +# 命令帮助 +comfy model --help +comfy model download --help +comfy node --help + +# 查看源码 +# https://github.com/Comfy-Org/comfy-cli +``` + +--- + +## 12. 附录 + +### 12.1 完整命令参考 + +``` +comfy [OPTIONS] COMMAND [ARGS] + +Options: + --workspace PATH Path to ComfyUI workspace + --recent Execute from recent path + --here Execute from current path + --version Print version and exit + --help Show help + +Commands: + install Download and install ComfyUI + update Update ComfyUI environment + launch Launch ComfyUI + stop Stop background ComfyUI + set-default Set default ComfyUI path + which Show which ComfyUI is selected + env Print environment variables + model Model management + download Download model file + list List models + remove Remove models + node Custom node management + install Install nodes + uninstall Uninstall nodes + update Update nodes + enable Enable nodes + disable Disable nodes + show Show node list + simple-show Show node list (simple) + save-snapshot Save snapshot + restore-snapshot Restore snapshot + run Run workflow + feedback Provide feedback +``` + +### 12.2 配置文件模板 + +```ini +# ~/.config/comfy-cli/config.ini +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +civitai_api_token = +hf_api_token = +``` + +### 12.3 Docker Compose 示例 + +```yaml +version: '3.8' + +services: + comfyui: + image: mmartial/comfyui-nvidia-docker:latest + container_name: comfyui-nvidia + runtime: nvidia + environment: + - USE_UV=true + - WANTED_UID=1000 + - WANTED_GID=1000 + - BASE_DIRECTORY=/basedir + - SECURITY_LEVEL=normal + - CIVITAI_API_TOKEN=${CIVITAI_API_TOKEN} + - HF_API_TOKEN=${HF_API_TOKEN} + volumes: + - ./run:/comfy/mnt + - ./basedir:/basedir + ports: + - "127.0.0.1:8188:8188" + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +### 12.4 参考资源 + +- **comfy-cli 官方仓库:** https://github.com/Comfy-Org/comfy-cli +- **comfy-cli PyPI:** https://pypi.org/project/comfy-cli/ +- **ComfyUI-Nvidia-Docker:** https://github.com/mmartial/ComfyUI-Nvidia-Docker +- **ComfyUI-Manager:** https://github.com/ltdrdata/ComfyUI-Manager +- **cm-cli-rest:** https://github.com/fno2010/cm-cli-rest + +--- + +## 修订历史 + +| 版本 | 日期 | 作者 | 变更 | +|------|------|------|------| +| 1.5 | 2026-03-17 | Zero | 添加表格解析指南 `comfy-cli-table-parser.md`,修正输出格式说明(box table) | +| 1.4 | 2026-03-16 | Zero | 添加 CLI 命令参考手册 `comfy-cli-command-reference.md` | +| 1.3 | 2026-03-16 | Zero | 添加 API Reference 和 REST 实现指南文档链接、输出解析策略说明 | +| 1.2 | 2026-03-16 | Zero | 添加 Docker 环境 `set-default` 失败解决方案、自动化脚本 `comfy-cli-docker-setup.sh` | +| 1.1 | 2026-03-16 | Zero | 添加参数命名规则说明、完成 v1.6.0 源码验证 | +| 1.0 | 2026-03-16 | Zero | 初始版本 | + +--- + +## 版本说明 + +**本文档基于以下 comfy-cli 版本调研编写:** + +| 来源 | 版本 | 说明 | +|------|------|------| +| PyPI 最新稳定版 | `1.6.0` | ✅ 已验证,推荐使用 | +| GitHub main 分支 | 开发中 | 文档编写时(2026-03-16)的代码状态 | +| 最低兼容版本 | `1.3.8` | 低于此版本可能有命令差异 | + +**验证你安装的版本:** + +```bash +comfy --version +``` + +如果版本低于 `1.3.8`,建议升级: + +```bash +pip install --upgrade comfy-cli +``` + +## 参数命名规则 + +comfy-cli 使用 **typer** 框架,Python 参数名会自动转换为 CLI 标志: + +| Python 参数名 | CLI 标志 | 转换规则 | +|--------------|----------|----------| +| `relative_path` | `--relative-path` | snake_case → kebab-case | +| `model_names` | `--model-names` | snake_case → kebab-case | +| `confirm` | `--confirm` | 无变化 | +| `fast_deps` | `--fast-deps` | snake_case → kebab-case | + +**示例:** + +```python +# Python 源码 +relative_path: Annotated[str | None, typer.Option(...)] + +# CLI 使用 +comfy model download --relative-path "models/checkpoints" +``` + +所有文档中的命令参数都遵循此规则。完整验证报告见 `comfy-cli-verification-report.md`。 + +--- + +*本文档由 Zero (零号) 生成。所有命令已通过 v1.6.0 源码验证。* diff --git a/docs/references/comfy-cli-api-reference.md b/docs/references/comfy-cli-api-reference.md new file mode 100644 index 0000000..9dd01dc --- /dev/null +++ b/docs/references/comfy-cli-api-reference.md @@ -0,0 +1,1191 @@ +# comfy-cli API Reference + +> 文档版本:1.0 +> 最后更新:2026-03-16 +> 基于 comfy-cli v1.6.0 +> 适用环境:ComfyUI-Nvidia-Docker + +本文档详细描述 comfy-cli 所有命令的输入输出格式,用于 REST API 集成开发。 + +--- + +## 目录 + +1. [概述](#1-概述) +2. [通用约定](#2-通用约定) +3. [配置命令](#3-配置命令) +4. [模型管理命令](#4-模型管理命令) +5. [节点管理命令](#5-节点管理命令) +6. [ComfyUI 管理命令](#6-comfyui-管理命令) +7. [Workflow 执行命令](#7-workflow-执行命令) +8. [输出解析指南](#8-输出解析指南) +9. [REST API 集成示例](#9-rest-api-集成示例) + +--- + +## 1. 概述 + +### 1.1 命令分类 + +| 类别 | 命令前缀 | 典型用途 | +|------|----------|----------| +| 配置 | `comfy` | 工作区设置、环境查询 | +| 模型 | `comfy model` | 下载、列表、删除模型 | +| 节点 | `comfy node` | 安装、更新、管理自定义节点 | +| ComfyUI | `comfy install/update/launch` | ComfyUI 生命周期管理 | +| Workflow | `comfy run` | 执行 workflow | + +### 1.2 执行模式 + +| 模式 | 说明 | 适用命令 | +|------|------|----------| +| 同步 | 等待命令完成,返回完整输出 | `model list`, `node simple-show` | +| 异步 | 启动任务后立即返回,需轮询状态 | `model download`, `node install` | + +--- + +## 2. 通用约定 + +### 2.1 基础命令格式 + +```bash +comfy [GLOBAL_OPTIONS] COMMAND [SUBCOMMAND] [OPTIONS] +``` + +### 2.2 全局选项 + +| 选项 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `--workspace PATH` | string | - | 指定工作区路径 | +| `--recent` | flag | false | 使用最近的工作区 | +| `--here` | flag | false | 使用当前目录 | +| `--version` | flag | false | 显示版本号 | +| `--help` | flag | false | 显示帮助 | + +### 2.3 通用响应结构(REST API) + +```json +{ + "success": true, + "data": { ... }, + "error": null, + "job_id": "uuid-optional", + "raw_output": "原始 CLI 输出", + "parsed_output": { ... } +} +``` + +### 2.4 错误响应结构 + +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "人类可读的错误描述", + "details": { ... } + }, + "job_id": null +} +``` + +### 2.5 常见错误码 + +| 错误码 | HTTP 状态 | 说明 | +|--------|-----------|------| +| `INVALID_WORKSPACE` | 400 | 工作区路径无效 | +| `MISSING_PARAMETER` | 400 | 缺少必需参数 | +| `COMMAND_FAILED` | 500 | CLI 命令执行失败 | +| `JOB_NOT_FOUND` | 404 | 异步任务 ID 不存在 | +| `ALREADY_RUNNING` | 409 | 任务已在运行中 | + +--- + +## 3. 配置命令 + +### 3.1 `comfy set-default` + +设置默认工作区路径。 + +**CLI 格式:** +```bash +comfy set-default PATH [--launch-extras "ARGS"] +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `PATH` | string | 是 | 工作区路径 | +| `--launch-extras` | string | 否 | 默认启动参数 | + +**REST API 请求:** +```json +POST /comfy-cli/config/set-default +{ + "path": "/basedir", + "launch_extras": "--listen 0.0.0.0 --port 8188" +} +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "default_workspace": "/basedir", + "config_file": "/root/.config/comfy-cli/config.ini" + }, + "raw_output": "Default workspace set to /basedir" +} +``` + +**失败响应(Docker 环境):** +```json +{ + "success": false, + "error": { + "code": "INVALID_WORKSPACE", + "message": "Specified path is not a ComfyUI path: /basedir", + "details": { + "reason": "git_repo_not_found", + "suggestion": "Use direct config.ini modification instead" + } + } +} +``` + +--- + +### 3.2 `comfy which` + +显示当前选中的 ComfyUI 路径。 + +**CLI 格式:** +```bash +comfy which +``` + +**REST API 请求:** +```json +GET /comfy-cli/config/which +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "workspace_path": "/basedir", + "comfyui_path": "/basedir/ComfyUI", + "is_default": true + }, + "raw_output": "/basedir" +} +``` + +--- + +### 3.3 `comfy env` + +打印环境信息。 + +**CLI 格式:** +```bash +comfy env +``` + +**REST API 请求:** +```json +GET /comfy-cli/config/env +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "default_workspace": "/basedir", + "recent_workspace": "/basedir", + "launch_extras": "--listen 0.0.0.0 --port 8188", + "enable_tracking": false, + "civitai_api_token": "set", + "hf_api_token": "not_set", + "background_process": null + }, + "raw_output": "Default Workspace: /basedir\n..." +} +``` + +--- + +## 4. 模型管理命令 + +### 4.1 `comfy model download` + +下载模型文件。 + +**CLI 格式:** +```bash +comfy model download \ + --url URL \ + [--relative-path PATH] \ + [--filename NAME] \ + [--set-civitai-api-token TOKEN] \ + [--set-hf-api-token TOKEN] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--url` | string | 是 | - | 模型下载 URL | +| `--relative-path` | string | 否 | `models/checkpoints` | 保存相对路径 | +| `--filename` | string | 否 | 自动 | 保存文件名 | +| `--set-civitai-api-token` | string | 否 | - | CivitAI API Token | +| `--set-hf-api-token` | string | 否 | - | HuggingFace API Token | + +**REST API 请求:** +```json +POST /comfy-cli/model/download +{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors", + "civitai_api_token": "optional_token" +} +``` + +**异步响应(202 Accepted):** +```json +{ + "success": true, + "job_id": "abc-123-def", + "status": "running", + "message": "Download started", + "data": { + "url": "https://civitai.com/models/43331", + "target_path": "/basedir/models/checkpoints/my_model.safetensors" + } +} +``` + +**完成响应(轮询 job_id):** +```json +{ + "success": true, + "job_id": "abc-123-def", + "status": "completed", + "data": { + "downloaded_file": "/basedir/models/checkpoints/my_model.safetensors", + "file_size": "2.1 GB", + "download_time": "45s" + }, + "parsed_output": { + "status": "success", + "path": "models/checkpoints/my_model.safetensors", + "size_bytes": 2254857830 + } +} +``` + +**失败响应:** +```json +{ + "success": false, + "job_id": "abc-123-def", + "status": "failed", + "error": { + "code": "DOWNLOAD_FAILED", + "message": "HTTP 401: Unauthorized", + "details": { + "reason": "missing_api_token", + "url": "https://civitai.com/models/43331" + } + } +} +``` + +--- + +### 4.2 `comfy model list` + +列出模型文件。 + +**CLI 格式:** +```bash +comfy model list \ + [--relative-path PATH] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--relative-path` | string | 否 | `models` | 列出目录 | + +**REST API 请求:** +```json +GET /comfy-cli/model/list?relative_path=models/checkpoints +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "path": "models/checkpoints", + "absolute_path": "/basedir/models/checkpoints", + "models": [ + { + "filename": "sd_xl_base_1.0.safetensors", + "size": "6.2 GB", + "modified": "2026-03-15T10:30:00Z" + }, + { + "filename": "realistic_vision_v5.safetensors", + "size": "2.1 GB", + "modified": "2026-03-14T08:15:00Z" + } + ], + "total_count": 2, + "total_size": "8.3 GB" + }, + "raw_output": "sd_xl_base_1.0.safetensors\nrealistic_vision_v5.safetensors", + "parsed_output": { + "models": ["sd_xl_base_1.0.safetensors", "realistic_vision_v5.safetensors"] + } +} +``` + +**空目录响应:** +```json +{ + "success": true, + "data": { + "path": "models/checkpoints", + "models": [], + "total_count": 0 + }, + "raw_output": "", + "parsed_output": { + "models": [] + } +} +``` + +--- + +### 4.3 `comfy model remove` + +删除模型文件。 + +**CLI 格式:** +```bash +comfy model remove \ + --relative-path PATH \ + --model-names NAME1 [NAME2 ...] \ + [--confirm] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--relative-path` | string | 否 | `models/checkpoints` | 模型目录 | +| `--model-names` | string[] | 是 | - | 模型文件名列表 | +| `--confirm` | flag | 否 | false | 跳过确认提示 | + +**REST API 请求:** +```json +POST /comfy-cli/model/remove +{ + "relative_path": "models/checkpoints", + "model_names": ["old_model.safetensors"], + "confirm": true +} +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "removed": ["old_model.safetensors"], + "count": 1, + "freed_space": "2.1 GB" + }, + "raw_output": "Successfully removed 1 model(s)" +} +``` + +**失败响应(文件不存在):** +```json +{ + "success": false, + "error": { + "code": "FILE_NOT_FOUND", + "message": "Model not found: old_model.safetensors", + "details": { + "path": "/basedir/models/checkpoints/old_model.safetensors" + } + } +} +``` + +--- + +## 5. 节点管理命令 + +### 5.1 `comfy node simple-show` + +显示节点列表(简化格式)。 + +**CLI 格式:** +```bash +comfy node simple-show {installed|enabled|disabled|not-installed|all} +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `mode` | string | 是 | `installed`, `enabled`, `disabled`, `not-installed`, `all` | + +**REST API 请求:** +```json +GET /comfy-cli/node/simple-show?mode=installed +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "mode": "installed", + "nodes": [ + { + "name": "ComfyUI-Impact-Pack", + "author": "ltdrdata", + "version": "v5.12.1", + "status": "enabled", + "path": "/basedir/custom_nodes/ComfyUI-Impact-Pack" + }, + { + "name": "ComfyUI-Manager", + "author": "ltdrdata", + "version": "v2.45.0", + "status": "enabled", + "path": "/basedir/custom_nodes/ComfyUI-Manager" + } + ], + "total_count": 2 + }, + "raw_output": "ComfyUI-Impact-Pack\nComfyUI-Manager", + "parsed_output": { + "node_names": ["ComfyUI-Impact-Pack", "ComfyUI-Manager"] + } +} +``` + +--- + +### 5.2 `comfy node show` + +显示节点详细信息。 + +**CLI 格式:** +```bash +comfy node show {installed|enabled|disabled|all} +``` + +**REST API 请求:** +```json +GET /comfy-cli/node/show?mode=installed +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "mode": "installed", + "nodes": [ + { + "name": "ComfyUI-Impact-Pack", + "author": "ltdrdata", + "version": "v5.12.1", + "status": "enabled", + "path": "/basedir/custom_nodes/ComfyUI-Impact-Pack", + "requirements": ["opencv-python", "pillow"], + "git_url": "https://github.com/ltdrdata/ComfyUI-Impact-Pack" + } + ] + } +} +``` + +--- + +### 5.3 `comfy node install` + +安装自定义节点。 + +**CLI 格式:** +```bash +comfy node install NODE_NAME [NODE_NAME2 ...] \ + [--fast-deps] \ + [--no-deps] \ + [--channel CHANNEL] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `NODE_NAME` | string[] | 是 | - | 节点名称(可多个) | +| `--fast-deps` | flag | 否 | false | 快速依赖安装 | +| `--no-deps` | flag | 否 | false | 跳过依赖安装 | +| `--channel` | string | 否 | `recent` | 节点频道 | + +**REST API 请求:** +```json +POST /comfy-cli/node/install +{ + "nodes": ["ComfyUI-Impact-Pack"], + "fast_deps": true, + "no_deps": false, + "channel": "recent" +} +``` + +**异步响应(202 Accepted):** +```json +{ + "success": true, + "job_id": "node-install-456", + "status": "running", + "message": "Installing 1 node(s)", + "data": { + "nodes": ["ComfyUI-Impact-Pack"], + "estimated_time": "60s" + } +} +``` + +**完成响应:** +```json +{ + "success": true, + "job_id": "node-install-456", + "status": "completed", + "data": { + "installed": ["ComfyUI-Impact-Pack"], + "failed": [], + "dependencies_installed": ["opencv-python", "pillow"] + } +} +``` + +--- + +### 5.4 `comfy node update` + +更新节点。 + +**CLI 格式:** +```bash +comfy node update {all|NODE_NAME} \ + [--channel CHANNEL] \ + [--mode MODE] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `target` | string | 是 | - | `all` 或节点名 | +| `--channel` | string | 否 | `recent` | 节点频道 | +| `--mode` | string | 否 | `remote` | 更新模式 | + +**REST API 请求:** +```json +POST /comfy-cli/node/update +{ + "target": "all", + "channel": "recent", + "mode": "remote" +} +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "updated": ["ComfyUI-Impact-Pack"], + "unchanged": ["ComfyUI-Manager"], + "failed": [] + } +} +``` + +--- + +### 5.5 `comfy node enable/disable` + +启用/禁用节点。 + +**CLI 格式:** +```bash +comfy node enable NODE_NAME +comfy node disable NODE_NAME +``` + +**REST API 请求:** +```json +POST /comfy-cli/node/enable +{"node": "ComfyUI-Impact-Pack"} + +POST /comfy-cli/node/disable +{"node": "ComfyUI-Impact-Pack"} +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "node": "ComfyUI-Impact-Pack", + "action": "enabled", + "path": "/basedir/custom_nodes/ComfyUI-Impact-Pack" + } +} +``` + +--- + +### 5.6 `comfy node uninstall` + +卸载节点。 + +**CLI 格式:** +```bash +comfy node uninstall NODE_NAME +``` + +**REST API 请求:** +```json +POST /comfy-cli/node/uninstall +{"node": "ComfyUI-Impact-Pack"} +``` + +--- + +### 5.7 `comfy node save-snapshot` + +保存当前节点快照。 + +**CLI 格式:** +```bash +comfy node save-snapshot [--output FILENAME] +``` + +**REST API 请求:** +```json +POST /comfy-cli/node/save-snapshot +{"output": "backup-2026-03-16.json"} +``` + +**成功响应:** +```json +{ + "success": true, + "data": { + "snapshot_file": "/basedir/custom_nodes/snapshot/backup-2026-03-16.json", + "nodes_count": 5, + "created_at": "2026-03-16T14:30:00Z" + } +} +``` + +--- + +### 5.8 `comfy node restore-snapshot` + +恢复节点快照。 + +**CLI 格式:** +```bash +comfy node restore-snapshot SNAPSHOT_FILE [--retry] +``` + +**REST API 请求:** +```json +POST /comfy-cli/node/restore-snapshot +{ + "snapshot_file": "backup-2026-03-16.json", + "retry": true +} +``` + +--- + +## 6. ComfyUI 管理命令 + +### 6.1 `comfy install` + +安装 ComfyUI。 + +**CLI 格式:** +```bash +comfy install \ + [--version VERSION] \ + [--nvidia|amd|cpu] \ + [--skip-manager] \ + [--pr PR_NUMBER] +``` + +**REST API 请求:** +```json +POST /comfy-cli/comfyui/install +{ + "version": "latest", + "gpu_type": "nvidia", + "skip_manager": false +} +``` + +--- + +### 6.2 `comfy launch` + +启动 ComfyUI。 + +**CLI 格式:** +```bash +comfy launch \ + [--background] \ + [-- ARGS...] +``` + +**REST API 请求:** +```json +POST /comfy-cli/comfyui/launch +{ + "background": true, + "extra_args": ["--listen", "0.0.0.0", "--port", "8188"] +} +``` + +--- + +### 6.3 `comfy update` + +更新 ComfyUI。 + +**CLI 格式:** +```bash +comfy update {comfy|all} +``` + +**REST API 请求:** +```json +POST /comfy-cli/comfyui/update +{"target": "comfy"} +``` + +--- + +## 7. Workflow 执行命令 + +### 7.1 `comfy run` + +执行 workflow 文件。 + +**CLI 格式:** +```bash +comfy run \ + --workflow WORKFLOW_FILE \ + [--wait] \ + [--verbose] \ + [--host HOST] \ + [--port PORT] \ + [--timeout SECONDS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--workflow` | string | 是 | - | Workflow JSON 文件路径 | +| `--wait` | flag | 否 | false | 等待完成 | +| `--verbose` | flag | 否 | false | 详细输出 | +| `--host` | string | 否 | `127.0.0.1` | ComfyUI 主机 | +| `--port` | int | 否 | `8188` | ComfyUI 端口 | +| `--timeout` | int | 否 | `60` | 超时秒数 | + +**REST API 请求:** +```json +POST /comfy-cli/workflow/run +{ + "workflow_path": "/basedir/workflows/my_workflow.json", + "wait": true, + "timeout": 300, + "parameters": { + "prompt": "a cat", + "steps": 30 + } +} +``` + +**异步响应:** +```json +{ + "success": true, + "job_id": "workflow-789", + "prompt_id": "comfyui-prompt-uuid", + "status": "queued" +} +``` + +**完成响应:** +```json +{ + "success": true, + "job_id": "workflow-789", + "prompt_id": "comfyui-prompt-uuid", + "status": "completed", + "data": { + "outputs": [ + { + "type": "image", + "filename": "ComfyUI_00001.png", + "path": "/basedir/output/ComfyUI_00001.png", + "url": "/view?filename=ComfyUI_00001.png&type=output" + } + ], + "execution_time": "12.5s" + } +} +``` + +--- + +## 8. 输出解析指南 + +### 8.1 CLI 原始输出类型 + +| 命令 | 原始输出格式 | 解析难度 | +|------|-------------|----------| +| `model list` | 每行一个文件名 | 简单 | +| `node simple-show` | 每行一个节点名 | 简单 | +| `node show` | 多行详细信息 | 中等 | +| `model download` | 进度 + 完成消息 | 复杂 | +| `env` | 键值对列表 | 中等 | + +### 8.2 解析建议 + +#### 简单列表输出(`model list`, `node simple-show`) + +```python +def parse_list_output(raw: str) -> List[str]: + """解析每行一个项目的输出""" + if not raw.strip(): + return [] + return [line.strip() for line in raw.strip().split('\n') if line.strip()] +``` + +#### 键值对输出(`env`) + +```python +def parse_env_output(raw: str) -> Dict[str, Any]: + """解析键值对输出""" + result = {} + for line in raw.strip().split('\n'): + if ':' in line: + key, value = line.split(':', 1) + result[key.strip().lower().replace(' ', '_')] = value.strip() + return result +``` + +#### 进度输出(`model download`) + +```python +import re + +def parse_download_progress(raw: str) -> Dict[str, Any]: + """解析下载进度""" + result = {"status": "unknown", "progress": 0} + + # 匹配进度行 + progress_match = re.search(r'(\d+)%', raw) + if progress_match: + result["progress"] = int(progress_match.group(1)) + result["status"] = "downloading" + + # 匹配完成 + if "Successfully downloaded" in raw or "Download complete" in raw: + result["status"] = "completed" + result["progress"] = 100 + + # 匹配错误 + if "Error" in raw or "Failed" in raw: + result["status"] = "failed" + + return result +``` + +### 8.3 推荐:直接文件系统操作 + +对于某些命令,直接操作文件系统比解析 CLI 输出更可靠: + +```python +# 替代 comfy model list +def list_models_direct(workspace: str, relative_path: str) -> List[Dict]: + """直接读取文件系统获取模型列表""" + import os + from pathlib import Path + + full_path = Path(workspace) / relative_path + if not full_path.exists(): + return [] + + models = [] + for f in full_path.iterdir(): + if f.is_file() and f.suffix in ['.safetensors', '.ckpt', '.pt']: + models.append({ + "filename": f.name, + "size": f.stat().st_size, + "modified": f.stat().st_mtime + }) + return models +``` + +--- + +## 9. REST API 集成示例 + +### 9.1 完整 Handler 示例 + +```python +# handlers/models.py +from aiohttp import web +import subprocess +import json +from pathlib import Path + +class ModelHandler: + def __init__(self, workspace: str = "/basedir"): + self.workspace = Path(workspace) + + async def list_models(self, request: web.Request) -> web.Response: + """列出模型(带解析)""" + relative_path = request.query.get("relative_path", "models/checkpoints") + + try: + # 方法 1:调用 CLI 并解析 + cmd = ["comfy", "--workspace", str(self.workspace), + "model", "list", "--relative-path", relative_path] + + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + + if process.returncode != 0: + return web.json_response({ + "success": False, + "error": {"code": "CLI_ERROR", "message": stderr.decode()} + }, status=500) + + # 解析输出 + raw_output = stdout.decode().strip() + models = [line.strip() for line in raw_output.split('\n') if line.strip()] + + # 增强:获取文件详细信息 + models_detail = [] + full_path = self.workspace / relative_path + for model in models: + model_path = full_path / model + if model_path.exists(): + stat = model_path.stat() + models_detail.append({ + "filename": model, + "size": stat.st_size, + "size_human": self._format_size(stat.st_size), + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat() + }) + + return web.json_response({ + "success": True, + "data": { + "path": relative_path, + "models": models_detail, + "total_count": len(models_detail) + }, + "raw_output": raw_output, + "parsed_output": {"models": models} + }) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + + async def download_model(self, request: web.Request) -> web.Response: + """下载模型(异步)""" + try: + data = await request.json() + url = data.get("url") + + if not url: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_URL", "message": "URL is required"} + }, status=400) + + # 构建命令 + cmd = ["comfy", "--workspace", str(self.workspace), + "model", "download", "--url", url] + + if data.get("relative_path"): + cmd.extend(["--relative-path", data["relative_path"]]) + if data.get("filename"): + cmd.extend(["--filename", data["filename"]]) + if data.get("civitai_api_token"): + cmd.extend(["--set-civitai-api-token", data["civitai_api_token"]]) + + # 异步执行 + job_id = str(uuid.uuid4()) + task = asyncio.create_task(self._run_download(job_id, cmd)) + + # 存储任务 + self.jobs[job_id] = { + "status": "running", + "task": task, + "created_at": datetime.utcnow().isoformat() + } + + return web.json_response({ + "success": True, + "job_id": job_id, + "status": "running", + "message": "Download started" + }, status=202) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + + async def _run_download(self, job_id: str, cmd: List[str]): + """后台运行下载任务""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + + if process.returncode == 0: + self.jobs[job_id] = { + "status": "completed", + "output": stdout.decode(), + "error": None + } + else: + self.jobs[job_id] = { + "status": "failed", + "output": stdout.decode(), + "error": stderr.decode() + } + except Exception as e: + self.jobs[job_id] = { + "status": "failed", + "error": str(e) + } + + def _format_size(self, bytes: int) -> str: + """格式化文件大小""" + for unit in ['B', 'KB', 'MB', 'GB']: + if bytes < 1024: + return f"{bytes:.1f} {unit}" + bytes /= 1024 + return f"{bytes:.1f} TB" +``` + +### 9.2 路由注册 + +```python +# routes.py +from aiohttp import web +from handlers.models import ModelHandler +from handlers.nodes import NodeHandler + +model_handler = ModelHandler(workspace="/basedir") +node_handler = NodeHandler(workspace="/basedir") + +def setup_routes(app): + # 模型管理 + app.router.add_get("/comfy-cli/model/list", model_handler.list_models) + app.router.add_post("/comfy-cli/model/download", model_handler.download_model) + app.router.add_post("/comfy-cli/model/remove", model_handler.remove_model) + + # 节点管理 + app.router.add_get("/comfy-cli/node/simple-show", node_handler.simple_show) + app.router.add_post("/comfy-cli/node/install", node_handler.install) + app.router.add_post("/comfy-cli/node/update", node_handler.update) + + # 任务状态 + app.router.add_get("/comfy-cli/jobs/{job_id}", get_job_status) +``` + +--- + +## 附录 A:完整命令列表 + +``` +comfy +├── install # 安装 ComfyUI +├── update # 更新 ComfyUI/节点 +│ ├── comfy +│ └── all +├── launch # 启动 ComfyUI +├── stop # 停止后台 ComfyUI +├── set-default # 设置默认工作区 +├── which # 显示当前工作区 +├── env # 显示环境信息 +├── model # 模型管理 +│ ├── download +│ ├── list +│ └── remove +├── node # 节点管理 +│ ├── install +│ ├── uninstall +│ ├── update +│ ├── enable +│ ├── disable +│ ├── show +│ ├── simple-show +│ ├── save-snapshot +│ ├── restore-snapshot +│ └── deps-in-workflow +└── run # 执行 workflow +``` + +--- + +## 附录 B:模型类型映射 + +| comfy-cli 路径 | ComfyUI 模型类型 | +|---------------|-----------------| +| `models/checkpoints` | 主模型检查点 | +| `models/loras` | LoRA 模型 | +| `models/vae` | VAE 模型 | +| `models/embeddings` | Textual Inversion | +| `models/controlnet` | ControlNet 模型 | +| `models/upscale_models` | 放大模型 | +| `models/clip_vision` | CLIP Vision 模型 | +| `models/diffusers` | Diffusers 模型 | + +--- + +*由 Zero (零号) 生成 | 2026-03-16* diff --git a/docs/references/comfy-cli-command-reference.md b/docs/references/comfy-cli-command-reference.md new file mode 100644 index 0000000..9ea058b --- /dev/null +++ b/docs/references/comfy-cli-command-reference.md @@ -0,0 +1,821 @@ +# comfy-cli 命令参考手册 + +> 文档版本:2.0 +> 最后更新:2026-03-17 +> 基于 comfy-cli v1.6.0 源码 +> **状态:输出格式已通过源码验证(rich table)** + +--- + +## 使用说明 + +本文档整理 comfy-cli 所有命令的: +- 完整参数列表 +- 参数类型和默认值 +- CLI 输出格式示例 + +**验证状态图例:** +- ✅ 已通过源码验证 +- ⚠️ 基于文档推断,需实际验证 +- ❓ 未知,需补充 + +--- + +## ⚠️ 重要:输出格式说明 + +comfy-cli 使用 **`rich` 库的 `Table` 组件**输出格式化的 **box-drawing table**(边框表格)。 + +**示例输出:** +``` +┌──────────────────────────┬─────────────┬─────────┐ +│ Name │ Version │ Status │ +├──────────────────────────┼─────────────┼─────────┤ +│ ComfyUI-Impact-Pack │ v5.12.1 │ enabled │ +│ ComfyUI-Manager │ v2.45.0 │ enabled │ +└──────────────────────────┴─────────────┴─────────┘ +``` + +**这不是纯文本!需要使用专门的解析器转换为 JSON/CSV。** + +**源码位置:** `comfy_cli/ui.py::display_table()` + +--- + +## 全局选项 + +所有命令都支持以下全局选项: + +```bash +comfy [GLOBAL_OPTIONS] COMMAND [SUBCOMMAND] [ARGS] +``` + +| 选项 | 短选项 | 类型 | 默认值 | 说明 | +|------|--------|------|--------|------| +| `--workspace` | `-w` | PATH | - | 指定 ComfyUI 工作区路径 | +| `--recent` | - | flag | false | 使用最近使用的工作区 | +| `--here` | - | flag | false | 使用当前目录作为工作区 | +| `--version` | -V | flag | false | 显示版本号并退出 | +| `--help` | -h | flag | false | 显示帮助信息 | + +**优先级:** `--workspace` > `--recent` > `--here` > 当前目录 > 默认配置 + +--- + +## 配置命令 + +### `comfy set-default` + +设置默认工作区路径。 + +**语法:** +```bash +comfy set-default PATH [--launch-extras "ARGS"] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `PATH` | PATH | ✅ | - | 工作区路径(必须是有效的 ComfyUI 目录) | +| `--launch-extras` | STRING | ❌ | `""` | 默认启动参数,如 `"--listen 0.0.0.0 --port 8188"` | + +**验证状态:** ✅ + +**成功输出:** +``` +Default workspace set to /path/to/ComfyUI +Config file updated: ~/.config/comfy-cli/config.ini +``` + +**失败输出(Docker 环境):** +``` +Error: Specified path is not a ComfyUI path: /basedir +Reason: Path does not contain a valid ComfyUI git repository +``` + +**备注:** Docker 环境中建议直接编辑 `~/.config/comfy-cli/config.ini` + +--- + +### `comfy which` + +显示当前选中的 ComfyUI 路径。 + +**语法:** +```bash +comfy which +``` + +**参数:** 无 + +**验证状态:** ✅ + +**输出格式:** +``` +# 单行输出,当前工作区路径 +/basedir +``` + +**返回码:** +- `0` - 成功 +- `1` - 未配置工作区 + +--- + +### `comfy env` + +打印环境配置信息。 + +**语法:** +```bash +comfy env +``` + +**参数:** 无 + +**验证状态:** ✅ + +**输出格式:** +``` +Default Workspace: /basedir +Recent Workspace: /basedir +Launch Extras: --listen 0.0.0.0 --port 8188 +Enable Tracking: false +CivitAI API Token: set|not_set +HuggingFace API Token: set|not_set +Background Process: (host, port, pid) | None +``` + +**解析提示:** 每行格式为 `Key: Value`,可用 `:` 分割解析 + +--- + +### `comfy version` + +显示版本号。 + +**语法:** +```bash +comfy --version +``` + +**输出格式:** +``` +comfy-cli 1.6.0 +``` + +--- + +## 模型管理命令 (`comfy model`) + +### `comfy model download` + +下载模型文件。 + +**语法:** +```bash +comfy model download --url URL [OPTIONS] +``` + +**参数:** +| 参数 | 短选项 | 类型 | 必需 | 默认值 | 说明 | +|------|--------|------|------|--------|------| +| `--url` | `-u` | URL | ✅ | - | 模型下载 URL | +| `--relative-path` | `-p` | PATH | ❌ | `models/checkpoints` | 保存的相对路径 | +| `--filename` | `-f` | STRING | ❌ | 自动 | 保存的文件名 | +| `--set-civitai-api-token` | - | STRING | ❌ | - | CivitAI API Token | +| `--set-hf-api-token` | - | STRING | ❌ | - | HuggingFace API Token | + +**验证状态:** ✅ + +**下载中输出:** +``` +Downloading model from https://... +Saving to: /basedir/models/checkpoints/model.safetensors +Progress: 45% (1.2GB / 2.7GB) +Speed: 15.3 MB/s +ETA: 1m 30s +``` + +**成功输出:** +``` +✓ Successfully downloaded model.safetensors +Path: /basedir/models/checkpoints/model.safetensors +Size: 2.7 GB +Time: 2m 15s +``` + +**失败输出:** +``` +✗ Download failed +Error: HTTP 401: Unauthorized +Reason: Missing or invalid API token +URL: https://civitai.com/models/43331 +``` + +**返回码:** +- `0` - 下载成功 +- `1` - 下载失败(网络错误、认证失败等) + +--- + +### `comfy model list` + +列出模型文件。 + +**语法:** +```bash +comfy model list [--relative-path PATH] +``` + +**参数:** +| 参数 | 短选项 | 类型 | 必需 | 默认值 | 说明 | +|------|--------|------|------|--------|------| +| `--relative-path` | `-p` | PATH | ❌ | `models` | 要列出的目录 | + +**验证状态:** ✅ + +**输出格式:** +``` +# 每行一个文件名,无其他信息 +sd_xl_base_1.0.safetensors +realistic_vision_v5.safetensors +dreamshaper_8.safetensors +``` + +**空目录:** +``` +# 空输出,无内容 +``` + +**错误输出:** +``` +Error: Directory not found: /basedir/models/nonexistent +``` + +**解析提示:** +```python +models = [line.strip() for line in output.strip().split('\n') if line.strip()] +``` + +--- + +### `comfy model remove` + +删除模型文件。 + +**语法:** +```bash +comfy model remove --relative-path PATH --model-names NAME1 [NAME2 ...] [--confirm] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--relative-path` | PATH | ❌ | `models/checkpoints` | 模型目录 | +| `--model-names` | STRING[] | ✅ | - | 模型文件名(可多个) | +| `--confirm` | flag | ❌ | false | 跳过确认提示 | + +**验证状态:** ⚠️ + +**交互模式输出:** +``` +The following models will be deleted: + /basedir/models/checkpoints/old_model.safetensors (2.1 GB) + +Are you sure? [y/N]: +``` + +**成功输出(--confirm):** +``` +✓ Successfully removed 1 model(s) +Freed space: 2.1 GB +``` + +**失败输出:** +``` +Error: Model not found: nonexistent.safetensors +Path: /basedir/models/checkpoints/nonexistent.safetensors +``` + +--- + +## 节点管理命令 (`comfy node`) + +### `comfy node simple-show` + +以简化格式显示节点列表。 + +**语法:** +```bash +comfy node simple-show MODE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `MODE` | STRING | ✅ | `installed`, `enabled`, `disabled`, `not-installed`, `all` | + +**验证状态:** ✅ + +**输出格式:** +``` +# 每行一个节点名称 +ComfyUI-Impact-Pack +ComfyUI-Manager +ComfyUI-Inspire-Pack +``` + +**返回码:** +- `0` - 成功 + +--- + +### `comfy node show` + +显示节点详细信息。 + +**语法:** +```bash +comfy node show MODE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `MODE` | STRING | ✅ | `installed`, `enabled`, `disabled`, `all` | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Node: ComfyUI-Impact-Pack + Author: ltdrdata + Version: v5.12.1 + Status: enabled + Path: /basedir/custom_nodes/ComfyUI-Impact-Pack + URL: https://github.com/ltdrdata/ComfyUI-Impact-Pack + Requirements: opencv-python, pillow + +Node: ComfyUI-Manager + Author: ltdrdata + Version: v2.45.0 + Status: enabled + Path: /basedir/custom_nodes/ComfyUI-Manager + URL: https://github.com/ltdrdata/ComfyUI-Manager + Requirements: - +``` + +**解析提示:** 多行块状格式,每块以 `Node:` 开始 + +--- + +### `comfy node install` + +安装自定义节点。 + +**语法:** +```bash +comfy node install NODE_NAME [NODE_NAME2 ...] [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `NODE_NAME` | STRING[] | ✅ | - | 节点名称(可多个) | +| `--fast-deps` | flag | ❌ | false | 使用快速依赖安装 | +| `--no-deps` | flag | ❌ | false | 跳过依赖安装 | +| `--channel` | STRING | ❌ | `recent` | 节点频道 (`recent`, `dev`, `archive`) | + +**验证状态:** ✅ + +**输出格式:** +``` +Installing 1 node(s)... + +[1/1] ComfyUI-Impact-Pack + Cloning from: https://github.com/ltdrdata/ComfyUI-Impact-Pack + Installing to: /basedir/custom_nodes/ComfyUI-Impact-Pack + Installing dependencies: opencv-python, pillow + ✓ Installation complete + +Total: 1 installed, 0 failed +Time: 45s +``` + +**失败输出:** +``` +Installing 1 node(s)... + +[1/1] SomeNode + ✗ Installation failed + Error: Git clone failed + Reason: Repository not found + +Total: 0 installed, 1 failed +``` + +**返回码:** +- `0` - 所有节点安装成功 +- `1` - 部分或全部失败 + +--- + +### `comfy node update` + +更新节点。 + +**语法:** +```bash +comfy node update TARGET [--channel CHANNEL] [--mode MODE] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `TARGET` | STRING | ✅ | - | `all` 或节点名称 | +| `--channel` | STRING | ❌ | `recent` | 节点频道 | +| `--mode` | STRING | ❌ | `remote` | 更新模式 (`remote`, `local`) | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Updating all nodes... + +[1/3] ComfyUI-Impact-Pack + Current: v5.12.0 + Latest: v5.12.1 + Updating... ✓ Updated + +[2/3] ComfyUI-Manager + Already up to date + +[3/3] ComfyUI-Inspire-Pack + Current: v1.0.0 + Latest: v1.0.0 + Already up to date + +Total: 1 updated, 2 unchanged, 0 failed +``` + +--- + +### `comfy node enable` + +启用节点。 + +**语法:** +```bash +comfy node enable NODE_NAME +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `NODE_NAME` | STRING | ✅ | 节点名称 | + +**验证状态:** ⚠️ + +**输出格式:** +``` +✓ Enabled: ComfyUI-Impact-Pack +Path: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node disable` + +禁用节点。 + +**语法:** +```bash +comfy node disable NODE_NAME +``` + +**输出格式:** +``` +✓ Disabled: ComfyUI-Impact-Pack +Path: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node uninstall` + +卸载节点。 + +**语法:** +```bash +comfy node uninstall NODE_NAME [--confirm] +``` + +**输出格式:** +``` +✓ Uninstalled: ComfyUI-Impact-Pack +Removed: /basedir/custom_nodes/ComfyUI-Impact-Pack +``` + +--- + +### `comfy node save-snapshot` + +保存当前节点快照。 + +**语法:** +```bash +comfy node save-snapshot [--output FILENAME] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--output` | STRING | ❌ | 自动生成 | 输出文件名 | + +**验证状态:** ⚠️ + +**输出格式:** +``` +✓ Snapshot saved +File: /basedir/custom_nodes/snapshot/snapshot-2026-03-16.json +Nodes: 5 +``` + +--- + +### `comfy node restore-snapshot` + +恢复节点快照。 + +**语法:** +```bash +comfy node restore-snapshot SNAPSHOT_FILE [--retry] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `SNAPSHOT_FILE` | PATH | ✅ | - | 快照文件路径 | +| `--retry` | flag | ❌ | false | 失败时重试 | + +**输出格式:** +``` +Restoring from: snapshot-2026-03-16.json + +[1/5] Installing ComfyUI-Impact-Pack... ✓ +[2/5] Installing ComfyUI-Manager... ✓ +[3/5] Installing ComfyUI-Inspire-Pack... ✗ (failed, retrying) +[3/5] Installing ComfyUI-Inspire-Pack... ✓ (retry success) + +Total: 5 restored, 0 failed +``` + +--- + +### `comfy node deps-in-workflow` + +从 workflow 文件生成依赖列表。 + +**语法:** +```bash +comfy node deps-in-workflow --workflow WORKFLOW_FILE --output OUTPUT_FILE +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `--workflow` | PATH | ✅ | Workflow JSON 文件 | +| `--output` | PATH | ✅ | 输出依赖文件 | + +**验证状态:** ❓ + +--- + +## ComfyUI 管理命令 + +### `comfy install` + +安装 ComfyUI。 + +**语法:** +```bash +comfy install [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--version` | STRING | ❌ | `latest` | 版本 (`latest`, `nightly`, 具体版本) | +| `--nvidia` | flag | ❌ | false | NVIDIA GPU 优化 | +| `--amd` | flag | ❌ | false | AMD GPU 优化 | +| `--cpu` | flag | ❌ | false | CPU 模式 | +| `--skip-manager` | flag | ❌ | false | 跳过 Manager 安装 | +| `--pr` | STRING | ❌ | - | 安装特定 PR | + +**验证状态:** ⚠️ + +**输出格式:** +``` +Installing ComfyUI... + +[1/4] Cloning ComfyUI repository... ✓ +[2/4] Setting up Python virtual environment... ✓ +[3/4] Installing dependencies... ✓ +[4/4] Installing ComfyUI-Manager... ✓ + +✓ Installation complete +Path: ~/comfy/ComfyUI +To launch: comfy launch +``` + +--- + +### `comfy launch` + +启动 ComfyUI。 + +**语法:** +```bash +comfy launch [--background] [-- ARGS...] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--background` | flag | ❌ | false | 后台运行 | +| `--` | - | - | - | 后续参数传递给 ComfyUI | + +**验证状态:** ⚠️ + +**前台输出:** +``` +Launching ComfyUI... + +[ComfyUI Output] +Starting server... +To see the GUI go to: http://0.0.0.0:8188 +^C +Shutting down... +``` + +**后台输出:** +``` +✓ ComfyUI started in background +PID: 12345 +URL: http://127.0.0.1:8188 +To stop: comfy stop +``` + +--- + +### `comfy stop` + +停止后台运行的 ComfyUI。 + +**语法:** +```bash +comfy stop +``` + +**输出格式:** +``` +✓ ComfyUI stopped (PID: 12345) +``` + +或(无运行实例): +``` +No running ComfyUI instance found +``` + +--- + +### `comfy update` + +更新 ComfyUI。 + +**语法:** +```bash +comfy update TARGET +``` + +**参数:** +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `TARGET` | STRING | ✅ | `comfy` 或 `all` | + +**输出格式:** +``` +Updating ComfyUI... + +Current: 1.2.3 +Latest: 1.2.4 +Updating... ✓ Updated + +Restart required. Run: comfy launch +``` + +--- + +## Workflow 执行命令 + +### `comfy run` + +执行 workflow 文件。 + +**语法:** +```bash +comfy run --workflow WORKFLOW_FILE [OPTIONS] +``` + +**参数:** +| 参数 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `--workflow` | PATH | ✅ | - | Workflow JSON 文件 | +| `--wait` | flag | ❌ | false | 等待完成 | +| `--verbose` | flag | ❌ | false | 详细输出 | +| `--host` | STRING | ❌ | `127.0.0.1` | ComfyUI 主机 | +| `--port` | INT | ❌ | `8188` | ComfyUI 端口 | +| `--timeout` | INT | ❌ | `60` | 超时秒数 | + +**验证状态:** ⚠️ + +**输出格式(--wait):** +``` +Submitting workflow... +Prompt ID: uuid-12345 + +Queue position: 1 +Processing... +[100%] Step 15/15 +✓ Completed + +Output: + [0] Image: /basedir/output/ComfyUI_00001.png + [1] Image: /basedir/output/ComfyUI_00002.png + +Execution time: 12.5s +``` + +**输出格式(无 --wait):** +``` +✓ Workflow submitted +Prompt ID: uuid-12345 +URL: http://127.0.0.1:8188/history +``` + +--- + +## 附录 + +### A. 返回码汇总 + +| 返回码 | 说明 | +|--------|------| +| `0` | 成功 | +| `1` | 一般错误 | +| `2` | 参数错误 | +| `3` | 工作区配置错误 | +| `4` | 网络错误 | +| `5` | 文件/目录不存在 | + +### B. 输出颜色 + +comfy-cli 使用颜色标识: +- 🟢 绿色 - 成功 +- 🔴 红色 - 错误 +- 🟡 黄色 - 警告 +- 🔵 蓝色 - 信息 + +**禁用颜色:** +```bash +comfy --no-color model list +# 或 +export NO_COLOR=1 +``` + +### C. 环境变量 + +| 变量 | 说明 | +|------|------| +| `CIVITAI_API_TOKEN` | CivitAI API Token | +| `HF_API_TOKEN` | HuggingFace API Token | +| `COMFY_CLI_WORKSPACE` | 覆盖默认工作区 | +| `NO_COLOR` | 禁用彩色输出 | + +--- + +## 待补充内容 + +以下命令需要实际运行验证: + +- [ ] `comfy node bisect` 系列命令 +- [ ] `comfy node install-deps` +- [ ] `comfy feedback` +- [ ] 所有命令的错误输出格式 +- [ ] 交互式提示的具体文本 + +--- + +*由 Zero (零号) 生成 | 2026-03-16* + +*注:本文档基于 comfy-cli v1.6.0 源码和相关文档整理。部分输出格式为推断,欢迎实际运行后补充修正。* diff --git a/docs/references/comfy-cli-docker-setup.sh b/docs/references/comfy-cli-docker-setup.sh new file mode 100644 index 0000000..7248430 --- /dev/null +++ b/docs/references/comfy-cli-docker-setup.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# user_script.bash - ComfyUI-Nvidia-Docker 容器启动时执行 +# +# 功能: +# 1. 安装 comfy-cli(如果未安装) +# 2. 直接配置 config.ini 绕过 Git 仓库检测 +# 3. 验证配置 +# +# 使用方法: +# 1. 将此文件保存为 run/user_script.bash +# 2. 确保文件有执行权限:chmod +x run/user_script.bash +# 3. 重启容器 +# +# 作者:Zero (零号) +# 日期:2026-03-16 + +set -e # 遇到错误立即退出 + +echo "=========================================" +echo " ComfyCLI 自动配置脚本" +echo "=========================================" + +# 配置变量 +VENV_PYTHON="/comfy/mnt/venv/bin/python" +VENV_PIP="/comfy/mnt/venv/bin/pip" +COMFY_CMD="/comfy/mnt/venv/bin/comfy" +CONFIG_DIR="$HOME/.config/comfy-cli" +CONFIG_FILE="$CONFIG_DIR/config.ini" +WORKSPACE_PATH="/basedir" + +# 检查 Python 环境 +echo "[1/5] 检查 Python 环境..." +if [ ! -f "$VENV_PYTHON" ]; then + echo "❌ 错误:虚拟环境不存在于 $VENV_PYTHON" + echo " 请确认容器启动脚本正确配置了虚拟环境" + exit 1 +fi +echo "✓ Python 环境正常:$VENV_PYTHON" + +# 安装 comfy-cli +echo "[2/5] 检查并安装 comfy-cli..." +if $COMFY_CMD --version &> /dev/null; then + VERSION=$($COMFY_CMD --version 2>&1 | head -1) + echo "✓ comfy-cli 已安装:$VERSION" +else + echo " 正在安装 comfy-cli..." + $VENV_PIP install comfy-cli -q + if [ $? -eq 0 ]; then + VERSION=$($COMFY_CMD --version 2>&1 | head -1) + echo "✓ comfy-cli 安装成功:$VERSION" + else + echo "❌ 错误:comfy-cli 安装失败" + exit 1 + fi +fi + +# 创建配置目录 +echo "[3/5] 创建配置目录..." +mkdir -p "$CONFIG_DIR" +echo "✓ 配置目录:$CONFIG_DIR" + +# 直接写入 config.ini(绕过 set-default 的 Git 检测) +echo "[4/5] 配置工作区..." +cat > "$CONFIG_FILE" << EOF +[DEFAULT] +# ComfyUI 工作区路径(Docker 环境持久化目录) +default_workspace = $WORKSPACE_PATH +recent_workspace = $WORKSPACE_PATH + +# ComfyUI 启动参数 +# --listen 0.0.0.0 允许局域网访问 +# --port 8188 默认端口 +default_launch_extras = --listen 0.0.0.0 --port 8188 + +# 使用追踪(可选,设为 false 保护隐私) +enable_tracking = false + +# API Tokens(可选,用于下载需要认证的模型) +# 建议通过 docker-compose.yml 的环境变量设置 +# civitai_api_token = YOUR_CIVITAI_TOKEN +# hf_api_token = YOUR_HF_TOKEN +EOF + +echo "✓ 配置文件已写入:$CONFIG_FILE" + +# 验证配置 +echo "[5/5] 验证配置..." +echo "" +echo "--- comfy which ---" +$COMFY_CMD which 2>&1 || echo "(可能显示警告,但配置已生效)" + +echo "" +echo "--- comfy env ---" +$COMFY_CMD env 2>&1 | head -10 + +echo "" +echo "=========================================" +echo " 配置完成!" +echo "=========================================" +echo "" +echo "常用命令:" +echo " # 列出模型" +echo " comfy model list" +echo "" +echo " # 下载模型" +echo " comfy model download --url \"https://...\"" +echo "" +echo " # 安装节点" +echo " comfy node install ComfyUI-Impact-Pack" +echo "" +echo " # 查看队列" +echo " comfy queue" +echo "" +echo "提示:如果 comfy 命令不在 PATH 中,使用完整路径:" +echo " $COMFY_CMD model list" +echo "" + +# 可选:设置环境变量(如果通过 docker-compose.yml 传递了 API Token) +if [ -n "$CIVITAI_API_TOKEN" ]; then + echo "✓ CIVITAI_API_TOKEN 已设置" +fi + +if [ -n "$HF_API_TOKEN" ]; then + echo "✓ HF_API_TOKEN 已设置" +fi + +echo "" +echo "脚本执行完毕。" diff --git a/docs/references/comfy-cli-guide.md b/docs/references/comfy-cli-guide.md new file mode 100644 index 0000000..8a92390 --- /dev/null +++ b/docs/references/comfy-cli-guide.md @@ -0,0 +1,1665 @@ +# ComfyCLI 配置与使用指南 + +> 文档版本:1.5 +> 最后更新:2026-03-17 +> 适用环境:ComfyUI-Nvidia-Docker 部署 +> **适配 comfy-cli 版本:≥1.3.8(已验证 1.6.0 稳定版)** +> **验证报告:** `comfy-cli-verification-report.md` +> **Docker 自动化脚本:** `comfy-cli-docker-setup.sh` +> **命令参考手册:** `comfy-cli-command-reference.md` — 所有 CLI 命令的参数和输出格式 +> **表格解析指南:** `comfy-cli-table-parser.md` — box table → JSON/CSV 解析方案 ⭐新增 +> **API Reference:** `comfy-cli-api-reference.md` — REST API 设计参考 +> **REST 实现指南:** `comfy-cli-rest-implementation.md` — REST 服务器实现代码 + +--- + +## 目录 + +1. [概述](#1-概述) +2. [ComfyCLI 架构](#2-comfycli-架构) +3. [安装指南](#3-安装指南) +4. [配置系统详解](#4-配置系统详解) +5. [工作区管理](#5-工作区管理) +6. [模型管理](#6-模型管理) +7. [自定义节点管理](#7-自定义节点管理) +8. [ComfyUI 生命周期管理](#8-comfyui-生命周期管理) +9. [Docker 环境特殊配置](#9-docker-环境特殊配置) +10. [REST API 集成方案](#10-rest-api-集成方案) +11. [故障排除](#11-故障排除) +12. [附录](#12-附录) + +--- + +## 1. 概述 + +### 1.1 什么是 ComfyCLI + +`comfy-cli` 是由 Comfy-Org 官方维护的命令行工具,用于管理和操作 ComfyUI。与 ComfyUI-Manager 的 `cm-cli` 不同,comfy-cli 是一个**独立的管理工具**,提供: + +- ✅ **模型下载与管理**(核心优势) +- ✅ 自定义节点管理 +- ✅ ComfyUI 安装与更新 +- ✅ 工作区管理 +- ✅ Workflow 执行 +- ✅ 后台服务管理 + +### 1.2 与 cm-cli 的对比 + +| 功能 | cm-cli | comfy-cli | +|------|--------|-----------| +| 模型下载 | ❌ | ✅ | +| 模型列表/删除 | ❌ | ✅ | +| 节点管理 | ✅ | ✅ | +| ComfyUI 安装 | ❌ | ✅ | +| 工作区管理 | ❌ | ✅ | +| REST API | ✅ (通过 custom node) | ❌ | + +### 1.3 适用场景 + +- 需要通过命令行下载和管理模型 +- 需要脚本化 ComfyUI 管理操作 +- 需要多工作区管理 +- 需要与现有 ComfyUI-Manager 共存 + +--- + +## 2. ComfyCLI 架构 + +### 2.1 核心组件 + +``` +comfy-cli/ +├── comfy_cli/ +│ ├── __init__.py # 包初始化 +│ ├── cmdline.py # 命令行入口和命令注册 +│ ├── config_manager.py # 配置文件管理 +│ ├── workspace_manager.py # 工作区管理 +│ ├── constants.py # 常量定义 +│ ├── command/ +│ │ ├── install.py # 安装命令 +│ │ ├── launch.py # 启动命令 +│ │ ├── models/ # 模型管理命令 +│ │ │ └── models.py # 下载/删除/列表 +│ │ └── custom_nodes/ # 自定义节点命令 +│ │ └── command.py # 节点管理实现 +│ └── utils/ # 工具函数 +└── pyproject.toml # 项目配置 +``` + +### 2.2 配置文件系统 + +``` +~/.config/comfy-cli/ +└── config.ini # 主配置文件 +``` + +**config.ini 结构:** + +```ini +[DEFAULT] +default_workspace = /path/to/ComfyUI +recent_workspace = /path/to/last/used +default_launch_extras = --listen 0.0.0.0 +enable_tracking = true +background = ('127.0.0.1', 8188, 12345) +civitai_api_token = xxx +hf_api_token = xxx +``` + +### 2.3 工作区检测机制 + +comfy-cli 通过 Git 仓库识别 ComfyUI 安装: + +```python +COMFY_ORIGIN_URL_CHOICES = { + "git@github.com:comfyanonymous/ComfyUI.git", + "https://github.com/comfyanonymous/ComfyUI.git", + "https://github.com/Comfy-Org/ComfyUI.git", + # ... +} +``` + +**工作区优先级:** + +1. `--workspace=` - 显式指定 +2. `--recent` - 最近使用 +3. `--here` - 当前目录 +4. 当前目录是 ComfyUI 仓库 +5. `set-default` 设置的默认路径 +6. 默认路径 (`~/comfy/ComfyUI`) + +--- + +## 3. 安装指南 + +### 3.1 前置要求 + +- **Python 3.9+**(必须与 ComfyUI 使用相同环境) +- **Git**(用于仓库检测) +- **pip** 或 **uv**(推荐) + +### 3.2 在 Docker 环境中安装 + +对于 ComfyUI-Nvidia-Docker 部署,有两种安装方式: + +#### 方式 A:在容器内安装(推荐) + +```bash +# 进入运行中的容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境(如果有) +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# 验证安装 +comfy --version +``` + +#### 方式 B:使用 user_script.bash 持久化 + +编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 在容器启动时执行 + +# 安装 comfy-cli +if ! command -v comfy &> /dev/null; then + echo "Installing comfy-cli..." + /comfy/mnt/venv/bin/pip install comfy-cli +fi + +# 设置默认工作区 +/comfy/mnt/venv/bin/comfy set-default /basedir 2>/dev/null || true +``` + +### 3.3 验证安装 + +```bash +# 检查版本 +comfy --version + +# 查看帮助 +comfy --help + +# 检查环境 +comfy env +``` + +--- + +## 4. 配置系统详解 + +### 4.1 配置文件位置 + +| 操作系统 | 配置文件路径 | +|----------|-------------| +| Linux | `~/.config/comfy-cli/config.ini` | +| macOS | `~/Library/Application Support/comfy-cli/config.ini` | +| Windows | `%LOCALAPPDATA%\comfy-cli\config.ini` | + +### 4.2 配置项说明 + +```ini +[DEFAULT] +# 默认工作区路径(通过 comfy set-default 设置) +default_workspace = /basedir + +# 最近使用的工作区(自动更新) +recent_workspace = /basedir + +# 启动 ComfyUI 时的默认参数 +default_launch_extras = --listen 0.0.0.0 --port 8188 + +# 是否启用使用追踪(可选) +enable_tracking = false + +# 后台运行的 ComfyUI 信息 (host, port, pid) +# 自动管理,不要手动修改 +background = ('127.0.0.1', 8188, 5678) + +# CivitAI API Token(用于下载需要认证的模型) +civitai_api_token = your_token_here + +# HuggingFace API Token(用于下载私有模型) +hf_api_token = your_token_here +``` + +### 4.3 配置管理命令 + +```bash +# 查看当前配置 +comfy env + +# 设置默认工作区 +comfy set-default /basedir + +# 设置启动参数 +comfy set-default /basedir --launch-extras="--listen 0.0.0.0" + +# 查看当前工作区 +comfy which +``` + +### 4.4 环境变量覆盖 + +comfy-cli 支持通过环境变量覆盖配置: + +| 环境变量 | 配置项 | 优先级 | +|----------|--------|--------| +| `CIVITAI_API_TOKEN` | civitai_api_token | 高 | +| `HF_API_TOKEN` | hf_api_token | 高 | +| `COMFY_CLI_WORKSPACE` | default_workspace | 高 | + +**优先级顺序:** + +1. CLI 参数(如 `--set-civitai-api-token`) +2. 环境变量 +3. config.ini 配置 + +--- + +## 5. 工作区管理 + +### 5.1 工作区概念 + +工作区是 ComfyUI 的安装目录,包含: + +``` +/basedir/ # 工作区根目录 +├── ComfyUI/ # ComfyUI 核心代码 +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +│ ├── checkpoints/ +│ ├── loras/ +│ ├── vae/ +│ └── ... +├── input/ # 输入文件 +├── output/ # 输出文件 +└── user/ # 用户文件 +``` + +### 5.2 指定工作区的方法 + +#### 方法 1:使用 `--workspace` 参数 + +```bash +comfy --workspace=/basedir model download --url "..." +comfy --workspace=/basedir node install ComfyUI-Impact-Pack +``` + +#### 方法 2:设置默认工作区 + +```bash +# 一次性设置 +comfy set-default /basedir + +# 之后可省略 --workspace +comfy model download --url "..." +``` + +#### 方法 3:使用 `--recent` + +```bash +# 自动使用最近的工作区 +comfy --recent model download --url "..." +``` + +#### 方法 4:使用 `--here` + +```bash +cd /basedir +comfy --here model download --url "..." +``` + +### 5.3 多工作区管理 + +```bash +# 设置多个工作区 +comfy set-default /basedir +comfy --workspace=/opt/comfy-test node install SomeNode + +# 查看当前工作区 +comfy which + +# 列出所有模型(在当前工作区) +comfy model list +``` + +--- + +## 6. 模型管理 + +### 6.1 下载模型 + +#### 从 CivitAI 下载 + +```bash +# 下载模型(需要 API Token) +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --set-civitai-api-token "YOUR_TOKEN" + +# 指定保存路径 +comfy --workspace=/basedir model download \ + --url "https://civitai.com/models/43331" \ + --relative-path "models/checkpoints" \ + --filename "my_model.safetensors" +``` + +#### 从 HuggingFace 下载 + +```bash +# 下载公开模型 +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors" + +# 下载私有模型(需要 Token) +comfy --workspace=/basedir model download \ + --url "https://huggingface.co/username/private-model/resolve/main/model.safetensors" \ + --set-hf-api-token "YOUR_HF_TOKEN" +``` + +#### 从直接 URL 下载 + +```bash +comfy --workspace=/basedir model download \ + --url "https://example.com/models/model.safetensors" \ + --relative-path "models/checkpoints" +``` + +### 6.2 列出模型 + +```bash +# 列出所有模型 +comfy --workspace=/basedir model list + +# 列出指定目录的模型 +comfy --workspace=/basedir model list \ + --relative-path "models/checkpoints" + +# 列出 LoRA 模型 +comfy --workspace=/basedir model list \ + --relative-path "models/loras" +``` + +### 6.3 删除模型 + +```bash +# 删除指定模型 +comfy --workspace=/basedir model remove \ + --relative-path "models/checkpoints" \ + --model-names "model1.safetensors" "model2.ckpt" + +# 交互式选择删除 +comfy --workspace=/basedir model remove + +# 跳过确认提示 +comfy --workspace=/basedir model remove \ + --model-names "model.safetensors" \ + --confirm +``` + +### 6.4 模型路径映射 + +comfy-cli 内置的模型类型映射: + +```python +model_path_map = { + "lora": "loras", + "hypernetwork": "hypernetworks", + "checkpoint": "checkpoints", + "textualinversion": "embeddings", + "controlnet": "controlnet", +} +``` + +--- + +## 7. 自定义节点管理 + +### 7.1 节点列表 + +```bash +# 显示已安装的节点 +comfy --workspace=/basedir node simple-show installed + +# 显示启用的节点 +comfy --workspace=/basedir node simple-show enabled + +# 显示禁用的节点 +comfy --workspace=/basedir node simple-show disabled + +# 显示未安装的节点 +comfy --workspace=/basedir node simple-show not-installed + +# 显示所有节点 +comfy --workspace=/basedir node simple-show all + +# 显示详细信息 +comfy --workspace=/basedir node show installed +``` + +### 7.2 安装节点 + +```bash +# 安装单个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack + +# 安装多个节点 +comfy --workspace=/basedir node install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack + +# 使用快速依赖安装 +comfy --workspace=/basedir node install SomeNode --fast-deps + +# 跳过依赖安装 +comfy --workspace=/basedir node install SomeNode --no-deps + +# 指定 channel +comfy --workspace=/basedir node install SomeNode --channel recent +``` + +### 7.3 更新节点 + +```bash +# 更新所有节点 +comfy --workspace=/basedir node update all + +# 更新特定节点 +comfy --workspace=/basedir node update ComfyUI-Impact-Pack + +# 指定 channel 和 mode +comfy --workspace=/basedir node update all --channel recent --mode remote +``` + +### 7.4 启用/禁用节点 + +```bash +# 禁用节点 +comfy --workspace=/basedir node disable ComfyUI-Impact-Pack + +# 启用节点 +comfy --workspace=/basedir node enable ComfyUI-Impact-Pack +``` + +### 7.5 卸载节点 + +```bash +# 卸载节点 +comfy --workspace=/basedir node uninstall ComfyUI-Impact-Pack +``` + +### 7.6 依赖管理 + +```bash +# 安装依赖 +comfy --workspace=/basedir node install-deps + +# 从 workflow 生成依赖 +comfy --workspace=/basedir node deps-in-workflow \ + --workflow /path/to/workflow.json \ + --output /path/to/deps.json +``` + +### 7.7 快照管理 + +```bash +# 保存快照 +comfy --workspace=/basedir node save-snapshot + +# 保存到指定文件 +comfy --workspace=/basedir node save-snapshot --output backup.json + +# 恢复快照 +comfy --workspace=/basedir node restore-snapshot backup.json + +# 列出快照 +comfy --workspace=/basedir node simple-show snapshot-list +``` + +### 7.8 调试节点问题(Bisect) + +```bash +# 开始 bisect 会话 +comfy --workspace=/basedir node bisect start + +# 标记当前状态为好 +comfy --workspace=/basedir node bisect good + +# 标记当前状态为坏 +comfy --workspace=/basedir node bisect bad + +# 重置 bisect 会话 +comfy --workspace=/basedir node bisect reset +``` + +--- + +## 8. ComfyUI 生命周期管理 + +### 8.1 安装 ComfyUI + +```bash +# 默认安装(最新 nightly 版本) +comfy install + +# 指定工作区 +comfy --workspace=~/my-comfy install + +# 安装特定版本 +comfy install --version latest + +# 安装 nightly 版本 +comfy install --version nightly + +# 跳过 Manager 安装 +comfy install --skip-manager + +# 从特定 PR 安装 +comfy install --pr "#1234" + +# 指定 GPU 类型 +comfy install --nvidia +comfy install --amd +comfy install --cpu +``` + +### 8.2 启动 ComfyUI + +```bash +# 前台启动 +comfy --workspace=/basedir launch + +# 后台启动 +comfy --workspace=/basedir launch --background + +# 带参数启动 +comfy --workspace=/basedir launch -- --listen 0.0.0.0 --port 8188 + +# 使用默认参数启动 +comfy --workspace=/basedir launch -- + +# 测试前端 PR +comfy --workspace=/basedir launch --frontend-pr "#456" +``` + +### 8.3 停止 ComfyUI + +```bash +# 停止后台运行的实例 +comfy stop +``` + +### 8.4 更新 ComfyUI + +```bash +# 更新 ComfyUI 核心 +comfy --workspace=/basedir update comfy + +# 更新所有(包括节点) +comfy --workspace=/basedir update all +``` + +### 8.5 运行 Workflow + +```bash +# 运行 workflow 文件 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json + +# 等待完成 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --wait + +# 详细输出 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --verbose + +# 指定主机和端口 +comfy --workspace=/basedir run \ + --workflow /path/to/workflow.json \ + --host 127.0.0.1 \ + --port 8188 \ + --timeout 60 +``` + +--- + +## 9. Docker 环境特殊配置 + +### 9.1 ComfyUI-Nvidia-Docker 目录结构 + +``` +run/ # 容器运行数据 +├── venv/ # Python 虚拟环境 +├── ComfyUI/ # ComfyUI 源代码 +└── ... + +basedir/ # 用户数据(持久化) +├── custom_nodes/ # 自定义节点 +├── models/ # 模型文件 +├── input/ # 输入 +├── output/ # 输出 +└── user/ # 用户配置 +``` + +### 9.2 在 Docker 中安装 comfy-cli + +#### 方法 A:通过 user_script.bash(推荐) + +**⚠️ 重要提示**:在 Docker 环境中,`comfy set-default` 会失败,因为 comfy-cli 通过 Git 仓库检测验证 ComfyUI 安装,而 `/basedir` 目录下没有 ComfyUI 的 Git 仓库(代码在 `/comfy/mnt/ComfyUI`)。 + +**解决方案**:直接写入 `config.ini` 配置文件,绕过 Git 检测。 + +##### 方案 A1:使用自动化脚本(推荐) + +我们提供了完整的自动化脚本 `docs/comfy-cli-docker-setup.sh`,包含: + +- ✅ 自动检测并安装 comfy-cli +- ✅ 直接配置 config.ini(绕过 Git 检测) +- ✅ 环境变量支持(CivitAI/HF API Token) +- ✅ 配置验证 + +**使用步骤:** + +```bash +# 1. 复制脚本到 run 目录 +cp docs/comfy-cli-docker-setup.sh run/user_script.bash + +# 2. 确保执行权限 +chmod +x run/user_script.bash + +# 3. 重启容器 +docker restart comfyui-nvidia + +# 4. 查看日志确认配置成功 +docker logs comfyui-nvidia | grep -A 20 "ComfyCLI" +``` + +##### 方案 A2:手动编写 user_script.bash + +创建/编辑 `run/user_script.bash`: + +```bash +#!/bin/bash +# user_script.bash - 容器启动时执行 + +echo "=== Setting up comfy-cli ===" + +# 配置变量 +VENV_PIP="/comfy/mnt/venv/bin/pip" +COMFY_CMD="/comfy/mnt/venv/bin/comfy" +CONFIG_DIR="$HOME/.config/comfy-cli" +CONFIG_FILE="$CONFIG_DIR/config.ini" + +# 检查并安装 comfy-cli +if ! $COMFY_CMD --version &> /dev/null; then + echo "Installing comfy-cli..." + $VENV_PIP install comfy-cli -q +fi + +# 创建配置目录 +mkdir -p "$CONFIG_DIR" + +# 直接写入 config.ini(绕过 set-default 的 Git 检测) +cat > "$CONFIG_FILE" << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +EOF + +echo "Config file written to $CONFIG_FILE" + +# 验证配置 +$COMFY_CMD which +``` + +##### 为什么不能直接用 `comfy set-default`? + +comfy-cli 的 `set-default` 命令会验证路径是否为有效的 ComfyUI Git 仓库: + +```python +# comfy_cli/workspace_manager.py +def _is_comfy_repo(path: Path) -> bool: + """检查是否为 ComfyUI 仓库""" + git_dir = path / ".git" + if not git_dir.exists(): + return False + # 检查 remote URLs 是否匹配 ComfyUI 官方仓库 + ... +``` + +在 Docker 环境中: +- `/comfy/mnt/ComfyUI` 是 ComfyUI 代码(可能有 Git) +- `/basedir` 是用户数据目录(没有 Git) +- `set-default /basedir` 会失败:`Specified path is not a ComfyUI path` + +**直接写入 config.ini 是安全的**,因为: +1. comfy-cli 读取配置时不验证 Git +2. 模型/节点管理只关心子目录结构(`models/`, `custom_nodes/`) +3. ComfyUI 启动时会正确加载模型路径 + +#### 方法 B:手动安装(不推荐) + +```bash +# 进入容器 +docker exec -it comfyui-nvidia bash + +# 激活虚拟环境 +source /comfy/mnt/venv/bin/activate + +# 安装 comfy-cli +pip install comfy-cli + +# ❌ 这会失败:Specified path is not a ComfyUI path +comfy set-default /basedir + +# ✅ 改为直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# 验证 +comfy which + +# 退出容器 +exit +``` + +### 9.3 环境变量配置 + +在 `docker-compose.yml` 或 `docker run` 中添加: + +```yaml +environment: + - CIVITAI_API_TOKEN=your_token_here + - HF_API_TOKEN=your_token_here + - COMFY_CLI_WORKSPACE=/basedir +``` + +### 9.4 权限问题处理 + +ComfyUI-Nvidia-Docker 使用 `WANTED_UID`/`WANTED_GID` 映射用户权限: + +```bash +docker run ... \ + -e WANTED_UID=$(id -u) \ + -e WANTED_GID=$(id -g) \ + ... +``` + +确保 comfy-cli 配置文件的权限正确: + +```bash +# 在容器内 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +``` + +### 9.5 模型下载优化 + +在 Docker 环境中,模型下载到 `basedir`,这是持久化存储: + +```bash +# 模型会保存到 /basedir/models/... +docker exec comfyui-nvidia comfy model download \ + --url "https://..." \ + --relative-path "models/checkpoints" +``` + +--- + +## 10. REST API 集成方案 + +### 10.1 方案概述 + +由于 comfy-cli 本身不提供 REST API,需要通过 custom node 将命令暴露为 HTTP 端点。 + +**📚 相关文档:** +- **API Reference:** `comfy-cli-api-reference.md` — 所有命令的输入输出格式详解 +- **实现指南:** `comfy-cli-rest-implementation.md` — 完整的 REST API 服务器实现代码 + +有两种实现方案: + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **方案 A:扩展现有 cm-cli-rest** | 复用现有代码 | 需要修改现有项目 | +| **方案 B:新建 comfy-cli-rest** | 独立、清晰 | 需要从头开发 | + +**推荐:方案 A**(扩展 cm-cli-rest) + +### 10.1.1 为什么需要 REST API? + +直接调用 CLI 的问题: + +| 问题 | CLI 直接调用 | REST API | +|------|-------------|----------| +| 输出格式 | 原始文本,需解析 | 结构化 JSON | +| 异步任务 | 需手动管理进程 | 统一任务队列 | +| 错误处理 | 退出码 + stderr | 统一错误响应 | +| 认证授权 | 无 | 可集成 | +| 并发控制 | 无 | 可限制 | + +### 10.1.2 输出解析策略 + +| 命令类型 | 原始输出 | 建议解析方式 | +|----------|----------|-------------| +| `model list` | 每行一个文件名 | 按行分割 → 数组 | +| `node simple-show` | 每行一个节点名 | 按行分割 → 数组 | +| `model download` | 进度 + 完成消息 | 正则提取进度% | +| `env` | 键值对列表 | 分割 `:` → 对象 | +| `node show` | 多行详细信息 | 复杂解析或改用文件系统 | + +**推荐:对于简单查询(list),直接文件系统访问比 CLI 更可靠。** + +### 10.2 方案 A:扩展 cm-cli-rest + +#### 10.2.1 项目结构 + +``` +cm-cli-rest/ +├── __init__.py # 入口点 +├── api/ +│ ├── routes.py # 路由定义 +│ └── handlers/ +│ ├── nodes.py # 节点管理 +│ ├── models.py # 【新增】模型管理 +│ └── jobs.py # 任务跟踪 +├── comfy_cli/ +│ └── executor.py # 【新增】comfy-cli 执行器 +├── config/ +│ └── config.json # 配置 +└── README.md +``` + +#### 10.2.2 comfy-cli 执行器实现 + +```python +# comfy_cli/executor.py +import asyncio +import json +import os +import subprocess +import uuid +from typing import Dict, Optional, Any + +class ComfyCliExecutor: + """comfy-cli 命令执行器""" + + def __init__(self, workspace_path: str = "/basedir"): + self.workspace_path = workspace_path + self.comfy_cmd = ["comfy", f"--workspace={workspace_path}"] + self.jobs: Dict[str, Dict] = {} + + async def execute( + self, + command: list, + async_mode: bool = True + ) -> Dict[str, Any]: + """执行 comfy-cli 命令""" + job_id = str(uuid.uuid4()) + + full_cmd = self.comfy_cmd + command + + if async_mode: + # 异步执行(用于长时间运行的命令) + task = asyncio.create_task( + self._run_async(job_id, full_cmd) + ) + self.jobs[job_id] = { + "status": "running", + "command": command, + "task": task + } + return { + "job_id": job_id, + "status": "running", + "message": f"Command started: {' '.join(command)}" + } + else: + # 同步执行 + result = await self._run_sync(full_cmd) + return result + + async def _run_async(self, job_id: str, cmd: list): + """异步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + self.jobs[job_id] = { + "status": "completed", + "output": stdout.decode(), + "error": stderr.decode() + } + else: + self.jobs[job_id] = { + "status": "failed", + "error": stderr.decode(), + "output": stdout.decode() + } + except Exception as e: + self.jobs[job_id] = { + "status": "failed", + "error": str(e) + } + + async def _run_sync(self, cmd: list) -> Dict[str, Any]: + """同步运行命令""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.workspace_path + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + return { + "success": True, + "data": stdout.decode(), + "error": None + } + else: + return { + "success": False, + "data": None, + "error": stderr.decode() + } + except Exception as e: + return { + "success": False, + "data": None, + "error": str(e) + } + + def get_job_status(self, job_id: str) -> Optional[Dict]: + """获取任务状态""" + return self.jobs.get(job_id) + + def remove_job(self, job_id: str) -> bool: + """移除任务""" + if job_id in self.jobs: + del self.jobs[job_id] + return True + return False +``` + +#### 10.2.3 模型管理 Handler + +```python +# api/handlers/models.py +from aiohttp import web +import json +from ..comfy_cli.executor import ComfyCliExecutor + +executor = ComfyCliExecutor(workspace_path="/basedir") + +async def download_model(request): + """下载模型""" + try: + data = await request.json() + url = data.get("url") + relative_path = data.get("relative_path", "models/checkpoints") + filename = data.get("filename") + civitai_token = data.get("civitai_api_token") + hf_token = data.get("hf_api_token") + + if not url: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_URL", "message": "URL is required"} + }, status=400) + + # 构建命令 + cmd = ["model", "download", "--url", url] + + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + if filename: + cmd.extend(["--filename", filename]) + + if civitai_token: + cmd.extend(["--set-civitai-api-token", civitai_token]) + + if hf_token: + cmd.extend(["--set-hf-api-token", hf_token]) + + # 执行命令(异步) + result = await executor.execute(cmd, async_mode=True) + + return web.json_response({ + "success": True, + "data": result + }, status=202) # 202 Accepted + + except json.JSONDecodeError: + return web.json_response({ + "success": False, + "error": {"code": "INVALID_JSON", "message": "Invalid JSON"} + }, status=400) + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def list_models(request): + """列出模型""" + try: + relative_path = request.query.get("relative_path", "models") + + cmd = ["model", "list"] + if relative_path: + cmd.extend(["--relative-path", relative_path]) + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + # 解析输出 + models = result["data"].strip().split("\n") if result["data"] else [] + return web.json_response({ + "success": True, + "data": {"models": models, "path": relative_path} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "LIST_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def remove_model(request): + """删除模型""" + try: + data = await request.json() + model_names = data.get("model_names", []) + relative_path = data.get("relative_path", "models/checkpoints") + confirm = data.get("confirm", False) + + if not model_names: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_NAMES", "message": "model_names is required"} + }, status=400) + + cmd = ["model", "remove", "--relative-path", relative_path] + + for name in model_names: + cmd.extend(["--model-names", name]) + + if confirm: + cmd.append("--confirm") + + result = await executor.execute(cmd, async_mode=False) + + if result["success"]: + return web.json_response({ + "success": True, + "data": {"message": f"Removed {len(model_names)} model(s)"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "REMOVE_FAILED", "message": result["error"]} + }, status=500) + + except Exception as e: + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + +async def get_job_status(request): + """获取任务状态""" + job_id = request.match_info.get("job_id") + + status = executor.get_job_status(job_id) + + if status: + return web.json_response({ + "success": True, + "data": status + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) + +async def remove_job(request): + """移除任务""" + job_id = request.match_info.get("job_id") + + if executor.remove_job(job_id): + return web.json_response({ + "success": True, + "data": {"message": f"Job {job_id} removed"} + }) + else: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) +``` + +#### 10.2.4 路由定义 + +```python +# api/routes.py +from aiohttp import web +from .handlers import nodes, models, jobs + +def setup_routes(app): + """注册路由""" + + # 健康检查 + app.router.add_get("/cm-cli-rest/health", health_check) + + # 节点管理(现有) + app.router.add_get("/cm-cli-rest/nodes", nodes.list_nodes) + app.router.add_post("/cm-cli-rest/nodes/install", nodes.install_node) + # ... + + # 模型管理(新增) + app.router.add_post("/cm-cli-rest/models/download", models.download_model) + app.router.add_get("/cm-cli-rest/models", models.list_models) + app.router.add_post("/cm-cli-rest/models/remove", models.remove_model) + + # 任务跟踪 + app.router.add_get("/cm-cli-rest/jobs", jobs.list_jobs) + app.router.add_get("/cm-cli-rest/jobs/{job_id}", models.get_job_status) + app.router.add_delete("/cm-cli-rest/jobs/{job_id}", models.remove_job) + +async def health_check(request): + """健康检查""" + return web.json_response({ + "status": "healthy", + "service": "cm-cli-rest", + "version": "0.2.0", # 更新版本号 + "features": ["nodes", "models", "jobs"], + "timestamp": datetime.utcnow().isoformat() + }) +``` + +#### 10.2.5 API 端点列表 + +| 方法 | 端点 | 描述 | 异步 | +|------|------|------|------| +| GET | `/cm-cli-rest/health` | 健康检查 | ❌ | +| POST | `/cm-cli-rest/models/download` | 下载模型 | ✅ | +| GET | `/cm-cli-rest/models` | 列出模型 | ❌ | +| POST | `/cm-cli-rest/models/remove` | 删除模型 | ❌ | +| GET | `/cm-cli-rest/jobs` | 列出任务 | ❌ | +| GET | `/cm-cli-rest/jobs/{id}` | 获取任务状态 | ❌ | +| DELETE | `/cm-cli-rest/jobs/{id}` | 移除任务 | ❌ | + +#### 10.2.6 请求/响应示例 + +**下载模型请求:** + +```bash +curl -X POST http://localhost:8188/cm-cli-rest/models/download \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints", + "filename": "my_model.safetensors", + "civitai_api_token": "your_token" + }' +``` + +**响应(202 Accepted):** + +```json +{ + "success": true, + "data": { + "job_id": "abc12345", + "status": "running", + "message": "Command started: model download --url ..." + } +} +``` + +**查询任务状态:** + +```bash +curl http://localhost:8188/cm-cli-rest/jobs/abc12345 +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "status": "completed", + "output": "Model downloaded successfully...", + "error": null + } +} +``` + +**列出模型:** + +```bash +curl "http://localhost:8188/cm-cli-rest/models?relative_path=models/checkpoints" +``` + +**响应:** + +```json +{ + "success": true, + "data": { + "models": [ + "model1.safetensors", + "model2.ckpt", + "model3.safetensors" + ], + "path": "models/checkpoints" + } +} +``` + +### 10.3 方案 B:新建 comfy-cli-rest + +如果希望保持独立,可以创建新的 custom node: + +```bash +# 目录结构 +custom_nodes/comfy-cli-rest/ +├── __init__.py +├── server.py +├── handlers/ +│ ├── models.py +│ ├── nodes.py +│ └── workflows.py +├── comfy_executor.py +├── config.json +└── requirements.txt +``` + +**requirements.txt:** + +``` +aiohttp>=3.9.0 +``` + +**__init__.py:** + +```python +import asyncio +import os +from aiohttp import web +from server import PromptServer + +from .handlers import models, nodes +from .comfy_executor import ComfyCliExecutor + +WEB_DIRECTORY = "./web" + +executor = ComfyCliExecutor( + workspace_path=os.environ.get("COMFYUI_BASEDIR", "/basedir") +) + +@PromptServer.instance.routes.get("/comfy-cli/health") +async def health_check(request): + return web.json_response({ + "status": "healthy", + "service": "comfy-cli-rest", + "version": "1.0.0" + }) + +@PromptServer.instance.routes.post("/comfy-cli/model/download") +async def download_model(request): + return await models.download_model(request, executor) + +@PromptServer.instance.routes.get("/comfy-cli/model/list") +async def list_models(request): + return await models.list_models(request, executor) + +# ... 其他路由 +``` + +### 10.4 安全考虑 + +1. **API 认证** + ```json + { + "api_key": "your_secret_key", + "enabled": true + } + ``` + +2. **输入验证** + - 验证所有路径参数,防止路径遍历攻击 + - 限制可执行的命令白名单 + +3. **速率限制** + - 限制每个 IP 的请求频率 + - 限制并发任务数量 + +4. **日志记录** + - 记录所有 API 调用 + - 记录错误和异常 + +--- + +## 11. 故障排除 + +### 11.1 常见问题 + +#### 问题 1:`comfy: command not found` + +**原因:** comfy-cli 未安装或未添加到 PATH + +**解决:** + +```bash +# 在容器内 +source /comfy/mnt/venv/bin/activate +pip install comfy-cli + +# 或使用完整路径 +/comfy/mnt/venv/bin/comfy --version +``` + +#### 问题 2:`ComfyUI not found` 或 `Specified path is not a ComfyUI path` + +**原因:** 工作区配置错误,或在 Docker 环境中使用 `set-default` 命令 + +**解决:** + +**Docker 环境特殊情况:** + +`comfy set-default /basedir` 会失败,因为 comfy-cli 通过 Git 仓库检测验证路径,而 `/basedir` 是数据目录而非 Git 仓库。 + +```bash +# ❌ 这会失败 +comfy set-default /basedir +# 错误:Specified path is not a ComfyUI path: /basedir + +# ✅ 方案 A:使用自动化脚本(推荐) +# 将 docs/comfy-cli-docker-setup.sh 复制为 run/user_script.bash 并重启容器 + +# ✅ 方案 B:直接编辑配置文件 +mkdir -p ~/.config/comfy-cli +cat > ~/.config/comfy-cli/config.ini << EOF +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +enable_tracking = false +EOF + +# ✅ 方案 C:每次使用 --workspace 参数 +comfy --workspace=/basedir model list +``` + +**非 Docker 环境:** + +```bash +# 设置默认工作区 +comfy set-default /path/to/ComfyUI + +# 或显式指定 +comfy --workspace=/basedir model list +``` + +#### 问题 3:模型下载失败(401/403) + +**原因:** 缺少 API Token + +**解决:** + +```bash +# 设置 Token +comfy model download --url "..." \ + --set-civitai-api-token "YOUR_TOKEN" + +# 或使用环境变量 +export CIVITAI_API_TOKEN="YOUR_TOKEN" +comfy model download --url "..." +``` + +#### 问题 4:权限错误 + +**原因:** Docker 用户权限映射问题 + +**解决:** + +```bash +# 在容器内修复权限 +chown -R $(id -u):$(id -g) ~/.config/comfy-cli/ +chown -R $(id -u):$(id -g) /basedir/ +``` + +#### 问题 5:Python 版本不兼容 + +**原因:** comfy-cli 需要 Python 3.9+ + +**解决:** + +```bash +# 检查 Python 版本 +python --version + +# 使用正确的 Python 环境 +/comfy/mnt/venv/bin/python -m pip install comfy-cli +``` + +### 11.2 调试技巧 + +```bash +# 启用详细日志 +comfy --workspace=/basedir model download --url "..." -v + +# 查看 comfy-cli 配置 +cat ~/.config/comfy-cli/config.ini + +# 检查环境 +comfy env + +# 验证工作区 +comfy which +``` + +### 11.3 获取帮助 + +```bash +# 主帮助 +comfy --help + +# 命令帮助 +comfy model --help +comfy model download --help +comfy node --help + +# 查看源码 +# https://github.com/Comfy-Org/comfy-cli +``` + +--- + +## 12. 附录 + +### 12.1 完整命令参考 + +``` +comfy [OPTIONS] COMMAND [ARGS] + +Options: + --workspace PATH Path to ComfyUI workspace + --recent Execute from recent path + --here Execute from current path + --version Print version and exit + --help Show help + +Commands: + install Download and install ComfyUI + update Update ComfyUI environment + launch Launch ComfyUI + stop Stop background ComfyUI + set-default Set default ComfyUI path + which Show which ComfyUI is selected + env Print environment variables + model Model management + download Download model file + list List models + remove Remove models + node Custom node management + install Install nodes + uninstall Uninstall nodes + update Update nodes + enable Enable nodes + disable Disable nodes + show Show node list + simple-show Show node list (simple) + save-snapshot Save snapshot + restore-snapshot Restore snapshot + run Run workflow + feedback Provide feedback +``` + +### 12.2 配置文件模板 + +```ini +# ~/.config/comfy-cli/config.ini +[DEFAULT] +default_workspace = /basedir +recent_workspace = /basedir +default_launch_extras = --listen 0.0.0.0 --port 8188 +enable_tracking = false +civitai_api_token = +hf_api_token = +``` + +### 12.3 Docker Compose 示例 + +```yaml +version: '3.8' + +services: + comfyui: + image: mmartial/comfyui-nvidia-docker:latest + container_name: comfyui-nvidia + runtime: nvidia + environment: + - USE_UV=true + - WANTED_UID=1000 + - WANTED_GID=1000 + - BASE_DIRECTORY=/basedir + - SECURITY_LEVEL=normal + - CIVITAI_API_TOKEN=${CIVITAI_API_TOKEN} + - HF_API_TOKEN=${HF_API_TOKEN} + volumes: + - ./run:/comfy/mnt + - ./basedir:/basedir + ports: + - "127.0.0.1:8188:8188" + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +### 12.4 参考资源 + +- **comfy-cli 官方仓库:** https://github.com/Comfy-Org/comfy-cli +- **comfy-cli PyPI:** https://pypi.org/project/comfy-cli/ +- **ComfyUI-Nvidia-Docker:** https://github.com/mmartial/ComfyUI-Nvidia-Docker +- **ComfyUI-Manager:** https://github.com/ltdrdata/ComfyUI-Manager +- **cm-cli-rest:** https://github.com/fno2010/cm-cli-rest + +--- + +## 修订历史 + +| 版本 | 日期 | 作者 | 变更 | +|------|------|------|------| +| 1.5 | 2026-03-17 | Zero | 添加表格解析指南 `comfy-cli-table-parser.md`,修正输出格式说明(box table) | +| 1.4 | 2026-03-16 | Zero | 添加 CLI 命令参考手册 `comfy-cli-command-reference.md` | +| 1.3 | 2026-03-16 | Zero | 添加 API Reference 和 REST 实现指南文档链接、输出解析策略说明 | +| 1.2 | 2026-03-16 | Zero | 添加 Docker 环境 `set-default` 失败解决方案、自动化脚本 `comfy-cli-docker-setup.sh` | +| 1.1 | 2026-03-16 | Zero | 添加参数命名规则说明、完成 v1.6.0 源码验证 | +| 1.0 | 2026-03-16 | Zero | 初始版本 | + +--- + +## 版本说明 + +**本文档基于以下 comfy-cli 版本调研编写:** + +| 来源 | 版本 | 说明 | +|------|------|------| +| PyPI 最新稳定版 | `1.6.0` | ✅ 已验证,推荐使用 | +| GitHub main 分支 | 开发中 | 文档编写时(2026-03-16)的代码状态 | +| 最低兼容版本 | `1.3.8` | 低于此版本可能有命令差异 | + +**验证你安装的版本:** + +```bash +comfy --version +``` + +如果版本低于 `1.3.8`,建议升级: + +```bash +pip install --upgrade comfy-cli +``` + +## 参数命名规则 + +comfy-cli 使用 **typer** 框架,Python 参数名会自动转换为 CLI 标志: + +| Python 参数名 | CLI 标志 | 转换规则 | +|--------------|----------|----------| +| `relative_path` | `--relative-path` | snake_case → kebab-case | +| `model_names` | `--model-names` | snake_case → kebab-case | +| `confirm` | `--confirm` | 无变化 | +| `fast_deps` | `--fast-deps` | snake_case → kebab-case | + +**示例:** + +```python +# Python 源码 +relative_path: Annotated[str | None, typer.Option(...)] + +# CLI 使用 +comfy model download --relative-path "models/checkpoints" +``` + +所有文档中的命令参数都遵循此规则。完整验证报告见 `comfy-cli-verification-report.md`。 + +--- + +*本文档由 Zero (零号) 生成。所有命令已通过 v1.6.0 源码验证。* diff --git a/docs/references/comfy-cli-rest-implementation.md b/docs/references/comfy-cli-rest-implementation.md new file mode 100644 index 0000000..dbe2474 --- /dev/null +++ b/docs/references/comfy-cli-rest-implementation.md @@ -0,0 +1,890 @@ +# comfy-cli REST API 实现指南 + +> 文档版本:1.0 +> 最后更新:2026-03-16 +> 目标:将 comfy-cli 命令转换为结构化 REST API + +--- + +## 1. 架构设计 + +### 1.1 为什么需要 REST API? + +| 问题 | CLI 直接调用 | REST API | +|------|-------------|----------| +| 输出格式 | 原始文本,需解析 | 结构化 JSON | +| 异步任务 | 需手动管理进程 | 统一任务队列 | +| 错误处理 | 退出码 + stderr | 统一错误响应 | +| 认证授权 | 无 | 可集成 | +| 并发控制 | 无 | 可限制 | + +### 1.2 设计原则 + +1. **CLI 作为实现细节**:外部调用者不应直接依赖 CLI 输出格式 +2. **结构化响应**:所有响应都是 JSON,包含 `success`, `data`, `error` +3. **异步任务统一处理**:长时间运行的命令返回 `job_id` +4. **解析层隔离**:CLI 输出解析在内部完成,不暴露给调用者 + +### 1.3 系统架构 + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐ +│ Client │────▶│ REST API │────▶│ Executor │────▶│ comfy-cli│ +│ (Browser/ │◀────│ Server │◀────│ (Python) │◀────│ (CLI) │ +│ App) │ │ (aiohttp) │ │ │ │ │ +└─────────────┘ └──────────────┘ └─────────────┘ └──────────┘ + │ │ + ▼ ▼ + ┌──────────────┐ ┌─────────────┐ + │ Task Queue │ │ Parsers │ + │ (in-memory) │ │ (CLI→JSON) │ + └──────────────┘ └─────────────┘ +``` + +--- + +## 2. 项目结构 + +``` +comfy-cli-rest/ +├── __init__.py # 包初始化 +├── server.py # aiohttp 服务器入口 +├── config.py # 配置管理 +├── executor.py # CLI 执行器 +├── parsers/ # 输出解析器 +│ ├── __init__.py +│ ├── base.py # 解析器基类 +│ ├── model_parser.py # 模型命令解析 +│ ├── node_parser.py # 节点命令解析 +│ └── env_parser.py # 环境命令解析 +├── handlers/ # API handlers +│ ├── __init__.py +│ ├── models.py # 模型管理 API +│ ├── nodes.py # 节点管理 API +│ ├── config.py # 配置 API +│ └── jobs.py # 任务管理 API +├── routes.py # 路由注册 +├── middleware.py # 中间件(认证、日志、错误处理) +├── requirements.txt # 依赖 +└── README.md # 使用说明 +``` + +--- + +## 3. 核心组件实现 + +### 3.1 配置管理 (`config.py`) + +```python +from dataclasses import dataclass +from pathlib import Path +import json + +@dataclass +class Config: + workspace: str = "/basedir" + comfy_cmd: str = "comfy" + host: str = "0.0.0.0" + port: int = 8189 # 注意:不是 8188(ComfyUI 端口) + api_prefix: str = "/comfy-cli" + max_concurrent_jobs: int = 5 + job_ttl_seconds: int = 3600 + + @classmethod + def from_file(cls, path: str) -> "Config": + with open(path) as f: + data = json.load(f) + return cls(**data) + + @property + def comfy_args(self) -> list: + return [self.comfy_cmd, "--workspace", self.workspace] + +# 默认配置 +DEFAULT_CONFIG = Config() +``` + +### 3.2 CLI 执行器 (`executor.py`) + +```python +import asyncio +import uuid +from datetime import datetime, timedelta +from typing import Dict, Optional, Any, List +from pathlib import Path +import logging + +logger = logging.getLogger(__name__) + +class JobStatus: + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + TIMEOUT = "timeout" + +@dataclass +class Job: + id: str + command: List[str] + status: str + created_at: datetime + started_at: Optional[datetime] = None + completed_at: Optional[datetime] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + return_code: Optional[int] = None + error: Optional[str] = None + +class ComfyCliExecutor: + """CLI 命令执行器,管理异步任务""" + + def __init__(self, config: Config): + self.config = config + self.jobs: Dict[str, Job] = {} + self._cleanup_task: Optional[asyncio.Task] = None + + async def start(self): + """启动执行器(启动清理任务)""" + self._cleanup_task = asyncio.create_task(self._cleanup_loop()) + + async def stop(self): + """停止执行器""" + if self._cleanup_task: + self._cleanup_task.cancel() + try: + await self._cleanup_task + except asyncio.CancelledError: + pass + + async def execute( + self, + command: List[str], + async_mode: bool = True, + timeout: Optional[int] = None + ) -> Dict[str, Any]: + """执行 CLI 命令""" + job_id = str(uuid.uuid4()) + full_cmd = self.config.comfy_args + command + + job = Job( + id=job_id, + command=full_cmd, + status=JobStatus.PENDING, + created_at=datetime.utcnow() + ) + self.jobs[job_id] = job + + if async_mode: + # 异步执行,立即返回 job_id + asyncio.create_task(self._run_job(job, timeout)) + return { + "job_id": job_id, + "status": JobStatus.PENDING, + "message": f"Command queued: {' '.join(command)}" + } + else: + # 同步执行,等待完成 + await self._run_job(job, timeout) + return self._job_to_response(job) + + async def _run_job(self, job: Job, timeout: Optional[int] = None): + """后台运行任务""" + job.status = JobStatus.RUNNING + job.started_at = datetime.utcnow() + + try: + process = await asyncio.create_subprocess_exec( + *job.command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=self.config.workspace + ) + + try: + stdout, stderr = await asyncio.wait_for( + process.communicate(), + timeout=timeout + ) + job.stdout = stdout.decode('utf-8', errors='replace') + job.stderr = stderr.decode('utf-8', errors='replace') + job.return_code = process.returncode + job.status = ( + JobStatus.COMPLETED if process.returncode == 0 + else JobStatus.FAILED + ) + except asyncio.TimeoutError: + process.kill() + await process.communicate() + job.status = JobStatus.TIMEOUT + job.error = f"Command timed out after {timeout}s" + + except Exception as e: + job.status = JobStatus.FAILED + job.error = str(e) + logger.exception(f"Job {job.id} failed") + + job.completed_at = datetime.utcnow() + + def _job_to_response(self, job: Job) -> Dict[str, Any]: + """将任务转换为 API 响应""" + if job.status == JobStatus.COMPLETED: + return { + "job_id": job.id, + "status": job.status, + "stdout": job.stdout, + "stderr": job.stderr, + "return_code": job.return_code + } + elif job.status == JobStatus.FAILED: + return { + "job_id": job.id, + "status": job.status, + "error": job.error or job.stderr, + "stdout": job.stdout + } + else: + return { + "job_id": job.id, + "status": job.status, + "message": job.error + } + + def get_job(self, job_id: str) -> Optional[Job]: + """获取任务状态""" + return self.jobs.get(job_id) + + def remove_job(self, job_id: str) -> bool: + """移除任务""" + if job_id in self.jobs: + del self.jobs[job_id] + return True + return False + + async def _cleanup_loop(self): + """定期清理过期任务""" + while True: + await asyncio.sleep(60) # 每分钟清理一次 + now = datetime.utcnow() + expired = [ + job_id for job_id, job in self.jobs.items() + if job.completed_at and + (now - job.completed_at).total_seconds() > self.config.job_ttl_seconds + ] + for job_id in expired: + del self.jobs[job_id] + if expired: + logger.info(f"Cleaned up {len(expired)} expired jobs") +``` + +### 3.3 解析器基类 (`parsers/base.py`) + +```python +from abc import ABC, abstractmethod +from typing import Any, Dict, Optional + +class ParseError(Exception): + """解析错误""" + pass + +class BaseParser(ABC): + """CLI 输出解析器基类""" + + @abstractmethod + def parse(self, raw_output: str, raw_error: str = "") -> Dict[str, Any]: + """解析 CLI 输出为结构化数据""" + pass + + def safe_parse(self, raw_output: str, raw_error: str = "") -> Dict[str, Any]: + """安全解析,失败时返回原始数据""" + try: + return { + "parsed": self.parse(raw_output, raw_error), + "parse_success": True + } + except ParseError as e: + return { + "parsed": {"raw": raw_output}, + "parse_success": False, + "parse_error": str(e) + } +``` + +### 3.4 模型解析器 (`parsers/model_parser.py`) + +```python +from .base import BaseParser, ParseError +from typing import List, Dict, Any +from pathlib import Path +import re + +class ModelListParser(BaseParser): + """解析 `comfy model list` 输出""" + + def parse(self, raw_output: str, raw_error: str = "") -> Dict[str, Any]: + if not raw_output.strip(): + return {"models": [], "count": 0} + + # 每行一个文件名 + models = [ + line.strip() + for line in raw_output.strip().split('\n') + if line.strip() and not line.startswith('#') + ] + + return { + "models": models, + "count": len(models) + } + +class ModelDownloadParser(BaseParser): + """解析 `comfy model download` 输出""" + + def parse(self, raw_output: str, raw_error: str = "") -> Dict[str, Any]: + result = { + "status": "unknown", + "progress": 0, + "filename": None, + "path": None + } + + # 检查错误 + if raw_error: + result["status"] = "error" + result["error"] = raw_error.strip() + return result + + # 检查完成消息 + if "Successfully downloaded" in raw_output: + result["status"] = "completed" + result["progress"] = 100 + + # 尝试提取文件名 + match = re.search(r'Successfully downloaded (.+) to (.+)', raw_output) + if match: + result["filename"] = match.group(1) + result["path"] = match.group(2) + + # 检查进度 + progress_match = re.search(r'(\d+)%', raw_output) + if progress_match: + result["status"] = "downloading" + result["progress"] = int(progress_match.group(1)) + + return result +``` + +### 3.5 模型 Handler (`handlers/models.py`) + +```python +from aiohttp import web +from typing import Dict, Any +import logging +from pathlib import Path + +from ..executor import ComfyCliExecutor, JobStatus +from ..parsers.model_parser import ModelListParser, ModelDownloadParser + +logger = logging.getLogger(__name__) + +class ModelHandler: + def __init__(self, executor: ComfyCliExecutor, workspace: str = "/basedir"): + self.executor = executor + self.workspace = Path(workspace) + self.list_parser = ModelListParser() + self.download_parser = ModelDownloadParser() + + async def list_models(self, request: web.Request) -> web.Response: + """ + GET /comfy-cli/model/list?relative_path=models/checkpoints + + 响应: + { + "success": true, + "data": { + "path": "models/checkpoints", + "models": [ + {"filename": "model1.safetensors", "size": 6200000000}, + ... + ], + "count": 5 + } + } + """ + relative_path = request.query.get("relative_path", "models/checkpoints") + use_direct_fs = request.query.get("method", "cli") == "fs" + + try: + if use_direct_fs: + # 方法 1:直接文件系统访问(更可靠) + data = await self._list_models_fs(relative_path) + else: + # 方法 2:CLI 调用 + result = await self.executor.execute( + ["model", "list", "--relative-path", relative_path], + async_mode=False + ) + + if result["status"] != JobStatus.COMPLETED: + return web.json_response({ + "success": False, + "error": {"code": "EXECUTION_FAILED", "message": result.get("error")} + }, status=500) + + # 解析 CLI 输出 + parsed = self.list_parser.safe_parse(result["stdout"], result.get("stderr", "")) + + # 增强:获取文件详细信息 + models_detail = await self._enhance_models_info( + parsed["parsed"].get("models", []), + relative_path + ) + + data = { + "path": relative_path, + "models": models_detail, + "count": len(models_detail), + "cli_output": parsed + } + + return web.json_response({ + "success": True, + "data": data + }) + + except Exception as e: + logger.exception("list_models failed") + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + + async def _list_models_fs(self, relative_path: str) -> Dict[str, Any]: + """直接文件系统方式列出模型""" + full_path = self.workspace / relative_path + + if not full_path.exists(): + return {"path": relative_path, "models": [], "count": 0, "error": "Path not found"} + + models = [] + for f in full_path.iterdir(): + if f.is_file() and f.suffix in ['.safetensors', '.ckpt', '.pt', '.bin']: + stat = f.stat() + models.append({ + "filename": f.name, + "size": stat.st_size, + "size_human": self._format_size(stat.st_size), + "modified": stat.st_mtime + }) + + return { + "path": relative_path, + "absolute_path": str(full_path), + "models": sorted(models, key=lambda x: x["filename"]), + "count": len(models) + } + + async def download_model(self, request: web.Request) -> web.Response: + """ + POST /comfy-cli/model/download + + 请求体: + { + "url": "https://...", + "relative_path": "models/checkpoints", + "filename": "optional.safetensors", + "civitai_api_token": "optional" + } + + 响应(202 Accepted): + { + "success": true, + "job_id": "uuid", + "status": "pending" + } + """ + try: + data = await request.json() + url = data.get("url") + + if not url: + return web.json_response({ + "success": False, + "error": {"code": "MISSING_URL", "message": "url is required"} + }, status=400) + + # 构建 CLI 命令 + cmd = ["model", "download", "--url", url] + + if data.get("relative_path"): + cmd.extend(["--relative-path", data["relative_path"]]) + if data.get("filename"): + cmd.extend(["--filename", data["filename"]]) + if data.get("civitai_api_token"): + cmd.extend(["--set-civitai-api-token", data["civitai_api_token"]]) + if data.get("hf_api_token"): + cmd.extend(["--set-hf-api-token", data["hf_api_token"]]) + + # 异步执行 + result = await self.executor.execute(cmd, async_mode=True, timeout=1800) + + return web.json_response({ + "success": True, + "job_id": result["job_id"], + "status": result["status"], + "message": result["message"] + }, status=202) + + except Exception as e: + logger.exception("download_model failed") + return web.json_response({ + "success": False, + "error": {"code": "INTERNAL_ERROR", "message": str(e)} + }, status=500) + + async def get_download_status(self, request: web.Request) -> web.Response: + """ + GET /comfy-cli/model/download/{job_id}/status + + 响应: + { + "success": true, + "data": { + "job_id": "uuid", + "status": "running|completed|failed", + "progress": 45, + "parsed_output": {...} + } + } + """ + job_id = request.match_info["job_id"] + + job = self.executor.get_job(job_id) + if not job: + return web.json_response({ + "success": False, + "error": {"code": "JOB_NOT_FOUND", "message": f"Job {job_id} not found"} + }, status=404) + + response_data = { + "job_id": job_id, + "status": job.status, + "created_at": job.created_at.isoformat(), + } + + if job.status == JobStatus.RUNNING: + # 解析进度 + parsed = self.download_parser.safe_parse(job.stdout or "", job.stderr or "") + response_data["progress"] = parsed["parsed"].get("progress", 0) + response_data["parsed_output"] = parsed["parsed"] + + elif job.status == JobStatus.COMPLETED: + parsed = self.download_parser.safe_parse(job.stdout or "", job.stderr or "") + response_data["parsed_output"] = parsed["parsed"] + response_data["parse_success"] = parsed["parse_success"] + + elif job.status == JobStatus.FAILED: + response_data["error"] = job.error or job.stderr + + return web.json_response({ + "success": True, + "data": response_data + }) + + async def _enhance_models_info(self, model_names: List[str], relative_path: str) -> List[Dict]: + """增强模型信息(添加文件大小等)""" + full_path = self.workspace / relative_path + result = [] + + for name in model_names: + model_file = full_path / name + if model_file.exists(): + stat = model_file.stat() + result.append({ + "filename": name, + "size": stat.st_size, + "size_human": self._format_size(stat.st_size), + "modified": stat.st_mtime + }) + else: + result.append({"filename": name, "size": None, "error": "File not found"}) + + return result + + def _format_size(self, bytes: int) -> str: + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if bytes < 1024: + return f"{bytes:.1f} {unit}" + bytes /= 1024 + return f"{bytes:.1f} TB" +``` + +--- + +## 4. 服务器入口 (`server.py`) + +```python +from aiohttp import web +import logging +from config import Config, DEFAULT_CONFIG +from executor import ComfyCliExecutor +from handlers.models import ModelHandler +from handlers.nodes import NodeHandler +from handlers.jobs import JobHandler +from routes import setup_routes +from middleware import setup_middleware + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +async def create_app(config: Config = DEFAULT_CONFIG) -> web.Application: + """创建应用""" + app = web.Application() + + # 创建共享组件 + executor = ComfyCliExecutor(config) + await executor.start() + + # 创建 handlers + model_handler = ModelHandler(executor, config.workspace) + node_handler = NodeHandler(executor, config.workspace) + job_handler = JobHandler(executor) + + # 存储到 app,便于清理 + app['executor'] = executor + app['handlers'] = { + 'model': model_handler, + 'node': node_handler, + 'job': job_handler + } + + # 设置中间件 + setup_middleware(app) + + # 设置路由 + setup_routes(app, model_handler, node_handler, job_handler) + + # 生命周期钩子 + app.on_shutdown.append(on_shutdown) + + return app + +async def on_shutdown(app: web.Application): + """清理资源""" + await app['executor'].stop() + +def main(): + app = web.run_app(create_app(), host=DEFAULT_CONFIG.host, port=DEFAULT_CONFIG.port) + +if __name__ == '__main__': + main() +``` + +--- + +## 5. 中间件 (`middleware.py`) + +```python +from aiohttp import web +import logging +import time +import json + +logger = logging.getLogger(__name__) + +@web.middleware +async def logging_middleware(request: web.Request, handler): + """请求日志""" + start = time.time() + try: + response = await handler(request) + duration = time.time() - start + logger.info(f"{request.method} {request.path} - {response.status} - {duration:.3f}s") + return response + except Exception as e: + logger.exception(f"{request.method} {request.path} - Error: {e}") + raise + +@web.middleware +async def error_handler_middleware(request: web.Request, handler): + """统一错误处理""" + try: + return await handler(request) + except web.HTTPException: + raise + except Exception as e: + logger.exception(f"Unhandled error in {request.path}") + return web.json_response({ + "success": False, + "error": { + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred", + "details": str(e) if request.app['debug'] else None + } + }, status=500) + +@web.middleware +async def json_body_middleware(request: web.Request, handler): + """自动解析 JSON 请求体""" + if request.content_type == 'application/json': + try: + request['json'] = await request.json() + except json.JSONDecodeError: + return web.json_response({ + "success": False, + "error": {"code": "INVALID_JSON", "message": "Invalid JSON in request body"} + }, status=400) + return await handler(request) + +def setup_middleware(app: web.Application): + app.middlewares.append(logging_middleware) + app.middlewares.append(error_handler_middleware) + app.middlewares.append(json_body_middleware) +``` + +--- + +## 6. 使用示例 + +### 6.1 启动服务器 + +```bash +# 安装依赖 +pip install aiohttp + +# 启动 +python -m comfy_cli_rest.server + +# 或使用配置 +python -c " +from comfy_cli_rest.server import create_app +from comfy_cli_rest.config import Config +from aiohttp import web + +config = Config.from_file('config.json') +app = create_app(config) +web.run_app(app, host=config.host, port=config.port) +" +``` + +### 6.2 API 调用示例 + +```bash +# 列出模型 +curl "http://localhost:8189/comfy-cli/model/list?relative_path=models/checkpoints" + +# 下载模型 +curl -X POST "http://localhost:8189/comfy-cli/model/download" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://civitai.com/models/43331", + "relative_path": "models/checkpoints" + }' + +# 查询任务状态 +curl "http://localhost:8189/comfy-cli/jobs/{job_id}" + +# 安装节点 +curl -X POST "http://localhost:8189/comfy-cli/node/install" \ + -H "Content-Type: application/json" \ + -d '{"nodes": ["ComfyUI-Impact-Pack"]}' +``` + +### 6.3 Python 客户端 + +```python +import aiohttp +import asyncio + +class ComfyCliClient: + def __init__(self, base_url: str = "http://localhost:8189"): + self.base_url = base_url + + async def list_models(self, relative_path: str = "models/checkpoints"): + async with aiohttp.ClientSession() as session: + async with session.get( + f"{self.base_url}/comfy-cli/model/list", + params={"relative_path": relative_path} + ) as resp: + data = await resp.json() + return data["data"]["models"] + + async def download_model(self, url: str, **kwargs): + async with aiohttp.ClientSession() as session: + async with session.post( + f"{self.base_url}/comfy-cli/model/download", + json={"url": url, **kwargs} + ) as resp: + return await resp.json() + + async def wait_for_job(self, job_id: str, poll_interval: float = 2.0): + async with aiohttp.ClientSession() as session: + while True: + async with session.get( + f"{self.base_url}/comfy-cli/jobs/{job_id}" + ) as resp: + data = await resp.json() + status = data["data"]["status"] + + if status in ["completed", "failed", "timeout"]: + return data + + await asyncio.sleep(poll_interval) + +# 使用示例 +async def main(): + client = ComfyCliClient() + + # 列出模型 + models = await client.list_models() + print(f"Found {len(models)} models") + + # 下载模型并等待完成 + result = await client.download_model("https://...") + job_id = result["job_id"] + + final = await client.wait_for_job(job_id) + print(f"Download {final['data']['status']}") + +asyncio.run(main()) +``` + +--- + +## 7. 部署建议 + +### 7.1 Docker Compose + +```yaml +version: '3.8' + +services: + comfyui: + image: mmartial/comfyui-nvidia-docker:latest + container_name: comfyui + # ... 其他配置 + + comfy-cli-rest: + build: ./comfy-cli-rest + container_name: comfy-cli-rest + volumes: + - ./basedir:/basedir + environment: + - COMFY_WORKSPACE=/basedir + - CIVITAI_API_TOKEN=${CIVITAI_API_TOKEN} + ports: + - "8189:8189" + depends_on: + - comfyui +``` + +### 7.2 安全建议 + +1. **不要暴露到公网**:默认监听 `127.0.0.1` +2. **添加 API 认证**:使用 API key 或 JWT +3. **限制命令白名单**:只允许安全的 CLI 命令 +4. **速率限制**:防止滥用 + +--- + +*由 Zero (零号) 生成 | 2026-03-16* diff --git a/docs/references/comfy-cli-table-parser.md b/docs/references/comfy-cli-table-parser.md new file mode 100644 index 0000000..51766ec --- /dev/null +++ b/docs/references/comfy-cli-table-parser.md @@ -0,0 +1,505 @@ +# comfy-cli Box Table 解析指南 + +> 文档版本:1.0 +> 最后更新:2026-03-17 +> 目标:将 rich box table 输出转换为结构化 JSON/CSV + +--- + +## 问题背景 + +comfy-cli 使用 `rich.table.Table` 输出格式化的表格,例如: + +``` +┌──────────────────────────┬─────────────┬─────────┐ +│ Name │ Version │ Status │ +├──────────────────────────┼─────────────┼─────────┤ +│ ComfyUI-Impact-Pack │ v5.12.1 │ enabled │ +│ ComfyUI-Manager │ v2.45.0 │ enabled │ +└──────────────────────────┴─────────────┴─────────┘ +``` + +这种格式对人类友好,但对程序解析不友好。需要专门的解析器。 + +--- + +## 方案对比 + +| 方案 | 优点 | 缺点 | 推荐场景 | +|------|------|------|----------| +| **方案 1:修改 comfy-cli 源码** | 最彻底,直接输出 JSON | 需要维护 fork 版本 | 长期方案 | +| **方案 2:使用 rich 的 Console 捕获** | 无需修改源码 | 需要导入 comfy-cli 内部模块 | Python 集成 | +| **方案 3:文本解析(推荐)** | 独立,无侵入 | 需要维护解析器 | REST API 集成 | +| **方案 4:直接文件系统访问** | 最可靠 | 仅适用于 list 类命令 | 模型/节点列表 | + +--- + +## 方案 1:修改 comfy-cli 源码(最彻底) + +在 `comfy_cli/ui.py` 中添加 JSON 输出选项: + +```python +# comfy_cli/ui.py 修改版 +import json + +def display_table( + data: list[tuple], + column_names: list[str], + title: str = "", + output_format: str = "table" # 新增参数 +) -> None: + if output_format == "json": + # 输出 JSON + result = [ + dict(zip(column_names, row)) + for row in data + ] + print(json.dumps(result, indent=2)) + else: + # 原有 table 输出 + table = Table(title=title) + for name in column_names: + table.add_column(name, overflow="fold") + for row in data: + table.add_row(*[str(item) for item in row]) + console.print(table) +``` + +**使用方式:** +```bash +comfy node show installed --format json +``` + +**优点:** +- 一劳永逸 +- 官方支持 + +**缺点:** +- 需要 fork 并维护 comfy-cli +- 每次更新需要合并上游变更 + +--- + +## 方案 2:使用 rich 的 Console 捕获(Python 集成) + +在 Python 代码中捕获 rich 的输出: + +```python +from io import StringIO +from rich.console import Console +from rich.table import Table + +def capture_table_output(data: list[tuple], column_names: list[str]) -> str: + """捕获 rich table 输出为字符串""" + output = StringIO() + console = Console(file=output, force_terminal=False) + + table = Table() + for name in column_names: + table.add_column(name) + for row in data: + table.add_row(*[str(item) for item in row]) + + console.print(table) + return output.getvalue() + +# 然后解析字符串... +``` + +**优点:** +- 无需修改 comfy-cli +- 可以在自己的代码中使用 + +**缺点:** +- 仍然需要解析 box table +- 不适用于 CLI 调用场景 + +--- + +## 方案 3:文本解析器(推荐用于 REST API) + +### 3.1 使用 `rich` 的内置解析 + +```python +from rich.console import Console +from rich.table import Table +import re + +def parse_rich_table(table_text: str) -> list[dict]: + """ + 解析 rich box table 文本为字典列表 + + Args: + table_text: rich table 的原始输出文本 + + Returns: + 字典列表,每个字典代表一行 + """ + lines = table_text.strip().split('\n') + + # 过滤掉边框行 + data_lines = [] + for line in lines: + # 跳过纯边框行(只包含 ─┌┐└┘├┤┬┴┼│ 的行) + if re.match(r'^[\s┌─┬┐├─┼┤└─┴┘│]+$', line): + continue + data_lines.append(line) + + if len(data_lines) < 2: + return [] + + # 第一行是表头 + header_line = data_lines[0] + # 提取列名(去掉 │ 符号) + headers = [h.strip() for h in header_line.split('│')[1:-1]] + + # 解析数据行 + result = [] + for line in data_lines[1:]: + if '│' not in line: + continue + cells = [c.strip() for c in line.split('│')[1:-1]] + if len(cells) == len(headers): + result.append(dict(zip(headers, cells))) + + return result + +# 示例 +table_output = """ +┌──────────────────────────┬─────────────┬─────────┐ +│ Name │ Version │ Status │ +├──────────────────────────┼─────────────┼─────────┤ +│ ComfyUI-Impact-Pack │ v5.12.1 │ enabled │ +│ ComfyUI-Manager │ v2.45.0 │ enabled │ +└──────────────────────────┴─────────────┴─────────┘ +""" + +result = parse_rich_table(table_output) +# [ +# {"Name": "ComfyUI-Impact-Pack", "Version": "v5.12.1", "Status": "enabled"}, +# {"Name": "ComfyUI-Manager", "Version": "v2.45.0", "Status": "enabled"} +# ] +``` + +### 3.2 使用第三方库 `rich-argparse` + `pandas` + +```python +import pandas as pd +from io import StringIO + +def parse_table_with_pandas(table_text: str) -> pd.DataFrame: + """使用 pandas 解析 box table""" + # 清理边框字符 + cleaned = table_text + for char in '┌─┬┐├─┼┤└─┴┘│': + cleaned = cleaned.replace(char, '│' if char == '│' else ' ') + + lines = [line for line in cleaned.split('\n') if line.strip() and '│' in line] + + if len(lines) < 2: + return pd.DataFrame() + + # 分割列 + data = [] + for line in lines: + cells = [c.strip() for c in line.split('│') if c.strip()] + data.append(cells) + + headers = data[0] + rows = data[1:] + + return pd.DataFrame(rows, columns=headers) +``` + +### 3.3 使用专用库 `asciitable` / `terminaltables` + +```bash +pip install asciitable +``` + +```python +import asciitable + +def parse_with_asciitable(table_text: str) -> list[dict]: + """使用 asciitable 解析""" + try: + return asciitable.read(table_text) + except Exception: + # 降级到手动解析 + return parse_rich_table(table_text) +``` + +### 3.4 完整的解析器实现(推荐) + +```python +# comfy_cli_parser.py +import re +import json +from typing import List, Dict, Any, Optional +from dataclasses import dataclass + +@dataclass +class ParseResult: + success: bool + data: Any + error: Optional[str] = None + raw_output: str = "" + +class ComfyCliTableParser: + """comfy-cli box table 解析器""" + + # Box-drawing 字符 + BOX_CHARS = set('┌─┬┐├─┼┤└─┴┘│') + SEPARATOR_CHARS = set('─┼┤├') + + def parse(self, output: str, command: str) -> ParseResult: + """解析 comfy-cli 输出""" + try: + if self._is_table(output): + return self._parse_table(output) + elif self._is_list(output): + return self._parse_list(output) + elif self._is_key_value(output): + return self._parse_key_value(output) + else: + return ParseResult( + success=False, + data={"raw": output}, + error="Unknown output format" + ) + except Exception as e: + return ParseResult( + success=False, + data=None, + error=str(e), + raw_output=output + ) + + def _is_table(self, text: str) -> bool: + """检测是否为 box table""" + return any(c in text for c in '┌├└│') + + def _is_list(self, text: str) -> bool: + """检测是否为简单列表(每行一个项目)""" + lines = [l.strip() for l in text.strip().split('\n') if l.strip()] + return len(lines) > 1 and not self._is_table(text) + + def _is_key_value(self, text: str) -> bool: + """检测是否为键值对格式""" + return ':' in text and not self._is_table(text) + + def _parse_table(self, text: str) -> ParseResult: + """解析 box table""" + lines = text.strip().split('\n') + + # 提取数据行(跳过纯边框行) + data_lines = [] + for line in lines: + if '│' in line and not re.match(r'^[\s┌─┬┐├─┼┤└─┴┘]+$', line): + data_lines.append(line) + + if len(data_lines) < 2: + return ParseResult( + success=True, + data=[], + raw_output=text + ) + + # 解析表头 + header_line = data_lines[0] + headers = self._extract_cells(header_line) + + # 解析数据行 + rows = [] + for line in data_lines[1:]: + cells = self._extract_cells(line) + if len(cells) == len(headers): + rows.append(dict(zip(headers, cells))) + + return ParseResult( + success=True, + data=rows, + raw_output=text + ) + + def _extract_cells(self, line: str) -> List[str]: + """从一行中提取单元格内容""" + # 分割并清理 + parts = line.split('│') + cells = [] + for part in parts: + cell = part.strip() + # 移除剩余的边框字符 + cell = re.sub(r'^[┌├└┬┼┴]+', '', cell) + cell = re.sub(r'[┐┤┘┬┼┴]+$', '', cell) + if cell: + cells.append(cell) + return cells + + def _parse_list(self, text: str) -> ParseResult: + """解析简单列表""" + lines = [l.strip() for l in text.strip().split('\n') if l.strip()] + return ParseResult( + success=True, + data={"items": lines, "count": len(lines)}, + raw_output=text + ) + + def _parse_key_value(self, text: str) -> ParseResult: + """解析键值对""" + result = {} + for line in text.strip().split('\n'): + if ':' in line: + key, value = line.split(':', 1) + result[key.strip().lower().replace(' ', '_')] = value.strip() + return ParseResult( + success=True, + data=result, + raw_output=text + ) + + def to_json(self, result: ParseResult) -> str: + """转换为 JSON 字符串""" + return json.dumps({ + "success": result.success, + "data": result.data, + "error": result.error + }, indent=2, ensure_ascii=False) + + +# 使用示例 +if __name__ == "__main__": + parser = ComfyCliTableParser() + + # 示例:解析 node show 输出 + node_output = """ +┌──────────────────────────┬─────────────┬─────────┐ +│ Name │ Version │ Status │ +├──────────────────────────┼─────────────┼─────────┤ +│ ComfyUI-Impact-Pack │ v5.12.1 │ enabled │ +│ ComfyUI-Manager │ v2.45.0 │ enabled │ +└──────────────────────────┴─────────────┴─────────┘ +""" + + result = parser.parse(node_output, "node show") + print(parser.to_json(result)) + # 输出: + # { + # "success": true, + # "data": [ + # {"Name": "ComfyUI-Impact-Pack", "Version": "v5.12.1", "Status": "enabled"}, + # {"Name": "ComfyUI-Manager", "Version": "v2.45.0", "Status": "enabled"} + # ] + # } +``` + +--- + +## 方案 4:直接文件系统访问(最可靠) + +对于 `model list` 和 `node simple-show` 这类命令,直接读取文件系统比解析 CLI 输出更可靠: + +```python +from pathlib import Path +from typing import List, Dict +import json + +def list_models_direct(workspace: str, relative_path: str) -> Dict: + """直接读取文件系统获取模型列表""" + full_path = Path(workspace) / relative_path + + if not full_path.exists(): + return {"success": False, "error": "Path not found"} + + models = [] + for f in full_path.iterdir(): + if f.is_file() and f.suffix in ['.safetensors', '.ckpt', '.pt', '.bin']: + stat = f.stat() + models.append({ + "filename": f.name, + "size": stat.st_size, + "size_human": _format_size(stat.st_size), + "modified": stat.st_mtime + }) + + return { + "success": True, + "data": { + "path": relative_path, + "models": sorted(models, key=lambda x: x["filename"]), + "count": len(models) + } + } + +def _format_size(bytes: int) -> str: + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if bytes < 1024: + return f"{bytes:.1f} {unit}" + bytes /= 1024 + return f"{bytes:.1f} TB" +``` + +--- + +## REST API 集成建议 + +在 REST API 层,推荐**组合方案**: + +```python +# handlers/models.py +from aiohttp import web +from comfy_cli_parser import ComfyCliTableParser +import subprocess + +class ModelHandler: + def __init__(self, workspace: str): + self.workspace = workspace + self.parser = ComfyCliTableParser() + + async def list_models(self, request: web.Request) -> web.Response: + """列出模型""" + # 方案 4:直接文件系统访问(推荐) + data = list_models_direct(self.workspace, "models/checkpoints") + return web.json_response(data) + + async def show_nodes(self, request: web.Request) -> web.Response: + """显示节点列表""" + # 方案 3:CLI + 解析器 + cmd = ["comfy", "--workspace", self.workspace, "node", "simple-show", "installed"] + + result = subprocess.run(cmd, capture_output=True, text=True) + + parse_result = self.parser.parse(result.stdout, "node simple-show") + + return web.json_response({ + "success": parse_result.success, + "data": parse_result.data, + "raw_output": parse_result.raw_output if request.query.get("debug") else None + }) +``` + +--- + +## 现有工具推荐 + +| 工具 | 用途 | 链接 | +|------|------|------| +| `rich` | 生成和解析 table | `pip install rich` | +| `pandas` | 数据框处理 | `pip install pandas` | +| `asciitable` | ASCII 表格解析 | `pip install asciitable` | +| `texttable` | 文本表格生成/解析 | `pip install texttable` | + +--- + +## 总结 + +| 场景 | 推荐方案 | +|------|----------| +| **REST API 集成** | 方案 3(文本解析器)+ 方案 4(直接文件系统) | +| **Python 脚本集成** | 方案 2(rich Console 捕获) | +| **长期使用** | 方案 1(修改 comfy-cli 源码,添加 `--format json` 选项) | +| **简单列表查询** | 方案 4(直接文件系统访问) | + +--- + +*由 Zero (零号) 生成 | 2026-03-17*