Skip to main content
Glama
mutations.js82.9 kB
/** * Mutation tools - tools that modify the Figma document */ /** * Set fills on a node */ export async function handleSetFills(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, fills } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_fills', { nodeId, fills }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set strokes on a node */ export async function handleSetStrokes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, strokes, strokeWeight } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_strokes', { nodeId, strokes, strokeWeight }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a rectangle */ export async function handleCreateRectangle(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_rectangle', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set text content */ export async function handleSetText(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, text } = args; if (!nodeId || text === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId and text are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_text', { nodeId, text }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Clone nodes */ export async function handleCloneNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds, parentId, offset } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('clone_nodes', { nodeIds, parentId, offset }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Delete nodes */ export async function handleDeleteNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('delete_nodes', { nodeIds }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Move nodes */ export async function handleMoveNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds, x, y, relative } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } if (x === undefined && y === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'At least one of x or y must be provided' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('move_nodes', { nodeIds, x, y, relative }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Resize nodes */ export async function handleResizeNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds, width, height } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } if (width === undefined && height === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'At least one of width or height must be provided' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('resize_nodes', { nodeIds, width, height }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set opacity */ export async function handleSetOpacity(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, opacity } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } if (opacity === undefined || opacity < 0 || opacity > 1) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'opacity must be a number between 0 and 1' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_opacity', { nodeId, opacity }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set corner radius */ export async function handleSetCornerRadius(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, radius, topLeft, topRight, bottomLeft, bottomRight } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } if (radius === undefined && topLeft === undefined && topRight === undefined && bottomLeft === undefined && bottomRight === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'At least one radius value must be provided' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_corner_radius', { nodeId, radius, topLeft, topRight, bottomLeft, bottomRight }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Group nodes */ export async function handleGroupNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds, name } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('group_nodes', { nodeIds, name }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Ungroup nodes */ export async function handleUngroupNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('ungroup_nodes', { nodeIds }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a frame */ export async function handleCreateFrame(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_frame', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a text node */ export async function handleCreateText(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_text', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set selection */ export async function handleSetSelection(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds } = args; if (!nodeIds || !Array.isArray(nodeIds)) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be an array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_selection', { nodeIds }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set current page */ export async function handleSetCurrentPage(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { pageId } = args; if (!pageId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'pageId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_current_page', { pageId }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Export node as image */ export async function handleExportNode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, format, scale } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('export_node', { nodeId, format, scale }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // New Commands (Phase 2) // ============================================================ /** * Create an ellipse */ export async function handleCreateEllipse(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_ellipse', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set effects on a node */ export async function handleSetEffects(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, effects } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } if (!effects || !Array.isArray(effects)) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'effects must be an array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_effects', { nodeId, effects }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set auto-layout on a frame */ export async function handleSetAutoLayout(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_auto_layout', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Get local styles */ export async function handleGetLocalStyles(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('get_local_styles', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Apply a style to a node */ export async function handleApplyStyle(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, styleId, property } = args; if (!nodeId || !styleId || !property) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId, styleId, and property are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('apply_style', { nodeId, styleId, property }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a component */ export async function handleCreateComponent(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_component', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create an instance of a component */ export async function handleCreateInstance(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { componentId } = args; if (!componentId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'componentId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_instance', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Phase 3 Commands: Variables, Lines, Constraints // ============================================================ /** * Get local variables and variable collections */ export async function handleGetLocalVariables(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('get_local_variables', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Search variables with filtering (optimized for reduced token usage) */ export async function handleSearchVariables(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('search_variables', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set variable value or bind variable to node */ export async function handleSetVariable(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { variableId, modeId, value, nodeId, field } = args; if (!variableId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'variableId is required' } }, null, 2) }], isError: true }; } // Validate: either (modeId + value) for setting, or (nodeId + field) for binding if (value !== undefined && !modeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'modeId is required when setting a value' } }, null, 2) }], isError: true }; } if (nodeId && !field) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'field is required when binding to a node' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_variable', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a line */ export async function handleCreateLine(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_line', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set constraints on a node */ export async function handleSetConstraints(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, horizontal, vertical } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } if (horizontal === undefined && vertical === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'At least one of horizontal or vertical must be provided' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_constraints', { nodeId, horizontal, vertical }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Phase 4 Commands: Polygons, Boolean Operations, Viewport, Blend Mode, Detach // ============================================================ /** * Create a polygon or star */ export async function handleCreatePolygon(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_polygon', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Perform boolean operation on nodes */ export async function handleBooleanOperation(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { operation, nodeIds } = args; if (!operation) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'operation is required' } }, null, 2) }], isError: true }; } if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length < 2) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be an array with at least 2 nodes' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('boolean_operation', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Zoom viewport to node(s) */ export async function handleZoomToNode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('zoom_to_node', { nodeIds }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set blend mode on a node */ export async function handleSetBlendMode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, blendMode } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } if (!blendMode) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'blendMode is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_blend_mode', { nodeId, blendMode }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Detach instance from component */ export async function handleDetachInstance(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('detach_instance', { nodeId }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Phase 5 Commands: Layout Align, Vector, Rename, Reorder // ============================================================ /** * Set layout align properties on a node (for auto-layout children) */ export async function handleSetLayoutAlign(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_layout_align', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a vector with custom path data */ export async function handleCreateVector(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { data } = args; if (!data) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'data (SVG path string) is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_vector', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Rename one or more nodes */ export async function handleRenameNode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, nodeIds, name } = args; if (!nodeId && (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0)) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId or nodeIds is required' } }, null, 2) }], isError: true }; } if (!name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'name is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('rename_node', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Reorder a node (change z-order) */ export async function handleReorderNode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, position } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } if (position === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'position is required (front, back, or index number)' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('reorder_node', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Smart Query Commands (token-efficient search) // ============================================================ /** * Search nodes by name within a scope */ export async function handleSearchNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { parentId } = args; if (!parentId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'parentId is required to scope the search' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('search_nodes', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Search local components by name */ export async function handleSearchComponents(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('search_components', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Search local styles by name */ export async function handleSearchStyles(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('search_styles', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Get immediate children of a node */ export async function handleGetChildren(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { parentId } = args; if (!parentId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'parentId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('get_children', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set text style properties on an existing text node */ export async function handleSetTextStyle(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_text_style', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a new local paint style */ export async function handleCreatePaintStyle(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { name, fills } = args; if (!name || !fills) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'name and fills are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_paint_style', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a new local text style */ export async function handleCreateTextStyle(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { name } = args; if (!name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'name is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_text_style', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a new variable collection */ export async function handleCreateVariableCollection(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { name } = args; if (!name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'name is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_variable_collection', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Create a new variable in a collection */ export async function handleCreateVariable(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { collectionId, name, type } = args; if (!collectionId || !name || !type) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'collectionId, name, and type are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_variable', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Rename an existing variable */ export async function handleRenameVariable(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { variableId, name } = args; if (!variableId || !name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'variableId and name are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('rename_variable', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Delete one or more variables */ export async function handleDeleteVariables(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { variableIds } = args; if (!variableIds || !Array.isArray(variableIds) || variableIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'variableIds array is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('delete_variables', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Delete a variable collection */ export async function handleDeleteVariableCollection(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { collectionId } = args; if (!collectionId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'collectionId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('delete_variable_collection', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Rename a variable collection */ export async function handleRenameVariableCollection(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { collectionId, name } = args; if (!collectionId || !name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'collectionId and name are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('rename_variable_collection', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Rename a mode in a variable collection */ export async function handleRenameMode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { collectionId, modeId, name } = args; if (!collectionId || !modeId || !name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'collectionId, modeId, and name are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('rename_mode', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Add a mode to a variable collection */ export async function handleAddMode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { collectionId, name } = args; if (!collectionId || !name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'collectionId and name are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('add_mode', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Delete a mode from a variable collection */ export async function handleDeleteMode(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { collectionId, modeId } = args; if (!collectionId || !modeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'collectionId and modeId are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('delete_mode', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Unbind a variable from a node property */ export async function handleUnbindVariable(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, field } = args; if (!nodeId || !field) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId and field are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('unbind_variable', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Page Management Commands // ============================================================ /** * Create a new page */ export async function handleCreatePage(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { name } = args; if (!name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'name is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('create_page', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Rename a page */ export async function handleRenamePage(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { pageId, name } = args; if (!pageId || !name) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'pageId and name are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('rename_page', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Delete a page */ export async function handleDeletePage(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { pageId } = args; if (!pageId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'pageId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('delete_page', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Reorder a page (change its position in the page list) */ export async function handleReorderPage(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { pageId, index } = args; if (!pageId || index === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'pageId and index are required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('reorder_page', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Node Structure Commands // ============================================================ /** * Reparent nodes - move nodes to a different parent */ export async function handleReparentNodes(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds, newParentId } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } if (!newParentId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'newParentId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('reparent_nodes', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Move nodes to a different page */ export async function handleMoveToPage(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds, targetPageId } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } if (!targetPageId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'targetPageId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('move_to_page', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Component Instance Commands // ============================================================ /** * Swap an instance to use a different component */ export async function handleSwapInstance(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { instanceId, newComponentId } = args; if (!instanceId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'instanceId is required' } }, null, 2) }], isError: true }; } if (!newComponentId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'newComponentId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('swap_instance', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } // ============================================================ // Additional Commands // ============================================================ /** * Duplicate a page - clone entire page with all contents */ export async function handleDuplicatePage(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { pageId } = args; if (!pageId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'pageId is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('duplicate_page', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set rotation on nodes */ export async function handleSetRotation(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeIds, rotation } = args; if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeIds must be a non-empty array' } }, null, 2) }], isError: true }; } if (rotation === undefined) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'rotation is required' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_rotation', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Set layout grids on a frame */ export async function handleSetLayoutGrids(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { nodeId, layoutGrids } = args; if (!nodeId) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'nodeId is required' } }, null, 2) }], isError: true }; } if (!layoutGrids || !Array.isArray(layoutGrids)) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'layoutGrids must be an array' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('set_layout_grids', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } } /** * Combine components as variants into a component set */ export async function handleCombineAsVariants(bridge, args) { if (!bridge.isConnected()) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' } }, null, 2) }], isError: true }; } const { componentIds } = args; if (!componentIds || !Array.isArray(componentIds) || componentIds.length < 2) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'INVALID_PARAMS', message: 'componentIds must be an array with at least 2 component IDs' } }, null, 2) }], isError: true }; } try { const result = await bridge.sendCommand('combine_as_variants', args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: error.code || 'UNKNOWN_ERROR', message: error.message } }, null, 2) }], isError: true }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/magic-spells/figma-mcp-bridge'

If you have feedback or need assistance with the MCP directory API, please join our Discord server