@biolab/talk-to-figma 0.4.0 → 0.5.0
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/README.md +10 -8
- package/dist/cli.cjs +61 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -1
- package/dist/talk_to_figma_mcp/server.cjs +61 -0
- package/dist/talk_to_figma_mcp/server.cjs.map +1 -1
- package/dist/talk_to_figma_mcp/server.js +61 -0
- package/dist/talk_to_figma_mcp/server.js.map +1 -1
- package/figma-plugin/code.js +106 -0
- package/figma-plugin/figma-plugin.zip +0 -0
- package/figma-plugin/manifest.json +2 -2
- package/figma-plugin/ui.html +15 -15
- package/package.json +1 -1
package/figma-plugin/code.js
CHANGED
|
@@ -253,6 +253,10 @@ async function handleCommand(command, params) {
|
|
|
253
253
|
return await setVariableModeName(params);
|
|
254
254
|
case "set_variable_binding":
|
|
255
255
|
return await setVariableBinding(params);
|
|
256
|
+
case "create_component":
|
|
257
|
+
return await createComponent(params);
|
|
258
|
+
case "create_component_set":
|
|
259
|
+
return await createComponentSet(params);
|
|
256
260
|
default:
|
|
257
261
|
throw new Error(`Unknown command: ${command}`);
|
|
258
262
|
}
|
|
@@ -1505,6 +1509,108 @@ async function createComponentInstance(params) {
|
|
|
1505
1509
|
}
|
|
1506
1510
|
}
|
|
1507
1511
|
|
|
1512
|
+
async function createComponent(params) {
|
|
1513
|
+
const { nodeId } = params || {};
|
|
1514
|
+
|
|
1515
|
+
if (!nodeId) {
|
|
1516
|
+
throw new Error("Missing nodeId parameter");
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const node = await figma.getNodeByIdAsync(nodeId);
|
|
1520
|
+
if (!node) {
|
|
1521
|
+
throw new Error(`Node not found with ID: ${nodeId}`);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
if (node.type === "COMPONENT" || node.type === "COMPONENT_SET") {
|
|
1525
|
+
throw new Error(`Node is already a ${node.type}. Cannot convert to component.`);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
let parent = node.parent;
|
|
1529
|
+
while (parent) {
|
|
1530
|
+
if (parent.type === "COMPONENT" || parent.type === "COMPONENT_SET" || parent.type === "INSTANCE") {
|
|
1531
|
+
throw new Error(
|
|
1532
|
+
`Cannot create component from a node nested inside a ${parent.type} ("${parent.name}"). Move the node outside first.`
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
parent = parent.parent;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
try {
|
|
1539
|
+
const component = figma.createComponentFromNode(node);
|
|
1540
|
+
return {
|
|
1541
|
+
id: component.id,
|
|
1542
|
+
name: component.name,
|
|
1543
|
+
key: component.key,
|
|
1544
|
+
type: component.type,
|
|
1545
|
+
x: component.x,
|
|
1546
|
+
y: component.y,
|
|
1547
|
+
width: component.width,
|
|
1548
|
+
height: component.height,
|
|
1549
|
+
};
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
throw new Error(`Error creating component from node: ${error.message}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
async function createComponentSet(params) {
|
|
1556
|
+
const { nodeIds, parentId } = params || {};
|
|
1557
|
+
|
|
1558
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
1559
|
+
throw new Error("Missing or empty nodeIds array. Provide at least one component node ID.");
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
const components = [];
|
|
1563
|
+
const errors = [];
|
|
1564
|
+
|
|
1565
|
+
for (const nid of nodeIds) {
|
|
1566
|
+
const node = await figma.getNodeByIdAsync(nid);
|
|
1567
|
+
if (!node) {
|
|
1568
|
+
errors.push(`Node not found: ${nid}`);
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
if (node.type !== "COMPONENT") {
|
|
1572
|
+
errors.push(`Node "${node.name}" (${nid}) is type ${node.type}, not COMPONENT. Convert it first.`);
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
components.push(node);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
if (errors.length > 0) {
|
|
1579
|
+
throw new Error(`Invalid nodes:\n${errors.join("\n")}`);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
let parentNode;
|
|
1583
|
+
if (parentId) {
|
|
1584
|
+
parentNode = await figma.getNodeByIdAsync(parentId);
|
|
1585
|
+
if (!parentNode) {
|
|
1586
|
+
throw new Error(`Parent node not found: ${parentId}`);
|
|
1587
|
+
}
|
|
1588
|
+
} else {
|
|
1589
|
+
parentNode = components[0].parent || figma.currentPage;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
try {
|
|
1593
|
+
const componentSet = figma.combineAsVariants(components, parentNode);
|
|
1594
|
+
return {
|
|
1595
|
+
id: componentSet.id,
|
|
1596
|
+
name: componentSet.name,
|
|
1597
|
+
type: componentSet.type,
|
|
1598
|
+
x: componentSet.x,
|
|
1599
|
+
y: componentSet.y,
|
|
1600
|
+
width: componentSet.width,
|
|
1601
|
+
height: componentSet.height,
|
|
1602
|
+
childCount: componentSet.children.length,
|
|
1603
|
+
children: componentSet.children.map((child) => ({
|
|
1604
|
+
id: child.id,
|
|
1605
|
+
name: child.name,
|
|
1606
|
+
type: child.type,
|
|
1607
|
+
})),
|
|
1608
|
+
};
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
throw new Error(`Error creating component set: ${error.message}`);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1508
1614
|
async function exportNodeAsImage(params) {
|
|
1509
1615
|
const { nodeId, scale = 1 } = params || {};
|
|
1510
1616
|
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "Talk To Figma MCP",
|
|
3
3
|
"id": "1485687494525374295",
|
|
4
4
|
"api": "1.0.0",
|
|
5
5
|
"main": "code.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"allowedDomains": [
|
|
14
14
|
"ws://localhost:3055"
|
|
15
15
|
],
|
|
16
|
-
"reasoning": "
|
|
16
|
+
"reasoning": "Connects to a local WebSocket relay for MCP communication with AI agents",
|
|
17
17
|
"devAllowedDomains": [
|
|
18
18
|
"http://localhost:3055",
|
|
19
19
|
"ws://localhost:3055"
|
package/figma-plugin/ui.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<title>
|
|
5
|
+
<title>Talk to Figma MCP</title>
|
|
6
6
|
<style>
|
|
7
7
|
body {
|
|
8
8
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
@@ -258,7 +258,7 @@
|
|
|
258
258
|
<div class="header">
|
|
259
259
|
<div class="header-text">
|
|
260
260
|
<h1>Talk To Figma MCP Plugin</h1>
|
|
261
|
-
<p>Connect Figma to
|
|
261
|
+
<p>Connect Figma to AI agents using MCP</p>
|
|
262
262
|
</div>
|
|
263
263
|
</div>
|
|
264
264
|
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
</div>
|
|
290
290
|
|
|
291
291
|
<div id="connection-status" class="status disconnected">
|
|
292
|
-
Not connected to
|
|
292
|
+
Not connected to MCP server
|
|
293
293
|
</div>
|
|
294
294
|
|
|
295
295
|
<div id="mcp-config" class="section hidden">
|
|
@@ -357,21 +357,21 @@
|
|
|
357
357
|
<div class="section">
|
|
358
358
|
<h2>About Talk To Figma Plugin</h2>
|
|
359
359
|
<p>
|
|
360
|
-
This plugin
|
|
361
|
-
|
|
360
|
+
This plugin lets AI agents (Cursor, Claude Code) read and modify
|
|
361
|
+
Figma designs via MCP.
|
|
362
362
|
<a
|
|
363
363
|
class="link"
|
|
364
|
-
onclick="window.open(`https://github.com/
|
|
364
|
+
onclick="window.open(`https://github.com/anthropics/cursor-talk-to-figma-mcp`, '_blank')"
|
|
365
365
|
>Github</a
|
|
366
366
|
>
|
|
367
367
|
</p>
|
|
368
|
-
<p>Version:
|
|
368
|
+
<p>Version: 0.4.0</p>
|
|
369
369
|
|
|
370
370
|
<h2>How to Use</h2>
|
|
371
371
|
<ol>
|
|
372
|
-
<li>
|
|
373
|
-
<li>Connect to the
|
|
374
|
-
<li>
|
|
372
|
+
<li>Start the relay: <code>bunx @biolab/talk-to-figma --relay</code></li>
|
|
373
|
+
<li>Connect to the relay using the port number (default: 3055)</li>
|
|
374
|
+
<li>In your AI agent, call <code>join_channel</code> with the channel name shown above</li>
|
|
375
375
|
</ol>
|
|
376
376
|
</div>
|
|
377
377
|
</div>
|
|
@@ -410,12 +410,12 @@
|
|
|
410
410
|
let statusMessage =
|
|
411
411
|
message ||
|
|
412
412
|
(isConnected
|
|
413
|
-
? "Connected to
|
|
414
|
-
: "Not connected to
|
|
413
|
+
? "Connected to MCP server"
|
|
414
|
+
: "Not connected to MCP server");
|
|
415
415
|
|
|
416
416
|
// Add instructions for localhost when disconnected
|
|
417
417
|
if (!isConnected) {
|
|
418
|
-
statusMessage += `<br><br>Run this in your terminal, then connect<br><code>bunx
|
|
418
|
+
statusMessage += `<br><br>Run this in your terminal, then connect<br><code>bunx @biolab/talk-to-figma --relay</code>`;
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
connectionStatus.innerHTML = statusMessage;
|
|
@@ -486,7 +486,7 @@
|
|
|
486
486
|
{
|
|
487
487
|
pluginMessage: {
|
|
488
488
|
type: "notify",
|
|
489
|
-
message: `Connected to
|
|
489
|
+
message: `Connected to MCP server on port ${port} in channel: ${channelName}`,
|
|
490
490
|
},
|
|
491
491
|
},
|
|
492
492
|
"*"
|
|
@@ -818,7 +818,7 @@
|
|
|
818
818
|
mcpServers: {
|
|
819
819
|
TalkToFigma: {
|
|
820
820
|
command: "bunx",
|
|
821
|
-
args: ["
|
|
821
|
+
args: ["@biolab/talk-to-figma@latest"],
|
|
822
822
|
},
|
|
823
823
|
},
|
|
824
824
|
};
|