/** * Standalone WebSocket bridge. * * Owns two ports and nothing else — it is a dumb relay with no knowledge of * Aseprite semantics: * plugin port : the Aseprite Lua extension dials in (at most one, last wins) * control port : MCP server processes dial in (N of them) * * It runs as its own process, detached from any MCP server, because agent hosts * own the MCP server lifecycle and restart it freely. A bridge living inside the * MCP process drops the Aseprite connection on every restart and duplicate * processes fight over the listen port. See docs/adr/0002-standalone-bridge.md. * * Singleton by port ownership: whoever binds the control port first is the * bridge; a loser of the bind race exits 0 without complaint. */ export interface BridgeOptions { pluginPort?: number; controlPort?: number; version: string; log?: (msg: string) => void; } export declare class Bridge { private readonly pluginPort; private readonly controlPort; private readonly version; private readonly log; private pluginServer; private controlServer; private plugin; private hello; private since; private clients; private nextClientId; constructor(opts: BridgeOptions); /** * Bind both ports. Resolves false when the control port is already taken, * which means another bridge already owns this machine's Aseprite link. */ start(): Promise; stop(): Promise; private onPluginConnect; private onPluginMessage; private onControlConnect; private onControlMessage; private stateFrame; private broadcastState; } export declare class BridgeBindError extends Error { readonly port: number; constructor(port: number, cause: NodeJS.ErrnoException); }