From 4d2fcc29df34537a230002b2fd1ed29e724a914f Mon Sep 17 00:00:00 2001 From: williamxie1989 <21219102@qq.com> Date: Tue, 7 Apr 2026 11:21:16 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20add=20comfyui=20plugin=20=E2=80=94?= =?UTF-8?q?=20manage=20nodes,=20models,=20queue,=20and=20workflow=20execut?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a complete plugin for interacting with ComfyUI servers: - nodes: list all registered node types - node-info: detailed definition of a node (inputs, types, defaults) - search-node: search nodes by keyword - models: list available model files - system-stats: server status and system information - run: execute workflows via JSON prompt - queue: view running/pending queue status - history: view execution history Configurable via COMFYUI_HOST environment variable. Co-Authored-By: Claude Opus 4.6 (1M context) --- clis/comfyui/README.md | 94 ++++++++++++++++++++++++++++++++++++ clis/comfyui/config.js | 19 ++++++++ clis/comfyui/history.js | 51 +++++++++++++++++++ clis/comfyui/models.js | 40 +++++++++++++++ clis/comfyui/node-info.js | 54 +++++++++++++++++++++ clis/comfyui/nodes.js | 26 ++++++++++ clis/comfyui/queue.js | 41 ++++++++++++++++ clis/comfyui/run.js | 48 ++++++++++++++++++ clis/comfyui/search-node.js | 39 +++++++++++++++ clis/comfyui/system-stats.js | 31 ++++++++++++ 10 files changed, 443 insertions(+) create mode 100644 clis/comfyui/README.md create mode 100644 clis/comfyui/config.js create mode 100644 clis/comfyui/history.js create mode 100644 clis/comfyui/models.js create mode 100644 clis/comfyui/node-info.js create mode 100644 clis/comfyui/nodes.js create mode 100644 clis/comfyui/queue.js create mode 100644 clis/comfyui/run.js create mode 100644 clis/comfyui/search-node.js create mode 100644 clis/comfyui/system-stats.js diff --git a/clis/comfyui/README.md b/clis/comfyui/README.md new file mode 100644 index 00000000..5c7a0627 --- /dev/null +++ b/clis/comfyui/README.md @@ -0,0 +1,94 @@ +# opencli-comfyui + +[ComfyUI](https://github.com/comfyanonymous/ComfyUI) plugin for [OpenCLI](https://github.com/jackwener/opencli). Manage your ComfyUI server from the command line โ€” list nodes, explore models, run workflows, and monitor queue status. + +## Features + +| Command | Description | +|---------|-------------| +| `comfyui nodes` | List all registered node types (700+) | +| `comfyui node-info ` | Detailed definition of a node (inputs, types, defaults, constraints) | +| `comfyui search-node ` | Search nodes by name or field | +| `comfyui models [--model_type ]` | List available model files | +| `comfyui system-stats` | Server status and system information | +| `comfyui run ` | Execute a workflow | +| `comfyui queue` | Queue status (running / pending) | +| `comfyui history [--limit N]` | Execution history | + +## Installation + +```bash +# Install the plugin +cd opencli-comfyui +npm link + +# Or symlink manually +opencli plugin install +``` + +## Configuration + +Set your ComfyUI server address: + +```bash +export COMFYUI_HOST=http://127.0.0.1:8188 +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `COMFYUI_HOST` | `http://127.0.0.1:8188` | ComfyUI server URL | + +## Usage Examples + +```bash +# Check server status +opencli comfyui system-stats + +# List all nodes +opencli comfyui nodes --limit 20 + +# Search for a node +opencli comfyui search-node sampler + +# View node details +opencli comfyui node-info KSampler + +# List models +opencli comfyui models +opencli comfyui models --model_type loras + +# Run a workflow from JSON +opencli comfyui run '{"4":{"class_type":"EmptyLatentImage","inputs":{"width":512,"height":512,"batch_size":1}}}' + +# Run a workflow from file +opencli comfyui run my-workflow.json + +# Check queue +opencli comfyui queue + +# View history +opencli comfyui history --limit 5 +opencli comfyui history --prompt_id +``` + +## ComfyUI API Endpoints Used + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/object_info` | GET | List all node definitions | +| `/api/models` | GET | List model directories | +| `/api/models/` | GET | List models of a type | +| `/api/system_stats` | GET | Server information | +| `/prompt` | POST | Queue a workflow for execution | +| `/api/queue` | GET | Queue status | +| `/api/history` | GET | Execution history | + +## Requirements + +- [OpenCLI](https://github.com/jackwener/opencli) >= 1.6.0 +- A running [ComfyUI](https://github.com/comfyanonymous/ComfyUI) server +- Node.js 18+ + +## License + +MIT diff --git a/clis/comfyui/config.js b/clis/comfyui/config.js new file mode 100644 index 00000000..06d4b65e --- /dev/null +++ b/clis/comfyui/config.js @@ -0,0 +1,19 @@ +/** + * Shared config for the ComfyUI plugin. + * + * Configuration via environment variables: + * COMFYUI_HOST โ€” ComfyUI server base URL (default: http://127.0.0.1:8188) + * + * Example: + * export COMFYUI_HOST=http://192.168.1.100:8008 + * opencli comfyui system-stats + */ + +export const COMFYUI_HOST = typeof process !== 'undefined' + ? (process.env.COMFYUI_HOST || 'http://127.0.0.1:8188') + : 'http://127.0.0.1:8188'; + +export function url(path) { + const host = COMFYUI_HOST.replace(/\/+$/, ''); + return `${host}${path.startsWith('/') ? path : '/' + path}`; +} diff --git a/clis/comfyui/history.js b/clis/comfyui/history.js new file mode 100644 index 00000000..ae5e736c --- /dev/null +++ b/clis/comfyui/history.js @@ -0,0 +1,51 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; +import { CommandExecutionError } from '@jackwener/opencli/errors'; + +cli({ + site: 'comfyui', + name: 'history', + description: 'View ComfyUI workflow execution history', + strategy: Strategy.PUBLIC, + args: [ + { name: 'limit', type: 'int', default: 10 }, + { name: 'prompt_id', type: 'str', default: '' }, + ], + columns: ['prompt_id', 'status', 'nodes', 'output_nodes'], + func: async (page, kwargs) => { + const res = await fetch(url('/api/history')); + const data = await res.json(); + + if (kwargs.prompt_id) { + const entry = data[kwargs.prompt_id]; + if (!entry) { + throw new CommandExecutionError(`History entry not found for prompt_id: ${kwargs.prompt_id}`); + } + const prompt = entry.prompt || []; + const promptData = prompt[2] || {}; + return [{ + prompt_id: kwargs.prompt_id, + status: entry.status?.status_str || 'unknown', + nodes: Object.keys(promptData).length, + output_nodes: entry.outputs ? Object.keys(entry.outputs).join(', ') : '-', + }]; + } + + const entries = Object.entries(data) + .sort((a, b) => b[0].localeCompare(a[0])) + .slice(0, kwargs.limit); + + return entries.map(([id, entry], index) => { + const prompt = entry.prompt || []; + const promptData = prompt[2] || {}; + const outputs = entry.outputs || {}; + return { + rank: index + 1, + prompt_id: id, + status: entry.status?.status_str || 'unknown', + nodes: Object.keys(promptData).length, + output_nodes: Object.keys(outputs).length > 0 ? Object.keys(outputs).join(', ') : '-', + }; + }); + }, +}); diff --git a/clis/comfyui/models.js b/clis/comfyui/models.js new file mode 100644 index 00000000..faee7908 --- /dev/null +++ b/clis/comfyui/models.js @@ -0,0 +1,40 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; + +cli({ + site: 'comfyui', + name: 'models', + description: 'List available model files in ComfyUI', + strategy: Strategy.PUBLIC, + args: [ + { name: 'model_type', type: 'str', default: 'all' }, + ], + columns: ['rank', 'type', 'name', 'count'], + func: async (page, kwargs) => { + const typesRes = await fetch(url('/api/models')); + const types = await typesRes.json(); + + if (kwargs.model_type !== 'all') { + const modelRes = await fetch(url(`/api/models/${kwargs.model_type}`)); + const models = await modelRes.json(); + return typeof models === 'object' && models.length !== undefined + ? models.map((m, idx) => ({ rank: idx + 1, type: kwargs.model_type, name: typeof m === 'string' ? m : m.name })) + : [{ rank: 1, type: kwargs.model_type, name: typeof models === 'string' ? models : JSON.stringify(models) }]; + } + + const results = []; + let idx = 0; + for (const t of types) { + const modelRes = await fetch(url(`/api/models/${t}`)); + const models = await modelRes.json(); + idx++; + results.push({ + rank: idx, + type: t, + count: Array.isArray(models) ? models.length : 0, + name: Array.isArray(models) ? models.slice(0, 3).join(', ') : JSON.stringify(models), + }); + } + return results; + }, +}); diff --git a/clis/comfyui/node-info.js b/clis/comfyui/node-info.js new file mode 100644 index 00000000..ec94c2df --- /dev/null +++ b/clis/comfyui/node-info.js @@ -0,0 +1,54 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; +import { CommandExecutionError } from '@jackwener/opencli/errors'; + +cli({ + site: 'comfyui', + name: 'node-info', + description: 'Show detailed definition of a ComfyUI node (inputs, types, defaults, constraints)', + strategy: Strategy.PUBLIC, + args: [ + { name: 'node', type: 'str', required: true, positional: true }, + ], + columns: ['field', 'mode', 'type', 'default', 'range'], + func: async (page, kwargs) => { + const res = await fetch(url('/api/object_info')); + const data = await res.json(); + const info = data[kwargs.node]; + + if (!info) { + const keys = Object.keys(data); + const similar = keys.filter(n => n.toLowerCase().includes(kwargs.node.toLowerCase())); + const hint = similar.length ? `Similar nodes: ${similar.slice(0, 5).join(', ')}` : 'No similar nodes found'; + throw new CommandExecutionError(`Node not found: "${kwargs.node}" โ€” ${hint}`); + } + + const result = []; + const inputs = info.input || {}; + const required = inputs.required || {}; + const optional = inputs.optional || {}; + + for (const [field, config] of Object.entries(required)) { + const [typeDef, opts = {}] = config; + result.push({ + field, + mode: 'required', + type: Array.isArray(typeDef) ? `choice[${typeDef.join(', ')}]` : String(typeDef), + default: opts.default != null ? String(opts.default) : '-', + range: (opts.min != null && opts.max != null) ? `${opts.min}~${opts.max}` : '', + }); + } + for (const [field, config] of Object.entries(optional)) { + const [typeDef, opts = {}] = config; + result.push({ + field, + mode: 'optional', + type: Array.isArray(typeDef) ? `choice[${typeDef.join(', ')}]` : String(typeDef), + default: opts.default != null ? String(opts.default) : '-', + range: (opts.min != null && opts.max != null) ? `${opts.min}~${opts.max}` : '', + }); + } + + return result.map((item, index) => ({ rank: index + 1, ...item })); + }, +}); diff --git a/clis/comfyui/nodes.js b/clis/comfyui/nodes.js new file mode 100644 index 00000000..07cdee4b --- /dev/null +++ b/clis/comfyui/nodes.js @@ -0,0 +1,26 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; + +cli({ + site: 'comfyui', + name: 'nodes', + description: 'List all registered node types in ComfyUI', + strategy: Strategy.PUBLIC, + args: [ + { name: 'limit', type: 'int', default: 9999 }, + { name: 'detail', type: 'str', default: 'none' }, + ], + columns: ['rank', 'name', 'inputs'], + func: async (page, kwargs) => { + const res = await fetch(url('/api/object_info')); + const data = await res.json(); + const names = Object.keys(data).sort((a, b) => a.localeCompare(b)); + + return names.slice(0, kwargs.limit).map((name, index) => { + const info = data[name]; + const inputs = info.input || {}; + const totalInputs = Object.keys(inputs.required || {}).length + Object.keys(inputs.optional || {}).length; + return { rank: index + 1, name, inputs: totalInputs }; + }); + }, +}); diff --git a/clis/comfyui/queue.js b/clis/comfyui/queue.js new file mode 100644 index 00000000..80b6bb84 --- /dev/null +++ b/clis/comfyui/queue.js @@ -0,0 +1,41 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; + +cli({ + site: 'comfyui', + name: 'queue', + description: 'Show currently running and pending ComfyUI queue tasks', + strategy: Strategy.PUBLIC, + columns: ['status', 'queue_number', 'prompt_id', 'nodes'], + func: async () => { + const res = await fetch(url('/api/queue')); + const data = await res.json(); + const results = []; + + const running = data.queue_running || []; + for (const q of running) { + results.push({ + status: 'running', + queue_number: q[0], + prompt_id: q[1], + nodes: typeof q[2] === 'object' ? Object.keys(q[2]).length : '-', + }); + } + + const pending = data.queue_pending || []; + for (const q of pending) { + results.push({ + status: 'pending', + queue_number: q[0], + prompt_id: q[1], + nodes: typeof q[2] === 'object' ? Object.keys(q[2]).length : '-', + }); + } + + if (results.length === 0) { + return [{ status: 'empty', queue_number: 0, prompt_id: '-', nodes: 'Queue is empty' }]; + } + + return results.map((item, index) => ({ rank: index + 1, ...item })); + }, +}); diff --git a/clis/comfyui/run.js b/clis/comfyui/run.js new file mode 100644 index 00000000..6c7879d2 --- /dev/null +++ b/clis/comfyui/run.js @@ -0,0 +1,48 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; +import { CommandExecutionError } from '@jackwener/opencli/errors'; + +cli({ + site: 'comfyui', + name: 'run', + description: 'Execute a ComfyUI workflow (pass a JSON workflow prompt string or a path to a JSON file)', + strategy: Strategy.PUBLIC, + args: [ + { name: 'prompt', type: 'str', required: true, positional: true, description: 'Workflow prompt as JSON string or path to a JSON file' }, + { name: 'client_id', type: 'str', default: 'opencli', description: 'Client ID for tracking the task' }, + ], + columns: ['status', 'prompt_id', 'queue_number'], + func: async (page, kwargs) => { + let promptObj; + try { + promptObj = JSON.parse(kwargs.prompt); + } catch (e) { + try { + const { readFileSync } = await import('fs'); + promptObj = JSON.parse(readFileSync(kwargs.prompt, 'utf-8')); + } catch (fileErr) { + throw new CommandExecutionError(`Failed to parse prompt: ${e.message}, and could not read file: ${kwargs.prompt}`); + } + } + + const res = await fetch(url('/prompt'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ prompt: promptObj, client_id: kwargs.client_id }), + }); + const data = await res.json(); + + if (data.error) { + throw new CommandExecutionError(`${data.error.type}: ${data.error.message}`); + } + + return [{ + status: 'submitted', + prompt_id: data.prompt_id || 'unknown', + queue_number: data.number ?? '-', + node_errors: Object.keys(data.node_errors || {}).length > 0 + ? Object.keys(data.node_errors).join(', ') + : 'none', + }]; + }, +}); diff --git a/clis/comfyui/search-node.js b/clis/comfyui/search-node.js new file mode 100644 index 00000000..da1b334b --- /dev/null +++ b/clis/comfyui/search-node.js @@ -0,0 +1,39 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; + +cli({ + site: 'comfyui', + name: 'search-node', + description: 'Search ComfyUI nodes by keyword (matches node name and input field names)', + strategy: Strategy.PUBLIC, + args: [ + { name: 'query', type: 'str', required: true, positional: true }, + { name: 'limit', type: 'int', default: 30 }, + ], + columns: ['rank', 'name', 'match_type', 'matching_fields'], + func: async (page, kwargs) => { + const res = await fetch(url('/api/object_info')); + const data = await res.json(); + const query = kwargs.query.toLowerCase(); + const results = []; + + for (const [name, info] of Object.entries(data)) { + const nameMatch = name.toLowerCase().includes(query); + const inputs = info.input || {}; + const allFields = [...Object.keys(inputs.required || {}), ...Object.keys(inputs.optional || {})]; + const matchingFields = allFields.filter(f => f.toLowerCase().includes(query)); + + if (nameMatch || matchingFields.length > 0) { + results.push({ + name, + match_type: nameMatch ? 'name' : 'field', + matching_fields: nameMatch ? name : matchingFields.slice(0, 5).join(', '), + total_fields: allFields.length, + }); + } + } + + results.sort((a, b) => a.name.localeCompare(b.name)); + return results.slice(0, kwargs.limit).map((item, index) => ({ rank: index + 1, ...item })); + }, +}); diff --git a/clis/comfyui/system-stats.js b/clis/comfyui/system-stats.js new file mode 100644 index 00000000..23b04a29 --- /dev/null +++ b/clis/comfyui/system-stats.js @@ -0,0 +1,31 @@ +import { cli, Strategy } from '../../registry.js'; +import { url } from './config.js'; + +cli({ + site: 'comfyui', + name: 'system-stats', + description: 'Show ComfyUI server status and system information', + strategy: Strategy.PUBLIC, + columns: ['comfyui_version', 'os', 'devices', 'ram_total', 'ram_free'], + func: async () => { + const res = await fetch(url('/api/system_stats')); + const data = await res.json(); + const sys = data.system || {}; + const devices = data.devices || []; + + const formatBytes = (bytes) => { + if (!bytes) return 'N/A'; + return (bytes / 1073741824).toFixed(1) + ' GB'; + }; + + const devNames = devices.map(d => `${d.name || d.device || 'unknown'} (${formatBytes(d.vram_total)})`).join(', '); + + return [{ + comfyui_version: sys.comfyui_version || 'unknown', + os: sys.os || 'unknown', + devices: devices.length > 0 ? `${devices.length} device(s): ${devNames}` : 'none', + ram_total: formatBytes(sys.ram_total), + ram_free: formatBytes(sys.ram_free), + }]; + }, +}); From c8b527024ac2a66882a7cb2dfe3ea846c2d19ffe Mon Sep 17 00:00:00 2001 From: williamxie1989 <21219102@qq.com> Date: Tue, 7 Apr 2026 11:34:40 +0800 Subject: [PATCH 2/2] docs: add ComfyUI adapter documentation Add browser adapter doc for ComfyUI with commands table, usage examples, and configuration instructions. Register in VitePress sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/.vitepress/config.mts | 1 + docs/adapters/browser/comfyui.md | 69 ++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 docs/adapters/browser/comfyui.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index c2614752..f10a19b1 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -108,6 +108,7 @@ export default defineConfig({ { text: 'Apple Podcasts', link: '/adapters/browser/apple-podcasts' }, { text: 'Xiaoyuzhou', link: '/adapters/browser/xiaoyuzhou' }, { text: 'Yahoo Finance', link: '/adapters/browser/yahoo-finance' }, + { text: 'ComfyUI', link: '/adapters/browser/comfyui' }, { text: 'arXiv', link: '/adapters/browser/arxiv' }, { text: 'paperreview.ai', link: '/adapters/browser/paperreview' }, { text: 'Barchart', link: '/adapters/browser/barchart' }, diff --git a/docs/adapters/browser/comfyui.md b/docs/adapters/browser/comfyui.md new file mode 100644 index 00000000..fea25f18 --- /dev/null +++ b/docs/adapters/browser/comfyui.md @@ -0,0 +1,69 @@ +# ComfyUI + +**Mode**: ๐ŸŒ Public ยท **Domain**: ComfyUI server (local or remote) + +Manage [ComfyUI](https://github.com/comfyanonymous/ComfyUI) servers from the command line โ€” list nodes, explore models, run workflows, and monitor queue status. + +## Commands + +| Command | Description | +|---------|-------------| +| `opencli comfyui nodes` | List all registered node types | +| `opencli comfyui node-info ` | Detailed definition of a node | +| `opencli comfyui search-node ` | Search nodes by keyword | +| `opencli comfyui models` | List available model files | +| `opencli comfyui models --model_type ` | List models of a specific type | +| `opencli comfyui system-stats` | Server status and system information | +| `opencli comfyui run ` | Execute a workflow | +| `opencli comfyui queue` | View running/pending queue | +| `opencli comfyui history` | View execution history | + +## Usage Examples + +```bash +# Check server status +opencli comfyui system-stats + +# List first 10 node types +opencli comfyui nodes --limit 10 + +# Search for nodes related to "sampler" +opencli comfyui search-node sampler --limit 5 + +# View detailed input fields for KSampler +opencli comfyui node-info KSampler + +# List all model types with counts +opencli comfyui models + +# List LoRA models +opencli comfyui models --model_type loras + +# Run a workflow from JSON +opencli comfyui run '{"4":{"class_type":"EmptyLatentImage","inputs":{"width":512,"height":512,"batch_size":1}}}' + +# Watch the queue +opencli comfyui queue + +# View last 5 execution history entries +opencli comfyui history --limit 5 +``` + +## Configuration + +Set the `COMFYUI_HOST` environment variable to point to your ComfyUI server: + +```bash +export COMFYUI_HOST=http://127.0.0.1:8188 +``` + +Default is `http://127.0.0.1:8188`. For remote servers on your local network: + +```bash +export COMFYUI_HOST=http://192.168.1.100:8008 +``` + +## Prerequisites + +- A running [ComfyUI](https://github.com/comfyanonymous/ComfyUI) server +- No browser required โ€” uses ComfyUI REST API directly