Skill: agentopen/comfyui-running (version 3.0.2)

--- name: comfyui-running description: 全自动运行 ComfyUI 工作流：通过 REST API 执行工作流，支持 Windows / Linux / WSL 跨平台。By comfyui资源网 - www.comfyorg.cn metadata: {"clawhub":{"emoji":"🎨","requires":{"bins":["python3","python"],"os":["windows","linux","darwin"]},"tags":["comfyui","automation","workflow","image-generation"]}} --- --- 全自动运行 ComfyUI 工作流：通过 CDP 协议控制 Edge 浏览器，自动化操作 ComfyUI 界面，并通过 REST API 执行工作流。 --- > 全自动运行 ComfyUI 工作流 | By **[comfyui资源网](https://www.comfyorg.cn)** [![comfyui资源网](https://img.shields.io/badge/comfyui%E8%B5%84%E6%BA%90%E7%BD%91-ComfyUI%E8%B5%84%E6%BA%90%E5%AE%A3%E5%92%8C-orange?style=for-the-badge&logo=firefox)](https://www.comfyorg.cn) --- ## 核心功能 | 功能 | 说明 | |------|------| | 🚀 自动启动 | 检测 ComfyUI 状态，未运行则自动启动 | | 📋 工作流管理 | 列出、加载、修改工作流 | | 🎨 文本生图 | 一句话生成图片 | | 🔄 跨平台 | Windows / Linux / WSL 自动适配 | --- ## 快速开始 ### 一句话生成图片 ```python from comfyui_automation import quick_generate result = quick_generate("a beautiful cat") # result = { # "success": True, # "image_path": "H:\\ComfyUI-aki-v3\\ComfyUI\\output\\ComfyUI_00001_.png", # "prompt_id": "xxx-xxx" # } ``` ### 完整控制 ```python from comfyui_automation import ComfyUIAutomation automation = ComfyUIAutomation() automation.ensure_comfyui_running() result = automation.generate( prompt="1girl, portrait", workflow_name="文生图", negative_prompt="low quality, blurry", steps=25, seed=None, # None=随机 batch_size=1 ) if result["success"]: print(f"图片路径: {result['image_path']}") ``` --- ## 配置说明 ### config.json 配置项 | 配置项 | 必填 | 默认值 | 说明 | |--------|------|--------|------| | `comfyui_root` | ✅ | - | ComfyUI 根目录（包含 main.py） | | `python_path` | | 自动检测 | Python 解释器路径 | | `workflows_dir` | | 自动检测 | 工作流 JSON 目录 | | `output_dir` | | `{comfyui_root}/output` | 图片输出目录 | | `comfyui_port` | | 8188 | ComfyUI 端口 | | `ui_type` | | `auto` | UI 类型：`aki`=秋叶版，`official`=官方版 | | `browser_path` | | 自动检测 | 浏览器路径（CDP 模式用） | ### 路径格式（跨平台） | 平台 | 示例 | |------|------| | **Windows** | `H:/ComfyUI-aki-v3/ComfyUI` 或 `H:\\ComfyUI-aki-v3\\ComfyUI` | | **Linux** | `/opt/ComfyUI/ComfyUI` | | **WSL** | `/mnt/h/ComfyUI-aki-v3/ComfyUI` | > ⚠️ 推荐使用正斜杠 `/`，Python 会自动处理跨平台兼容性 ### 跨平台自动检测路径 | 平台 | 检测路径 | |------|---------| | **Windows** | `H:\ComfyUI-aki-v3\ComfyUI`, `D:\ComfyUI\ComfyUI`, 用户目录等 | | **Linux** | `/opt/ComfyUI/ComfyUI`, `~/ComfyUI/ComfyUI` | | **WSL** | `/mnt/h/ComfyUI-aki-v3/ComfyUI` 等 | --- ## API 参考 ### `ComfyUIAutomation` 类 ```python automation = ComfyUIAutomation() # ===== 生命周期 ===== automation.is_comfyui_running() # 检测是否运行 automation.start_comfyui() # 启动 ComfyUI automation.ensure_comfyui_running() # 确保运行（未运行则启动） # ===== 工作流 ===== automation.list_workflows() # 列出可用工作流 automation.load_workflow(name) # 加载工作流 # ===== 执行生成 ===== automation.generate( prompt="描述", # 正向提示词 workflow_name="文生图", # 工作流名称 negative_prompt="", # 负向提示词 steps=20, # 采样步数 seed=None, # 种子，None=随机 batch_size=1 # 批次数 ) ``` ### `quick_generate(prompt, **kwargs)` 一句话生成快捷函数。 --- ## 命令行使用 ```bash # 直接生成 python -m lib.comfyui_automation "a cute cat" -s 20 -w 文生图 # 参数 # prompt - 提示词 # -s, --steps - 采样步数（默认20） # -w, --workflow - 工作流名称（默认default） # -n, --negative - 负向提示词 # --seed - 随机种子 # --batch - 批次数（默认1） ``` --- ## 依赖 ```bash pip install requests websockets ``` --- ## 故障排除 ### 1. 'int' object is not subscriptable **原因：** 节点链接格式错误 ```json "model": ["4", 0] ✅ [node_id, output_slot] "model": 4 ❌ 错误！ ``` ### 2. main.py not found **解决：** 检查 `config.json` 中 `comfyui_root` 是否指向包含 `main.py` 的目录 ### 3. Windows PowerShell `&&` 语法错误 ```powershell # ❌ 错误 (PowerShell 5.x) cd "..." && python -c "..." # ✅ 正确 Set-Location "..." python -c "..." ``` --- ## 📌 推荐资源 | 资源类型 | 链接 | |----------|------| | 🏠 首页 | https://www.comfyorg.cn | | 📖 教程 | https://www.comfyorg.cn/tutorial | | 🎭 工作流 | https://www.comfyorg.cn/workflow | | 💬 整合包 | https://www.comfyorg.cn/shop/1680.html | --- **版本**：2.0.0 **许可**：MIT

path: comfyui_browser.py
content: #!/usr/bin/env python3
"""
ComfyUI 全自动运行脚本 - 修复版 v2

关键修复（2026-03-29）：
1. graphToPrompt() 返回空{} → 用 REST API /prompt 代替 Ctrl+Enter
2. UI 批次字段不控制 KSampler batch_size → 用 number=1 API 调用控制批次数
3. number=N 多次执行会共享文件名 → 改用 N 次 number=1 调用
4. Ctrl+Enter 可能添加陈旧队列条目 → 先清空队列再运行

用法:
    python3 comfyui_browser.py <TAB_ID> <action> [arg]

示例:
    # 完整流程：加载 → 设批次 → 运行
    python3 comfyui_browser.py <TAB_ID> run_workflow "fireredimage" 2

    # 单独步骤
    python3 comfyui_browser.py <TAB_ID> click_workflow "fireredimage"
    python3 comfyui_browser.py <TAB_ID> set_batch 2
    python3 comfyui_browser.py <TAB_ID> api_run 2
"""
import json, asyncio, websockets, base64, sys, urllib.request, os, time, random

# 清除所有代理环境变量
for k in list(os.environ.keys()):
    if 'proxy' in k.lower():
        del os.environ[k]

# 添加 lib 目录到 path
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
if os.path.isdir(os.path.join(_SCRIPT_DIR, "lib")):
    sys.path.insert(0, os.path.join(_SCRIPT_DIR, "lib"))

from comfyui_config import get_comfyui_root, get_workflows_dir, get_cdp_port, get_comfyui_port

DEBUG_PORT = get_cdp_port()
COMFYUI_PORT = get_comfyui_port()


# ============ CDP 核心函数 ============

async def cdp(ws, method, params=None):
    nid = [0]
    async def send():
        nid[0] += 1
        await ws.send(json.dumps({"id": nid[0], "method": method, "params": params or {}}))
        return nid[0]
    async def recv(wait_id):
        while True:
            msg = await ws.recv()
            obj = json.loads(msg)
            if obj.get("id") == wait_id:
                return obj
    sid = await send()
    return await recv(sid)


def find_comfyui_tab(debug_port=DEBUG_PORT):
    try:
        url = f"http://127.0.0.1:{debug_port}/json"
        tabs = json.loads(urllib.request.urlopen(url, timeout=5).read())
        for t in tabs:
            if "127.0.0.1:8188" in t.get("url", "") and t.get("type") == "page":
                return t
    except Exception as e:
        print(f"Error finding tab: {e}", file=sys.stderr)
    return None


def get_all_tabs(debug_port=DEBUG_PORT):
    try:
        url = f"http://127.0.0.1:{debug_port}/json"
        return json.loads(urllib.request.urlopen(url, timeout=5).read())
    except:
        return []


def get_ws_url(tab_id, debug_port=DEBUG_PORT):
    return f"ws://127.0.0.1:{debug_port}/devtools/page/{tab_id}"


async def ws_connect_with_retry(ws_url, max_retries=3, delay=1.0):
    """WebSocket连接，带重试（处理瞬时500错误）"""
    last_err = None
    for attempt in range(max_retries):
        try:
            ws = await websockets.connect(ws_url, ping_interval=None, open_timeout=10)
            return ws
        except Exception as e:
            last_err = e
            if attempt < max_retries - 1:
                await asyncio.sleep(delay * (attempt + 1))
    raise last_err


async def screenshot(ws, path):
    result = await cdp(ws, "Page.captureScreenshot", {"format": "png"})
    if "result" in result and "data" in result["result"]:
        with open(path, "wb") as f:
            f.write(base64.b64decode(result["result"]["data"]))
        return True
    return False


async def press_key(ws, key):
    """发送按键（Enter=确认, Escape=关闭, w=打开侧边栏等）"""
    modifiers = 2 if key in ("Ctrl+Enter", "Control+Enter") else 0
    actual_key = {"Ctrl+Enter": "Enter", "Control+Enter": "Enter"}.get(key, key)
    code = f"Key{actual_key.upper()}" if len(actual_key) == 1 else actual_key

    await cdp(ws, "Input.dispatchKeyEvent", {"type": "keyDown", "modifiers": modifiers, "key": actual_key, "code": code})
    await asyncio.sleep(0.05)
    await cdp(ws, "Input.dispatchKeyEvent", {"type": "keyUp", "modifiers": modifiers, "key": actual_key, "code": code})
    return True


async def click_at(ws, x, y):
    await cdp(ws, "Input.dispatchMouseEvent", {"type": "mousePressed", "x": x, "y": y, "button": "left", "clickCount": 1})
    await asyncio.sleep(0.05)
    await cdp(ws, "Input.dispatchMouseEvent", {"type": "mouseReleased", "x": x, "y": y, "button": "left", "clickCount": 1})


# ============ 工作流点击（PrimeVue Tree）============

async def click_workflow(ws, name_fragment):
    """
    点击侧边栏中的工作流文件并加载。
    ComfyUI-aki 使用 PrimeVue Tree 组件，文件嵌套在文件夹中。
    """
    async def find_workflow_file():
        result = await cdp(ws, "Runtime.evaluate", {
            "expression": """
            (function(){
                var all = document.querySelectorAll('*');
                var result = null;
                all.forEach(function(el) {
                    var text = el.textContent.trim();
                    if (text.endsWith('.json')) {
                        var r = el.getBoundingClientRect();
                        if (r.width > 50 && r.height >= 15 && r.height < 60 && r.y < 2000) {
                            result = {
                                text: text,
                                x: Math.round(r.x + r.width/2),
                                y: Math.round(r.y + r.height/2),
                                y_abs: Math.round(r.y)
                            };
                        }
                    }
                });
                return result ? JSON.stringify(result) : 'null';
            })()
            """.replace("fireredimage", name_fragment),
            "returnByValue": True
        })
        raw = result.get("result", {}).get("result", {}).get("value", "null")
        if raw and raw != "null":
            try:
                return json.loads(raw)
            except:
                pass
        return None

    async def find_parent_folder(file_y):
        result = await cdp(ws, "Runtime.evaluate", {
            "expression": """
            (function(){
                var all = document.querySelectorAll('*');
                var result = null;
                var targetY = %d;
                all.forEach(function(el) {
                    var text = el.textContent.trim();
                    var cls = el.className || '';
                    var isFolder = cls.includes('tree-folder') || cls.includes('p-tree-node-content');
                    var r = el.getBoundingClientRect();
                    if (isFolder && r.width > 50 && r.height > 0 && r.y < targetY && r.y > 100) {
                        if (!result || r.y > result.y) {
                            result = {
                                text: text.substring(0, 60),
                                x: Math.round(r.x + 10),
                                y: Math.round(r.y + r.height/2),
                                y_abs: Math.round(r.y)
                            };
                        }
                    }
                });
                return result ? JSON.stringify(result) : 'null';
            })()
            """ % file_y,
            "returnByValue": True
        })
        raw = result.get("result", {}).get("result", {}).get("value", "null")
        if raw and raw != "null":
            try:
                return json.loads(raw)
            except:
                pass
        return None

    # 尝试直接找到文件
    file_info = await find_workflow_file()

    if not file_info:
        # 文件未找到？找并展开父文件夹
        keyword = name_fragment.split('-')[0].split('image')[0][:10]
        result = await cdp(ws, "Runtime.evaluate", {
            "expression": """
            (function(){
                var all = document.querySelectorAll('*');
                var candidates = [];
                all.forEach(function(el) {
                    var text = el.textContent.trim();
                    var cls = el.className || '';
                    var isFolder = cls.includes('tree-folder') || cls.includes('p-tree-node-content');
                    var r = el.getBoundingClientRect();
                    if (isFolder && r.width > 50 && r.height > 0 && r.y > 100 && r.y < 2000) {
                        candidates.push({
                            text: text.substring(0, 60),
                            x: Math.round(r.x + 10),
                            y: Math.round(r.y + r.height/2),
                            y_abs: Math.round(r.y)
                        });
                    }
                });
                if (candidates.length > 0) {
                    candidates.sort(function(a, b) { return b.y_abs - a.y_abs; });
                    return JSON.stringify(candidates[0]);
                }
                return 'null';
            })()
            """.replace("firered", keyword),
            "returnByValue": True
        })
        raw = result.get("result", {}).get("result", {}).get("value", "null")
        if raw and raw != "null":
            try:
                folder = json.loads(raw)
                await click_at(ws, folder["x"], folder["y"])
                await asyncio.sleep(1.5)
                file_info = await find_workflow_file()
            except:
                pass

    if not file_info:
        return False, f"工作流文件 '{name_fragment}' 未找到"

    # 点击文件
    click_result = await cdp(ws, "Runtime.evaluate", {
        "expression": json.dumps(
            """
            (function() {
                var all = document.querySelectorAll('*');
                for (var i = 0; i < all.length; i++) {
                    var el = all[i];
                    var r = el.getBoundingClientRect();
                    if (r.width > 50 && r.height >= 15 && r.height < 60 && r.y < 2000) {
                        var text = el.textContent.trim();
                        if (text === '%s') {
                            el.click();
                            return JSON.stringify({success: true, clicked: text});
                        }
                    }
                }
                return JSON.stringify({success: false});
            })()
            """ % file_info["text"]
        ),
        "returnByValue": True
    })
    val = click_result.get("result", {}).get("result", {}).get("value", "{}")
    try:
        val = json.loads(val)
    except:
        return False, "点击结果解析失败"

    if not val.get("success"):
        return False, "点击工作流文件失败"

    return True, val.get("clicked", "")


# ============ 工作流标签激活 ============

async def click_workflow_tab(ws):
    """点击左上角的工作流名称标签，激活当前工作流"""
    result = await cdp(ws, "Runtime.evaluate", {
        "expression": """
        (function() {
            var tabs = document.querySelectorAll('[class*="tab"]');
            for (var i = 0; i < tabs.length; i++) {
                var el = tabs[i];
                var r = el.getBoundingClientRect();
                var text = el.textContent.trim();
                if (r.y < 80 && r.width > 80 && r.height > 20 && text.length > 3) {
                    el.click();
                    return {success: true, text: text.substring(0, 60), x: Math.round(r.x), y: Math.round(r.y)};
                }
            }
            return {success: false};
        })()
        """,
        "returnByValue": True
    })
    val = result.get("result", {}).get("value", {})
    return val.get("success", False), val.get("text", "")


# ============ 批次设置（PrimeVue InputNumber）============

async def set_batch(ws, count=2):
    """设置批次数量。使用 PrimeVue nativeSetter 方法。"""
    result = await cdp(ws, "Runtime.evaluate", {
        "expression": """
        (function(){
            var input = document.querySelector('.p-inputnumber-input');
            if (!input) return 'null';
            var r = input.getBoundingClientRect();
            return JSON.stringify({x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2), val: input.value});
        })()
        """,
        "returnByValue": True
    })
    raw = result.get("result", {}).get("result", {}).get("value", "null")
    try:
        data = json.loads(raw) if raw != 'null' else None
    except:
        data = None

    if not data:
        return False, {"error": "no batch input found"}

    current = int(data.get('val', '1'))
    if current == count:
        return True, {"method": "already_correct", "value": current}

    # PrimeVue 专用方法
    js_result = await cdp(ws, "Runtime.evaluate", {
        "expression": f"""
        (function(){{
            var input = document.querySelector('.p-inputnumber-input');
            if (!input) return 'no input';
            input.focus();
            input.select();
            var nativeSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
            nativeSetter.call(input, '');
            input.dispatchEvent(new Event('input', {{bubbles: true}}));
            nativeSetter.call(input, '{count}');
            input.dispatchEvent(new Event('input', {{bubbles: true}}));
            input.dispatchEvent(new Event('change', {{bubbles: true}}));
            input.blur();
            return input.value;
        }})()
        """
    })
    js_val = js_result.get("result", {}).get("result", {}).get("value", "")
    await asyncio.sleep(0.5)

    # 验证
    verify = await cdp(ws, "Runtime.evaluate", {
        "expression": "(function(){var el=document.querySelector('.p-inputnumber-input');return el?el.value:'not found';})()",
        "returnByValue": True
    })
    val = verify.get("result", {}).get("result", {}).get("value", "")
    ok = str(count) in str(val)
    return ok, {"method": "primevue_nativeSetter", "verified_value": val, "target": count}


# ============ API 运行（核心修复）============

def get_history_prompt(workflow_prefix="Firered"):
    """
    从历史中获取目标工作流的 prompt 结构。
    返回 (prompt_dict, history_id) 或 (None, None)
    """
    try:
        url = f"http://127.0.0.1:{COMFYUI_PORT}/history"
        resp = urllib.request.urlopen(url, timeout=10)
        data = json.loads(resp.read())

        for history_id, entry in data.items():
            prompt = entry.get("prompt", [])
            if isinstance(prompt, list) and len(prompt) >= 3 and len(prompt[2]) > 0:
                nodes = prompt[2]
                for nid, ndata in nodes.items():
                    if ndata.get("class_type") == "SaveImage":
                        prefix = ndata.get("inputs", {}).get("filename_prefix", "")
                        if workflow_prefix in str(prefix):
                            # 深拷贝避免修改原数据
                            import copy
                            prompt_copy = copy.deepcopy(nodes)
                            return prompt_copy, history_id
    except Exception as e:
        print(f"Error getting history: {e}", file=sys.stderr)
    return None, None


def randomize_seeds(prompt_nodes, seed_range=(10000000000000, 99999999999999)):
    """随机化所有 KSampler/KSamplerAdvanced 节点的种子"""
    for nid, ndata in prompt_nodes.items():
        ctype = ndata.get("class_type", "")
        if "Sampler" in ctype and "inputs" in ndata:
            if "seed" in ndata["inputs"]:
                ndata["inputs"]["seed"] = random.randint(*seed_range)


def clear_queue():
    """清空当前队列（删除 pending 状态的条目）"""
    try:
        # 获取队列
        url = f"http://127.0.0.1:{COMFYUI_PORT}/queue"
        resp = urllib.request.urlopen(url, timeout=5)
        queue_data = json.loads(resp.read())

        # 删除队列中的项目
        for item in queue_data.get("queue_pending", []):
            prompt_id = item.get("prompt_id")
            if prompt_id:
                try:
                    del_url = f"http://127.0.0.1:{COMFYUI_PORT}/queue/pending/{prompt_id}"
                    req = urllib.request.Request(del_url, method="DELETE")
                    urllib.request.urlopen(req, timeout=5)
                    print(f"Cleared stale queue entry: {prompt_id[:20]}")
                except:
                    pass
    except Exception as e:
        print(f"Queue clear warning: {e}", file=sys.stderr)


def api_run_workflow(prompt_nodes, batch_count=1):
    """
    通过 REST API 运行工作流（绕过 graphToPrompt 返回空的问题）。

    重要：必须使用 number=1 多次调用，而非 number=N！
    因为 number=N 的多次执行会共享 SaveImage 的文件名计数器，导致覆盖。

    Args:
        prompt_nodes: 从历史获取的 prompt 结构
        batch_count: 批次数（会调用 N 次 API）

    Returns:
        list of (success, prompt_id)
    """
    results = []

    # 清空陈旧队列条目
    clear_queue()

    for i in range(batch_count):
        # 每次调用都随机化种子
        prompt_copy = json.loads(json.dumps(prompt_nodes))
        randomize_seeds(prompt_copy)

        url = f"http://127.0.0.1:{COMFYUI_PORT}/prompt"
        payload = json.dumps({"number": 1, "prompt": prompt_copy}).encode()
        req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})

        try:
            resp = urllib.request.urlopen(req, timeout=30)
            result = json.loads(resp.read())
            prompt_id = result.get("prompt_id", "?")
            results.append((True, prompt_id))
            print(f"  Batch {i+1}/{batch_count}: prompt_id={prompt_id[:20]}, queued successfully")
        except urllib.error.HTTPError as e:
            body = e.read().decode()[:200]
            results.append((False, f"HTTP {e.code}: {body}"))
            print(f"  Batch {i+1}/{batch_count}: FAILED {e.code}")
        except Exception as e:
            results.append((False, str(e)))
            print(f"  Batch {i+1}/{batch_count}: ERROR {e}")

        if i < batch_count - 1:
            time.sleep(1)  # 避免过快提交

    return results


