UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

132 lines • 5.11 kB
import { chmodSync, closeSync, fstatSync, ftruncateSync, mkdirSync, openSync, readSync, writeSync, } from "node:fs"; import { dirname } from "node:path"; // Logs to stderr by default — critical for MCP stdio transport. The // orchestrator may additionally opt into a bounded file sink before it starts; // the ordinary MCP server never does, so its wire transport stays untouched. const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; /** Keep one diagnostic file from growing forever across a long-lived install. */ export const MAX_PERSISTENT_LOG_BYTES = 2 * 1024 * 1024; const TRUNCATION_MARKER = "\n[comfyui-mcp] persistent log truncated; keeping the newest records\n"; let persistentFileSink; const currentLevel = process.env.LOG_LEVEL || "info"; function shouldLog(level) { return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel]; } function retainNewestBytes(fd, size, reserveBytes = 0) { const marker = Buffer.from(TRUNCATION_MARKER, "utf8"); const keep = Math.max(0, MAX_PERSISTENT_LOG_BYTES - marker.length - reserveBytes); const tailLength = Math.min(size, keep); const tail = Buffer.alloc(tailLength); if (tailLength > 0) readSync(fd, tail, 0, tailLength, size - tailLength); ftruncateSync(fd, 0); writeSync(fd, marker, 0, marker.length, 0); if (tailLength > 0) writeSync(fd, tail, 0, tailLength, marker.length); return marker.length + tailLength; } /** * Open a best-effort persistent mirror for a long-lived process. * * The sink is deliberately explicit rather than env-enabled: a normal MCP * stdio process must never write an unexpected file, while the panel * orchestrator has a user-visible need for evidence after its terminal dies. * Writes are synchronous because the orchestrator has several hard * `process.exit()` paths; an async stream could lose the last fatal line. */ export function configurePersistentLogFile(path) { closePersistentLogFile(); let fd; try { mkdirSync(dirname(path), { recursive: true }); // Create first when needed, then reopen read/write. Windows does not allow // ftruncate on an append-mode descriptor, and bounded rotation needs both // operations on the same file. try { fd = openSync(path, "r+", 0o600); } catch (err) { if (err.code !== "ENOENT") throw err; fd = openSync(path, "w+", 0o600); } try { // chmod is a no-op on platforms whose ACLs own the mode, and best-effort // there. On POSIX this keeps diagnostic contents owner-readable only. chmodSync(path, 0o600); } catch { /* best-effort permissions */ } const size = fstatSync(fd).size; const bytes = size > MAX_PERSISTENT_LOG_BYTES ? retainNewestBytes(fd, size) : size; persistentFileSink = { fd, bytes }; return true; } catch { if (fd !== undefined) { try { closeSync(fd); } catch { /* best-effort cleanup */ } } return false; } } export function closePersistentLogFile() { const sink = persistentFileSink; persistentFileSink = undefined; if (!sink) return; try { closeSync(sink.fd); } catch { /* best-effort cleanup */ } } function appendPersistentLog(line) { const sink = persistentFileSink; if (!sink) return; try { const marker = Buffer.from(TRUNCATION_MARKER, "utf8"); let bytes = Buffer.from(`${line}\n`, "utf8"); if (bytes.length + marker.length >= MAX_PERSISTENT_LOG_BYTES) { const keep = Math.max(0, MAX_PERSISTENT_LOG_BYTES - marker.length); bytes = bytes.subarray(bytes.length - keep); ftruncateSync(sink.fd, 0); writeSync(sink.fd, marker, 0, marker.length, 0); sink.bytes = marker.length; } else if (sink.bytes + bytes.length > MAX_PERSISTENT_LOG_BYTES) { sink.bytes = retainNewestBytes(sink.fd, sink.bytes, bytes.length); } writeSync(sink.fd, bytes, 0, bytes.length, sink.bytes); sink.bytes += bytes.length; } catch { // Diagnostics must never become the reason the orchestrator fails to start // or finish shutting down. Keep stderr logging alive if the file vanishes. closePersistentLogFile(); } } function log(level, message, data) { if (!shouldLog(level)) return; const ts = new Date().toISOString(); const line = data ? `[${ts}] [${level.toUpperCase()}] ${message} ${JSON.stringify(data)}` : `[${ts}] [${level.toUpperCase()}] ${message}`; appendPersistentLog(line); process.stderr.write(line + "\n"); } export const logger = { debug: (msg, data) => log("debug", msg, data), info: (msg, data) => log("info", msg, data), warn: (msg, data) => log("warn", msg, data), error: (msg, data) => log("error", msg, data), }; //# sourceMappingURL=logger.js.map