#!/usr/bin/env node import fs from 'fs/promises'; const DEFAULT_PACKS = [ ['ComfyUI-Manager', 'https://github.com/Comfy-Org/ComfyUI-Manager.git'], ['christian-byrne/audio-separation-nodes-comfyui', 'https://github.com/christian-byrne/audio-separation-nodes-comfyui.git'], ['yvann-ba/ComfyUI_Yvann-Nodes', 'https://github.com/yvann-ba/ComfyUI_Yvann-Nodes.git'], ['yolain/ComfyUI-Easy-Use', 'https://github.com/yolain/ComfyUI-Easy-Use.git'], ['wildminder/ComfyUI-VoxCPM', 'https://github.com/wildminder/ComfyUI-VoxCPM.git'], ['kijai/ComfyUI-WanVideoWrapper', 'https://github.com/kijai/ComfyUI-WanVideoWrapper.git'], ['civen-cn/ComfyUI-Whisper-Translator', 'https://github.com/civen-cn/ComfyUI-Whisper-Translator.git'], ['kijai/ComfyUI-KJNodes', 'https://github.com/kijai/ComfyUI-KJNodes.git'], ]; const args = new Set(process.argv.slice(2)); const configPath = valueArg('--config') || 'config.toml'; const baseUrl = trimSlash( process.env.BOOSAPI_COMFYUI__BASE_URL || process.env.COMFYUI_CLOUD_BASE_URL || (await readComfyConfig(configPath)).base_url || 'https://cloud.comfy.org' ); const apiKey = process.env.BOOSAPI_COMFYUI__API_KEY || process.env.COMFYUI_CLOUD_API_KEY || (await readComfyConfig(configPath)).api_key || ''; const dryRun = args.has('--dry-run'); if (!baseUrl) { fail('Missing Cloud base URL. Set BOOSAPI_COMFYUI__BASE_URL or [comfyui].base_url.'); } if (!apiKey) { fail('Missing Cloud API key. Set BOOSAPI_COMFYUI__API_KEY or [comfyui].api_key.'); } console.log(`Cloud backend: ${baseUrl}`); console.log(`Packs: ${DEFAULT_PACKS.length}`); if (dryRun) { for (const [id, repository] of DEFAULT_PACKS) { console.log(`[dry-run] ${id} <- ${repository}`); } process.exit(0); } await request('GET', '/api/manager/queue/start').catch((error) => { console.warn(`manager queue start warning: ${error.message}`); }); const results = []; for (const [id, repository] of DEFAULT_PACKS) { const payload = { id, version: 'nightly', selected_version: 'nightly', repository, mode: 'remote', channel: 'default', skip_post_install: false, }; try { await request('POST', '/api/manager/queue/install', payload); console.log(`queued: ${id}`); results.push({ id, ok: true }); } catch (error) { console.error(`failed: ${id}: ${error.message}`); results.push({ id, ok: false, error: error.message }); } } await request('GET', '/api/manager/queue/status') .then((status) => console.log(`queue status: ${JSON.stringify(status)}`)) .catch((error) => console.warn(`queue status warning: ${error.message}`)); const failed = results.filter((item) => !item.ok); if (failed.length > 0) { console.error(`\n${failed.length} pack(s) failed to queue. If Cloud returned 403/404, the hosted backend does not allow Manager installs for this workspace/API key.`); process.exit(1); } console.log('\nAll install jobs were queued. Restart the Cloud ComfyUI runtime if Manager asks for it.'); async function request(method, path, body) { const response = await fetch(`${baseUrl}${path}`, { method, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-API-Key': apiKey, }, body: body ? JSON.stringify(body) : undefined, }); const text = await response.text(); const data = text ? safeJson(text) : null; if (!response.ok) { throw new Error(`${response.status} ${response.statusText}: ${text.slice(0, 500)}`); } return data; } function safeJson(text) { try { return JSON.parse(text); } catch { return { body: text }; } } async function readComfyConfig(path) { const result = {}; let section = ''; let text = ''; try { text = await fs.readFile(path, 'utf8'); } catch { return result; } for (const rawLine of text.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) continue; const sectionMatch = line.match(/^\[([^\]]+)\]$/); if (sectionMatch) { section = sectionMatch[1]; continue; } if (section !== 'comfyui') continue; const match = line.match(/^([A-Za-z0-9_]+)\s*=\s*"(.*)"\s*$/); if (match) result[match[1]] = match[2]; } return result; } function trimSlash(value) { return String(value || '').trim().replace(/\/+$/, ''); } function valueArg(name) { const prefix = `${name}=`; const found = process.argv.slice(2).find((arg) => arg.startsWith(prefix)); return found ? found.slice(prefix.length) : ''; } function fail(message) { console.error(message); process.exit(2); }