async def wait_for_completion(prompt_ids, check_interval=30, timeout=600):
    """等待所有 prompt_ids 执行完成"""
    start = time.time()
    completed = set()

    while time.time() - start < timeout:
        try:
            url = f"http://127.0.0.1:{COMFYUI_PORT}/history"
            resp = urllib.request.urlopen(url, timeout=10)
            data = json.loads(resp.read())

            for pid in prompt_ids:
                if pid in completed:
                    continue
                entry = data.get(pid, {})
                status = entry.get("status", {}).get("status_str", "")
                if status in ("success", "failed"):
                    completed.add(pid)
                    print(f"  {pid[:20]}: {status}")

        except Exception as e:
            print(f"  Wait error: {e}")

        if len(completed) == len(prompt_ids):
            print(f"All {len(completed)} executions completed!")
            return True

        await asyncio.sleep(check_interval)

    print(f"Timeout: {len(completed)}/{len(prompt_ids)} completed")
    return False


# ============ 主命令处理 ============

async def main():
    if len(sys.argv) < 2:
        print("用法: comfyui_browser.py <TAB_ID> <action> [arg]")
        print("  find_tab                  发现 ComfyUI 标签页")
        print("  screenshot [path]          截图")
        print("  press_key <key>           按键（w/Escape/Enter/F11）")
        print("  click_workflow <name>      点击侧边栏工作流")
        print("  click_workflow_tab         点击工作流标签激活")
        print("  set_batch <n>             设置批次")
        print("  api_run <n>               用 REST API 运行 N 批次")
        print("  run_workflow <name> <n>   完整流程：点击→设批次→API运行N批次")
        print("  clear_queue               清空队列")
        print("  status                    队列状态")
        sys.exit(1)

    action = sys.argv[1]

    # Actions that don't require TAB_ID
    if action == "find_tab":
        tab = find_comfyui_tab()
        if tab:
            print(f"Found: {tab['id']} | {tab.get('title','?')[:50]}")
        else:
            print("ComfyUI tab not found")
            sys.exit(1)
        return

    if action == "clear_queue":
        clear_queue()
        print("Queue cleared")
        return

    if action == "status":
        try:
            url = f"http://127.0.0.1:{COMFYUI_PORT}/queue"
            resp = urllib.request.urlopen(url, timeout=5)
            data = json.loads(resp.read())
            running = len(data.get("queue_running", []))
            pending = len(data.get("queue_pending", []))
            print(f"Queue: running={running}, pending={pending}")
        except Exception as e:
            print(f"Error: {e}")
        return

    if action == "api_run":
        # api_run [batch]  - no tab_id needed, uses REST API directly
        if len(sys.argv) < 3:
            batch = 2
        else:
            batch = int(sys.argv[2])

        print(f"Running workflow with batch={batch} via REST API...")
        prompt_nodes, hist_id = get_history_prompt()
        if not prompt_nodes:
            print("ERROR: No workflow found in history. Please run the workflow once via UI first.")
            sys.exit(1)
        print(f"Using history entry: {hist_id[:30]}")

        results = api_run_workflow(prompt_nodes, batch)
        successes = sum(1 for ok, _ in results if ok)
        print(f"\nResults: {successes}/{len(results)} queued successfully")

        if successes > 0:
            prompt_ids = [pid for ok, pid in results if ok]
            await wait_for_completion(prompt_ids)

        return

    if action == "run_workflow":
        # run_workflow <TAB_ID> run_workflow <workflow_name> <batch_count>
        if len(sys.argv) < 5:
            print("Usage: comfyui_browser.py <TAB_ID> run_workflow <workflow_name> <batch_count>")
            sys.exit(1)
        tab_id = sys.argv[1]
        wf_name = sys.argv[3]
        batch_count = int(sys.argv[4])

        ws_url = get_ws_url(tab_id)
        async with await ws_connect_with_retry(ws_url) as ws:
            # 1. 打开侧边栏
            print("Step 1: Opening sidebar...")
            await press_key(ws, "w")
            await asyncio.sleep(2)

            # 2. 清空搜索（如有残留）
            await cdp(ws, "Runtime.evaluate", {
                "expression": """
                (function(){
                    var inputs = document.querySelectorAll('input');
                    for (var i = 0; i < inputs.length; i++) {
                        var ph = inputs[i].placeholder || '';
                        if (ph.includes('搜索') || ph.includes('search')) {
                            inputs[i].value = '';
                            inputs[i].dispatchEvent(new Event('input', {bubbles: true}));
                            return 'cleared';
                        }
                    }
                    return 'not found';
                })()
                """,
                "returnByValue": True
            })
            await asyncio.sleep(0.5)

            # 3. 点击工作流文件
            print(f"Step 2: Loading workflow '{wf_name}'...")
            ok, msg = await click_workflow(ws, wf_name)
            if not ok:
                print(f"WARNING: {msg}")
            else:
                print(f"  Clicked: {msg}")
            await asyncio.sleep(3)

            # 4. ESC 关闭侧边栏
            await press_key(ws, "Escape")
            await asyncio.sleep(1)

            # 5. 点击工作流标签激活
            print("Step 3: Activating workflow tab...")
            ok, text = await click_workflow_tab(ws)
            if ok:
                print(f"  Tab: {text}")
            await asyncio.sleep(1)

            # 6. 设置批次（仅修改 UI 显示，不影响实际执行）
            print(f"Step 4: Setting batch to {batch_count}...")
            ok, detail = await set_batch(ws, batch_count)
            print(f"  {'OK' if ok else 'FAILED'}: {detail}")
            await asyncio.sleep(0.5)

        # 7. 用 REST API 运行
        print(f"Step 5: Running {batch_count} batches via REST API...")
        prompt_nodes, hist_id = get_history_prompt()
        if not prompt_nodes:
            print("ERROR: No workflow found in history.")
            sys.exit(1)
        print(f"  Using history: {hist_id[:30]}")

        results = api_run_workflow(prompt_nodes, batch_count)
        successes = sum(1 for ok, _ in results if ok)
        print(f"\nResults: {successes}/{len(results)} queued")

        if successes > 0:
            prompt_ids = [pid for ok, pid in results if ok]
            await wait_for_completion(prompt_ids)

        return

    if len(sys.argv) < 3:
        print(f"Error: {action} requires TAB_ID")
        print("Usage:")
        print("  TAB_ID ACTION [ARG]:  python3 comfyui_browser.py <TAB_ID> <ACTION> [ARG]")
        print("  ACTION (no TAB):       python3 comfyui_browser.py find_tab|clear_queue|api_run [batch]")
        sys.exit(1)

    tab_id = sys.argv[1]  # sys.argv[1] = TAB_ID
    real_action = sys.argv[2]  # sys.argv[2] = ACTION
    real_arg = sys.argv[3] if len(sys.argv) > 3 else None  # sys.argv[3] = arg

    ws_url = get_ws_url(tab_id)
    try:
        ws = await ws_connect_with_retry(ws_url)
        async with ws:
            if real_action == "screenshot":
                path = real_arg or "/tmp/comfyui.png"
                ok = await screenshot(ws, path)
                print(f"{'OK' if ok else 'FAILED'}: {path}")

            elif real_action == "press_key":
                ok = await press_key(ws, real_arg or "Enter")
                print(f"Key sent: {real_arg}")

            elif real_action == "set_batch":
                count = int(real_arg) if real_arg else 2
                ok, detail = await set_batch(ws, count)
                print(f"Batch {'OK' if ok else 'FAILED'}: {detail}")

            elif real_action == "click_workflow":
                # 按 w 打开侧边栏
                await press_key(ws, "w")
                await asyncio.sleep(2)
                ok, clicked = await click_workflow(ws, real_arg or "")
                if ok:
                    await asyncio.sleep(3)
                    await press_key(ws, "Escape")
                    await asyncio.sleep(1)
                    ok2, text = await click_workflow_tab(ws)
                    print(f"OK: {clicked} loaded, tab={text}")
                else:
                    print(f"FAILED: {clicked}")

            elif real_action == "click_workflow_tab":
                ok, text = await click_workflow_tab(ws)
                print(f"{'OK' if ok else 'FAILED'}: {text}")

            elif real_action == "status":
                r = await cdp(ws, "Runtime.evaluate", {
                    "expression": "document.querySelector('.comfy-menu-header')?.textContent || 'not found'",
                    "returnByValue": True
                })
                print(f"Queue: {r.get('result',{}).get('result',{}).get('value')}")

            else:
                print(f"Unknown action: {real_action}")
                sys.exit(1)

    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    asyncio.run(main())


