mirror of
https://github.com/HuangYuChuh/ComfyUI_Skills_OpenClaw.git
synced 2026-08-14 00:47:57 +00:00
refactor(core): simplify workflow identification and update default ui port
Motivation: 1. Maintaining a redundant 'workflow_id' in schema.json was error-prone and confusing for users. 2. The previous default port 8189 frequently collided with other AI services or ComfyUI instances during multi-server deployment. Implementation: 1. Revoked 'workflow_id' reliance in schema logic; the system now strictly uses the directory name as the unique identifier. 2. Updated default web UI port to 18189 and added support for the 'OPENCLAW_UI_PORT' environment variable across scripts and settings. 3. Synchronized documentation and UI launcher scripts to reflect these changes.
This commit is contained in:
@@ -23,39 +23,22 @@ It converts natural language requests into structured skill arguments, maps them
|
||||
|
||||
## Installation
|
||||
|
||||
### Install As An OpenClaw Skill
|
||||
<details>
|
||||
<summary><strong>ComfyUI Skills for OpenClaw</strong></summary>
|
||||
|
||||
OpenClaw creates the default skill directory for you. Install this repository by entering that directory first, then cloning the project:
|
||||
Manual install:
|
||||
|
||||
```bash
|
||||
cd ~/.openclaw/workspace/skills
|
||||
git clone https://github.com/HuangYuChuh/ComfyUI_Skills_OpenClaw.git comfyui-skill-openclaw
|
||||
cd comfyui-skill-openclaw
|
||||
pip install -r requirements.txt
|
||||
cp config.example.json config.json
|
||||
```
|
||||
|
||||
Installed path example:
|
||||
Let OpenClaw install it for you:
|
||||
|
||||
- `~/.openclaw/workspace/skills/comfyui-skill-openclaw/`
|
||||
|
||||
OpenClaw will read `SKILL.md` and call:
|
||||
|
||||
- `scripts/registry.py list --agent`
|
||||
- `scripts/comfyui_client.py --workflow ... --args '...json...'`
|
||||
|
||||
Minimal checklist:
|
||||
|
||||
1. The project is placed under `~/.openclaw/workspace/skills/`.
|
||||
2. `SKILL.md` exists at the project root.
|
||||
3. Python dependencies are installed.
|
||||
4. `config.json` points to a reachable ComfyUI server.
|
||||
5. At least one workflow and schema are configured.
|
||||
|
||||
### AI-Native Install Via Agent
|
||||
|
||||
You can also ask an OpenClaw Agent to install this skill for you.
|
||||
|
||||
Use a prompt like this:
|
||||
Send this prompt to OpenClaw:
|
||||
|
||||
```text
|
||||
Please install this ComfyUI skill into my OpenClaw workspace.
|
||||
@@ -68,85 +51,126 @@ Requirements:
|
||||
2. Clone this repository into `comfyui-skill-openclaw`.
|
||||
3. Keep SKILL.md at the project root.
|
||||
4. Install Python dependencies from requirements.txt.
|
||||
5. Create config.json from config.example.json if missing.
|
||||
5. Run `cp config.example.json config.json`.
|
||||
6. Set the default ComfyUI server URL to http://127.0.0.1:8188 unless I specify another one.
|
||||
7. Make sure the skill can be discovered by OpenClaw after installation.
|
||||
7. Make sure OpenClaw can discover and call this skill after installation.
|
||||
```
|
||||
|
||||
### 1) Requirements
|
||||
</details>
|
||||
|
||||
- Python 3.10+
|
||||
- A running ComfyUI server (default: `http://127.0.0.1:8188`)
|
||||
## How To Configure ComfyUI Workflows
|
||||
|
||||
### 2) Prepare runtime config
|
||||
Before you start, make sure your ComfyUI server is already running. The default local address is `http://127.0.0.1:8188`.
|
||||
|
||||
`config.json` is the runtime config for this project. The CLI, UI, and OpenClaw-facing scripts all use it.
|
||||
### I. Configure Through The UI (Recommended)
|
||||
|
||||
Choose one of these two approaches:
|
||||
- macOS/Linux: `./ui/run_ui.sh`, or double-click `ui/run_ui.command`
|
||||
- Windows: `ui\run_ui.bat`
|
||||
- Open: `http://localhost:18189`
|
||||
- Upload a workflow JSON exported from ComfyUI in **Save (API Format)**
|
||||
- Add your first ComfyUI server in the UI
|
||||
- Select which parameters should be exposed to OpenClaw and save the mapping
|
||||
|
||||
- Manual: create `config.json` from `config.example.json` and fill in your first server yourself
|
||||
- UI-based (recommended): start the UI first, then add your first server there, and the UI will write it back into `config.json`
|
||||
### II. Configure Through Files
|
||||
|
||||
`config.json` example:
|
||||
#### 1) Edit `config.json`
|
||||
|
||||
```json
|
||||
Configure your server first. Minimal example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"id": "local",
|
||||
"name": "Local Mac",
|
||||
"url": "http://127.0.0.1:8188",
|
||||
"enabled": true,
|
||||
"output_dir": "./outputs"
|
||||
"id": "local", // Server ID, also used as the directory name and workflow prefix
|
||||
"name": "Local", // Display name
|
||||
"url": "http://127.0.0.1:8188", // ComfyUI server URL
|
||||
"enabled": true, // Whether this server is enabled
|
||||
"output_dir": "./outputs" // Image output directory
|
||||
}
|
||||
],
|
||||
"default_server": "local"
|
||||
"default_server": "local" // Default server ID
|
||||
}
|
||||
```
|
||||
|
||||
#### 2) Place Workflow Files
|
||||
|
||||
### 3) Start the local UI
|
||||
Each workflow uses its own directory, for example:
|
||||
|
||||
- macOS/Linux:
|
||||
```bash
|
||||
./ui/run_ui.sh
|
||||
```
|
||||
or double-click `ui/run_ui.command`
|
||||
- Windows:
|
||||
```bat
|
||||
ui\run_ui.bat
|
||||
```
|
||||
```bash
|
||||
data/local/Default/
|
||||
workflow.json # ComfyUI API-format workflow export
|
||||
schema.json # Parameter mapping exposed to OpenClaw/Agent
|
||||
```
|
||||
|
||||
Then open:
|
||||
#### 3) Write `schema.json`
|
||||
|
||||
- `http://localhost:8189`
|
||||
`schema.json` should include at least:
|
||||
|
||||
### 4) Add your first server and workflow
|
||||
- `description`
|
||||
- `enabled`
|
||||
- `parameters`
|
||||
|
||||
In the UI:
|
||||
Minimal example:
|
||||
|
||||
1. If you have not already configured a server in `config.json`, add a ComfyUI server first.
|
||||
2. Upload a workflow exported from ComfyUI via **Save (API Format)**.
|
||||
3. Expose the parameters you want the agent to use.
|
||||
4. Save the workflow mapping.
|
||||
```jsonc
|
||||
{
|
||||
"description": "Default test workflow", // Human-readable description for OpenClaw/Agent
|
||||
"enabled": true, // Whether this workflow is enabled
|
||||
"parameters": {
|
||||
"prompt": { // Parameter name exposed to OpenClaw/Agent
|
||||
"node_id": 10, // Node ID in workflow.json
|
||||
"field": "prompt", // Input field name under that node
|
||||
"required": true, // Whether this field is required
|
||||
"type": "string", // Parameter type
|
||||
"description": "Prompt text" // Parameter description
|
||||
},
|
||||
"seed": {
|
||||
"node_id": 10,
|
||||
"field": "seed",
|
||||
"required": false,
|
||||
"type": "int",
|
||||
"description": "Random seed"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5) Verify the installation
|
||||
Notes:
|
||||
|
||||
Check the registry:
|
||||
- The workflow ID comes directly from the directory name. For example, `data/local/Default/` means the workflow ID is `Default`
|
||||
- Each entry in `parameters` defines one input exposed to OpenClaw/Agent
|
||||
- `node_id` and `field` must match the actual node and input field in `workflow.json`
|
||||
|
||||
If you want a full example, refer to:
|
||||
|
||||
- `data/local/Default/workflow.json`
|
||||
- `data/local/Default/schema.json`
|
||||
|
||||
#### 4) Verify The Configuration
|
||||
|
||||
List installed workflows:
|
||||
|
||||
```bash
|
||||
python scripts/registry.py list
|
||||
```
|
||||
|
||||
Run one test job:
|
||||
Run a test generation:
|
||||
|
||||
```bash
|
||||
python scripts/comfyui_client.py \
|
||||
--workflow local/test \
|
||||
--args '{"prompt":"A premium product photo on aged driftwood, warm cinematic light","size":"3:4,1728x2304","seed":20260307}'
|
||||
--workflow <server_id>/<workflow_id> \
|
||||
--args '{"prompt":"test"}'
|
||||
```
|
||||
|
||||
If successful, output JSON includes local image path(s), for example:
|
||||
Example:
|
||||
|
||||
```bash
|
||||
python scripts/comfyui_client.py \
|
||||
--workflow local/Default \
|
||||
--args '{"prompt":"A premium product photo"}'
|
||||
```
|
||||
|
||||
If it succeeds, the output will look like:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -156,65 +180,51 @@ If successful, output JSON includes local image path(s), for example:
|
||||
}
|
||||
```
|
||||
|
||||
## Local Dashboard (UI)
|
||||
### III. Let OpenClaw/Agent Configure It For You
|
||||
|
||||
Start dashboard:
|
||||
- Let OpenClaw or another agent edit `config.json`
|
||||
- Let the agent write `workflow.json` and `schema.json` into the target workflow directory
|
||||
- After writing the files, let the agent run one verification step
|
||||
|
||||
- Via OpenClaw or any agent that can run local commands:
|
||||
```bash
|
||||
python3 ./ui/open_ui.py
|
||||
```
|
||||
- macOS/Linux:
|
||||
```bash
|
||||
./ui/run_ui.sh
|
||||
```
|
||||
or double-click `ui/run_ui.command`
|
||||
- Windows:
|
||||
```bat
|
||||
ui\run_ui.bat
|
||||
```
|
||||
### Workflow Requirements (Important)
|
||||
|
||||
Then open:
|
||||
**API-format workflows + a `Save Image` output node** are the baseline requirements for stable use. To avoid failed or empty runs:
|
||||
|
||||
- `http://localhost:8189`
|
||||
1. **The workflow must be exported in ComfyUI API format**
|
||||
- In ComfyUI, click **Save (API Format)**
|
||||
- Place the exported JSON at `data/<server_id>/<workflow_id>/workflow.json`
|
||||
|
||||
Use it to configure ComfyUI server URLs, outputs, and manage workflow/schema mapping.
|
||||
|
||||
Current highlights:
|
||||
|
||||
- Multi-server management with per-server and per-workflow enable/disable controls
|
||||
- Workflow search, sort, and drag-to-reorder
|
||||
- Upload workflow JSON and auto-fill workflow ID
|
||||
- Custom dialogs, dropdowns, and language switching for daily editing
|
||||
- One-click export/import for migrating the current skill configuration across machines
|
||||
2. **The workflow must end with a `Save Image` node**
|
||||
- The current client downloads results from ComfyUI output images
|
||||
- Without a `Save Image` node (or equivalent image output), the workflow may finish but return no downloadable image
|
||||
|
||||
---
|
||||
|
||||
## Multi-Server Management
|
||||
|
||||
You can now configure multiple ComfyUI servers, enabling your agent to dispatch workflows across different hardware (e.g., local machines, cloud A100s).
|
||||
You can configure multiple ComfyUI servers, so OpenClaw or another agent can dispatch jobs across different hardware targets such as a local GPU or a cloud instance.
|
||||
|
||||
### Concept
|
||||
### Core Concepts
|
||||
- **Dual-Layer Toggles**: Both *servers* and *individual workflows* can be enabled or disabled. A workflow is only visible to the AI agent if **both** the server and the workflow itself are enabled.
|
||||
- **Namespacing**: Workflows are identified with a composite ID: `<server_id>/<workflow_id>` (e.g., `local/sdxl-base` vs. `cloud-a100/sdxl-base`).
|
||||
|
||||
### Configuration via CLI
|
||||
A built-in CLI tool (`scripts/server_manager.py`) allows server management on headless Linux machines:
|
||||
### CLI Configuration
|
||||
On headless machines, you can use the built-in CLI tool `scripts/server_manager.py`:
|
||||
```bash
|
||||
python scripts/server_manager.py list
|
||||
python scripts/server_manager.py add --id cloud --name "Cloud Node" --url http://10.0.0.1:8188
|
||||
python scripts/server_manager.py disable cloud
|
||||
```
|
||||
*You can also manage servers fully via the Web UI.*
|
||||
*You can still manage all server settings through the Web UI.*
|
||||
|
||||
### Configuration Migration (Export / Import)
|
||||
|
||||
If you move this skill to a new path or deploy it on another machine, use the built-in bundle flow to transfer your current config and workflow mappings.
|
||||
If you move this skill to another path or another machine, use the built-in bundle flow to transfer your current config and workflow mappings.
|
||||
|
||||
UI flow:
|
||||
|
||||
- Click `Export Config` on the main page to download `openclaw-skill-export.json`
|
||||
- Before export, you can expand each server and uncheck workflows you do not want to include; everything is selected by default and servers start collapsed
|
||||
- Before export, you can expand each server and uncheck workflows you do not want to include; all workflows are selected by default and servers are collapsed by default
|
||||
- Open the UI on the target machine and click `Import Config`
|
||||
- Select the exported JSON bundle
|
||||
- Review the preview summary, then decide whether to also apply the source machine's default server, URL, and output directory
|
||||
@@ -240,22 +250,6 @@ Default import behavior:
|
||||
|
||||
---
|
||||
|
||||
## Workflow Requirements (Important)
|
||||
|
||||
To ensure a workflow can be executed by this project reliably:
|
||||
|
||||
1. **Export ComfyUI workflow in API format**
|
||||
- In ComfyUI, click **Save (API Format)**.
|
||||
- Use that exported JSON in `data/<server_id>/<workflow_id>/workflow.json`.
|
||||
|
||||
2. **The final output path should include a `Save Image` node**
|
||||
- The current client downloads generated results from ComfyUI output images.
|
||||
- Without a `Save Image` node (or equivalent image output in history), the tool may finish but return no downloadable image.
|
||||
|
||||
In short: **API-format workflow + Save Image output node** are required for stable usage.
|
||||
|
||||
---
|
||||
|
||||
## Known Caveats
|
||||
|
||||
- If ComfyUI returns HTTP 400 on `/prompt`, the workflow payload or parameter value is usually invalid.
|
||||
|
||||
+2
-4
@@ -62,7 +62,7 @@ cp config.example.json config.json
|
||||
|
||||
- macOS/Linux:`./ui/run_ui.sh`,或双击 `ui/run_ui.command`
|
||||
- Windows:`ui\run_ui.bat`
|
||||
- 访问:`http://localhost:8189`
|
||||
- 访问:`http://localhost:18189`
|
||||
- 上传从 ComfyUI 导出的工作流 JSON,格式必须是 **Save (API Format)**
|
||||
- 在 UI 中添加第一个 ComfyUI 服务器
|
||||
- 选择要暴露给 OpenClaw 的参数并保存映射
|
||||
@@ -102,7 +102,6 @@ data/local/Default/
|
||||
|
||||
`schema.json` 至少需要包含:
|
||||
|
||||
- `workflow_id`
|
||||
- `description`
|
||||
- `enabled`
|
||||
- `parameters`
|
||||
@@ -111,7 +110,6 @@ data/local/Default/
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"workflow_id": "Default", // 建议与目录名保持一致
|
||||
"description": "默认测试工作流", // 给 OpenClaw/Agent 看的工作流说明
|
||||
"enabled": true, // 是否启用这个工作流
|
||||
"parameters": {
|
||||
@@ -135,7 +133,7 @@ data/local/Default/
|
||||
|
||||
说明:
|
||||
|
||||
- `workflow_id` 建议与目录名保持一致;例如目录是 `data/local/Default/`,这里就写 `Default`
|
||||
- 工作流 ID 直接由目录名决定;例如目录是 `data/local/Default/`,工作流 ID 就是 `Default`
|
||||
- `parameters` 里的每个字段,表示一个要暴露给 OpenClaw/Agent 的输入参数
|
||||
- `node_id` 和 `field` 需要对应到 `workflow.json` 里实际的节点和输入字段
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ def get_workflows(is_agent=False):
|
||||
schema_data = json.load(f)
|
||||
|
||||
workflow_enabled = schema_data.get("enabled", True)
|
||||
workflow_id = schema_data.get("workflow_id", workflow_id)
|
||||
desc = schema_data.get("description", "")
|
||||
|
||||
# Apply dual-layer switch logic
|
||||
|
||||
@@ -298,19 +298,10 @@ def _collect_export_inventory() -> tuple[list[dict[str, Any]], list[ValidationIs
|
||||
))
|
||||
continue
|
||||
|
||||
actual_workflow_id = _normalize_identifier(schema_data.get("workflow_id")) or workflow_id
|
||||
if not _is_valid_identifier(actual_workflow_id):
|
||||
warnings.append(ValidationIssue(
|
||||
code="invalid_workflow_id",
|
||||
message="Skipped workflow with invalid workflow id during export.",
|
||||
context={"server_id": server_id, "workflow_id": actual_workflow_id},
|
||||
))
|
||||
continue
|
||||
|
||||
schema_payload = copy.deepcopy(schema_data)
|
||||
schema_payload["workflow_id"] = actual_workflow_id
|
||||
schema_payload.pop("workflow_id", None)
|
||||
server_entry["workflows"].append({
|
||||
"workflow_id": actual_workflow_id,
|
||||
"workflow_id": workflow_id,
|
||||
"workflow_data": workflow_data,
|
||||
"schema_data": schema_payload,
|
||||
"enabled": bool(schema_payload.get("enabled", True)),
|
||||
@@ -779,7 +770,7 @@ def apply_bundle_import(
|
||||
|
||||
workflow_payload = copy.deepcopy(workflow.get("workflow_data", {}))
|
||||
schema_payload = copy.deepcopy(workflow.get("schema_data", {}))
|
||||
schema_payload["workflow_id"] = workflow_id
|
||||
schema_payload.pop("workflow_id", None)
|
||||
|
||||
workflow_path = get_server_workflow_path(server_id, workflow_id)
|
||||
schema_path = get_server_schema_path(server_id, workflow_id)
|
||||
|
||||
+9
-3
@@ -2,8 +2,14 @@
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo Ensuring port 8189 is free...
|
||||
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8189') do taskkill /f /pid %%a >nul 2>&1
|
||||
if "%OPENCLAW_UI_PORT%"=="" (
|
||||
set "UI_PORT=18189"
|
||||
) else (
|
||||
set "UI_PORT=%OPENCLAW_UI_PORT%"
|
||||
)
|
||||
|
||||
echo Ensuring port %UI_PORT% is free...
|
||||
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :%UI_PORT%') do taskkill /f /pid %%a >nul 2>&1
|
||||
|
||||
where python >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
@@ -12,7 +18,7 @@ if errorlevel 1 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Starting ComfyUI OpenClaw Skill UI on http://127.0.0.1:8189
|
||||
echo Starting ComfyUI OpenClaw Skill UI on http://127.0.0.1:%UI_PORT%
|
||||
python app.py
|
||||
if errorlevel 1 (
|
||||
echo UI exited with an error.
|
||||
|
||||
+4
-3
@@ -3,6 +3,7 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||
UI_PORT="${OPENCLAW_UI_PORT:-18189}"
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
@@ -12,9 +13,9 @@ if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
|
||||
fi
|
||||
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
echo "Ensuring port 8189 is free..."
|
||||
lsof -ti:8189 | xargs kill -9 2>/dev/null || true
|
||||
echo "Ensuring port $UI_PORT is free..."
|
||||
lsof -ti:"$UI_PORT" | xargs kill -9 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "Starting ComfyUI OpenClaw Skill UI on http://127.0.0.1:8189"
|
||||
echo "Starting ComfyUI OpenClaw Skill UI on http://127.0.0.1:$UI_PORT"
|
||||
exec "$PYTHON_BIN" app.py
|
||||
|
||||
+2
-4
@@ -171,7 +171,6 @@ class UIStorageService:
|
||||
schema_data = _read_json(schema_path, fallback={})
|
||||
if isinstance(schema_data, dict):
|
||||
enabled = bool(schema_data.get("enabled", True))
|
||||
wf_id = str(schema_data.get("workflow_id") or wf_id)
|
||||
description = str(schema_data.get("description") or "")
|
||||
except Exception:
|
||||
enabled = True
|
||||
@@ -210,7 +209,7 @@ class UIStorageService:
|
||||
raise ValueError(f"Workflow data is invalid for {workflow_id}")
|
||||
|
||||
return {
|
||||
"workflow_id": str(schema_data.get("workflow_id") or workflow_id),
|
||||
"workflow_id": workflow_id,
|
||||
"server_id": server_id,
|
||||
"description": str(schema_data.get("description") or ""),
|
||||
"enabled": bool(schema_data.get("enabled", True)),
|
||||
@@ -247,7 +246,6 @@ class UIStorageService:
|
||||
|
||||
_write_json(workflow_path, workflow_data)
|
||||
schema = {
|
||||
"workflow_id": workflow_id,
|
||||
"description": description,
|
||||
"enabled": enabled,
|
||||
"parameters": schema_params,
|
||||
@@ -280,7 +278,7 @@ class UIStorageService:
|
||||
if not isinstance(schema, dict):
|
||||
schema = {}
|
||||
|
||||
schema["workflow_id"] = str(schema.get("workflow_id") or workflow_id)
|
||||
schema.pop("workflow_id", None)
|
||||
schema["enabled"] = enabled
|
||||
schema.setdefault("description", "")
|
||||
schema.setdefault("parameters", {})
|
||||
|
||||
+13
-1
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from shared.config import (
|
||||
@@ -15,7 +16,18 @@ CONFIG_EXAMPLE_PATH = BASE_DIR / "config.example.json"
|
||||
OUTPUTS_DIR = BASE_DIR / "outputs"
|
||||
|
||||
DEFAULT_HOST = "127.0.0.1"
|
||||
DEFAULT_PORT = 8189
|
||||
|
||||
|
||||
def _read_default_port() -> int:
|
||||
raw = os.environ.get("OPENCLAW_UI_PORT", "18189").strip()
|
||||
try:
|
||||
port = int(raw)
|
||||
except ValueError:
|
||||
return 18189
|
||||
return port if 1 <= port <= 65535 else 18189
|
||||
|
||||
|
||||
DEFAULT_PORT = _read_default_port()
|
||||
DEFAULT_COMFYUI_SERVER_URL = "http://127.0.0.1:8188"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user