/** * Figma Bridge — Dual-mode transport to Figma. * * Mode 1 (Server): Starts a WebSocket server, waits for the Mémoire * plugin to connect. Supports multiple simultaneous plugin connections * and chat relay between Figma and terminal. * * Mode 2 (Client): Connects to an existing figma-console-mcp WebSocket * server as a fallback for upstream compatibility. * * The bridge abstracts both modes behind a single API. */ import { EventEmitter } from "events"; import { MemoireWsServer, type ConnectionState } from "./ws-server.js"; import type { MemoireEvent } from "../engine/core.js"; import type { DesignSystem } from "../engine/registry.js"; import type { IASpec } from "../specs/types.js"; import type { AgentBoxState } from "../plugin/shared/contracts.js"; import { BRIDGE_PORT_START, BRIDGE_PORT_END } from "./port-scanner.js"; export interface FigmaBridgeConfig { token?: string; fileKey?: string; port?: number; instanceName?: string; studioUrl?: string; runtimeUrl?: string; onEvent?: (event: MemoireEvent) => void; onChat?: (text: string, from: string) => void; } interface RawPageTreeNode { id: string; name: string; type: string; visible?: boolean; children?: RawPageTreeNode[]; } interface RawPageTree { fileKey: string; fileName: string; pages: { id: string; name: string; children: RawPageTreeNode[]; }[]; } export type { ConnectionState }; export { BRIDGE_PORT_START, BRIDGE_PORT_END }; export declare class FigmaBridge extends EventEmitter { private log; private config; private server; private resyncTimer; private reconnectAttempt; private static readonly RECONNECT_BASE_MS; private static readonly RECONNECT_MAX_MS; constructor(config: FigmaBridgeConfig); /** Check connection state directly from server — no stale cache. */ get isConnected(): boolean; get wsServer(): MemoireWsServer; /** * Logical connection state: "connected" | "reconnecting" | "disconnected". * Delegates to the underlying WS server's backoff tracker. */ getConnectionState(): ConnectionState; /** Number of consecutive reconnect attempts since the last successful connection. */ get reconnectAttempts(): number; /** Timestamp of the last time a Figma plugin connected. */ get lastConnectedAt(): Date | null; /** Timestamp of the last time all Figma plugins disconnected. */ get lastDisconnectedAt(): Date | null; /** * Start the bridge server and wait for Figma plugins to connect. * Returns the port the server is listening on. */ connect(preferPort?: number): Promise; disconnect(): Promise; /** * Send a command to the connected Figma plugin. */ execute(code: string, timeout?: number): Promise; getSelection(): Promise; /** * Pulls the operator-surface snapshot (Jobs / Selection / System + metrics) * exposed by the plugin main thread as the `widgetSnapshot` command. Used by * `memi doctor --json` to attach live widget health to its diagnostic report * so agents can consume machine-readable bridge state in one call. */ getWidgetSnapshot(timeout?: number): Promise; getFileData(depth?: number): Promise; /** * Send a chat message to all connected Figma plugins. */ sendChat(text: string): void; publishAgentStatus(status: AgentBoxState): void; /** * Get connection status for all bridges. */ getStatus(): { running: boolean; port: number; bridgeStatus: "stopped" | "running"; pluginStatus: "disconnected" | "connected"; clients: { id: string; file: string; fileKey: string; editor: string; connectedAt: string; lastPing: string; }[]; connectionState: ConnectionState; reconnectAttempts: number; lastConnectedAt: string | null; lastDisconnectedAt: string | null; }; /** * Capture a screenshot of a Figma node (or current page if no nodeId). */ captureScreenshot(nodeId?: string, format?: "PNG" | "SVG", scale?: number): Promise<{ base64: string; format: string; scale: number; byteLength: number; }>; createNode(params: Record): Promise; updateNode(nodeId: string, properties: Record, expectedVersion?: string): Promise; deleteNode(nodeId: string): Promise; setSelection(nodeIds: string[]): Promise; navigateTo(nodeId: string): Promise; extractDesignSystem(): Promise; extractStickies(): Promise; /** * Get the full page tree from Figma — all pages with their top-level frames. */ getPageTree(depth?: number): Promise; /** * Extract an IASpec from the Figma file's page structure. * Converts Figma pages → IA nodes, top-level frames → sections. */ extractIA(name: string, depth?: number): Promise; /** * Navigate to a specific page by name or ID. * Uses figma.setCurrentPageAsync (required for dynamic-page documentAccess). */ navigateToPage(pageNameOrId: string): Promise; getComponentImage(nodeId: string, format?: "png" | "svg"): Promise; /** * Push token values to Figma (code → Figma direction). * Sends a token-push message to the connected plugin. */ pushTokens(tokens: { name: string; values: Record; }[], sourceOrOptions?: "code" | "manual" | { source?: "code" | "manual"; createMissing?: boolean; collectionName?: string; }): Promise; private parseTokens; private parseComponents; private parseStyles; /** Exponential backoff with jitter for reconnection delays. */ private getReconnectDelay; private emitEvent; } export interface StickyNote { id: string; text: string; color?: string; position: { x: number; y: number; }; size: { width: number; height: number; }; }