path: config.json
content: {
  // =============================================================================
  // ComfyUI Running Skill - 配置文件
  // =============================================================================
  // 
  // 【配置优先级】
  // 环境变量 > config.json > 自动检测默认值
  //
  // 【环境变量】
  // COMFYUI_ROOT       - ComfyUI 安装根目录
  // COMFYUI_PORT       - ComfyUI 端口（默认 8188）
  // CDP_PORT           - Chrome/Edge 调试端口（默认 9222）
  // COMFYUI_WORKFLOWS_DIR - 工作流目录
  // COMFYUI_OUTPUT_DIR    - 输出目录
  //
  // =============================================================================

  // ---------------------------------------------------------------------------
  // 【必填】ComfyUI 安装根目录
  // ---------------------------------------------------------------------------
  // Windows 示例:
  //   "H:/ComfyUI-aki-v3/ComfyUI"   ✅ 斜杠兼容 Windows/Linux
  //   "H:\\ComfyUI-aki-v3\\ComfyUI" ✅ 反斜杠（需双写转义）
  //   "D:/ComfyUI/ComfyUI"
  //
  // Linux/macOS 示例:
  //   "/opt/ComfyUI/ComfyUI"
  //   "/home/user/ComfyUI/ComfyUI"
  //   "~/ComfyUI/ComfyUI"
  //
  // WSL 示例:
  //   "/mnt/h/ComfyUI-aki-v3/ComfyUI"  ✅ 访问 Windows 盘符
  //
  // ⚠️ 注意: 路径必须包含 main.py 文件所在目录
  // ---------------------------------------------------------------------------
  "comfyui_root": "H:/ComfyUI-aki-v3/ComfyUI",

  // ---------------------------------------------------------------------------
  // 【可选】Python 解释器路径
  // ---------------------------------------------------------------------------
  // Windows 示例:
  //   "H:/ComfyUI-aki-v3/python/python.exe"  ✅ 整合包自带 Python
  //   "C:/Python311/python.exe"              ✅ 系统 Python
  //
  // Linux/macOS 示例:
  //   "/usr/bin/python3"
  //   "/home/user/anaconda3/envs/comfyui/bin/python"
  //
  // ⚠️ 如不填写，将自动检测以下顺序:
  //    1. conda 环境 (CONDA_DEFAULT_ENV)
  //    2. 虚拟环境 (VIRTUAL_ENV)
  //    3. 系统 Python (python3 / python.exe)
  // ---------------------------------------------------------------------------
  "python_path": "H:/ComfyUI-aki-v3/python/python.exe",

  // ---------------------------------------------------------------------------
  // 【可选】工作流 JSON 文件目录
  // ---------------------------------------------------------------------------
  // 默认: 优先使用 skill 自带的 example_workflows 目录
  // 如果需要使用自定义工作流，指定此路径
  // ---------------------------------------------------------------------------
  "workflows_dir": "C:/Users/Administrator/.openclaw/workspace/skills/comfyui-running/example_workflows",

  // ---------------------------------------------------------------------------
  // 【可选】图片输出目录
  // ---------------------------------------------------------------------------
  // Windows 示例:
  //   "H:/ComfyUI-aki-v3/ComfyUI/output"
  //
  // Linux/macOS 示例:
  //   "/opt/ComfyUI/ComfyUI/output"
  //
  // ⚠️ 如不填写，默认使用 {comfyui_root}/output
  // ---------------------------------------------------------------------------
  "output_dir": "H:/ComfyUI-aki-v3/ComfyUI/output",

  // ---------------------------------------------------------------------------
  // ComfyUI 服务端口
  // ---------------------------------------------------------------------------
  // 默认: 8188
  // 如果你修改过 ComfyUI 启动端口，请同步修改此值
  // ---------------------------------------------------------------------------
  "comfyui_port": 8188,

  // ---------------------------------------------------------------------------
  // Chrome DevTools Protocol (CDP) 端口
  // ---------------------------------------------------------------------------
  // 默认: 9222
  // 用于浏览器自动化控制（可选功能）
  // ⚠️ 仅 Edge/Chrome 浏览器支持
  // ---------------------------------------------------------------------------
  "cdp_port": 9222,

  // ---------------------------------------------------------------------------
  // UI 类型
  // ---------------------------------------------------------------------------
  // 可选值:
  //   "aki"      - 秋叶版 ComfyUI-aki-v3（默认）
  //   "official" - 官方原版 ComfyUI
  //   "auto"     - 自动检测（根据路径判断）
  //
  // 【自动检测规则】
  //   - 路径包含 "aki"、"秋叶"、"v3" → aki
  //   - 存在 extra_model_paths.yaml → aki
  //   - 其他 → official
  // ---------------------------------------------------------------------------
  "ui_type": "aki",

  // ---------------------------------------------------------------------------
  // 【可选】浏览器路径（用于 CDP 自动化，可不填自动检测）
  // ---------------------------------------------------------------------------
  // Windows Edge:
  //   "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
  //   "C:/Program Files/Microsoft/Edge/Application/msedge.exe"
  //
  // Windows Chrome:
  //   "C:/Users/{用户名}/AppData/Local/Google/Chrome/Application/chrome.exe"
  //
  // Linux:
  //   "/usr/bin/google-chrome"
  //   "/usr/bin/chromium-browser"
  //   "/snap/bin/chromium"
  //
  // macOS:
  //   "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
  // ---------------------------------------------------------------------------
  "browser_path": "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",

  // ---------------------------------------------------------------------------
  // 【可选】浏览器类型（可不填，根据 browser_path 自动判断）
  // ---------------------------------------------------------------------------
  // 可选值: "edge", "chrome"
  // ---------------------------------------------------------------------------
  "browser_type": "edge"
}


