#
tokens: 27309/50000 5/63 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 2. Use http://codebase.md/sichang824/mcp-figma?page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .envrc
├── .gitignore
├── .mcp.pid
├── bun.lockb
├── docs
│   ├── 01-overview.md
│   ├── 02-implementation-steps.md
│   ├── 03-components-and-features.md
│   ├── 04-usage-guide.md
│   ├── 05-project-status.md
│   ├── image.png
│   ├── README.md
│   └── widget-tools-guide.md
├── Makefile
├── manifest.json
├── package.json
├── prompt.md
├── README.md
├── README.zh.md
├── src
│   ├── config
│   │   └── env.ts
│   ├── index.ts
│   ├── plugin
│   │   ├── code.js
│   │   ├── code.ts
│   │   ├── creators
│   │   │   ├── componentCreators.ts
│   │   │   ├── containerCreators.ts
│   │   │   ├── elementCreator.ts
│   │   │   ├── imageCreators.ts
│   │   │   ├── shapeCreators.ts
│   │   │   ├── sliceCreators.ts
│   │   │   ├── specialCreators.ts
│   │   │   └── textCreator.ts
│   │   ├── manifest.json
│   │   ├── README.md
│   │   ├── tsconfig.json
│   │   ├── ui.html
│   │   └── utils
│   │       ├── colorUtils.ts
│   │       └── nodeUtils.ts
│   ├── resources.ts
│   ├── services
│   │   ├── figma-api.ts
│   │   ├── websocket.ts
│   │   └── widget-api.ts
│   ├── tools
│   │   ├── canvas.ts
│   │   ├── comment.ts
│   │   ├── component.ts
│   │   ├── file.ts
│   │   ├── frame.ts
│   │   ├── image.ts
│   │   ├── index.ts
│   │   ├── node.ts
│   │   ├── page.ts
│   │   ├── search.ts
│   │   ├── utils
│   │   │   └── widget-utils.ts
│   │   ├── version.ts
│   │   ├── widget
│   │   │   ├── analyze-widget-structure.ts
│   │   │   ├── get-widget-sync-data.ts
│   │   │   ├── get-widget.ts
│   │   │   ├── get-widgets.ts
│   │   │   ├── index.ts
│   │   │   ├── README.md
│   │   │   ├── search-widgets.ts
│   │   │   └── widget-tools.ts
│   │   └── zod-schemas.ts
│   ├── utils
│   │   ├── figma-utils.ts
│   │   └── widget-utils.ts
│   ├── utils.ts
│   ├── widget
│   │   └── utils
│   │       └── widget-tools.ts
│   └── widget-tools.ts
├── tsconfig.json
└── tsconfig.widget.json
```

# Files

--------------------------------------------------------------------------------
/src/plugin/creators/shapeCreators.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Shape creation functions for Figma plugin
 */

import { createSolidPaint } from "../utils/colorUtils";
import { selectAndFocusNodes } from "../utils/nodeUtils";

/**
 * Create a rectangle from data
 * @param data Rectangle configuration data
 * @returns Created rectangle node
 */
export function createRectangleFromData(data: any): RectangleNode {
  const rect = figma.createRectangle();

  // Size
  rect.resize(data.width || 100, data.height || 100);

  // Fill
  if (data.fills) {
    rect.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      // Handle hex color
      rect.fills = [createSolidPaint(data.fill)];
    } else {
      // Handle fill object
      rect.fills = [data.fill];
    }
  }

  // Stroke
  if (data.strokes) rect.strokes = data.strokes;
  if (data.strokeWeight !== undefined) rect.strokeWeight = data.strokeWeight;
  if (data.strokeAlign) rect.strokeAlign = data.strokeAlign;
  if (data.strokeCap) rect.strokeCap = data.strokeCap;
  if (data.strokeJoin) rect.strokeJoin = data.strokeJoin;
  if (data.dashPattern) rect.dashPattern = data.dashPattern;

  // Corner radius
  if (data.cornerRadius !== undefined) rect.cornerRadius = data.cornerRadius;
  if (data.topLeftRadius !== undefined) rect.topLeftRadius = data.topLeftRadius;
  if (data.topRightRadius !== undefined)
    rect.topRightRadius = data.topRightRadius;
  if (data.bottomLeftRadius !== undefined)
    rect.bottomLeftRadius = data.bottomLeftRadius;
  if (data.bottomRightRadius !== undefined)
    rect.bottomRightRadius = data.bottomRightRadius;

  return rect;
}

/**
 * Create a simple rectangle
 * Convenient function for basic rectangle creation
 *
 * @param x X coordinate
 * @param y Y coordinate
 * @param width Width of rectangle
 * @param height Height of rectangle
 * @param color Fill color as hex string
 * @returns Created rectangle node
 */
export function createRectangle(
  x: number,
  y: number,
  width: number,
  height: number,
  color: string
): RectangleNode {
  // Use the new data-driven function
  const rect = createRectangleFromData({
    width,
    height,
    fill: color,
  });

  // Set position
  rect.x = x;
  rect.y = y;

  // Select and focus
  selectAndFocusNodes(rect);

  return rect;
}

/**
 * Create an ellipse/circle from data
 * @param data Ellipse configuration data
 * @returns Created ellipse node
 */
export function createEllipseFromData(data: any): EllipseNode {
  const ellipse = figma.createEllipse();

  // Size
  ellipse.resize(data.width || 100, data.height || 100);

  // Fill
  if (data.fills) {
    ellipse.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      // Handle hex color
      ellipse.fills = [createSolidPaint(data.fill)];
    } else {
      // Handle fill object
      ellipse.fills = [data.fill];
    }
  }

  // Arc data for partial ellipses (arcs/donuts)
  if (data.arcData) {
    ellipse.arcData = {
      startingAngle: data.arcData.startingAngle !== undefined ? data.arcData.startingAngle : 0,
      endingAngle: data.arcData.endingAngle !== undefined ? data.arcData.endingAngle : 360,
      innerRadius: data.arcData.innerRadius !== undefined ? data.arcData.innerRadius : 0
    };
  }

  // Stroke
  if (data.strokes) ellipse.strokes = data.strokes;
  if (data.strokeWeight !== undefined) ellipse.strokeWeight = data.strokeWeight;
  if (data.strokeAlign) ellipse.strokeAlign = data.strokeAlign;
  if (data.strokeCap) ellipse.strokeCap = data.strokeCap;
  if (data.strokeJoin) ellipse.strokeJoin = data.strokeJoin;
  if (data.dashPattern) ellipse.dashPattern = data.dashPattern;

  return ellipse;
}

/**
 * Create a simple circle or ellipse
 * @param x X coordinate
 * @param y Y coordinate
 * @param width Width of ellipse
 * @param height Height of ellipse
 * @param color Fill color as hex string
 * @returns Created ellipse node
 */
export function createCircle(
  x: number,
  y: number,
  width: number,
  height: number,
  color: string
): EllipseNode {
  // Use the data-driven function
  const ellipse = createEllipseFromData({
    width,
    height,
    fill: color,
  });

  // Set position
  ellipse.x = x;
  ellipse.y = y;

  // Select and focus
  selectAndFocusNodes(ellipse);

  return ellipse;
}

/**
 * Create a polygon from data
 * @param data Polygon configuration data
 * @returns Created polygon node
 */
export function createPolygonFromData(data: any): PolygonNode {
  const polygon = figma.createPolygon();
  polygon.resize(data.width || 100, data.height || 100);

  // Set number of sides
  if (data.pointCount) polygon.pointCount = data.pointCount;

  // Fill
  if (data.fills) {
    polygon.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      polygon.fills = [createSolidPaint(data.fill)];
    } else {
      polygon.fills = [data.fill];
    }
  } else if (data.color) {
    // For consistency with other shape creation functions
    polygon.fills = [createSolidPaint(data.color)];
  }

  // Stroke
  if (data.strokes) polygon.strokes = data.strokes;
  if (data.strokeWeight !== undefined) polygon.strokeWeight = data.strokeWeight;
  if (data.strokeAlign) polygon.strokeAlign = data.strokeAlign;
  if (data.strokeCap) polygon.strokeCap = data.strokeCap;
  if (data.strokeJoin) polygon.strokeJoin = data.strokeJoin;
  if (data.dashPattern) polygon.dashPattern = data.dashPattern;

  return polygon;
}

/**
 * Create a simple polygon
 * @param x X coordinate
 * @param y Y coordinate
 * @param width Width of polygon
 * @param height Height of polygon
 * @param sides Number of sides (≥ 3)
 * @param color Fill color as hex string
 * @returns Created polygon node
 */
export function createPolygon(
  x: number,
  y: number,
  width: number,
  height: number,
  sides: number = 3,
  color: string
): PolygonNode {
  // Use the data-driven function
  const polygon = createPolygonFromData({
    width,
    height,
    pointCount: sides,
    fill: color
  });

  // Set position
  polygon.x = x;
  polygon.y = y;

  // Select and focus
  selectAndFocusNodes(polygon);

  return polygon;
}

/**
 * Create a star from data
 * @param data Star configuration data
 * @returns Created star node
 */
export function createStarFromData(data: any): StarNode {
  const star = figma.createStar();
  star.resize(data.width || 100, data.height || 100);

  // Star specific properties
  if (data.pointCount) star.pointCount = data.pointCount;
  if (data.innerRadius) star.innerRadius = data.innerRadius;

  // Fill
  if (data.fills) {
    star.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      star.fills = [createSolidPaint(data.fill)];
    } else {
      star.fills = [data.fill];
    }
  }

  return star;
}

/**
 * Create a line from data
 * @param data Line configuration data
 * @returns Created line node
 */
export function createLineFromData(data: any): LineNode {
  const line = figma.createLine();
  
  // Set line length (width)
  line.resize(data.width || 100, 0); // Line height is always 0
  
  // Set rotation if provided
  if (data.rotation !== undefined) line.rotation = data.rotation;

  // Stroke properties
  if (data.strokeWeight) line.strokeWeight = data.strokeWeight;
  if (data.strokeAlign) line.strokeAlign = data.strokeAlign;
  if (data.strokeCap) line.strokeCap = data.strokeCap;
  if (data.strokeJoin) line.strokeJoin = data.strokeJoin;
  if (data.dashPattern) line.dashPattern = data.dashPattern;

  // Stroke color
  if (data.strokes) {
    line.strokes = data.strokes;
  } else if (data.stroke) {
    if (typeof data.stroke === "string") {
      line.strokes = [createSolidPaint(data.stroke)];
    } else {
      line.strokes = [data.stroke];
    }
  } else if (data.color) {
    // For consistency with other shape creation functions
    line.strokes = [createSolidPaint(data.color)];
  }

  return line;
}

/**
 * Create a simple line
 * @param x X coordinate
 * @param y Y coordinate
 * @param length Length of the line (width)
 * @param color Stroke color as hex string
 * @param rotation Rotation in degrees
 * @param strokeWeight Stroke thickness
 * @returns Created line node
 */
export function createLine(
  x: number,
  y: number,
  length: number,
  color: string,
  rotation: number = 0,
  strokeWeight: number = 1
): LineNode {
  // Use the data-driven function
  const line = createLineFromData({
    width: length,
    stroke: color,
    strokeWeight: strokeWeight,
    rotation: rotation
  });

  // Set position
  line.x = x;
  line.y = y;

  // Select and focus
  selectAndFocusNodes(line);

  return line;
}

/**
 * Create a simple arc (partial ellipse)
 * @param x X coordinate
 * @param y Y coordinate
 * @param width Width of ellipse
 * @param height Height of ellipse
 * @param startAngle Starting angle in degrees
 * @param endAngle Ending angle in degrees
 * @param innerRadius Inner radius ratio (0-1) for donut shapes
 * @param color Fill color as hex string
 * @returns Created ellipse node as an arc
 */
export function createArc(
  x: number,
  y: number,
  width: number,
  height: number,
  startAngle: number,
  endAngle: number,
  innerRadius: number = 0,
  color: string
): EllipseNode {
  // Use the data-driven function
  const arc = createEllipseFromData({
    width,
    height,
    fill: color,
    arcData: {
      startingAngle: startAngle,
      endingAngle: endAngle,
      innerRadius: innerRadius
    }
  });

  // Set position
  arc.x = x;
  arc.y = y;

  // Select and focus
  selectAndFocusNodes(arc);

  return arc;
}

/**
 * Create a vector from data
 * @param data Vector configuration data
 * @returns Created vector node
 */
export function createVectorFromData(data: any): VectorNode {
  const vector = figma.createVector();
  
  try {
    // Resize the vector
    vector.resize(data.width || 100, data.height || 100);

    // Set vector-specific properties
    if (data.vectorNetwork) {
      vector.vectorNetwork = data.vectorNetwork;
    }

    if (data.vectorPaths) {
      vector.vectorPaths = data.vectorPaths;
    }

    if (data.handleMirroring) {
      vector.handleMirroring = data.handleMirroring;
    }

    // Fill
    if (data.fills) {
      vector.fills = data.fills;
    } else if (data.fill) {
      if (typeof data.fill === "string") {
        vector.fills = [createSolidPaint(data.fill)];
      } else {
        vector.fills = [data.fill];
      }
    } else if (data.color) {
      // For consistency with other shape creation functions
      vector.fills = [createSolidPaint(data.color)];
    }

    // Stroke
    if (data.strokes) vector.strokes = data.strokes;
    if (data.strokeWeight !== undefined) vector.strokeWeight = data.strokeWeight;
    if (data.strokeAlign) vector.strokeAlign = data.strokeAlign;
    if (data.strokeCap) vector.strokeCap = data.strokeCap;
    if (data.strokeJoin) vector.strokeJoin = data.strokeJoin;
    if (data.dashPattern) vector.dashPattern = data.dashPattern;
    if (data.strokeMiterLimit) vector.strokeMiterLimit = data.strokeMiterLimit;

    // Corner properties
    if (data.cornerRadius !== undefined) vector.cornerRadius = data.cornerRadius;
    if (data.cornerSmoothing !== undefined) vector.cornerSmoothing = data.cornerSmoothing;

    // Blend properties
    if (data.opacity !== undefined) vector.opacity = data.opacity;
    if (data.blendMode) vector.blendMode = data.blendMode;
    if (data.isMask !== undefined) vector.isMask = data.isMask;
    if (data.effects) vector.effects = data.effects;

    // Layout properties
    if (data.constraints) vector.constraints = data.constraints;
    if (data.layoutAlign) vector.layoutAlign = data.layoutAlign;
    if (data.layoutGrow !== undefined) vector.layoutGrow = data.layoutGrow;
    if (data.layoutPositioning) vector.layoutPositioning = data.layoutPositioning;
    if (data.rotation !== undefined) vector.rotation = data.rotation;
    if (data.layoutSizingHorizontal) vector.layoutSizingHorizontal = data.layoutSizingHorizontal;
    if (data.layoutSizingVertical) vector.layoutSizingVertical = data.layoutSizingVertical;

    console.log("Vector created successfully:", vector);
  } catch (error) {
    console.error("Error creating vector:", error);
  }

  return vector;
}

/**
 * Create a simple vector
 * @param x X coordinate
 * @param y Y coordinate
 * @param width Width of vector
 * @param height Height of vector
 * @param color Fill color as hex string
 * @returns Created vector node
 */
export function createVector(
  x: number,
  y: number,
  width: number,
  height: number,
  color: string
): VectorNode {
  // Use the data-driven function
  const vector = createVectorFromData({
    width,
    height,
    fill: color
  });

  // Set position
  vector.x = x;
  vector.y = y;

  // Select and focus
  selectAndFocusNodes(vector);

  return vector;
}

```

