Files
Jensen Zhang d7a15a7d5d Add comfy-cli REST API support
- Implement ComfyCliExecutor for async command execution
- Add REST API endpoints for comfy-cli:
  - Configuration management (/comfy-cli/config/*)
  - Model management (/comfy-cli/model/*)
  - Node management (/comfy-cli/node/*)
- Create API handlers: ConfigHandler, ModelHandler, NodeHandler
- Register routes in comfy_cli_routes.py
- Update main __init__.py to support dual CLI (cm-cli + comfy-cli)
- Add comprehensive documentation:
  - COMFY_CLI_API.md: Complete API reference
  - COMFY_CLI_QUICKSTART.md: Quick start guide
  - Update README.md with dual CLI support
  - Update AGENTS.md development guide
- Add comfy-cli reference documentation in docs/references/
- Remove trailing spaces from all Python and Markdown files
- Ensure all code passes syntax validation

Features:
- Async job tracking for long-running operations
- Structured JSON responses
- Comprehensive error handling
- Direct filesystem access for model listing (more reliable)
- Support for CivitAI and HuggingFace API tokens
2026-03-17 21:55:32 +08:00

135 lines
4.4 KiB
Python

"""
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,
)