path: SHARING_PACKAGE.md
content: # 🎁 ComfyUI 全自动运行 Skill 分享包

> **适用版本**：ComfyUI-aki-v3 / ComfyUI-aki-v1.4 + OpenClaw agent + Edge 浏览器  
> **更新日期**：2026-04-05

---

## 📦 分享文件清单

```
comfyui-running/
├── SKILL.md                      ✅ AI Agent 主文档
├── SHARING_PACKAGE.md            ✅ 分享说明（本文件）
├── comfyui_browser.py             ✅ 浏览器 CDP 自动化脚本
├── lib/
│   ├── __init__.py               ✅ 包初始化
│   ├── comfyui_config.py         ✅ 配置读取器（跨平台自动检测）
│   └── comfyui_automation.py     ✅ REST API 自动化核心
├── scripts/
│   └── comfyui_setup.py          ✅ 自动配置脚本
└── example_workflows/
    ├── default_api_format.json   ✅ 默认文生图工作流（已修复）
    └── 文生图_api_format.json     ✅ 完整文生图工作流（9节点）
```

**❌ 不包含的文件（自动生成/缓存）**：
- `config.json` — 自动检测生成
- `__pycache__/` — Python 缓存（自动生成）
- `memory/` — 记忆目录

---

## 🚀 快速安装