--------------------------------------------------------------------------------
/src/plugin/ui.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          Helvetica, Arial, sans-serif;
        margin: 0;
        padding: 0;
      }
      .container {
        padding: 20px;
      }
      h2 {
        font-size: 16px;
        margin-bottom: 15px;
      }
      .control-group {
        margin-bottom: 15px;
      }
      label {
        display: block;
        margin-bottom: 5px;
        font-size: 12px;
      }
      input {
        width: 100%;
        padding: 6px;
        margin-bottom: 10px;
        box-sizing: border-box;
        border: 1px solid #ccc;
        border-radius: 4px;
      }
      .button-group {
        display: flex;
        gap: 8px;
        margin-top: 15px;
      }
      button {
        background-color: #18a0fb;
        color: white;
        border: none;
        padding: 8px 12px;
        border-radius: 6px;
        cursor: pointer;
        flex: 1;
        font-size: 12px;
      }
      button:hover {
        background-color: #0d8ee3;
      }
      button.cancel {
        background-color: #f24822;
      }
      button.cancel:hover {
        background-color: #d83b17;
      }
      .connection-status {
        margin-top: 15px;
        padding: 8px;
        border-radius: 4px;
        font-size: 12px;
        text-align: center;
      }
      .status-connected {
        background-color: #ecfdf5;
        color: #047857;
      }
      .status-disconnected {
        background-color: #fef2f2;
        color: #b91c1c;
      }
      .status-connecting {
        background-color: #fef3c7;
        color: #92400e;
      }
      .mcp-section {
        margin-top: 20px;
        padding-top: 15px;
        border-top: 1px solid #e5e7eb;
      }
      .log-area {
        margin-top: 10px;
        height: 100px;
        overflow-y: auto;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 8px;
        font-size: 11px;
        font-family: monospace;
        background-color: #f9fafb;
      }
      .log-item {
        margin-bottom: 4px;
      }
      .server-input {
        display: flex;
        gap: 8px;
        margin-bottom: 10px;
      }
      .server-input input {
        flex: 1;
      }
      .checkbox-group {
        display: flex;
        align-items: center;
        margin-bottom: 10px;
      }
      .checkbox-group input[type="checkbox"] {
        width: auto;
        margin-right: 8px;
      }
      .checkbox-group label {
        display: inline;
        font-size: 12px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h2>Figma MCP 画布操作工具</h2>

      <div class="control-group">
        <label for="x">X 位置:</label>
        <input type="number" id="x" value="100" />

        <label for="y">Y 位置:</label>
        <input type="number" id="y" value="100" />
      </div>

      <div class="control-group">
        <label for="width">宽度:</label>
        <input type="number" id="width" value="150" />

        <label for="height">高度:</label>
        <input type="number" id="height" value="150" />
      </div>

      <div class="control-group">
        <label for="color">颜色:</label>
        <input type="color" id="color" value="#ff0000" />
      </div>

      <div class="control-group">
        <label for="text">文本:</label>
        <input type="text" id="text" value="Hello Figma!" />

        <label for="fontSize">字体大小:</label>
        <input type="number" id="fontSize" value="24" />
      </div>

      <div class="button-group">
        <button id="create-rectangle">矩形</button>
        <button id="create-circle">圆形</button>
        <button id="create-text">文本</button>
      </div>

      <div class="mcp-section">
        <h2>MCP 连接</h2>
        <div class="server-input">
          <input
            type="text"
            id="server-url"
            value="ws://localhost:3001/ws"
            placeholder="输入 MCP 服务器 WebSocket URL"
          />
          <button id="connect-button">连接</button>
        </div>

        <div class="checkbox-group">
          <input type="checkbox" id="auto-reconnect" checked />
          <label for="auto-reconnect">自动重连</label>
        </div>

        <div
          id="connection-status"
          class="connection-status status-disconnected"
        >
          未连接到 MCP
        </div>

        <div class="log-area" id="log-area">
          <div class="log-item">等待 MCP 连接和命令...</div>
        </div>
      </div>

      <div class="button-group">
        <button class="cancel" id="cancel">关闭</button>
      </div>
    </div>

    <script>
      // 获取所有输入元素
      const xInput = document.getElementById("x");
      const yInput = document.getElementById("y");
      const widthInput = document.getElementById("width");
      const heightInput = document.getElementById("height");
      const colorInput = document.getElementById("color");
      const textInput = document.getElementById("text");
      const fontSizeInput = document.getElementById("fontSize");
      const connectionStatus = document.getElementById("connection-status");
      const logArea = document.getElementById("log-area");
      const serverUrlInput = document.getElementById("server-url");
      const connectButton = document.getElementById("connect-button");
      const autoReconnectCheckbox = document.getElementById("auto-reconnect");

      // MCP 连接状态和 WebSocket 对象
      let mcpConnected = false;
      let ws = null;
      let isConnecting = false;
      let isManualDisconnect = false;
      let retryCount = 0;
      let maxRetries = 10;
      let reconnectTimer = null;

      // 添加按钮事件监听器
      document.getElementById("create-rectangle").onclick = () => {
        parent.postMessage(
          {
            pluginMessage: {
              type: "create-rectangle",
              x: parseInt(xInput.value),
              y: parseInt(yInput.value),
              width: parseInt(widthInput.value),
              height: parseInt(heightInput.value),
              color: colorInput.value,
            },
          },
          "*"
        );
      };

      document.getElementById("create-circle").onclick = () => {
        parent.postMessage(
          {
            pluginMessage: {
              type: "create-circle",
              x: parseInt(xInput.value),
              y: parseInt(yInput.value),
              width: parseInt(widthInput.value),
              height: parseInt(heightInput.value),
              color: colorInput.value,
            },
          },
          "*"
        );
      };

      document.getElementById("create-text").onclick = () => {
        parent.postMessage(
          {
            pluginMessage: {
              type: "create-text",
              x: parseInt(xInput.value),
              y: parseInt(yInput.value),
              text: textInput.value,
              fontSize: parseInt(fontSizeInput.value),
            },
          },
          "*"
        );
      };

      document.getElementById("cancel").onclick = () => {
        parent.postMessage(
          {
            pluginMessage: { type: "cancel" },
          },
          "*"
        );
      };

      // 添加日志条目
      function addLogEntry(message) {
        const logItem = document.createElement("div");
        logItem.classList.add("log-item");
        logItem.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
        logArea.appendChild(logItem);
        logArea.scrollTop = logArea.scrollHeight;
      }

      // 设置 MCP 连接状态
      function setMcpConnectionStatus(status) {
        if (status === "connected") {
          mcpConnected = true;
          isConnecting = false;
          connectionStatus.className = "connection-status status-connected";
          connectionStatus.textContent = "已连接到 MCP";
          connectButton.textContent = "断开";
          addLogEntry("MCP 已连接");
          retryCount = 0;
        } else if (status === "connecting") {
          mcpConnected = false;
          isConnecting = true;
          connectionStatus.className = "connection-status status-connecting";
          connectionStatus.textContent = `正在连接 MCP (尝试 ${
            retryCount + 1
          }/${maxRetries})`;
          connectButton.textContent = "取消";
          addLogEntry(`尝试连接 MCP (${retryCount + 1}/${maxRetries})...`);
        } else {
          // disconnected
          mcpConnected = false;
          isConnecting = false;
          connectionStatus.className = "connection-status status-disconnected";
          connectionStatus.textContent = "未连接到 MCP";
          connectButton.textContent = "连接";
          addLogEntry("MCP 已断开连接");
        }
      }

      // 计算重连延迟,使用指数退避策略
      function getReconnectDelay() {
        // 1秒, 2秒, 4秒, 8秒...
        return Math.min(1000 * Math.pow(2, retryCount), 30000);
      }

      // 清除所有重连定时器
      function clearReconnectTimer() {
        if (reconnectTimer) {
          clearTimeout(reconnectTimer);
          reconnectTimer = null;
        }
      }

      // 重置连接状态
      function resetConnectionState() {
        isConnecting = false;
        isManualDisconnect = false;
        clearReconnectTimer();

        if (ws) {
          try {
            ws.close();
          } catch (e) {
            // 忽略关闭错误
          }
          ws = null;
        }
      }

      // 尝试重连
      function attemptReconnect() {
        if (
          isManualDisconnect ||
          !autoReconnectCheckbox.checked ||
          retryCount >= maxRetries
        ) {
          if (retryCount >= maxRetries) {
            addLogEntry(`已达到最大重试次数 (${maxRetries}),停止重连`);
          }
          setMcpConnectionStatus("disconnected");
          retryCount = 0;
          return;
        }

        retryCount++;
        setMcpConnectionStatus("connecting");

        const delay = getReconnectDelay();
        addLogEntry(`将在 ${delay / 1000}秒后重新连接...`);

        clearReconnectTimer();
        reconnectTimer = setTimeout(() => {
          connectToMcp();
        }, delay);
      }

      // 连接到 MCP 服务器
      function connectToMcp() {
        // 如果已经连接或正在连接,返回
        if (mcpConnected) {
          // 如果已连接,则断开
          isManualDisconnect = true;
          resetConnectionState();
          setMcpConnectionStatus("disconnected");
          return;
        }

        // 如果正在尝试连接,则取消
        if (isConnecting) {
          isManualDisconnect = true;
          resetConnectionState();
          setMcpConnectionStatus("disconnected");
          return;
        }

        // 清除之前的连接
        resetConnectionState();
        isManualDisconnect = false;

        const serverUrl = serverUrlInput.value.trim();
        if (!serverUrl) {
          addLogEntry("错误: 服务器 URL 不能为空");
          return;
        }

        try {
          setMcpConnectionStatus("connecting");

          // 创建 WebSocket 连接
          ws = new WebSocket(serverUrl);

          ws.onopen = function () {
            setMcpConnectionStatus("connected");

            // 发送初始化消息
            ws.send(
              JSON.stringify({
                type: "figma-plugin-connected",
                pluginId: "figma-mcp-canvas-tools",
              })
            );
          };

          ws.onmessage = function (event) {
            try {
              const message = JSON.parse(event.data);
              addLogEntry(`收到 MCP 命令: ${message.command}`);

              // 转发给插件代码
              parent.postMessage(
                {
                  pluginMessage: {
                    type: "mcp-command",
                    command: message.command,
                    params: message.params || {},
                  },
                },
                "*"
              );
            } catch (error) {
              addLogEntry(`解析消息错误: ${error.message}`);
            }
          };

          ws.onclose = function () {
            // 只有在不是手动断开连接的情况下才尝试重连
            if (!isManualDisconnect) {
              addLogEntry("与 MCP 服务器的连接已关闭");
              attemptReconnect();
            } else {
              setMcpConnectionStatus("disconnected");
            }
            ws = null;
          };

          ws.onerror = function (error) {
            addLogEntry(`WebSocket 错误: ${error.message || "未知错误"}`);
            // 错误会触发关闭事件,关闭事件会处理重连
          };
        } catch (error) {
          addLogEntry(`连接错误: ${error.message}`);
          attemptReconnect();
        }
      }

      // 连接按钮点击事件
      connectButton.addEventListener("click", connectToMcp);

      // 自动重连选项变更
      autoReconnectCheckbox.addEventListener("change", function () {
        if (this.checked) {
          addLogEntry("自动重连已启用");
          // 如果目前未连接并且不是手动断开,尝试立即连接
          if (!mcpConnected && !isManualDisconnect && !isConnecting) {
            retryCount = 0;
            connectToMcp();
          }
        } else {
          addLogEntry("自动重连已禁用");
          isManualDisconnect = true;
          clearReconnectTimer();
        }
      });

      // 监听来自插件代码的消息
      window.addEventListener("message", (event) => {
        const message = event.data.pluginMessage;
        console.log("Received message from plugin:", message);
        // 处理来自插件代码的消息
        if (message && message.type === "mcp-response") {
          addLogEntry(
            `命令 ${message.command} ${
              message.success ? "成功执行" : "执行失败"
            }`
          );

          // 如果连接了 MCP 服务器,则将响应发送给服务器
          if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(
              JSON.stringify({
                type: "figma-plugin-response",
                success: message.success,
                command: message.command,
                result: message.result,
                error: message.error,
              })
            );
          }
        }
      });

      // 页面加载后自动尝试连接
      window.addEventListener("load", () => {
        if (autoReconnectCheckbox.checked) {
          // 小延迟后开始连接,给 UI 渲染一些时间
          setTimeout(() => {
            connectToMcp();
          }, 1000);
        }
      });
    </script>
  </body>
