with-figma 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/with-figma.js CHANGED
@@ -24,6 +24,9 @@ if (command === "init") {
24
24
  mcpJson.servers["with-figma"] = {
25
25
  command: "npx",
26
26
  args: ["-y", "with-figma", "serve"],
27
+ env: {
28
+ OPENAI_API_KEY: "${env:OPENAI_API_KEY}",
29
+ },
27
30
  };
28
31
  fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpJson, null, 2) + "\n");
29
32
  console.log("✓ .vscode/mcp.json updated");
@@ -41,6 +44,9 @@ if (command === "init") {
41
44
  claudeJson.mcpServers["with-figma"] = {
42
45
  command: "npx",
43
46
  args: ["-y", "with-figma", "serve"],
47
+ env: {
48
+ OPENAI_API_KEY: "${env:OPENAI_API_KEY}",
49
+ },
44
50
  };
45
51
  fs.writeFileSync(claudePath, JSON.stringify(claudeJson, null, 2) + "\n");
46
52
  console.log("✓ .claude/settings.json updated");
@@ -30114,6 +30114,222 @@ var StdioServerTransport = class {
30114
30114
 
30115
30115
  // mcp-server/index.ts
30116
30116
  var import_ws = require("ws");
30117
+
30118
+ // mcp-server/agent.ts
30119
+ var import_openai = __toESM(require("openai"));
30120
+ var TOOLS = [
30121
+ {
30122
+ type: "function",
30123
+ function: {
30124
+ name: "create_frame",
30125
+ description: "Create a new frame (artboard)",
30126
+ parameters: {
30127
+ type: "object",
30128
+ properties: {
30129
+ name: { type: "string" },
30130
+ width: { type: "number", default: 375 },
30131
+ height: { type: "number", default: 812 },
30132
+ x: { type: "number", default: 0 },
30133
+ y: { type: "number", default: 0 }
30134
+ },
30135
+ required: ["name"]
30136
+ }
30137
+ }
30138
+ },
30139
+ {
30140
+ type: "function",
30141
+ function: {
30142
+ name: "create_rectangle",
30143
+ description: "Create a rectangle, optionally inside a parent frame",
30144
+ parameters: {
30145
+ type: "object",
30146
+ properties: {
30147
+ name: { type: "string" },
30148
+ width: { type: "number" },
30149
+ height: { type: "number" },
30150
+ x: { type: "number", default: 0 },
30151
+ y: { type: "number", default: 0 },
30152
+ parentId: { type: "string" },
30153
+ cornerRadius: { type: "number" },
30154
+ fillColor: {
30155
+ type: "object",
30156
+ properties: { r: { type: "number" }, g: { type: "number" }, b: { type: "number" }, a: { type: "number" } }
30157
+ }
30158
+ },
30159
+ required: ["width", "height"]
30160
+ }
30161
+ }
30162
+ },
30163
+ {
30164
+ type: "function",
30165
+ function: {
30166
+ name: "create_text",
30167
+ description: "Create a text element",
30168
+ parameters: {
30169
+ type: "object",
30170
+ properties: {
30171
+ characters: { type: "string" },
30172
+ name: { type: "string" },
30173
+ fontSize: { type: "number", default: 16 },
30174
+ x: { type: "number", default: 0 },
30175
+ y: { type: "number", default: 0 },
30176
+ parentId: { type: "string" },
30177
+ fillColor: {
30178
+ type: "object",
30179
+ properties: { r: { type: "number" }, g: { type: "number" }, b: { type: "number" }, a: { type: "number" } }
30180
+ }
30181
+ },
30182
+ required: ["characters"]
30183
+ }
30184
+ }
30185
+ },
30186
+ {
30187
+ type: "function",
30188
+ function: {
30189
+ name: "modify_node",
30190
+ description: "Modify properties of an existing node",
30191
+ parameters: {
30192
+ type: "object",
30193
+ properties: {
30194
+ nodeId: { type: "string" },
30195
+ properties: { type: "object" }
30196
+ },
30197
+ required: ["nodeId", "properties"]
30198
+ }
30199
+ }
30200
+ },
30201
+ {
30202
+ type: "function",
30203
+ function: {
30204
+ name: "delete_node",
30205
+ description: "Delete a node",
30206
+ parameters: {
30207
+ type: "object",
30208
+ properties: { nodeId: { type: "string" } },
30209
+ required: ["nodeId"]
30210
+ }
30211
+ }
30212
+ },
30213
+ {
30214
+ type: "function",
30215
+ function: {
30216
+ name: "get_node",
30217
+ description: "Get detailed info about a node by ID",
30218
+ parameters: {
30219
+ type: "object",
30220
+ properties: { nodeId: { type: "string" } },
30221
+ required: ["nodeId"]
30222
+ }
30223
+ }
30224
+ }
30225
+ ];
30226
+ async function executeTool(name, args, deps) {
30227
+ try {
30228
+ switch (name) {
30229
+ case "create_frame": {
30230
+ const r = await deps.sendToFigma({ type: "create-frame", ...args });
30231
+ return JSON.stringify(r.node);
30232
+ }
30233
+ case "create_rectangle": {
30234
+ const cmd = { type: "create-rectangle", ...args };
30235
+ if (args.fillColor) {
30236
+ cmd.fills = [{ type: "SOLID", color: args.fillColor }];
30237
+ delete cmd.fillColor;
30238
+ }
30239
+ const r = await deps.sendToFigma(cmd);
30240
+ return JSON.stringify(r.node);
30241
+ }
30242
+ case "create_text": {
30243
+ const cmd = { type: "create-text", ...args };
30244
+ if (args.fillColor) {
30245
+ cmd.fills = [{ type: "SOLID", color: args.fillColor }];
30246
+ delete cmd.fillColor;
30247
+ }
30248
+ const r = await deps.sendToFigma(cmd);
30249
+ return JSON.stringify(r.node);
30250
+ }
30251
+ case "modify_node": {
30252
+ const r = await deps.sendToFigma({ type: "modify-node", nodeId: args.nodeId, properties: args.properties });
30253
+ return JSON.stringify(r.node);
30254
+ }
30255
+ case "delete_node": {
30256
+ const r = await deps.sendToFigma({ type: "delete-node", nodeId: args.nodeId });
30257
+ return `Deleted: ${r.nodeId}`;
30258
+ }
30259
+ case "get_node": {
30260
+ const r = await deps.sendToFigma({ type: "get-node", nodeId: args.nodeId });
30261
+ return JSON.stringify(r.node);
30262
+ }
30263
+ default:
30264
+ return `Unknown tool: ${name}`;
30265
+ }
30266
+ } catch (err) {
30267
+ return `Error: ${err.message}`;
30268
+ }
30269
+ }
30270
+ var SYSTEM_PROMPT = `You are a Figma design agent. You receive messages from a user via a Figma plugin chat.
30271
+ You directly manipulate their Figma document using the provided tools.
30272
+
30273
+ Guidelines:
30274
+ - When creating wireframes: use a Frame as container, Rectangles for sections/buttons/cards, Text for labels
30275
+ - Mobile wireframe: 375\xD7812, Desktop: 1440\xD7900
30276
+ - Wireframe fills: light gray (r:0.95,g:0.95,b:0.95), text: dark (r:0.2,g:0.2,b:0.2)
30277
+ - Respond in the same language as the user
30278
+ - Keep responses short \u2014 the chat panel is small
30279
+ - When the user has selected elements, use their IDs to modify/inspect them`;
30280
+ var conversationHistory = [
30281
+ { role: "system", content: SYSTEM_PROMPT }
30282
+ ];
30283
+ async function handleChatMessage(text, selection, page, deps) {
30284
+ const apiKey = process.env.OPENAI_API_KEY;
30285
+ if (!apiKey) {
30286
+ deps.sendChat("OPENAI_API_KEY \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
30287
+ return;
30288
+ }
30289
+ const openai = new import_openai.default({ apiKey });
30290
+ let userContent = text;
30291
+ if (selection.length > 0) {
30292
+ userContent += `
30293
+
30294
+ [Selected elements: ${JSON.stringify(selection)}]`;
30295
+ }
30296
+ if (page) {
30297
+ userContent += `
30298
+ [Page: ${page.name}]`;
30299
+ }
30300
+ conversationHistory.push({ role: "user", content: userContent });
30301
+ deps.sendChat("\uC791\uC5C5 \uC911...");
30302
+ try {
30303
+ for (let i = 0; i < 20; i++) {
30304
+ const response = await openai.chat.completions.create({
30305
+ model: process.env.OPENAI_MODEL || "gpt-4o",
30306
+ messages: conversationHistory,
30307
+ tools: TOOLS,
30308
+ tool_choice: "auto"
30309
+ });
30310
+ const msg = response.choices[0].message;
30311
+ conversationHistory.push(msg);
30312
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
30313
+ for (const tc of msg.tool_calls) {
30314
+ const args = JSON.parse(tc.function.arguments);
30315
+ console.error(`[agent] ${tc.function.name}(${JSON.stringify(args)})`);
30316
+ const result = await executeTool(tc.function.name, args, deps);
30317
+ conversationHistory.push({ role: "tool", tool_call_id: tc.id, content: result });
30318
+ }
30319
+ continue;
30320
+ }
30321
+ if (msg.content) {
30322
+ deps.sendChat(msg.content);
30323
+ }
30324
+ break;
30325
+ }
30326
+ } catch (err) {
30327
+ console.error("[agent] Error:", err.message);
30328
+ deps.sendChat(`Error: ${err.message}`);
30329
+ }
30330
+ }
30331
+
30332
+ // mcp-server/index.ts
30117
30333
  var figmaSocket = null;
30118
30334
  var currentSelection = [];
30119
30335
  var currentPage = null;
@@ -30155,20 +30371,20 @@ function handleFigmaMessage(msg) {
30155
30371
  case "chat-message":
30156
30372
  currentSelection = msg.selection || currentSelection;
30157
30373
  currentPage = msg.page || currentPage;
30158
- chatQueue.push({
30159
- text: msg.text,
30160
- selection: msg.selection || [],
30161
- page: msg.page || null,
30162
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
30163
- });
30164
- console.error(`[with-figma] Chat message queued: "${msg.text}"`);
30165
- if (figmaSocket) {
30166
- figmaSocket.send(JSON.stringify({
30167
- type: "chat-response",
30168
- text: `Queued for AI agent. Tell Codex in VS Code:
30169
- "Check Figma messages and process them"`
30170
- }));
30171
- }
30374
+ console.error(`[with-figma] Chat: "${msg.text}"`);
30375
+ handleChatMessage(
30376
+ msg.text,
30377
+ msg.selection || currentSelection,
30378
+ msg.page || currentPage,
30379
+ {
30380
+ sendToFigma,
30381
+ sendChat: (text) => {
30382
+ if (figmaSocket && figmaSocket.readyState === import_ws.WebSocket.OPEN) {
30383
+ figmaSocket.send(JSON.stringify({ type: "chat-response", text }));
30384
+ }
30385
+ }
30386
+ }
30387
+ );
30172
30388
  break;
30173
30389
  // Results from Figma plugin operations
30174
30390
  case "node-data":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "with-figma",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "MCP server that connects AI agents to Figma via WebSocket",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -15,11 +15,12 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build:plugin": "esbuild figma-plugin/code.ts --bundle --outfile=figma-plugin/dist/code.js --target=es2020",
18
- "build:server": "esbuild mcp-server/index.ts --bundle --outfile=mcp-server/dist/index.js --platform=node --target=node18 --format=cjs --external:ws",
18
+ "build:server": "esbuild mcp-server/index.ts --bundle --outfile=mcp-server/dist/index.js --platform=node --target=node18 --format=cjs --external:ws --external:openai",
19
19
  "build": "npm run build:plugin && npm run build:server",
20
20
  "prepublishOnly": "npm run build"
21
21
  },
22
22
  "dependencies": {
23
+ "openai": "^6.34.0",
23
24
  "ws": "^8.18.0"
24
25
  },
25
26
  "devDependencies": {