### 方式一：复制文件夹（推荐）

将整个 `comfyui-running/` 文件夹复制到 OpenClaw 的 `skills/` 目录下：

```
OpenClaw安装目录/
└── skills/
    └── comfyui-running/    ← 复制到这里
```

### 方式二：手动指定路径

对 AI 说：
```
帮我启动ComfyUI并生成一张猫咪图片
```

AI 会自动：
1. 从 config.json 读取配置（如无，自动检测）
2. 启动 ComfyUI（如未运行）
3. 加载工作流 → 执行 → 下载图片

---

## ⚙️ 自动检测路径

Skill 会在以下路径自动搜索 ComfyUI：

| 平台 | 搜索路径 |
|------|---------|
| Windows | `C:/D:/E:/F:/G:/H:/I:/J:/K:/` + `ComfyUI-aki-v3/ComfyUI`, `ComfyUI-aki-v1.4/...`, `ComfyUI/ComfyUI` 等 |
| Linux/WSL | `/mnt/h/g/f/e/d/c/...` + 同上，以及 `/opt/ComfyUI/ComfyUI` |

---

## 📁 工作流说明

| 文件 | 节点数 | 说明 |
|------|--------|------|
| `default_api_format.json` | 7 | 简洁文生图，含 CLIPTextEncode（正/负向） |
| `文生图_api_format.json` | 9 | 完整版，同 `文生图`（KSampler + CheckpointLoader + VAEDecode + SaveImage）|

两个工作流均已验证可在 ComfyUI 0.15 下正常运行。

---

## 🔧 依赖

Skill 会自动安装以下依赖（如未安装）：
- `requests` — HTTP 请求
- `websockets` — WebSocket（CDP 模式用）

---

## ⚠️ 已知问题

### 1. 语法警告（Python 3.12+）
`comfyui_config.py` 的 docstring 中包含 `\D` 等路径写法，会产生 SyntaxWarning，不影响运行。

### 2. UI 格式工作流
原有的 `default.json`（UI 格式）因 ComfyUI 版本差异不再包含完整参数，已移除。仅保留 API 格式工作流（`.json` 直接可用）。

---

*文档版本：v3.0 | 更新日期：2026-04-05*


path: _meta.json
content: ownerId: kn7bb7pfn42artma1mn3xhg7dh82k0ac
slug: comfyui-running
version: 3.0.0
publishedAt: 1775402972026

path: 新手教程.md
content: # 🎨 ComfyUI Running 新手教程