</html>

```

--------------------------------------------------------------------------------
/src/tools/canvas.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Canvas Tools - MCP server tools for interacting with Figma canvas elements
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
  isPluginConnected,
  sendCommandToPlugin,
} from "../services/websocket.js";
import { logError } from "../utils.js";
import {
  arcParams,
  elementParams,
  elementsParams,
  ellipseParams,
  lineParams,
  polygonParams,
  rectangleParams,
  starParams,
  textParams,
  vectorParams,
} from "./zod-schemas.js";

/**
 * Register canvas-related tools with the MCP server
 * @param server The MCP server instance
 */
export function registerCanvasTools(server: McpServer) {
  // Create a rectangle in Figma
  server.tool("create_rectangle", rectangleParams, async (params) => {
    try {
      // Send command to Figma plugin
      const response = await sendCommandToPlugin(
        "create-rectangle",
        params
      ).catch((error: Error) => {
        throw error;
      });

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Rectangle Created Successfully` },
          {
            type: "text",
            text: `A new rectangle has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Size: ${params.width}×${params.height}px\n- Color: ${params.color}`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating rectangle in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating rectangle: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Create a circle in Figma
  server.tool("create_circle", ellipseParams, async (params) => {
    try {
      // Send command to Figma plugin
      const response = await sendCommandToPlugin("create-circle", params).catch(
        (error: Error) => {
          throw error;
        }
      );

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Circle Created Successfully` },
          {
            type: "text",
            text: `A new circle has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Size: ${params.width}×${params.height}px\n- Color: ${params.color}`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating circle in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating circle: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Create an arc (partial ellipse) in Figma
  server.tool("create_arc", arcParams, async (params) => {
    try {
      // Prepare parameters for the plugin
      const arcParams = {
        ...params,
        startAngle: params.startAngle,
        endAngle: params.endAngle,
        innerRadius: params.innerRadius,
      };

      // Send command to Figma plugin
      const response = await sendCommandToPlugin("create-arc", arcParams).catch(
        (error: Error) => {
          throw error;
        }
      );

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Arc Created Successfully` },
          {
            type: "text",
            text: `A new arc has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Size: ${params.width}×${params.height}px\n- Angles: ${params.startAngle}° to ${params.endAngle}°\n- Inner radius: ${params.innerRadius}\n- Color: ${params.color}`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating arc in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating arc: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Create a polygon in Figma
  server.tool("create_polygon", polygonParams, async (params) => {
    try {
      // Prepare parameters for the plugin
      const polygonParams = {
        ...params,
        pointCount: params.pointCount,
      };

      // Send command to Figma plugin
      const response = await sendCommandToPlugin(
        "create-polygon",
        polygonParams
      ).catch((error: Error) => {
        throw error;
      });

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Polygon Created Successfully` },
          {
            type: "text",
            text: `A new polygon has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Size: ${params.width}×${params.height}px\n- Sides: ${params.pointCount}\n- Color: ${params.color}`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating polygon in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating polygon: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Create a star in Figma
  server.tool("create_star", starParams, async (params) => {
    try {
      // Prepare parameters for the plugin
      const starParams = {
        ...params,
        pointCount: params.pointCount,
        innerRadius: params.innerRadius,
      };

      // Send command to Figma plugin
      const response = await sendCommandToPlugin(
        "create-star",
        starParams
      ).catch((error: Error) => {
        throw error;
      });

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Star Created Successfully` },
          {
            type: "text",
            text: `A new star has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Size: ${params.width}×${params.height}px\n- Points: ${params.pointCount}\n- Inner Radius: ${params.innerRadius}\n- Color: ${params.color}`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating star in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating star: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Create a vector in Figma
  server.tool("create_vector", vectorParams, async (params) => {
    try {
      // Prepare parameters for the plugin
      const vectorParams = {
        ...params,
        vectorNetwork: params.vectorNetwork,
        vectorPaths: params.vectorPaths,
        handleMirroring: params.handleMirroring,
      };

      // Send command to Figma plugin
      const response = await sendCommandToPlugin(
        "create-vector",
        vectorParams
      ).catch((error: Error) => {
        throw error;
      });

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Vector Created Successfully` },
          {
            type: "text",
            text: `A new vector has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Size: ${params.width}×${params.height}px\n- Color: ${params.color}`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating vector in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating vector: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Create a line in Figma
  server.tool("create_line", lineParams, async (params) => {
    try {
      // Send command to Figma plugin
      const response = await sendCommandToPlugin("create-line", params).catch(
        (error: Error) => {
          throw error;
        }
      );

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Line Created Successfully` },
          {
            type: "text",
            text: `A new line has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Length: ${params.width}px\n- Color: ${params.color}`,
          },
          {
            type: "text",
            text: `- Rotation: ${params.rotation || 0}°`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating line in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating line: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Create text in Figma
  server.tool("create_text", textParams, async (params) => {
    try {
      // Send command to Figma plugin
      const response = await sendCommandToPlugin("create-text", params).catch(
        (error: Error) => {
          throw error;
        }
      );

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Text Created Successfully` },
          {
            type: "text",
            text: `New text has been created in your Figma canvas.`,
          },
          {
            type: "text",
            text: `- Position: (${params.x}, ${params.y})\n- Font Size: ${params.fontSize}px\n- Content: "${params.text}"`,
          },
          {
            type: "text",
            text:
              response.result && response.result.id
                ? `Node ID: ${response.result.id}`
                : `Creation successful`,
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error creating text in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error creating text: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Get current selection in Figma
  server.tool("get_selection", {}, async () => {
    try {
      // Send command to Figma plugin
      const response = await sendCommandToPlugin("get-selection", {}).catch(
        (error: Error) => {
          throw error;
        }
      );

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      return {
        content: [
          { type: "text", text: `# Current Selection` },
          {
            type: "text",
            text: `Information about currently selected elements in Figma:`,
          },
          {
            type: "text",
            text: response.result
              ? JSON.stringify(response.result, null, 2)
              : "No selection information available",
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error getting selection in Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error getting selection: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Check plugin connection status
  server.tool("check_connection", {}, async () => {
    return {
      content: [
        { type: "text", text: `# Figma Plugin Connection Status` },
        {
          type: "text",
          text: isPluginConnected()
            ? `✅ Figma plugin is connected to MCP server`
            : `❌ No Figma plugin is currently connected`,
        },
        {
          type: "text",
          text: isPluginConnected()
            ? `You can now use MCP tools to interact with the Figma canvas.`
            : `Please make sure the Figma plugin is running and connected to the MCP server.`,
        },
      ],
    };
  });

  // Get all elements from current page or specified page
  server.tool("get_elements", elementsParams, async (params) => {
    try {
      // Send command to Figma plugin
      const response = await sendCommandToPlugin("get-elements", params).catch(
        (error: Error) => {
          throw error;
        }
      );

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      const elements = response.result;
      const count = Array.isArray(elements) ? elements.length : 0;
      const typeValue = params.type || "ALL";
      const pageName = params.page_id ? `specified page` : "current page";

      return {
        content: [
          { type: "text", text: `# Elements Retrieved` },
          {
            type: "text",
            text: `Found ${count} element${
              count !== 1 ? "s" : ""
            } of type ${typeValue} on ${pageName}.`,
          },
          {
            type: "text",
            text:
              count > 0
                ? `Element information: ${JSON.stringify(elements, null, 2)}`
                : "No elements matched your criteria.",
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error getting elements from Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error retrieving elements: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });

  // Get a specific element by ID
  server.tool("get_element", elementParams, async (params) => {
    try {
      // Send command to Figma plugin
      const response = await sendCommandToPlugin("get-element", params).catch(
        (error: Error) => {
          throw error;
        }
      );

      if (!response.success) {
        throw new Error(response.error || "Unknown error");
      }

      const element = response.result;
      const isArray = Array.isArray(element);
      const hasChildren = isArray && element.length > 1;

      return {
        content: [
          { type: "text", text: `# Element Retrieved` },
          {
            type: "text",
            text: `Successfully retrieved element with ID: ${params.node_id}`,
          },
          {
            type: "text",
            text: hasChildren
              ? `Element and ${element.length - 1} children retrieved.`
              : `Element information:`,
          },
          {
            type: "text",
            text: JSON.stringify(element, null, 2),
          },
        ],
      };
    } catch (error: unknown) {
      logError("Error getting element from Figma", error);
      return {
        content: [
          {
            type: "text",
            text: `Error retrieving element: ${
              error instanceof Error ? error.message : "Unknown error"
            }`,
          },
          {
            type: "text",
            text: `Make sure the Figma plugin is running and connected to the MCP server.`,
          },
        ],
      };
    }
  });
}

```

--------------------------------------------------------------------------------
/src/tools/zod-schemas.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Figma Zod Schemas
 * Zod schemas for validating Figma API parameters
 */

import { z } from "zod";

// Base types
export const colorSchema = z.object({
  r: z.number().min(0).max(1).describe("Red channel (0-1)"),
  g: z.number().min(0).max(1).describe("Green channel (0-1)"),
  b: z.number().min(0).max(1).describe("Blue channel (0-1)"),
});

// Position and size base params
const positionParams = {
  x: z.number().default(0).describe("X position of the element"),
  y: z.number().default(0).describe("Y position of the element"),
};

const sizeParams = {
  width: z.number().min(1).default(100).describe("Width of the element in pixels"),
  height: z.number().min(1).default(100).describe("Height of the element in pixels"),
};

// Base node properties
const baseNodeParams = {
  name: z.string().optional().describe("Name of the node"),
};

// Scene node properties
const sceneNodeParams = {
  visible: z.boolean().optional().describe("Whether the node is visible"),
  locked: z.boolean().optional().describe("Whether the node is locked"),
};

// Blend-related properties
const blendParams = {
  opacity: z.number().min(0).max(1).optional().describe("Opacity of the node (0-1)"),
  blendMode: z.enum([
    "NORMAL", "DARKEN", "MULTIPLY", "LINEAR_BURN", "COLOR_BURN", 
    "LIGHTEN", "SCREEN", "LINEAR_DODGE", "COLOR_DODGE", 
    "OVERLAY", "SOFT_LIGHT", "HARD_LIGHT", 
    "DIFFERENCE", "EXCLUSION", "SUBTRACT", "DIVIDE", 
    "HUE", "SATURATION", "COLOR", "LUMINOSITY",
    "PASS_THROUGH"
  ]).optional().describe("Blend mode of the node"),
  isMask: z.boolean().optional().describe("Whether this node is a mask"),
  maskType: z.enum(["ALPHA", "LUMINANCE"]).optional().describe("Type of masking to use if this node is a mask"),
  effects: z.array(z.any()).optional().describe("Array of effects"),
  effectStyleId: z.string().optional().describe("The id of the EffectStyle object"),
};

// Corner-related properties
const cornerParams = {
  cornerRadius: z.number().min(0).optional().describe("Rounds all corners by this amount"),
  cornerSmoothing: z.number().min(0).max(1).optional().describe("Corner smoothing between 0 and 1"),
  topLeftRadius: z.number().min(0).optional().describe("Top left corner radius override"),
  topRightRadius: z.number().min(0).optional().describe("Top right corner radius override"),
  bottomLeftRadius: z.number().min(0).optional().describe("Bottom left corner radius override"),
  bottomRightRadius: z.number().min(0).optional().describe("Bottom right corner radius override"),
};

// Geometry-related properties
const geometryParams = {
  fills: z.array(z.any()).optional().describe("The paints used to fill the area of the shape"),
  fillStyleId: z.string().optional().describe("The id of the PaintStyle object linked to fills"),
  strokes: z.array(z.any()).optional().describe("The paints used to fill the area of the shape's strokes"),
  strokeStyleId: z.string().optional().describe("The id of the PaintStyle object linked to strokes"),
  strokeWeight: z.number().min(0).optional().describe("The thickness of the stroke, in pixels"),
  strokeJoin: z.enum(["MITER", "BEVEL", "ROUND"]).optional().describe("The decoration applied to vertices"),
  strokeAlign: z.enum(["CENTER", "INSIDE", "OUTSIDE"]).optional().describe("The alignment of the stroke"),
  dashPattern: z.array(z.number().min(0)).optional().describe("Array of numbers for dash pattern"),
  strokeCap: z.enum(["NONE", "ROUND", "SQUARE", "ARROW_LINES", "ARROW_EQUILATERAL"]).optional().describe("The decoration applied to vertices"),
  strokeMiterLimit: z.number().min(0).optional().describe("The miter limit on the stroke"),
  color: z.string().regex(/^#([0-9A-F]{6}|[0-9A-F]{8})$/i).default("#ff0000").describe("Fill color as hex code (#RRGGBB or #RRGGBBAA)"),
};

// Individual strokes-related properties
const rectangleStrokeParams = {
  strokeTopWeight: z.number().min(0).optional().describe("Top stroke weight"),
  strokeBottomWeight: z.number().min(0).optional().describe("Bottom stroke weight"),
  strokeLeftWeight: z.number().min(0).optional().describe("Left stroke weight"),
  strokeRightWeight: z.number().min(0).optional().describe("Right stroke weight"),
};

// Layout-related properties
const layoutParams = {
  minWidth: z.number().nullable().optional().describe("Minimum width constraint"),
  maxWidth: z.number().nullable().optional().describe("Maximum width constraint"),
  minHeight: z.number().nullable().optional().describe("Minimum height constraint"),
  maxHeight: z.number().nullable().optional().describe("Maximum height constraint"),
  layoutAlign: z.enum(["MIN", "CENTER", "MAX", "STRETCH", "INHERIT"]).optional().describe("Alignment within parent"),
  layoutGrow: z.number().min(0).default(0).optional().describe("Stretch along parent's primary axis"),
  layoutPositioning: z.enum(["AUTO", "ABSOLUTE"]).optional().describe("Layout positioning mode"),
  constrainProportions: z.boolean().optional().describe("Whether to keep proportions when resizing"),
  rotation: z.number().min(-180).max(180).optional().describe("Rotation in degrees (-180 to 180)"),
  layoutSizingHorizontal: z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing mode"),
  layoutSizingVertical: z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing mode"),
  constraints: z.any().optional().describe("Constraints relative to containing frame"),
};

// Common properties for all export settings
const commonExportSettingsProps = {
  contentsOnly: z.boolean().optional().describe("Whether only the contents of the node are exported. Defaults to true."),
  useAbsoluteBounds: z.boolean().optional().describe("Use full dimensions regardless of cropping. Defaults to false."),
  suffix: z.string().optional().describe("Suffix appended to the file name when exporting."),
  colorProfile: z.enum(["DOCUMENT", "SRGB", "DISPLAY_P3_V4"]).optional().describe("Color profile of the export."),
};

// Common SVG export properties
const commonSvgExportProps = {
  ...commonExportSettingsProps,
  svgOutlineText: z.boolean().optional().describe("Whether text elements are rendered as outlines. Defaults to true."),
  svgIdAttribute: z.boolean().optional().describe("Whether to include layer names as ID attributes. Defaults to false."),
  svgSimplifyStroke: z.boolean().optional().describe("Whether to simplify inside and outside strokes. Defaults to true."),
};

// Export constraints
const exportConstraintsSchema = z.object({
  type: z.enum(["SCALE", "WIDTH", "HEIGHT"]).describe("Type of constraint for the export"),
  value: z.number().positive().describe("Value for the constraint")
});

// Export Settings Image (JPG/PNG)
const exportSettingsImageSchema = z.object({
  format: z.enum(["JPG", "PNG"]).describe("The export format (JPG or PNG)"),
  constraint: exportConstraintsSchema.optional().describe("Constraint on the image size when exporting"),
  ...commonExportSettingsProps
});

// Export Settings SVG
const exportSettingsSvgSchema = z.object({
  format: z.literal("SVG").describe("The export format (SVG)"),
  ...commonSvgExportProps
});

// Export Settings SVG String (for exportAsync only)
const exportSettingsSvgStringSchema = z.object({
  format: z.literal("SVG_STRING").describe("The export format (SVG_STRING)"),
  ...commonSvgExportProps
});

// Export Settings PDF
const exportSettingsPdfSchema = z.object({
  format: z.literal("PDF").describe("The export format (PDF)"),
  ...commonExportSettingsProps
});

// Export Settings REST
const exportSettingsRestSchema = z.object({
  format: z.literal("JSON_REST_V1").describe("The export format (JSON_REST_V1)"),
  ...commonExportSettingsProps
});

// Combined Export Settings type
const exportSettingsSchema = z.discriminatedUnion("format", [
  exportSettingsImageSchema,
  exportSettingsSvgSchema,
  exportSettingsSvgStringSchema,
  exportSettingsPdfSchema,
  exportSettingsRestSchema
]);

// Export-related properties
const exportParams = {
  exportSettings: z.array(exportSettingsSchema).optional().describe("Export settings stored on the node"),
};

// Prototyping - Trigger and Action types
const triggerSchema = z.enum([
  "ON_CLICK",
  "ON_HOVER",
  "ON_PRESS",
  "ON_DRAG",
  "AFTER_TIMEOUT",
  "MOUSE_ENTER",
  "MOUSE_LEAVE",
  "MOUSE_UP",
  "MOUSE_DOWN",
  "ON_KEY_DOWN"
]).nullable().describe("The trigger that initiates the prototype interaction");

// Action represents what happens when a trigger is activated
const actionSchema = z.object({
  type: z.enum([
    "BACK",
    "CLOSE",
    "URL",
    "NODE",
    "SWAP",
    "OVERLAY",
    "SCROLL_TO",
    "OPEN_LINK"
  ]).describe("The type of action to perform"),
  url: z.string().optional().describe("URL to navigate to if action type is URL"),
  nodeID: z.string().optional().describe("ID of the node if action type is NODE"),
  destinationID: z.string().optional().describe("Destination node ID"),
  navigation: z.enum(["NAVIGATE", "SWAP", "OVERLAY", "SCROLL_TO"]).optional().describe("Navigation type"),
  transitionNode: z.string().optional().describe("ID of the node to use for transition"),
  preserveScrollPosition: z.boolean().optional().describe("Whether to preserve scroll position"),
  overlayRelativePosition: z.object({
    x: z.number(),
    y: z.number()
  }).optional().describe("Relative position for overlay"),
  // Additional properties can be added as needed based on Figma API
});

// Reaction combines a trigger with actions for prototyping
const reactionSchema = z.object({
  action: actionSchema.optional().describe("DEPRECATED: The action triggered by this reaction"),
  actions: z.array(actionSchema).optional().describe("The actions triggered by this reaction"),
  trigger: triggerSchema.describe("The trigger that initiates this reaction")
});

// Reaction properties
const reactionParams = {
  reactions: z.array(reactionSchema).optional().describe("List of reactions for prototyping"),
};

// Annotation properties
const annotationPropertySchema = z.object({
  type: z.enum([
    "width",
    "height",
    "fills",
    "strokes",
    "strokeWeight",
    "cornerRadius",
    "opacity",
    "blendMode",
    "effects",
    "layoutConstraints",
    "padding",
    "itemSpacing",
    "layoutMode",
    "primaryAxisAlignment",
    "counterAxisAlignment",
    "fontName",
    "fontSize",
    "letterSpacing",
    "lineHeight",
    "textCase",
    "textDecoration",
    "textAlignHorizontal",
    "textAlignVertical",
    "characters"
  ]).describe("The type of property being annotated"),
  value: z.any().optional().describe("The value of the property (if applicable)")
});

// Annotation schema
const annotationSchema = z.object({
  label: z.string().optional().describe("Text label for the annotation"),
  labelMarkdown: z.string().optional().describe("Markdown-formatted text label"),
  properties: z.array(annotationPropertySchema).optional().describe("Properties pinned in this annotation"),
  categoryId: z.string().optional().describe("ID of the annotation category")
});

// Annotation properties
const annotationParams = {
  annotations: z.array(annotationSchema).optional().describe("Annotations on the node"),
};

// Line parameters (width represents length, height is always 0)
export const lineParams = {
  ...positionParams,
  width: z.number().min(1).default(100).describe("Length of the line in pixels"),
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...geometryParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
};

// Combined parameters for rectangles
export const rectangleParams = {
  ...positionParams,
  ...sizeParams,
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...cornerParams,
  ...geometryParams,
  ...rectangleStrokeParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
};

// Ellipse Arc data for creating arcs and donuts
const arcDataSchema = z.object({
  startingAngle: z.number().describe("Starting angle in degrees from 0 to 360"),
  endingAngle: z.number().describe("Ending angle in degrees from 0 to 360"),
  innerRadius: z.number().min(0).max(1).describe("Inner radius ratio from 0 to 1")
});

// Circle/Ellipse parameters
export const ellipseParams = {
  ...positionParams,
  ...sizeParams,
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...geometryParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
  arcData: arcDataSchema.optional().describe("Arc data for creating partial ellipses and donuts")
};

// Text parameters
export const textParams = {
  ...positionParams,
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
  text: z.string().default("Hello Figma!").describe("The text content"),
  characters: z.string().optional().describe("Alternative for text content"),
  fontSize: z.number().min(1).default(24).describe("The font size in pixels"),
  fontFamily: z.string().optional().describe("Font family name"),
  fontStyle: z.string().optional().describe("Font style (e.g., 'Regular', 'Bold')"),
  fontName: z.object({
    family: z.string().optional().describe("Font family name"),
    style: z.string().optional().describe("Font style (e.g., 'Regular', 'Bold')"),
  }).optional().describe("Font family and style"),
  textAlignHorizontal: z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional().describe("Horizontal text alignment"),
  textAlignVertical: z.enum(["TOP", "CENTER", "BOTTOM"]).optional().describe("Vertical text alignment"),
  textAutoResize: z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional().describe("How text box adjusts to fit characters"),
  textTruncation: z.enum(["DISABLED", "ENDING"]).optional().describe("Whether text will truncate with ellipsis"),
  maxLines: z.number().nullable().optional().describe("Max number of lines before truncation"),
  paragraphIndent: z.number().optional().describe("Indentation of paragraphs"),
  paragraphSpacing: z.number().optional().describe("Vertical distance between paragraphs"),
  listSpacing: z.number().optional().describe("Vertical distance between lines of a list"),
  hangingPunctuation: z.boolean().optional().describe("Whether punctuation hangs outside the text box"),
  hangingList: z.boolean().optional().describe("Whether list counters/bullets hang outside the text box"),
  autoRename: z.boolean().optional().describe("Whether to update node name based on text content"),
  letterSpacing: z.union([
    z.number(),
    z.object({
      value: z.number(),
      unit: z.enum(["PIXELS", "PERCENT"])
    })
  ]).optional().describe("Letter spacing between characters"),
  lineHeight: z.union([
    z.number(),
    z.object({
      value: z.number(),
      unit: z.enum(["PIXELS", "PERCENT"])
    })
  ]).optional().describe("Line height"),
  leadingTrim: z.enum(["NONE", "CAP_HEIGHT", "BOTH"]).optional().describe("Removal of vertical space above/below text glyphs"),
  textCase: z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional().describe("Text case transformation"),
  textDecoration: z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional().describe("Text decoration"),
  textDecorationStyle: z.enum(["SOLID", "DASHED", "DOTTED", "WAVY", "DOUBLE"]).optional().describe("Text decoration style"),
  textDecorationOffset: z.union([
    z.number(),
    z.object({
      value: z.number(),
      unit: z.enum(["PIXELS", "PERCENT"])
    })
  ]).optional().describe("Text decoration offset"),
  textDecorationThickness: z.union([
    z.number(),
    z.object({
      value: z.number(),
      unit: z.enum(["PIXELS", "PERCENT"])
    })
  ]).optional().describe("Text decoration thickness"),
  textDecorationColor: z.union([
    z.object({
      r: z.number().min(0).max(1),
      g: z.number().min(0).max(1),
      b: z.number().min(0).max(1),
      a: z.number().min(0).max(1).optional()
    }),
    z.string()
  ]).optional().describe("Text decoration color"),
  textDecorationSkipInk: z.boolean().optional().describe("Whether text decoration skips descenders"),
  textStyleId: z.string().optional().describe("ID of linked TextStyle object"),
  hyperlink: z.object({
    type: z.enum(["URL", "NODE"]),
    url: z.string().optional(),
    nodeID: z.string().optional()
  }).nullable().optional().describe("Hyperlink target"),
  fill: z.string().optional().describe("Fill color as hex code (shorthand for fills)"),
  rangeStyles: z.array(
    z.object({
      start: z.number().describe("Start index (inclusive)"),
      end: z.number().describe("End index (exclusive)"),
      style: z.object({}).passthrough().describe("Style properties to apply to range")
    })
  ).optional().describe("Character-level styling for text ranges"),
  width: z.number().optional().describe("Width of the text box")
};

// Frame parameters
export const frameParams = {
  ...positionParams,
  ...sizeParams,
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...cornerParams,
  ...geometryParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
  itemSpacing: z.number().min(0).optional().describe("Space between children in auto-layout"),
  layoutMode: z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Auto-layout direction"),
  primaryAxisSizingMode: z.enum(["FIXED", "AUTO"]).optional().describe("How frame sizes along primary axis"),
  counterAxisSizingMode: z.enum(["FIXED", "AUTO"]).optional().describe("How frame sizes along counter axis"),
  primaryAxisAlignItems: z.enum(["MIN", "CENTER", "MAX", "SPACE_BETWEEN"]).optional().describe("Alignment along primary axis"),
  counterAxisAlignItems: z.enum(["MIN", "CENTER", "MAX"]).optional().describe("Alignment along counter axis"),
  paddingLeft: z.number().min(0).optional().describe("Padding on left side"),
  paddingRight: z.number().min(0).optional().describe("Padding on right side"),
  paddingTop: z.number().min(0).optional().describe("Padding on top side"),
  paddingBottom: z.number().min(0).optional().describe("Padding on bottom side"),
};

// Arc parameters (based on ellipse parameters with added angle parameters)
export const arcParams = {
  ...ellipseParams,
  startAngle: z.number().default(0).describe("Starting angle in degrees"),
  endAngle: z.number().default(180).describe("Ending angle in degrees"),
  innerRadius: z.number().min(0).max(1).default(0).describe("Inner radius ratio (0-1) for donut shapes")
};

// Polygon parameters
export const polygonParams = {
  ...positionParams,
  ...sizeParams,
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...cornerParams,
  ...geometryParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
  pointCount: z.number().int().min(3).default(3).describe("Number of sides of the polygon. Must be an integer >= 3.")
};

// Star parameters
export const starParams = {
  ...positionParams,
  ...sizeParams,
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...geometryParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
  pointCount: z.number().int().min(3).default(5).describe("Number of points on the star. Must be an integer >= 3."),
  innerRadius: z.number().min(0).max(1).default(0.5).describe("Ratio of inner radius to outer radius (0-1).")
};

// Vector parameters for Vector Node
export const vectorParams = {
  ...positionParams,
  ...sizeParams,
  ...baseNodeParams,
  ...sceneNodeParams,
  ...blendParams,
  ...cornerParams,
  ...geometryParams,
  ...layoutParams,
  ...exportParams,
  ...reactionParams,
  ...annotationParams,
  // Vector specific parameters
  vectorNetwork: z.any().optional().describe("Complete representation of vectors as a network of edges between vertices"),
  vectorPaths: z.any().optional().describe("Simple representation of vectors as paths"),
  handleMirroring: z.enum(["NONE", "ANGLE", "ANGLE_AND_LENGTH"]).optional().describe("Whether the vector handles are mirrored or independent")
};

export const elementParams = {
  node_id: z.string().describe("ID of the element to retrieve"),
  include_children: z.boolean().optional().default(false).describe("Whether to include children of the element"),
}

export const elementsParams = {
  type: z.enum([
    "ALL", "RECTANGLE", "ELLIPSE", "POLYGON", "STAR", "VECTOR", 
    "TEXT", "FRAME", "COMPONENT", "INSTANCE", "BOOLEAN_OPERATION", 
    "GROUP", "SECTION", "SLICE", "LINE", "CONNECTOR", "SHAPE_WITH_TEXT", 
    "CODE_BLOCK", "STAMP", "WIDGET", "STICKY", "TABLE", "SECTION", "HIGHLIGHT"
  ]).optional().default("ALL").describe("Type of elements to filter (default: ALL)"),
  page_id: z.string().optional().describe("ID of page to get elements from (default: current page)"),
  limit: z.number().int().min(1).max(1000).optional().default(100).describe("Maximum number of elements to return"),
  include_hidden: z.boolean().optional().default(false).describe("Whether to include hidden elements"),
};

```

--------------------------------------------------------------------------------
/src/plugin/code.js:
--------------------------------------------------------------------------------

```javascript
// src/plugin/utils/colorUtils.ts
function hexToRgb(hex) {
  hex = hex.replace("#", "");
  const r = parseInt(hex.substring(0, 2), 16) / 255;
  const g = parseInt(hex.substring(2, 4), 16) / 255;
  const b = parseInt(hex.substring(4, 6), 16) / 255;
  return { r, g, b };
}
function createSolidPaint(color) {
  if (typeof color === "string") {
    return { type: "SOLID", color: hexToRgb(color) };
  }
  return { type: "SOLID", color };
}

// src/plugin/utils/nodeUtils.ts
function applyCommonProperties(node, data) {
  if (data.x !== undefined)
    node.x = data.x;
  if (data.y !== undefined)
    node.y = data.y;
  if (data.name)
    node.name = data.name;
  if (data.opacity !== undefined && "opacity" in node) {
    node.opacity = data.opacity;
  }
  if (data.blendMode && "blendMode" in node) {
    node.blendMode = data.blendMode;
  }
  if (data.effects && "effects" in node) {
    node.effects = data.effects;
  }
  if (data.constraints && "constraints" in node) {
    node.constraints = data.constraints;
  }
  if (data.isMask !== undefined && "isMask" in node) {
    node.isMask = data.isMask;
  }
  if (data.visible !== undefined)
    node.visible = data.visible;
  if (data.locked !== undefined)
    node.locked = data.locked;
}
function selectAndFocusNodes(nodes) {
  const nodesToFocus = Array.isArray(nodes) ? nodes : [nodes];
  figma.currentPage.selection = nodesToFocus;
  figma.viewport.scrollAndZoomIntoView(nodesToFocus);
}
function buildResultObject(result) {
  let resultObject = {};
  if (!result)
    return resultObject;
  if (Array.isArray(result)) {
    resultObject.count = result.length;
    if (result.length > 0) {
      resultObject.items = result.map((node) => ({
        id: node.id,
        type: node.type,
        name: node.name
      }));
    }
  } else {
    const node = result;
    resultObject.id = node.id;
    resultObject.type = node.type;
    resultObject.name = node.name;
  }
  return resultObject;
}

// src/plugin/creators/shapeCreators.ts
function createRectangleFromData(data) {
  const rect = figma.createRectangle();
  rect.resize(data.width || 100, data.height || 100);
  if (data.fills) {
    rect.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      rect.fills = [createSolidPaint(data.fill)];
    } else {
      rect.fills = [data.fill];
    }
  }
  if (data.strokes)
    rect.strokes = data.strokes;
  if (data.strokeWeight !== undefined)
    rect.strokeWeight = data.strokeWeight;
  if (data.strokeAlign)
    rect.strokeAlign = data.strokeAlign;
  if (data.strokeCap)
    rect.strokeCap = data.strokeCap;
  if (data.strokeJoin)
    rect.strokeJoin = data.strokeJoin;
  if (data.dashPattern)
    rect.dashPattern = data.dashPattern;
  if (data.cornerRadius !== undefined)
    rect.cornerRadius = data.cornerRadius;
  if (data.topLeftRadius !== undefined)
    rect.topLeftRadius = data.topLeftRadius;
  if (data.topRightRadius !== undefined)
    rect.topRightRadius = data.topRightRadius;
  if (data.bottomLeftRadius !== undefined)
    rect.bottomLeftRadius = data.bottomLeftRadius;
  if (data.bottomRightRadius !== undefined)
    rect.bottomRightRadius = data.bottomRightRadius;
  return rect;
}
function createEllipseFromData(data) {
  const ellipse = figma.createEllipse();
  ellipse.resize(data.width || 100, data.height || 100);
  if (data.fills) {
    ellipse.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      ellipse.fills = [createSolidPaint(data.fill)];
    } else {
      ellipse.fills = [data.fill];
    }
  }
  if (data.arcData) {
    ellipse.arcData = {
      startingAngle: data.arcData.startingAngle !== undefined ? data.arcData.startingAngle : 0,
      endingAngle: data.arcData.endingAngle !== undefined ? data.arcData.endingAngle : 360,
      innerRadius: data.arcData.innerRadius !== undefined ? data.arcData.innerRadius : 0
    };
  }
  if (data.strokes)
    ellipse.strokes = data.strokes;
  if (data.strokeWeight !== undefined)
    ellipse.strokeWeight = data.strokeWeight;
  if (data.strokeAlign)
    ellipse.strokeAlign = data.strokeAlign;
  if (data.strokeCap)
    ellipse.strokeCap = data.strokeCap;
  if (data.strokeJoin)
    ellipse.strokeJoin = data.strokeJoin;
  if (data.dashPattern)
    ellipse.dashPattern = data.dashPattern;
  return ellipse;
}
function createPolygonFromData(data) {
  const polygon = figma.createPolygon();
  polygon.resize(data.width || 100, data.height || 100);
  if (data.pointCount)
    polygon.pointCount = data.pointCount;
  if (data.fills) {
    polygon.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      polygon.fills = [createSolidPaint(data.fill)];
    } else {
      polygon.fills = [data.fill];
    }
  } else if (data.color) {
    polygon.fills = [createSolidPaint(data.color)];
  }
  if (data.strokes)
    polygon.strokes = data.strokes;
  if (data.strokeWeight !== undefined)
    polygon.strokeWeight = data.strokeWeight;
  if (data.strokeAlign)
    polygon.strokeAlign = data.strokeAlign;
  if (data.strokeCap)
    polygon.strokeCap = data.strokeCap;
  if (data.strokeJoin)
    polygon.strokeJoin = data.strokeJoin;
  if (data.dashPattern)
    polygon.dashPattern = data.dashPattern;
  return polygon;
}
function createStarFromData(data) {
  const star = figma.createStar();
  star.resize(data.width || 100, data.height || 100);
  if (data.pointCount)
    star.pointCount = data.pointCount;
  if (data.innerRadius)
    star.innerRadius = data.innerRadius;
  if (data.fills) {
    star.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      star.fills = [createSolidPaint(data.fill)];
    } else {
      star.fills = [data.fill];
    }
  }
  return star;
}
function createLineFromData(data) {
  const line = figma.createLine();
  line.resize(data.width || 100, 0);
  if (data.rotation !== undefined)
    line.rotation = data.rotation;
  if (data.strokeWeight)
    line.strokeWeight = data.strokeWeight;
  if (data.strokeAlign)
    line.strokeAlign = data.strokeAlign;
  if (data.strokeCap)
    line.strokeCap = data.strokeCap;
  if (data.strokeJoin)
    line.strokeJoin = data.strokeJoin;
  if (data.dashPattern)
    line.dashPattern = data.dashPattern;
  if (data.strokes) {
    line.strokes = data.strokes;
  } else if (data.stroke) {
    if (typeof data.stroke === "string") {
      line.strokes = [createSolidPaint(data.stroke)];
    } else {
      line.strokes = [data.stroke];
    }
  } else if (data.color) {
    line.strokes = [createSolidPaint(data.color)];
  }
  return line;
}
function createVectorFromData(data) {
  const vector = figma.createVector();
  try {
    vector.resize(data.width || 100, data.height || 100);
    if (data.vectorNetwork) {
      vector.vectorNetwork = data.vectorNetwork;
    }
    if (data.vectorPaths) {
      vector.vectorPaths = data.vectorPaths;
    }
    if (data.handleMirroring) {
      vector.handleMirroring = data.handleMirroring;
    }
    if (data.fills) {
      vector.fills = data.fills;
    } else if (data.fill) {
      if (typeof data.fill === "string") {
        vector.fills = [createSolidPaint(data.fill)];
      } else {
        vector.fills = [data.fill];
      }
    } else if (data.color) {
      vector.fills = [createSolidPaint(data.color)];
    }
    if (data.strokes)
      vector.strokes = data.strokes;
    if (data.strokeWeight !== undefined)
      vector.strokeWeight = data.strokeWeight;
    if (data.strokeAlign)
      vector.strokeAlign = data.strokeAlign;
    if (data.strokeCap)
      vector.strokeCap = data.strokeCap;
    if (data.strokeJoin)
      vector.strokeJoin = data.strokeJoin;
    if (data.dashPattern)
      vector.dashPattern = data.dashPattern;
    if (data.strokeMiterLimit)
      vector.strokeMiterLimit = data.strokeMiterLimit;
    if (data.cornerRadius !== undefined)
      vector.cornerRadius = data.cornerRadius;
    if (data.cornerSmoothing !== undefined)
      vector.cornerSmoothing = data.cornerSmoothing;
    if (data.opacity !== undefined)
      vector.opacity = data.opacity;
    if (data.blendMode)
      vector.blendMode = data.blendMode;
    if (data.isMask !== undefined)
      vector.isMask = data.isMask;
    if (data.effects)
      vector.effects = data.effects;
    if (data.constraints)
      vector.constraints = data.constraints;
    if (data.layoutAlign)
      vector.layoutAlign = data.layoutAlign;
    if (data.layoutGrow !== undefined)
      vector.layoutGrow = data.layoutGrow;
    if (data.layoutPositioning)
      vector.layoutPositioning = data.layoutPositioning;
    if (data.rotation !== undefined)
      vector.rotation = data.rotation;
    if (data.layoutSizingHorizontal)
      vector.layoutSizingHorizontal = data.layoutSizingHorizontal;
    if (data.layoutSizingVertical)
      vector.layoutSizingVertical = data.layoutSizingVertical;
    console.log("Vector created successfully:", vector);
  } catch (error) {
    console.error("Error creating vector:", error);
  }
  return vector;
}

// src/plugin/creators/containerCreators.ts
function createFrameFromData(data) {
  const frame = figma.createFrame();
  frame.resize(data.width || 100, data.height || 100);
  if (data.fills) {
    frame.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      frame.fills = [createSolidPaint(data.fill)];
    } else {
      frame.fills = [data.fill];
    }
  }
  if (data.layoutMode)
    frame.layoutMode = data.layoutMode;
  if (data.primaryAxisSizingMode)
    frame.primaryAxisSizingMode = data.primaryAxisSizingMode;
  if (data.counterAxisSizingMode)
    frame.counterAxisSizingMode = data.counterAxisSizingMode;
  if (data.primaryAxisAlignItems)
    frame.primaryAxisAlignItems = data.primaryAxisAlignItems;
  if (data.counterAxisAlignItems)
    frame.counterAxisAlignItems = data.counterAxisAlignItems;
  if (data.paddingLeft !== undefined)
    frame.paddingLeft = data.paddingLeft;
  if (data.paddingRight !== undefined)
    frame.paddingRight = data.paddingRight;
  if (data.paddingTop !== undefined)
    frame.paddingTop = data.paddingTop;
  if (data.paddingBottom !== undefined)
    frame.paddingBottom = data.paddingBottom;
  if (data.itemSpacing !== undefined)
    frame.itemSpacing = data.itemSpacing;
  if (data.cornerRadius !== undefined)
    frame.cornerRadius = data.cornerRadius;
  if (data.topLeftRadius !== undefined)
    frame.topLeftRadius = data.topLeftRadius;
  if (data.topRightRadius !== undefined)
    frame.topRightRadius = data.topRightRadius;
  if (data.bottomLeftRadius !== undefined)
    frame.bottomLeftRadius = data.bottomLeftRadius;
  if (data.bottomRightRadius !== undefined)
    frame.bottomRightRadius = data.bottomRightRadius;
  return frame;
}
function createComponentFromData(data) {
  const component = figma.createComponent();
  component.resize(data.width || 100, data.height || 100);
  if (data.fills) {
    component.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      component.fills = [createSolidPaint(data.fill)];
    } else {
      component.fills = [data.fill];
    }
  }
  if (data.layoutMode)
    component.layoutMode = data.layoutMode;
  if (data.primaryAxisSizingMode)
    component.primaryAxisSizingMode = data.primaryAxisSizingMode;
  if (data.counterAxisSizingMode)
    component.counterAxisSizingMode = data.counterAxisSizingMode;
  if (data.primaryAxisAlignItems)
    component.primaryAxisAlignItems = data.primaryAxisAlignItems;
  if (data.counterAxisAlignItems)
    component.counterAxisAlignItems = data.counterAxisAlignItems;
  if (data.paddingLeft !== undefined)
    component.paddingLeft = data.paddingLeft;
  if (data.paddingRight !== undefined)
    component.paddingRight = data.paddingRight;
  if (data.paddingTop !== undefined)
    component.paddingTop = data.paddingTop;
  if (data.paddingBottom !== undefined)
    component.paddingBottom = data.paddingBottom;
  if (data.itemSpacing !== undefined)
    component.itemSpacing = data.itemSpacing;
  if (data.description)
    component.description = data.description;
  return component;
}
function createGroupFromData(data, children) {
  const group = figma.group(children, figma.currentPage);
  applyCommonProperties(group, data);
  return group;
}
function createInstanceFromData(data) {
  if (!data.componentId) {
    console.error("Cannot create instance: componentId is required");
    return null;
  }
  const component = figma.getNodeById(data.componentId);
  if (!component || component.type !== "COMPONENT") {
    console.error(`Cannot create instance: component with id ${data.componentId} not found`);
    return null;
  }
  const instance = component.createInstance();
  applyCommonProperties(instance, data);
  if (data.componentProperties) {
    for (const [key, value] of Object.entries(data.componentProperties)) {
      if (key in instance.componentProperties) {
        const prop = instance.componentProperties[key];
        if (prop.type === "BOOLEAN") {
          instance.setProperties({ [key]: !!value });
        } else if (prop.type === "TEXT") {
          instance.setProperties({ [key]: String(value) });
        } else if (prop.type === "INSTANCE_SWAP") {
          instance.setProperties({ [key]: String(value) });
        } else if (prop.type === "VARIANT") {
          instance.setProperties({ [key]: String(value) });
        }
      }
    }
  }
  return instance;
}
function createSectionFromData(data) {
  const section = figma.createSection();
  if (data.name)
    section.name = data.name;
  if (data.sectionContentsHidden !== undefined)
    section.sectionContentsHidden = data.sectionContentsHidden;
  if (data.x !== undefined)
    section.x = data.x;
  if (data.y !== undefined)
    section.y = data.y;
  return section;
}

// src/plugin/creators/textCreator.ts
async function createTextFromData(data) {
  const text = figma.createText();
  const fontFamily = data.fontFamily || data.fontName.family || "Inter";
  const fontStyle = data.fontStyle || data.fontName.style || "Regular";
  try {
    await figma.loadFontAsync({ family: fontFamily, style: fontStyle });
  } catch (error) {
    console.warn(`Failed to load font ${fontFamily} ${fontStyle}. Falling back to Inter Regular.`);
    await figma.loadFontAsync({ family: "Inter", style: "Regular" });
  }
  text.characters = data.text || data.characters || "Text";
  if (data.x !== undefined)
    text.x = data.x;
  if (data.y !== undefined)
    text.y = data.y;
  if (data.fontSize)
    text.fontSize = data.fontSize;
  if (data.width)
    text.resize(data.width, text.height);
  if (data.fontName)
    text.fontName = data.fontName;
  if (data.textAlignHorizontal)
    text.textAlignHorizontal = data.textAlignHorizontal;
  if (data.textAlignVertical)
    text.textAlignVertical = data.textAlignVertical;
  if (data.textAutoResize)
    text.textAutoResize = data.textAutoResize;
  if (data.textTruncation)
    text.textTruncation = data.textTruncation;
  if (data.maxLines !== undefined)
    text.maxLines = data.maxLines;
  if (data.paragraphIndent)
    text.paragraphIndent = data.paragraphIndent;
  if (data.paragraphSpacing)
    text.paragraphSpacing = data.paragraphSpacing;
  if (data.listSpacing)
    text.listSpacing = data.listSpacing;
  if (data.hangingPunctuation !== undefined)
    text.hangingPunctuation = data.hangingPunctuation;
  if (data.hangingList !== undefined)
    text.hangingList = data.hangingList;
  if (data.autoRename !== undefined)
    text.autoRename = data.autoRename;
  if (data.letterSpacing)
    text.letterSpacing = data.letterSpacing;
  if (data.lineHeight)
    text.lineHeight = data.lineHeight;
  if (data.leadingTrim)
    text.leadingTrim = data.leadingTrim;
  if (data.textCase)
    text.textCase = data.textCase;
  if (data.textDecoration)
    text.textDecoration = data.textDecoration;
  if (data.textStyleId)
    text.textStyleId = data.textStyleId;
  if (data.textDecorationStyle)
    text.textDecorationStyle = data.textDecorationStyle;
  if (data.textDecorationOffset)
    text.textDecorationOffset = data.textDecorationOffset;
  if (data.textDecorationThickness)
    text.textDecorationThickness = data.textDecorationThickness;
  if (data.textDecorationColor)
    text.textDecorationColor = data.textDecorationColor;
  if (data.textDecorationSkipInk !== undefined)
    text.textDecorationSkipInk = data.textDecorationSkipInk;
  if (data.fills) {
    text.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === "string") {
      text.fills = [createSolidPaint(data.fill)];
    } else {
      text.fills = [data.fill];
    }
  }
  if (data.hyperlink) {
    text.hyperlink = data.hyperlink;
  }
  if (data.layoutAlign)
    text.layoutAlign = data.layoutAlign;
  if (data.layoutGrow !== undefined)
    text.layoutGrow = data.layoutGrow;
  if (data.layoutSizingHorizontal)
    text.layoutSizingHorizontal = data.layoutSizingHorizontal;
  if (data.layoutSizingVertical)
    text.layoutSizingVertical = data.layoutSizingVertical;
  if (data.rangeStyles && Array.isArray(data.rangeStyles)) {
    applyTextRangeStyles(text, data.rangeStyles);
  }
  if (data.name)
    text.name = data.name;
  if (data.visible !== undefined)
    text.visible = data.visible;
  if (data.locked !== undefined)
    text.locked = data.locked;
  if (data.opacity !== undefined)
    text.opacity = data.opacity;
  if (data.blendMode)
    text.blendMode = data.blendMode;
  if (data.effects)
    text.effects = data.effects;
  if (data.effectStyleId)
    text.effectStyleId = data.effectStyleId;
  if (data.exportSettings)
    text.exportSettings = data.exportSettings;
  if (data.constraints)
    text.constraints = data.constraints;
  return text;
}
function applyTextRangeStyles(textNode, ranges) {
  for (const range of ranges) {
    for (const [property, value] of Object.entries(range.style)) {
      if (property === "fills") {
        textNode.setRangeFills(range.start, range.end, value);
      } else if (property === "fillStyleId") {
        textNode.setRangeFillStyleId(range.start, range.end, value);
      } else if (property === "fontName") {
        textNode.setRangeFontName(range.start, range.end, value);
      } else if (property === "fontSize") {
        textNode.setRangeFontSize(range.start, range.end, value);
      } else if (property === "textCase") {
        textNode.setRangeTextCase(range.start, range.end, value);
      } else if (property === "textDecoration") {
        textNode.setRangeTextDecoration(range.start, range.end, value);
      } else if (property === "textDecorationStyle") {
        textNode.setRangeTextDecorationStyle(range.start, range.end, value);
      } else if (property === "textDecorationOffset") {
        textNode.setRangeTextDecorationOffset(range.start, range.end, value);
      } else if (property === "textDecorationThickness") {
        textNode.setRangeTextDecorationThickness(range.start, range.end, value);
      } else if (property === "textDecorationColor") {
        textNode.setRangeTextDecorationColor(range.start, range.end, value);
      } else if (property === "textDecorationSkipInk") {
        textNode.setRangeTextDecorationSkipInk(range.start, range.end, value);
      } else if (property === "letterSpacing") {
        textNode.setRangeLetterSpacing(range.start, range.end, value);
      } else if (property === "lineHeight") {
        textNode.setRangeLineHeight(range.start, range.end, value);
      } else if (property === "hyperlink") {
        textNode.setRangeHyperlink(range.start, range.end, value);
      } else if (property === "textStyleId") {
        textNode.setRangeTextStyleId(range.start, range.end, value);
      } else if (property === "indentation") {
        textNode.setRangeIndentation(range.start, range.end, value);
      } else if (property === "paragraphIndent") {
        textNode.setRangeParagraphIndent(range.start, range.end, value);
      } else if (property === "paragraphSpacing") {
        textNode.setRangeParagraphSpacing(range.start, range.end, value);
      } else if (property === "listOptions") {
        textNode.setRangeListOptions(range.start, range.end, value);
      } else if (property === "listSpacing") {
        textNode.setRangeListSpacing(range.start, range.end, value);
      }
    }
  }
}

// src/plugin/creators/specialCreators.ts
function createBooleanOperationFromData(data) {
  if (!data.children || !Array.isArray(data.children) || data.children.length < 2) {
    console.error("Boolean operation requires at least 2 child nodes");
    return null;
  }
  let childNodes = [];
  try {
    for (const childData of data.children) {
      const node = figma.createRectangle();
      childNodes.push(node);
    }
    const booleanOperation = figma.createBooleanOperation();
    if (data.booleanOperation) {
      booleanOperation.booleanOperation = data.booleanOperation;
    }
    applyCommonProperties(booleanOperation, data);
    return booleanOperation;
  } catch (error) {
    console.error("Failed to create boolean operation:", error);
    childNodes.forEach((node) => node.remove());
    return null;
  }
}
function createConnectorFromData(data) {
  const connector = figma.createConnector();
  if (data.connectorStart)
    connector.connectorStart = data.connectorStart;
  if (data.connectorEnd)
    connector.connectorEnd = data.connectorEnd;
  if (data.connectorStartStrokeCap)
    connector.connectorStartStrokeCap = data.connectorStartStrokeCap;
  if (data.connectorEndStrokeCap)
    connector.connectorEndStrokeCap = data.connectorEndStrokeCap;
  if (data.connectorLineType)
    connector.connectorLineType = data.connectorLineType;
  if (data.strokes)
    connector.strokes = data.strokes;
  if (data.strokeWeight)
    connector.strokeWeight = data.strokeWeight;
  applyCommonProperties(connector, data);
  return connector;
}
function createShapeWithTextFromData(data) {
  if (!("createShapeWithText" in figma)) {
    console.error("ShapeWithText creation is not supported in this Figma version");
    return null;
  }
  try {
    const shapeWithText = figma.createShapeWithText();
    if (data.shapeType)
      shapeWithText.shapeType = data.shapeType;
    if (data.text || data.characters) {
      shapeWithText.text.characters = data.text || data.characters;
    }
    try {
      if (data.fontSize)
        shapeWithText.text.fontSize = data.fontSize;
      if (data.fontName)
        shapeWithText.text.fontName = data.fontName;
      if (data.textAlignHorizontal && "textAlignHorizontal" in shapeWithText.text) {
        shapeWithText.text.textAlignHorizontal = data.textAlignHorizontal;
      }
      if (data.textAlignVertical && "textAlignVertical" in shapeWithText.text) {
        shapeWithText.text.textAlignVertical = data.textAlignVertical;
      }
    } catch (e) {
      console.warn("Some text properties could not be set on ShapeWithText:", e);
    }
    if (data.fills)
      shapeWithText.fills = data.fills;
    if (data.strokes)
      shapeWithText.strokes = data.strokes;
    applyCommonProperties(shapeWithText, data);
    return shapeWithText;
  } catch (error) {
    console.error("Failed to create shape with text:", error);
    return null;
  }
}
function createCodeBlockFromData(data) {
  const codeBlock = figma.createCodeBlock();
  if (data.code)
    codeBlock.code = data.code;
  if (data.codeLanguage)
    codeBlock.codeLanguage = data.codeLanguage;
  applyCommonProperties(codeBlock, data);
  return codeBlock;
}
function createTableFromData(data) {
  const table = figma.createTable(data.numRows || 2, data.numColumns || 2);
  if (data.fills && "fills" in table) {
    table.fills = data.fills;
  }
  if (data.cells && Array.isArray(data.cells)) {
    for (const cellData of data.cells) {
      if (cellData.rowIndex !== undefined && cellData.columnIndex !== undefined) {
        try {
          let cell;
          if ("cellAt" in table) {
            cell = table.cellAt(cellData.rowIndex, cellData.columnIndex);
          } else if ("getCellAt" in table) {
            cell = table.getCellAt(cellData.rowIndex, cellData.columnIndex);
          }
          if (cell) {
            if (cellData.text && cell.text)
              cell.text.characters = cellData.text;
            if (cellData.fills && "fills" in cell)
              cell.fills = cellData.fills;
            if (cellData.rowSpan && "rowSpan" in cell)
              cell.rowSpan = cellData.rowSpan;
            if (cellData.columnSpan && "columnSpan" in cell)
              cell.columnSpan = cellData.columnSpan;
          }
        } catch (e) {
          console.warn(`Could not set properties for cell at ${cellData.rowIndex}, ${cellData.columnIndex}:`, e);
        }
      }
    }
  }
  applyCommonProperties(table, data);
  return table;
}
function createWidgetFromData(data) {
  if (!("createWidget" in figma)) {
    console.error("Widget creation is not supported in this Figma version");
    return null;
  }
  if (!data.widgetId) {
    console.error("Widget creation requires a widgetId");
    return null;
  }
  try {
    const widget = figma.createWidget(data.widgetId);
    if (data.widgetData)
      widget.widgetData = JSON.stringify(data.widgetData);
    if (data.width && data.height && "resize" in widget)
      widget.resize(data.width, data.height);
    applyCommonProperties(widget, data);
    return widget;
  } catch (error) {
    console.error("Failed to create widget:", error);
    return null;
  }
}
function createMediaFromData(data) {
  if (!("createMedia" in figma)) {
    console.error("Media creation is not supported in this Figma version");
    return null;
  }
  if (!data.hash) {
    console.error("Media creation requires a valid media hash");
    return null;
  }
  try {
    const media = figma.createMedia(data.hash);
    applyCommonProperties(media, data);
    return media;
  } catch (error) {
    console.error("Failed to create media:", error);
    return null;
  }
}

// src/plugin/creators/imageCreators.ts
function createImageFromData(data) {
  try {
    if (!data.hash) {
      console.error("Image creation requires an image hash");
      return null;
    }
    const image = figma.createImage(data.hash);
    const rect = figma.createRectangle();
    if (data.width && data.height) {
      rect.resize(data.width, data.height);
    }
    rect.fills = [{
      type: "IMAGE",
      scaleMode: data.scaleMode || "FILL",
      imageHash: image.hash
    }];
    applyCommonProperties(rect, data);
    return rect;
  } catch (error) {
    console.error("Failed to create image:", error);
    return null;
  }
}
async function createImageFromBytesAsync(data) {
  try {
    if (!data.bytes && !data.file) {
      console.error("Image creation requires image bytes or file");
      return null;
    }
    let image;
    if (data.bytes) {
      image = await figma.createImageAsync(data.bytes);
    } else if (data.file) {
      image = await figma.createImageAsync(data.file);
    } else {
      return null;
    }
    const rect = figma.createRectangle();
    if (data.width && data.height) {
      rect.resize(data.width, data.height);
    }
    rect.fills = [{
      type: "IMAGE",
      scaleMode: data.scaleMode || "FILL",
      imageHash: image.hash
    }];
    applyCommonProperties(rect, data);
    return rect;
  } catch (error) {
    console.error("Failed to create image asynchronously:", error);
    return null;
  }
}
function createGifFromData(data) {
  console.error("createGif API is not directly available or implemented");
  return null;
}
async function createVideoFromDataAsync(data) {
  if (!("createVideoAsync" in figma)) {
    console.error("Video creation is not supported in this Figma version");
    return null;
  }
  try {
    if (!data.bytes) {
      console.error("Video creation requires video bytes");
      return null;
    }
    const video = await figma.createVideoAsync(data.bytes);
    applyCommonProperties(video, data);
    return video;
  } catch (error) {
    console.error("Failed to create video:", error);
    return null;
  }
}
async function createLinkPreviewFromDataAsync(data) {
  if (!("createLinkPreviewAsync" in figma)) {
    console.error("Link preview creation is not supported in this Figma version");
    return null;
  }
  try {
    if (!data.url) {
      console.error("Link preview creation requires a URL");
      return null;
    }
    const linkPreview = await figma.createLinkPreviewAsync(data.url);
    applyCommonProperties(linkPreview, data);
    return linkPreview;
  } catch (error) {
    console.error("Failed to create link preview:", error);
    return null;
  }
}

// src/plugin/creators/sliceCreators.ts
function createSliceFromData(data) {
  const slice = figma.createSlice();
  if (data.width && data.height) {
    slice.resize(data.width, data.height);
  }
  if (data.x !== undefined)
    slice.x = data.x;
  if (data.y !== undefined)
    slice.y = data.y;
  if (data.exportSettings && Array.isArray(data.exportSettings)) {
    slice.exportSettings = data.exportSettings;
  }
  if (data.name)
    slice.name = data.name;
  if (data.visible !== undefined)
    slice.visible = data.visible;
  return slice;
}
function createPageFromData(data) {
  const page = figma.createPage();
  if (data.name)
    page.name = data.name;
  if (data.backgrounds)
    page.backgrounds = data.backgrounds;
  return page;
}
function createPageDividerFromData(data) {
  if (!("createPageDivider" in figma)) {
    console.error("createPageDivider is not supported in this Figma version");
    return null;
  }
  try {
    const pageDivider = figma.createPageDivider();
    if (data.name)
      pageDivider.name = data.name;
    return pageDivider;
  } catch (error) {
    console.error("Failed to create page divider:", error);
    return null;
  }
}
function createSlideFromData(data) {
  if (!("createSlide" in figma)) {
    console.error("createSlide is not supported in this Figma version");
    return null;
  }
  try {
    const slide = figma.createSlide();
    if (data.name)
      slide.name = data.name;
    applyCommonProperties(slide, data);
    return slide;
  } catch (error) {
    console.error("Failed to create slide:", error);
    return null;
  }
}
function createSlideRowFromData(data) {
  if (!("createSlideRow" in figma)) {
    console.error("createSlideRow is not supported in this Figma version");
    return null;
  }
  try {
    const slideRow = figma.createSlideRow();
    if (data.name)
      slideRow.name = data.name;
    applyCommonProperties(slideRow, data);
    return slideRow;
  } catch (error) {
    console.error("Failed to create slide row:", error);
    return null;
  }
}

// src/plugin/creators/componentCreators.ts
function createComponentFromNodeData(data) {
  if (!data.sourceNode) {
    console.error("createComponentFromNode requires a sourceNode");
    return null;
  }
  try {
    let sourceNode;
    if (typeof data.sourceNode === "string") {
      sourceNode = figma.getNodeById(data.sourceNode);
      if (!sourceNode || !("type" in sourceNode)) {
        console.error(`Node with ID ${data.sourceNode} not found or is not a valid node`);
        return null;
      }
    } else {
      sourceNode = data.sourceNode;
    }
    const component = figma.createComponentFromNode(sourceNode);
    if (data.description)
      component.description = data.description;
    applyCommonProperties(component, data);
    return component;
  } catch (error) {
    console.error("Failed to create component from node:", error);
    return null;
  }
}
function createComponentSetFromData(data) {
  try {
    if (!data.components || !Array.isArray(data.components) || data.components.length === 0) {
      console.error("Component set creation requires component nodes");
      return null;
    }
    const componentNodes = [];
    for (const component of data.components) {
      let node;
      if (typeof component === "string") {
        node = figma.getNodeById(component);
      } else {
        node = component;
      }
      if (node && node.type === "COMPONENT") {
        componentNodes.push(node);
      }
    }
    if (componentNodes.length === 0) {
      console.error("No valid component nodes provided");
      return null;
    }
    const componentSet = figma.combineAsVariants(componentNodes, figma.currentPage);
    if (data.name)
      componentSet.name = data.name;
    applyCommonProperties(componentSet, data);
    return componentSet;
  } catch (error) {
    console.error("Failed to create component set:", error);
    return null;
  }
}

// src/plugin/creators/elementCreator.ts
async function createElementFromData(data) {
  if (!data || !data.type) {
    console.error("Invalid element data: missing type");
    return null;
  }
  let element = null;
  try {
    switch (data.type.toLowerCase()) {
      case "rectangle":
        element = createRectangleFromData(data);
        break;
      case "ellipse":
      case "circle":
        element = createEllipseFromData(data);
        break;
      case "polygon":
        element = createPolygonFromData(data);
        break;
      case "star":
        element = createStarFromData(data);
        break;
      case "line":
        element = createLineFromData(data);
        break;
      case "vector":
        element = createVectorFromData(data);
        break;
      case "frame":
        element = createFrameFromData(data);
        break;
      case "component":
        element = createComponentFromData(data);
        break;
      case "componentfromnode":
        element = createComponentFromNodeData(data);
        break;
      case "componentset":
        element = createComponentSetFromData(data);
        break;
      case "instance":
        element = createInstanceFromData(data);
        break;
      case "section":
        element = createSectionFromData(data);
        break;
      case "text":
        element = await createTextFromData(data);
        break;
      case "boolean":
      case "booleanoperation":
        element = createBooleanOperationFromData(data);
        break;
      case "connector":
        element = createConnectorFromData(data);
        break;
      case "shapewithtext":
        element = createShapeWithTextFromData(data);
        break;
      case "codeblock":
        element = createCodeBlockFromData(data);
        break;
      case "table":
        element = createTableFromData(data);
        break;
      case "widget":
        element = createWidgetFromData(data);
        break;
      case "media":
        element = createMediaFromData(data);
        break;
      case "image":
        if (data.bytes || data.file) {
          element = await createImageFromBytesAsync(data);
        } else {
          element = createImageFromData(data);
        }
        break;
      case "gif":
        element = createGifFromData(data);
        break;
      case "video":
        element = await createVideoFromDataAsync(data);
        break;
      case "linkpreview":
        element = await createLinkPreviewFromDataAsync(data);
        break;
      case "slice":
        element = createSliceFromData(data);
        break;
      case "page":
        const page = createPageFromData(data);
        console.log(`Created page: ${page.name}`);
        return null;
      case "pagedivider":
        element = createPageDividerFromData(data);
        break;
      case "slide":
        element = createSlideFromData(data);
        break;
      case "sliderow":
        element = createSlideRowFromData(data);
        break;
      case "group":
        if (!data.children || !Array.isArray(data.children) || data.children.length < 1) {
          console.error("Cannot create group: children array is required");
          return null;
        }
        const childNodes = [];
        for (const childData of data.children) {
          const child = await createElementFromData(childData);
          if (child)
            childNodes.push(child);
        }
        if (childNodes.length > 0) {
          element = createGroupFromData(data, childNodes);
        } else {
          console.error("Cannot create group: no valid children were created");
          return null;
        }
        break;
      default:
        console.error(`Unsupported element type: ${data.type}`);
        return null;
    }
    if (element) {
      applyCommonProperties(element, data);
      if (data.select !== false) {
        selectAndFocusNodes(element);
      }
    }
    return element;
  } catch (error) {
    console.error(`Error creating element: ${error instanceof Error ? error.message : "Unknown error"}`);
    return null;
  }
}
async function createElementsFromDataArray(dataArray) {
  const createdNodes = [];
  for (const data of dataArray) {
    const node = await createElementFromData(data);
    if (node)
      createdNodes.push(node);
  }
  if (createdNodes.length > 0) {
    selectAndFocusNodes(createdNodes);
  }
  return createdNodes;
}

// src/plugin/code.ts
figma.showUI(__html__, { width: 320, height: 500 });
console.log("Figma MCP Plugin loaded");
var elementCreators = {
  "create-rectangle": createRectangleFromData,
  "create-circle": createEllipseFromData,
  "create-ellipse": createEllipseFromData,
  "create-polygon": createPolygonFromData,
  "create-line": createLineFromData,
  "create-text": createTextFromData,
  "create-star": createStarFromData,
  "create-vector": createVectorFromData,
  "create-arc": (params) => {
    const ellipse = createEllipseFromData(params);
    if (params.arcData || params.startAngle !== undefined && params.endAngle !== undefined) {
      ellipse.arcData = {
        startingAngle: params.startAngle || params.arcData.startingAngle || 0,
        endingAngle: params.endAngle || params.arcData.endingAngle || 360,
        innerRadius: params.innerRadius || params.arcData.innerRadius || 0
      };
    }
    return ellipse;
  }
};
async function createElement(type, params) {
  console.log(`Creating ${type} with params:`, params);
  const creator = elementCreators[type];
  if (!creator) {
    console.error(`Unknown element type: ${type}`);
    return null;
  }
  try {
    const element = await Promise.resolve(creator(params));
    if (element && params) {
      if (params.x !== undefined)
        element.x = params.x;
      if (params.y !== undefined)
        element.y = params.y;
    }
    if (element) {
      selectAndFocusNodes(element);
    }
    return element;
  } catch (error) {
    console.error(`Error creating ${type}:`, error);
    return null;
  }
}
figma.ui.onmessage = async function(msg) {
  console.log("Received message from UI:", msg);
  if (elementCreators[msg.type]) {
    await createElement(msg.type, msg);
  } else if (msg.type === "create-element") {
    console.log("Creating element with data:", msg.data);
    createElementFromData(msg.data);
  } else if (msg.type === "create-elements") {
    console.log("Creating multiple elements with data:", msg.data);
    createElementsFromDataArray(msg.data);
  } else if (msg.type === "mcp-command") {
    console.log("Received MCP command:", msg.command, "with params:", msg.params);
    handleMcpCommand(msg.command, msg.params);
  } else if (msg.type === "cancel") {
    console.log("Closing plugin");
    figma.closePlugin();
  } else {
    console.log("Unknown message type:", msg.type);
  }
};
async function handleMcpCommand(command, params) {
  let result = null;
  try {
    const pluginCommand = command.replace(/_/g, "-");
    switch (pluginCommand) {
      case "create-rectangle":
      case "create-circle":
      case "create-polygon":
      case "create-line":
      case "create-arc":
      case "create-vector":
        console.log(`MCP command: Creating ${pluginCommand.substring(7)} with params:`, params);
        result = await createElement(pluginCommand, params);
        break;
      case "create-text":
        console.log("MCP command: Creating text with params:", params);
        result = await createElement(pluginCommand, params);
        break;
      case "create-element":
        console.log("MCP command: Creating element with params:", params);
        result = await createElementFromData(params);
        break;
      case "create-elements":
        console.log("MCP command: Creating multiple elements with params:", params);
        result = await createElementsFromDataArray(params);
        break;
      case "get-selection":
        console.log("MCP command: Getting current selection");
        result = figma.currentPage.selection;
        break;
      case "get-elements":
        console.log("MCP command: Getting elements with params:", params);
        const page = params.page_id ? figma.getNodeById(params.page_id) : figma.currentPage;
        if (!page || page.type !== "PAGE") {
          throw new Error("Invalid page ID or node is not a page");
        }
        const nodeType = params.type || "ALL";
        const limit = params.limit || 100;
        const includeHidden = params.include_hidden || false;
        if (nodeType === "ALL") {
          result = includeHidden ? page.children.slice(0, limit) : page.children.filter((node2) => node2.visible).slice(0, limit);
        } else {
          result = page.findAll((node2) => {
            const typeMatch = node2.type === nodeType;
            const visibilityMatch = includeHidden || node2.visible;
            return typeMatch && visibilityMatch;
          }).slice(0, limit);
        }
        break;
      case "get-element":
        console.log("MCP command: Getting element with ID:", params.node_id);
        const node = figma.getNodeById(params.node_id);
        if (!node) {
          throw new Error("Element not found with ID: " + params.node_id);
        }
        if (!["DOCUMENT", "PAGE"].includes(node.type)) {
          if (params.include_children && "children" in node) {
            result = [node, ...node.children || []];
          } else {
            result = node;
          }
        } else if (node.type === "PAGE") {
          result = node;
        } else {
          throw new Error("Unsupported node type: " + node.type);
        }
        break;
      case "get-pages":
        console.log("MCP command: Getting all pages");
        result = figma.root.children;
        break;
      case "get-page":
        console.log("MCP command: Getting page with ID:", params.page_id);
        if (!params.page_id) {
          console.log("No page_id provided, using current page");
          result = figma.currentPage;
        } else {
          const pageNode = figma.getNodeById(params.page_id);
          if (!pageNode || pageNode.type !== "PAGE")
            throw new Error("Invalid page ID or node is not a page");
          result = pageNode;
        }
        break;
      case "create-page":
        console.log("MCP command: Creating new page with name:", params.name);
        const newPage = figma.createPage();
        newPage.name = params.name || "New Page";
        result = newPage;
        break;
      case "switch-page":
        console.log("MCP command: Switching to page with ID:", params.id);
        if (!params.id)
          throw new Error("Page ID is required");
        const switchPageNode = figma.getNodeById(params.id);
        if (!switchPageNode || switchPageNode.type !== "PAGE")
          throw new Error("Invalid page ID");
        figma.currentPage = switchPageNode;
        result = switchPageNode;
        break;
      case "modify-rectangle":
        console.log("MCP command: Modifying rectangle with ID:", params.id);
        if (!params.id)
          throw new Error("Rectangle ID is required");
        const modifyNode = figma.getNodeById(params.id);
        if (!modifyNode || modifyNode.type !== "RECTANGLE")
          throw new Error("Invalid rectangle ID");
        const rect = modifyNode;
        if (params.x !== undefined)
          rect.x = params.x;
        if (params.y !== undefined)
          rect.y = params.y;
        if (params.width !== undefined && params.height !== undefined)
          rect.resize(params.width, params.height);
        if (params.cornerRadius !== undefined)
          rect.cornerRadius = params.cornerRadius;
        if (params.color)
          rect.fills = [{ type: "SOLID", color: hexToRgb(params.color) }];
        result = rect;
        break;
      default:
        console.log("Unknown MCP command:", command);
        throw new Error("Unknown command: " + command);
    }
    let resultForBuilder = null;
    if (result === null) {
      resultForBuilder = null;
    } else if (Array.isArray(result)) {
      resultForBuilder = result;
    } else if ("type" in result && result.type === "PAGE") {
      resultForBuilder = result;
    } else {
      resultForBuilder = result;
    }
    const resultObject = buildResultObject(resultForBuilder);
    console.log("Command result:", resultObject);
    figma.ui.postMessage({
      type: "mcp-response",
      success: true,
      command,
      result: resultObject
    });
    console.log("Response sent to UI");
    return resultObject;
  } catch (error) {
    console.error("Error handling MCP command:", error);
    figma.ui.postMessage({
      type: "mcp-response",
      success: false,
      command,
      error: error instanceof Error ? error.message : "Unknown error"
    });
    console.log("Error response sent to UI");
    throw error;
  }
}

```
Page 2/2FirstPrevNextLast