> **让 AI 一句话帮你生成图片的全自动工具**
> 
> 👉 **由 [comfyui资源网](https://www.comfyorg.cn) 提供skills支持**

---
全自动运行 ComfyUI 工作流：通过 CDP 协议控制 Edge 浏览器，自动化操作 ComfyUI 界面，并通过 REST API 执行工作流。
---

> 全自动运行 ComfyUI 工作流 | By **[comfyui资源网](https://www.comfyorg.cn)**

[![comfyui资源网](https://img.shields.io/badge/comfyui%E8%B5%84%E6%BA%90%E7%BD%91-ComfyUI%E8%B5%84%E6%BA%90%E5%AE%A3%E5%92%8C-orange?style=for-the-badge&logo=firefox)](https://www.comfyorg.cn)

---

## 🚀 3分钟快速上手！

### 第一步：告诉 AI 你想要什么

最简单的方式，直接说：

```
生成一张猫咪图片
```

```
画一个海边日落
```

```
生成一只穿着衣服的小狗
```

就这么多！AI 会自动帮你完成所有事情 🎉

---

## 📁 文件在哪？

这个技能包含以下文件：

```
comfyui-running/
├── config.json              ← 配置文件（重要！需要配置）
├── 新手教程.md              ← 你现在看的这个！
├── lib/
│   └── comfyui_automation.py  ← 核心程序（别动它）
└── example_workflows/
    └── default.json         ← 工作流配置
```

---

## ⚙️ 配置文件怎么改？（重点！）

### config.json 是什么？

这是告诉 AI"你的 ComfyUI 在哪"的配置文件！

用**记事本**（或写字板）打开 `config.json`，长这样：

```json
{
  "comfyui_root": "这里填你的路径",
  "python_path": "这里填Python路径",
  "workflows_dir": "工作流文件夹（一般不用改）",
  "output_dir": "图片输出位置（一般不用改）",
  "comfyui_port": 8188,
  "ui_type": "aki"
}
```

### ⚠️ 不同电脑填法不一样！看下面：

---

### 🪟 Windows 用户（大多数人的选择）

#### 怎么找到我的路径？

**方法：按 Win + E 打开文件管理器**

1. 找到你的 ComfyUI 文件夹
2. 点击地址栏，会显示路径
3. 复制这个路径

#### 常见路径对照表

| 你的 ComfyUI 在哪 | config.json 里的 comfyui_root 怎么写 |
|------------------|-------------------------------------|
| `H:\ComfyUI-aki-v3\ComfyUI` | `"H:\\ComfyUI-aki-v3\\ComfyUI"` |
| `D:\ComfyUI-aki-v3\ComfyUI` | `"D:\\ComfyUI-aki-v3\\ComfyUI"` |
| `D:\ComfyUI\ComfyUI` | `"D:\\ComfyUI\\ComfyUI"` |
| `C:\Users\张三\ComfyUI` | `"C:\\Users\\张三\\ComfyUI"` |
| `E:\ComfyUI-aki\ComfyUI` | `"E:\\ComfyUI-aki\\ComfyUI"` |

> 💡 **注意**：路径里的 `\` 要变成 `\\`（写两遍）！

#### Windows Python 路径

| 你的 Python 在哪 | python_path 怎么写 |
|-----------------|-------------------|
| `H:\ComfyUI-aki-v3\python\python.exe` | `"H:\\ComfyUI-aki-v3\\python\\python.exe"` |
| `D:\ComfyUI\python\python.exe` | `"D:\\ComfyUI\\python\\python.exe"` |

#### Windows 配置示例

```json
{
  "comfyui_root": "H:\\ComfyUI-aki-v3\\ComfyUI",
  "python_path": "H:\\ComfyUI-aki-v3\\python\\python.exe",
  "workflows_dir": "skills\\comfyui-running\\example_workflows",
  "output_dir": "H:\\ComfyUI-aki-v3\\ComfyUI\\output",
  "comfyui_port": 8188,
  "ui_type": "aki"
}
```

---

### 🐧 Linux 用户

#### 怎么找到我的路径？

**打开终端，输入：**
```bash
ls ~/ComfyUI
# 或者
ls /opt/ComfyUI
```

#### 常见路径对照表

| 你的 ComfyUI 在哪 | config.json 里的 comfyui_root 怎么写 |
|------------------|-------------------------------------|
| `/home/用户名/ComfyUI` | `"/home/用户名/ComfyUI"` |
| `/home/用户名/ComfyUI-aki/ComfyUI` | `"/home/用户名/ComfyUI-aki/ComfyUI"` |
| `/opt/ComfyUI` | `"/opt/ComfyUI"` |
| `/usr/local/ComfyUI` | `"/usr/local/ComfyUI"` |

> 💡 **注意**：Linux 路径用 `/`（斜杠），不用 `\`！

#### Linux Python 路径

| 你的 Python 在哪 | python_path 怎么写 |
|-----------------|-------------------|
| 系统 Python | `"/usr/bin/python3"` |
| Conda 环境 | `"/home/用户名/anaconda3/envs/comfyui/bin/python"` |
| Conda 虚拟环境 | `"/home/用户名/miniconda3/envs/comfyui/bin/python"` |

#### Linux 配置示例

```json
{
  "comfyui_root": "/home/用户名/ComfyUI",
  "python_path": "/usr/bin/python3",
  "workflows_dir": "/home/用户名/.openclaw/skills/comfyui-running/example_workflows",
  "output_dir": "/home/用户名/ComfyUI/output",
  "comfyui_port": 8188,
  "ui_type": "official"
}
```

---

### 🍎 macOS 用户

#### 常见路径对照表

| 你的 ComfyUI 在哪 | config.json 里的 comfyui_root 怎么写 |
|------------------|-------------------------------------|
| `/Applications/ComfyUI.app` | `"/Applications/ComfyUI.app/Contents/MacOS/ComfyUI"` |
| `/Users/用户名/ComfyUI` | `"/Users/用户名/ComfyUI"` |

#### macOS Python 路径

| 你的 Python 在哪 | python_path 怎么写 |
|-----------------|-------------------|
| 系统 Python | `"/usr/bin/python3"` |
| Homebrew | `"/opt/homebrew/bin/python3"` |

#### macOS 配置示例

```json
{
  "comfyui_root": "/Users/用户名/ComfyUI",
  "python_path": "/usr/bin/python3",
  "workflows_dir": "/Users/用户名/.openclaw/skills/comfyui-running/example_workflows",
  "output_dir": "/Users/用户名/ComfyUI/output",
  "comfyui_port": 8188,
  "ui_type": "official"
}
```

---

### 🔄 WSL 用户（Windows 上的 Linux 子系统）

#### 什么是 WSL？

WSL = Windows Subsystem for Linux，让你在 Windows 里跑 Linux。

#### 怎么找到我的路径？

**方法1：Windows 文件管理器**
```
\\wsl$\Ubuntu\home\用户名\ComfyUI
```

**方法2：WSL 终端**
```bash
pwd
# 会显示类似：/home/用户名/ComfyUI
```

#### WSL 路径对照表

| 你的 ComfyUI 在哪 | config.json 里的 comfyui_root 怎么写 |
|------------------|-------------------------------------|
| Windows 盘符里的 ComfyUI | `"/mnt/h/ComfyUI-aki-v3/ComfyUI"` |
| WSL 里的 ComfyUI | `"/home/用户名/ComfyUI"` |

> 💡 **注意**：`/mnt/c` = C盘，`/mnt/h` = H盘

#### WSL Python 路径

| 你的 Python 在哪 | python_path 怎么写 |
|-----------------|-------------------|
| WSL 系统 Python | `"/usr/bin/python3"` |
| Conda in WSL | `"/home/用户名/miniconda3/bin/python"` |
| 访问 Windows Python | `"/mnt/h/ComfyUI-aki-v3/python/python.exe"` |

#### WSL 配置示例

```json
{
  "comfyui_root": "/mnt/h/ComfyUI-aki-v3/ComfyUI",
  "python_path": "/usr/bin/python3",
  "workflows_dir": "/home/用户名/.openclaw/skills/comfyui-running/example_workflows",
  "output_dir": [REDACTED_SECRET],
  "comfyui_port": 8188,
  "ui_type": "aki"
}
```

---

### 📋 config.json 所有配置项说明

| 配置项 | 必须改？ | 默认值 | Windows 示例 | Linux/macOS 示例 |
|--------|---------|--------|-------------|------------------|
| `comfyui_root` | ✅ 必填 | 无 | `"H:\\...\\ComfyUI"` | `"/home/.../ComfyUI"` |
| `python_path` | ⭐ 建议填 | 自动检测 | `"H:\\...\\python.exe"` | `"/usr/bin/python3"` |
| `workflows_dir` | ❌ 不用改 | 自动检测 | `"skills\\...\\workflows"` | `"skills/.../workflows"` |
| `output_dir` | ❌ 不用改 | 自动检测 | `"H:\\...\\output"` | `"/home/.../output"` |
| `comfyui_port` | ❌ 不用改 | 8188 | `8188` | `8188` |
| `ui_type` | ❌ 不用改 | auto | `"aki"` 或 `"official"` | `"official"` |

---

### 🎯 快速配置检查清单

**Windows 用户确认：**
- [ ] `comfyui_root` 填了？路径用 `\\`
- [ ] `python_path` 填了？指向 `python.exe`
- [ ] 如果是秋叶版，`ui_type` 填 `"aki"`
- [ ] 如果是官方版，`ui_type` 填 `"official"`

**Linux/macOS 用户确认：**
- [ ] `comfyui_root` 填了？路径用 `/`
- [ ] `python_path` 填了？指向 `python3`
- [ ] `ui_type` 一般填 `"official"`

---

## 💬 怎么用？（直接跟 AI 说就行）

### 最简单的说话方式

```
生成一张猫咪图片
```

```
画一个卡通人物
```

```
生成一个未来城市
```

### 想指定样式？这样说

| 你想说 | AI 会理解 |
|--------|-----------|
| `生成一张 1024x1024 的图` | 图片尺寸 |
| `用 30 步生成` | 质量更高但更慢 |
| `种子用 12345` | 固定图片，不随机 |
| `生成 4 张` | 一次生成多张 |

### 示例

```
生成一张海边日落图，尺寸用 1024x768
```

```
画一只穿着公主裙的猫，步数用 25
```

```
生成 4 张不同的风景图
```

---

## 🔧 参数说明（可选）

### 常用参数

| 参数 | 是什么意思 | 怎么用 |
|------|-----------|--------|
| `prompt` | 你想要什么 | 直接描述就行 |
| `steps` | 图片质量（越大越好） | 默认 20，推荐 20-30 |
| `seed` | 随机种子 | 不填就随机 |
| `width` | 图片宽度 | 默认 512 |
| `height` | 图片高度 | 默认 512 |
| `batch_size` | 一次生成几张 | 默认 1 |

### 尺寸推荐

| 尺寸 | 适合 |
|------|------|
| 512 × 512 | 快速预览 |
| 768 × 768 | 头像/头像 |
| 1024 × 1024 | 高清大图 |
| 896 × 512 | 横版风景 |
| 512 × 896 | 竖版人像 |

### steps 质量对比

| steps | 速度 | 质量 |
|-------|------|------|
| 10-15 | ⚡ 很快 | 一般（预览用） |
| 20-25 | ⏱️ 正常 | 够用（推荐） |
| 30-40 | ⏳ 慢 | 很好 |
| 40+ | 🐌 很慢 | 极致 |

---

## ❓ 遇到问题了？

### 问题1：AI 说找不到 ComfyUI

**解决方法：**
1. 确认 config.json 里的路径正确
2. 路径分隔符用对了吗？
   - Windows: `\\` (两个反斜杠)
   - Linux/macOS: `/` (一个斜杠)

### 问题2：生成的图片很奇怪/模糊

**解决方法：**
- 把 steps 改成 25 或 30
- 提示词说得更具体：
  - ❌ "一只猫" 
  - ✅ "一只橘色的胖猫，阳光下，毛茸茸的"

### 问题3：生成太慢了

**解决方法：**
- 把 steps 改成 15 或 20
- 把尺寸改小一点，比如 512 × 512

### 问题4：想生成一样的图片

**解决方法：**
指定种子（seed）：

```
生成一张海边日落，种子用 123456789
```

同样的种子 + 同样的描述 = 同样的图片！

### 问题5：不知道提示词怎么写

**英文提示词更好！** 参考：

| 想要什么 | 英文提示词 |
|---------|-----------|
| 美女 | `a beautiful girl, realistic` |
| 风景 | `landscape, mountains, sunset` |
| 猫 | `a cute fluffy cat` |
| 动漫风格 | `anime style, cartoon` |

---

## 📋 工作流文件（高级）

### default.json 是什么？

这是一个"配方"文件，告诉 AI 怎么一步一步生成图片。

### 我能改什么？

**分辨率（图片大小）：**
找到 `EmptyLatentImage` 那部分，改 `width` 和 `height`：

```json
"width": 1024,
"height": 1024,
```

**质量（步数）：**
找到 `KSampler` 那部分，改 `steps`：

```json
"steps": 30,
```

**模型：**
找到 `CheckpointLoaderSimple` 那部分，改 `ckpt_name`：

```json
"ckpt_name": "你的模型文件名.safetensors"
```

> ⚠️ 模型文件要去 `ComfyUI目录\models\checkpoints\` 找！

---

## 🎯 常用命令收藏

```python
# 最简单的
quick_generate("一只可爱的猫")

# 高质量
quick_generate("一个美女", steps=30)

# 指定尺寸
quick_generate("风景图", width=1024, height=768)

# 一次4张
quick_generate("一只狗", number=4)

# 固定图片（可复现）
quick_generate("海边日落", seed=123456789)

# 带负向提示词（去掉不要的）
quick_generate("一个女孩", negative="模糊, 低质量")
```

---

## 📁 图片生成在哪？

图片会保存在你的 ComfyUI 安装目录里的 `output` 文件夹：

**Windows:**
```
H:\ComfyUI-aki-v3\ComfyUI\output\
```

**Linux/macOS:**
```
/home/用户名/ComfyUI/output/
```

打开文件夹，找到 `ComfyUI_00001_.png` 这样的文件就是生成的图片！

---

## 📝 提示词模板

### 人像
```
a beautiful Asian girl, portrait, soft lighting, detailed skin
```
（亚洲女孩肖像，柔和光照，细腻皮肤）

### 风景
```
a serene landscape, mountains, lake, golden sunset, detailed
```
（宁静风景，山，湖泊，金色日落，细节丰富）

### 动物
```
a cute fluffy puppy, bright eyes, soft fur, natural lighting
```
（可爱的毛茸茸小狗，明亮眼睛，柔软毛发，自然光）

### 动漫
```
anime girl, traditional Chinese hanfu, elegant, detailed
```
（动漫女孩，传统汉服，优雅，细节）

### 科幻
```
futuristic city, neon lights, cyberpunk, detailed architecture
```
（未来城市，霓虹灯，赛博朋克，建筑细节）

---

## 🆘 还能问谁？

- 📖 [comfyui资源网](https://www.comfyorg.cn) - 教程和工作流
- 💬 问 AI 助手 - 我随时在线！

---

## 📌 推荐资源（by [comfyui资源网](https://www.comfyorg.cn)）

| 资源类型 | 链接 |
|----------|------|
| 🏠 首页 | https://www.comfyorg.cn |
| 📖 ComfyUI 教程 | https://www.comfyorg.cn/tutorial |
| 🎭 工作流广场 | https://www.comfyorg.cn/workflow |
| 🛠️ 工具下载 | https://www.comfyorg.cn/kjqy/bbgj |
| 💬 满血整合包 | https://www.comfyorg.cn/shop/1680.html |

---

🎉 **恭喜！你已经学会使用了！**

现在去生成你的第一张 AI 图片吧！


path: scripts/comfyui_setup.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ComfyUI 自动配置脚本
==================

【功能】
    1. 自动检测 ComfyUI 安装路径
    2. 检测 ComfyUI 是否正在运行
    3. 生成/更新 config.json

【用法】
    python comfyui_setup.py

【跨平台支持】
    - Windows: 自动扫描 D:/ E:/ H:/ 等盘符
    - Linux: 检测 /opt/ComfyUI, ~/ComfyUI 等
    - WSL: 检测 /mnt/h/ 等 Windows 盘符
"""
import os
import sys

# 添加父目录到路径，以便导入 lib 模块
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from lib.comfyui_config import (
    detect_and_save_config,
    diagnose_comfyui_installations,
    get_comfyui_root,
    get_comfyui_port,
    get_workflows_dir,
    get_output_dir,
    get_python_path,
    _get_system,
    _is_wsl,
)


def print_header():
    """打印标题"""
    print("=" * 60)
    print("  ComfyUI Running Skill - 自动配置工具")
    print("=" * 60)
    print()


def print_system_info():
    """打印系统信息"""
    system = _get_system()
    is_wsl = _is_wsl()
    
    print(f"[系统信息]")
    print(f"  平台: {system}" + (" (WSL)" if is_wsl else ""))
    print()
    
    if system == "windows" and is_wsl:
        print("  ⚠️ 检测到 WSL 环境，将同时搜索 Windows 盘符")
        print()
    elif system == "windows":
        print("  💻 Windows 环境")
        print()
    elif system == "linux":
        print("  🐧 Linux 环境")
        print()
    elif system == "darwin":
        print("  🍎 macOS 环境")
        print()


def print_current_config():
    """打印当前配置"""
    print("[当前配置]")
    
    root = get_comfyui_root()
    port = get_comfyui_port()
    workflows = get_workflows_dir()
    output = get_output_dir()
    python = get_python_path()
    
    print(f"  ComfyUI 路径: {root or '(未设置)'}")
    print(f"  端口: {port}")
    print(f"  工作流目录: {workflows or '(未设置)'}")
    print(f"  输出目录: {output or '(未设置)'}")
    print(f"  Python: {python or '(未设置)'}")
    print()


def print_detection_results():
    """打印检测结果"""
    print("[检测结果]")
    
    results = diagnose_comfyui_installations()
    
    if not results:
        print("  ❌ 未找到 ComfyUI 安装")
        print()
        print("  请手动配置 config.json 中的 comfyui_root")
        print("  或确保 ComfyUI 正在运行（将自动检测端口）")
        print()
        return False
    
    print(f"  找到 {len(results)} 个 ComfyUI 安装:")
    print()
    
    for i, d in enumerate(results[:5], 1):  # 最多显示5个
        is_running = "✅ 运行中" if d.get('is_running') else "⏸️ 未运行"
        version = d.get('version', 'unknown')
        score = d.get('score', 0)
        
        print(f"  {i}. {d.get('detected_path', 'N/A')}")
        print(f"     版本: {version} | {is_running} | 评分: {score:.1f}")
        print()
    
    return True


def run_auto_setup():
    """运行自动配置"""
    print("[自动配置]")
    print("  正在检测并保存配置...")
    print()
    
    config = detect_and_save_config()
    
    if config:
        print("  ✅ 配置已保存到 config.json")
        print()
        print(f"  comfyui_root: {config.get('comfyui_root')}")
        print(f"  comfyui_port: {config.get('comfyui_port')}")
        print(f"  ui_type: {config.get('ui_type', 'official')}")
        print()
        
        if config.get('_is_running'):
            print("  ✅ ComfyUI 正在运行中")
        else:
            print("  ⚠️ ComfyUI 未运行，将在首次生成时自动启动")
        print()
        
        return True
    else:
        print("  ❌ 自动检测失败")
        print()
        print("  请手动创建 config.json，示例：")
        print()
        print('  {')
        print('    "comfyui_root": "H:/ComfyUI-aki-v3/ComfyUI",')
        print('    "python_path": "H:/ComfyUI-aki-v3/python/python.exe",')
        print('    "comfyui_port": 8188')
        print('  }')
        print()
        return False


def print_next_steps():
    """打印后续步骤"""
    print("[后续步骤]")
    print("  1. 确保 config.json 中的路径正确")
    print("  2. 运行测试生成:")
    print()
    
    system = _get_system()
    if system == "windows":
        print('  python -m lib.comfyui_automation "a cute cat"')
    else:
        print('  python3 -m lib.comfyui_automation "a cute cat"')
    print()
    print("  3. 详细文档请查看 新手教程.md")
    print()


def main():
    """主函数"""
    print_header()
    print_system_info()
    print_current_config()
    
    found = print_detection_results()
    
    if not found:
        # 即使没找到也尝试保存配置（会检测正在运行的）
        run_auto_setup()
    else:
        # 找到多个版本，让用户确认
        response = input("是否使用评分最高的安装进行配置? (Y/n): ").strip().lower()
        if response != 'n':
            run_auto_setup()
    
    print_next_steps()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n操作已取消")
        sys.exit(0)


path: scripts/debug_detect.py
content: #!/usr/bin/env python3
"""诊断自动检测为什么失败"""
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))

import platform
from comfyui_config import _get_detection_candidates, _windows_path_exists

print(f"Platform: {platform.system()}")
print()

candidates = _get_detection_candidates()
print(f"总候选路径数: {len(candidates)}")
print()

# 测试每个路径
found = []
not_found = []
for path in candidates:
    if not path:
        continue
    exists = _windows_path_exists(path)
    if exists:
        found.append(path)
        print(f"  [FOUND] {path}")
    elif path.startswith("H:"):
        # 只打印 H 盘的详细状态
        wsl_path = f"/mnt/h/{path[3:].replace(chr(92), '/')}"
        real_exists = os.path.exists(wsl_path)
        print(f"  [H盘] {path}")
        print(f"         WSL路径: {wsl_path}")
        print(f"         WSL exists: {real_exists}")
        print(f"         _windows_path_exists: {exists}")

print()
print(f"找到的路径数: {len(found)}")
for p in found:
    print(f"  {p}")

# 直接测试 H:\ 路径
print()
print("=== 直接测试 H:\\ 路径 ===")
direct = os.path.exists("H:\\ComfyUI-aki-v3\\ComfyUI\\main.py")
print(f"H:\\ComfyUI-aki-v3\\ComfyUI\\main.py exists: {direct}")


path: lib/__init__.py
content: # ComfyUI Running Skill - Python Package Init
"""
ComfyUI 全自动运行技能包

用法：
    from comfyui_automation import quick_generate, ComfyUIAutomation
    
    # 一句话生成
    result = quick_generate("a cute cat")
    
    # 完整控制
    automation = ComfyUIAutomation()
    result = automation.generate("a cat", steps=25)
"""

from .comfyui_automation import (
    ComfyUIAutomation,
    quick_generate,
    _ensure_dependencies,
)

__all__ = [
    'ComfyUIAutomation',
    'quick_generate',
    '_ensure_dependencies',
]

# 版本信息
__version__ = '2.0.0'


path: example_workflows/default_api_format.json
content: {
  "1": {
    "class_type": "CheckpointLoaderSimple",
    "inputs": {
      "ckpt_name": ""
    }
  },
  "2": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "a beautiful landscape",
      "clip": ["1", 0]
    }
  },
  "3": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "low quality, blurry",
      "clip": ["1", 0]
    }
  },
  "4": {
    "class_type": "KSampler",
    "inputs": {
      "seed": 0,
      "steps": 20,
      "cfg": 8,
      "sampler_name": "euler",
      "scheduler": "normal",
      "positive": ["2", 0],
      "negative": ["3", 0],
      "model": ["1", 0],
      "latent_image": ["5", 0]
    }
  },
  "5": {
    "class_type": "EmptyLatentImage",
    "inputs": {
      "width": 512,
      "height": 512,
      "batch_size": 1
    }
  },
  "6": {
    "class_type": "VAEDecode",
    "inputs": {
      "samples": ["4", 0],
      "vae": ["1", 1]
    }
  },
  "7": {
    "class_type": "SaveImage",
    "inputs": {
      "images": ["6", 0],
      "filename_prefix": "ComfyUI"
    }
  }
}


path: example_workflows/文生图_api_format.json
content: {
  "1": {
    "class_type": "CheckpointLoaderSimple",
    "inputs": {
      "ckpt_name": ""
    }
  },
  "2": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "prompt",
      "clip": ["1", 0]
    }
  },
  "3": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "low quality, worst quality, blurry",
      "clip": ["1", 0]
    }
  },
  "4": {
    "class_type": "KSampler",
    "inputs": {
      "seed": 0,
      "steps": 20,
      "cfg": 8,
      "sampler_name": "euler",
      "scheduler": "normal",
      "positive": ["2", 0],
      "negative": ["3", 0],
      "model": ["1", 0],
      "latent_image": ["5", 0]
    }
  },
  "5": {
    "class_type": "EmptyLatentImage",
    "inputs": {
      "width": 512,
      "height": 512,
      "batch_size": 1
    }
  },
  "6": {
    "class_type": "VAEDecode",
    "inputs": {
      "samples": ["4", 0],
      "vae": ["1", 1]
    }
  },
  "7": {
    "class_type": "SaveImage",
    "inputs": {
      "images": ["6", 0],
      "filename_prefix": "ComfyUI"
    }
  }
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)