with-figma 0.1.5 → 0.2.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/figma-plugin/ui.html +83 -143
- package/mcp-server/dist/index.js +47 -78
- package/package.json +1 -1
package/figma-plugin/ui.html
CHANGED
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
height: 100vh;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/* ─── Header ─── */
|
|
17
16
|
.header {
|
|
18
|
-
padding:
|
|
17
|
+
padding: 10px 16px;
|
|
19
18
|
border-bottom: 1px solid var(--figma-color-border, #e5e5e5);
|
|
20
19
|
display: flex;
|
|
21
20
|
align-items: center;
|
|
@@ -25,13 +24,14 @@
|
|
|
25
24
|
width: 8px; height: 8px;
|
|
26
25
|
border-radius: 50%;
|
|
27
26
|
background: #ccc;
|
|
27
|
+
flex-shrink: 0;
|
|
28
28
|
}
|
|
29
29
|
.status-dot.connected { background: #18a957; }
|
|
30
30
|
.status-dot.connecting { background: #f5a623; animation: pulse 1s infinite; }
|
|
31
31
|
@keyframes pulse { 50% { opacity: 0.5; } }
|
|
32
|
-
.header-title { font-weight: 600; flex: 1; }
|
|
32
|
+
.header-title { font-weight: 600; flex: 1; font-size: 13px; }
|
|
33
|
+
.header-status { font-size: 11px; color: var(--figma-color-text-secondary, #999); }
|
|
33
34
|
|
|
34
|
-
/* ─── Selection Info ─── */
|
|
35
35
|
.selection-bar {
|
|
36
36
|
padding: 8px 16px;
|
|
37
37
|
background: var(--figma-color-bg-secondary, #f5f5f5);
|
|
@@ -44,96 +44,69 @@
|
|
|
44
44
|
color: var(--figma-color-text, #333);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/*
|
|
48
|
-
.
|
|
47
|
+
/* Activity Log */
|
|
48
|
+
.log-area {
|
|
49
49
|
flex: 1;
|
|
50
50
|
overflow-y: auto;
|
|
51
|
-
padding: 16px;
|
|
51
|
+
padding: 12px 16px;
|
|
52
52
|
display: flex;
|
|
53
53
|
flex-direction: column;
|
|
54
|
-
gap:
|
|
54
|
+
gap: 6px;
|
|
55
55
|
}
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
.log-entry {
|
|
57
|
+
display: flex;
|
|
58
|
+
gap: 8px;
|
|
59
|
+
align-items: flex-start;
|
|
60
|
+
font-size: 12px;
|
|
60
61
|
line-height: 1.5;
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
.message.user {
|
|
64
|
-
align-self: flex-end;
|
|
65
|
-
background: #0d99ff;
|
|
66
|
-
color: #fff;
|
|
67
|
-
border-bottom-right-radius: 4px;
|
|
68
|
-
}
|
|
69
|
-
.message.assistant {
|
|
70
|
-
align-self: flex-start;
|
|
71
|
-
background: var(--figma-color-bg-secondary, #f0f0f0);
|
|
72
|
-
border-bottom-left-radius: 4px;
|
|
62
|
+
padding: 4px 0;
|
|
73
63
|
}
|
|
74
|
-
.
|
|
75
|
-
|
|
64
|
+
.log-entry .log-time {
|
|
65
|
+
color: var(--figma-color-text-tertiary, #aaa);
|
|
76
66
|
font-size: 11px;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
padding: 4px;
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
font-variant-numeric: tabular-nums;
|
|
80
69
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
border-top: 1px solid var(--figma-color-border, #e5e5e5);
|
|
86
|
-
display: flex;
|
|
87
|
-
gap: 8px;
|
|
70
|
+
.log-entry .log-icon {
|
|
71
|
+
flex-shrink: 0;
|
|
72
|
+
width: 16px;
|
|
73
|
+
text-align: center;
|
|
88
74
|
}
|
|
89
|
-
.
|
|
90
|
-
flex: 1;
|
|
91
|
-
border: 1px solid var(--figma-color-border, #ddd);
|
|
92
|
-
border-radius: 8px;
|
|
93
|
-
padding: 8px 12px;
|
|
94
|
-
font-size: 13px;
|
|
95
|
-
font-family: inherit;
|
|
96
|
-
resize: none;
|
|
97
|
-
outline: none;
|
|
98
|
-
background: var(--figma-color-bg, #fff);
|
|
75
|
+
.log-entry .log-text {
|
|
99
76
|
color: var(--figma-color-text, #333);
|
|
100
|
-
|
|
101
|
-
max-height: 120px;
|
|
77
|
+
word-break: break-word;
|
|
102
78
|
}
|
|
103
|
-
.
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
79
|
+
.log-entry.status .log-text { color: var(--figma-color-text-secondary, #888); }
|
|
80
|
+
.log-entry.action .log-text { color: #0d99ff; }
|
|
81
|
+
.log-entry.success .log-text { color: #18a957; }
|
|
82
|
+
.log-entry.error .log-text { color: #f24822; }
|
|
83
|
+
|
|
84
|
+
.empty-state {
|
|
85
|
+
flex: 1;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
color: var(--figma-color-text-tertiary, #aaa);
|
|
90
|
+
font-size: 12px;
|
|
91
|
+
text-align: center;
|
|
92
|
+
padding: 40px;
|
|
93
|
+
line-height: 1.6;
|
|
114
94
|
}
|
|
115
|
-
.input-area button:hover { background: #0b85e0; }
|
|
116
|
-
.input-area button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
117
95
|
</style>
|
|
118
96
|
</head>
|
|
119
97
|
<body>
|
|
120
98
|
<div class="header">
|
|
121
99
|
<div class="status-dot" id="statusDot"></div>
|
|
122
|
-
<span class="header-title">With Figma
|
|
123
|
-
<span
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
<div class="selection-bar" id="selectionBar">
|
|
127
|
-
No selection
|
|
100
|
+
<span class="header-title">With Figma</span>
|
|
101
|
+
<span class="header-status" id="statusText">Disconnected</span>
|
|
128
102
|
</div>
|
|
129
103
|
|
|
130
|
-
<div class="
|
|
131
|
-
<div class="message system">Select an element and describe what you want to do.</div>
|
|
132
|
-
</div>
|
|
104
|
+
<div class="selection-bar" id="selectionBar">No selection</div>
|
|
133
105
|
|
|
134
|
-
<div class="
|
|
135
|
-
<
|
|
136
|
-
|
|
106
|
+
<div class="log-area" id="logArea">
|
|
107
|
+
<div class="empty-state" id="emptyState">
|
|
108
|
+
Select elements in Figma,<br>then tell your AI agent in VS Code<br>what to do with them.
|
|
109
|
+
</div>
|
|
137
110
|
</div>
|
|
138
111
|
|
|
139
112
|
<script>
|
|
@@ -144,9 +117,8 @@
|
|
|
144
117
|
let currentPage = null;
|
|
145
118
|
let reconnectTimer = null;
|
|
146
119
|
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const sendBtn = document.getElementById("sendBtn");
|
|
120
|
+
const logArea = document.getElementById("logArea");
|
|
121
|
+
const emptyState = document.getElementById("emptyState");
|
|
150
122
|
const statusDot = document.getElementById("statusDot");
|
|
151
123
|
const statusText = document.getElementById("statusText");
|
|
152
124
|
const selectionBar = document.getElementById("selectionBar");
|
|
@@ -156,19 +128,13 @@
|
|
|
156
128
|
function connect() {
|
|
157
129
|
if (ws && ws.readyState <= 1) return;
|
|
158
130
|
setStatus("connecting");
|
|
159
|
-
|
|
160
131
|
ws = new WebSocket(WS_URL);
|
|
161
132
|
|
|
162
133
|
ws.onopen = () => {
|
|
163
134
|
setStatus("connected");
|
|
164
|
-
|
|
165
|
-
// Send current selection state
|
|
135
|
+
addLog("status", "Connected to MCP server");
|
|
166
136
|
if (currentSelection.length > 0) {
|
|
167
|
-
ws.send(JSON.stringify({
|
|
168
|
-
type: "selection-update",
|
|
169
|
-
nodes: currentSelection,
|
|
170
|
-
page: currentPage,
|
|
171
|
-
}));
|
|
137
|
+
ws.send(JSON.stringify({ type: "selection-update", nodes: currentSelection, page: currentPage }));
|
|
172
138
|
}
|
|
173
139
|
};
|
|
174
140
|
|
|
@@ -176,9 +142,7 @@
|
|
|
176
142
|
try {
|
|
177
143
|
const msg = JSON.parse(event.data);
|
|
178
144
|
handleServerMessage(msg);
|
|
179
|
-
} catch (e) {
|
|
180
|
-
console.error("Failed to parse message:", e);
|
|
181
|
-
}
|
|
145
|
+
} catch (e) {}
|
|
182
146
|
};
|
|
183
147
|
|
|
184
148
|
ws.onclose = () => {
|
|
@@ -186,9 +150,7 @@
|
|
|
186
150
|
scheduleReconnect();
|
|
187
151
|
};
|
|
188
152
|
|
|
189
|
-
ws.onerror = () =>
|
|
190
|
-
ws.close();
|
|
191
|
-
};
|
|
153
|
+
ws.onerror = () => ws.close();
|
|
192
154
|
}
|
|
193
155
|
|
|
194
156
|
function scheduleReconnect() {
|
|
@@ -199,28 +161,23 @@
|
|
|
199
161
|
function setStatus(state) {
|
|
200
162
|
statusDot.className = "status-dot " + (state === "connected" ? "connected" : state === "connecting" ? "connecting" : "");
|
|
201
163
|
statusText.textContent = state === "connected" ? "Connected" : state === "connecting" ? "Connecting..." : "Disconnected";
|
|
202
|
-
sendBtn.disabled = state !== "connected";
|
|
203
164
|
}
|
|
204
165
|
|
|
205
|
-
// ─── Server
|
|
166
|
+
// ─── Server messages ──────────────────────────────────────────
|
|
206
167
|
|
|
207
168
|
function handleServerMessage(msg) {
|
|
208
169
|
switch (msg.type) {
|
|
209
|
-
case "
|
|
210
|
-
|
|
170
|
+
case "activity":
|
|
171
|
+
addLog(msg.level || "status", msg.text);
|
|
211
172
|
break;
|
|
212
|
-
|
|
213
173
|
case "figma-command":
|
|
214
|
-
// Relay command to Figma plugin sandbox
|
|
215
174
|
parent.postMessage({ pluginMessage: msg.command }, "*");
|
|
216
175
|
break;
|
|
217
|
-
|
|
218
176
|
case "request-selection":
|
|
219
177
|
parent.postMessage({ pluginMessage: { type: "get-selection" } }, "*");
|
|
220
178
|
break;
|
|
221
|
-
|
|
222
179
|
case "status":
|
|
223
|
-
|
|
180
|
+
addLog("status", msg.text);
|
|
224
181
|
break;
|
|
225
182
|
}
|
|
226
183
|
}
|
|
@@ -236,13 +193,8 @@
|
|
|
236
193
|
currentSelection = msg.nodes;
|
|
237
194
|
currentPage = msg.page;
|
|
238
195
|
updateSelectionUI();
|
|
239
|
-
// Forward to server
|
|
240
196
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
241
|
-
ws.send(JSON.stringify({
|
|
242
|
-
type: "selection-update",
|
|
243
|
-
nodes: msg.nodes,
|
|
244
|
-
page: msg.page,
|
|
245
|
-
}));
|
|
197
|
+
ws.send(JSON.stringify({ type: "selection-update", nodes: msg.nodes, page: msg.page }));
|
|
246
198
|
}
|
|
247
199
|
break;
|
|
248
200
|
|
|
@@ -252,7 +204,6 @@
|
|
|
252
204
|
}
|
|
253
205
|
break;
|
|
254
206
|
|
|
255
|
-
// Forward all result messages to server
|
|
256
207
|
case "node-data":
|
|
257
208
|
case "node-created":
|
|
258
209
|
case "node-modified":
|
|
@@ -265,57 +216,46 @@
|
|
|
265
216
|
}
|
|
266
217
|
};
|
|
267
218
|
|
|
268
|
-
// ─── UI
|
|
219
|
+
// ─── UI ───────────────────────────────────────────────────────
|
|
269
220
|
|
|
270
221
|
function updateSelectionUI() {
|
|
271
222
|
if (currentSelection.length === 0) {
|
|
272
223
|
selectionBar.innerHTML = "No selection";
|
|
273
224
|
} else if (currentSelection.length === 1) {
|
|
274
225
|
const n = currentSelection[0];
|
|
275
|
-
selectionBar.innerHTML =
|
|
226
|
+
selectionBar.innerHTML = '<span class="node-name">' + esc(n.name) + '</span> (' + n.type + ') — ' + Math.round(n.width) + '×' + Math.round(n.height);
|
|
276
227
|
} else {
|
|
277
|
-
selectionBar.innerHTML =
|
|
228
|
+
selectionBar.innerHTML = '<span class="node-name">' + currentSelection.length + ' elements</span> selected';
|
|
278
229
|
}
|
|
279
230
|
}
|
|
280
231
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
232
|
+
const ICONS = { status: "○", action: "▶", success: "✓", error: "✗" };
|
|
233
|
+
|
|
234
|
+
function addLog(level, text) {
|
|
235
|
+
if (emptyState) emptyState.remove();
|
|
236
|
+
const entry = document.createElement("div");
|
|
237
|
+
entry.className = "log-entry " + level;
|
|
288
238
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}));
|
|
302
|
-
input.value = "";
|
|
303
|
-
input.style.height = "auto";
|
|
239
|
+
const now = new Date();
|
|
240
|
+
const time = now.getHours().toString().padStart(2, "0") + ":" +
|
|
241
|
+
now.getMinutes().toString().padStart(2, "0") + ":" +
|
|
242
|
+
now.getSeconds().toString().padStart(2, "0");
|
|
243
|
+
|
|
244
|
+
entry.innerHTML =
|
|
245
|
+
'<span class="log-time">' + time + '</span>' +
|
|
246
|
+
'<span class="log-icon">' + (ICONS[level] || "○") + '</span>' +
|
|
247
|
+
'<span class="log-text">' + esc(text) + '</span>';
|
|
248
|
+
|
|
249
|
+
logArea.appendChild(entry);
|
|
250
|
+
logArea.scrollTop = logArea.scrollHeight;
|
|
304
251
|
}
|
|
305
252
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
input.addEventListener("input", () => {
|
|
314
|
-
input.style.height = "auto";
|
|
315
|
-
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
|
316
|
-
});
|
|
253
|
+
function esc(s) {
|
|
254
|
+
const d = document.createElement("div");
|
|
255
|
+
d.textContent = s;
|
|
256
|
+
return d.innerHTML;
|
|
257
|
+
}
|
|
317
258
|
|
|
318
|
-
// Start connection
|
|
319
259
|
connect();
|
|
320
260
|
</script>
|
|
321
261
|
</body>
|
package/mcp-server/dist/index.js
CHANGED
|
@@ -30120,16 +30120,21 @@ var currentPage = null;
|
|
|
30120
30120
|
var pages = [];
|
|
30121
30121
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
30122
30122
|
var requestCounter = 0;
|
|
30123
|
-
var chatQueue = [];
|
|
30124
30123
|
function genRequestId() {
|
|
30125
30124
|
return `req_${++requestCounter}_${Date.now()}`;
|
|
30126
30125
|
}
|
|
30126
|
+
function sendActivity(text, level = "status") {
|
|
30127
|
+
if (figmaSocket && figmaSocket.readyState === import_ws.WebSocket.OPEN) {
|
|
30128
|
+
figmaSocket.send(JSON.stringify({ type: "activity", text, level }));
|
|
30129
|
+
}
|
|
30130
|
+
console.error(`[with-figma] [${level}] ${text}`);
|
|
30131
|
+
}
|
|
30127
30132
|
var wss = new import_ws.WebSocketServer({ port: 3055 });
|
|
30128
30133
|
console.error("[with-figma] WebSocket server listening on ws://127.0.0.1:3055");
|
|
30129
30134
|
wss.on("connection", (socket) => {
|
|
30130
30135
|
console.error("[with-figma] Figma plugin connected");
|
|
30131
30136
|
figmaSocket = socket;
|
|
30132
|
-
|
|
30137
|
+
sendActivity("MCP server connected. Waiting for agent commands.");
|
|
30133
30138
|
socket.on("message", (raw) => {
|
|
30134
30139
|
try {
|
|
30135
30140
|
const msg = JSON.parse(raw.toString());
|
|
@@ -30152,24 +30157,6 @@ function handleFigmaMessage(msg) {
|
|
|
30152
30157
|
case "pages-list":
|
|
30153
30158
|
pages = msg.pages || [];
|
|
30154
30159
|
break;
|
|
30155
|
-
case "chat-message":
|
|
30156
|
-
currentSelection = msg.selection || currentSelection;
|
|
30157
|
-
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
|
-
}
|
|
30172
|
-
break;
|
|
30173
30160
|
// Results from Figma plugin operations
|
|
30174
30161
|
case "node-data":
|
|
30175
30162
|
case "node-created":
|
|
@@ -30206,7 +30193,7 @@ function sendToFigma(command, timeoutMs = 1e4) {
|
|
|
30206
30193
|
}
|
|
30207
30194
|
var server = new McpServer({
|
|
30208
30195
|
name: "with-figma",
|
|
30209
|
-
version: "0.
|
|
30196
|
+
version: "0.2.0"
|
|
30210
30197
|
});
|
|
30211
30198
|
server.resource("selection", "figma://selection", async (uri) => ({
|
|
30212
30199
|
contents: [
|
|
@@ -30230,24 +30217,25 @@ server.tool(
|
|
|
30230
30217
|
"get_selection",
|
|
30231
30218
|
"Get the currently selected elements in Figma. Returns node details including type, size, position, and properties.",
|
|
30232
30219
|
{},
|
|
30233
|
-
async () =>
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
|
|
30237
|
-
|
|
30238
|
-
|
|
30239
|
-
|
|
30240
|
-
|
|
30220
|
+
async () => {
|
|
30221
|
+
sendActivity("Inspecting current selection...", "action");
|
|
30222
|
+
const result = currentSelection.length > 0 ? JSON.stringify({ page: currentPage, nodes: currentSelection }, null, 2) : "No elements are currently selected in Figma.";
|
|
30223
|
+
sendActivity(
|
|
30224
|
+
currentSelection.length > 0 ? `Found ${currentSelection.length} selected element(s)` : "No elements selected",
|
|
30225
|
+
currentSelection.length > 0 ? "success" : "status"
|
|
30226
|
+
);
|
|
30227
|
+
return { content: [{ type: "text", text: result }] };
|
|
30228
|
+
}
|
|
30241
30229
|
);
|
|
30242
30230
|
server.tool(
|
|
30243
30231
|
"get_node",
|
|
30244
30232
|
"Get detailed information about a specific Figma node by ID.",
|
|
30245
30233
|
{ nodeId: external_exports3.string().describe("The Figma node ID") },
|
|
30246
30234
|
async ({ nodeId }) => {
|
|
30235
|
+
sendActivity(`Inspecting node ${nodeId}...`, "action");
|
|
30247
30236
|
const result = await sendToFigma({ type: "get-node", nodeId });
|
|
30248
|
-
|
|
30249
|
-
|
|
30250
|
-
};
|
|
30237
|
+
sendActivity(`Inspected: ${result.node?.name || nodeId}`, "success");
|
|
30238
|
+
return { content: [{ type: "text", text: JSON.stringify(result.node, null, 2) }] };
|
|
30251
30239
|
}
|
|
30252
30240
|
);
|
|
30253
30241
|
server.tool(
|
|
@@ -30261,10 +30249,10 @@ server.tool(
|
|
|
30261
30249
|
y: external_exports3.number().default(0).describe("Y position")
|
|
30262
30250
|
},
|
|
30263
30251
|
async (params) => {
|
|
30252
|
+
sendActivity(`Creating frame "${params.name}" (${params.width}\xD7${params.height})...`, "action");
|
|
30264
30253
|
const result = await sendToFigma({ type: "create-frame", ...params });
|
|
30265
|
-
|
|
30266
|
-
|
|
30267
|
-
};
|
|
30254
|
+
sendActivity(`Created frame "${params.name}"`, "success");
|
|
30255
|
+
return { content: [{ type: "text", text: `Frame created: ${JSON.stringify(result.node, null, 2)}` }] };
|
|
30268
30256
|
}
|
|
30269
30257
|
);
|
|
30270
30258
|
server.tool(
|
|
@@ -30281,15 +30269,15 @@ server.tool(
|
|
|
30281
30269
|
fillColor: external_exports3.object({ r: external_exports3.number(), g: external_exports3.number(), b: external_exports3.number(), a: external_exports3.number().default(1) }).optional().describe("Fill color (RGBA 0-1)")
|
|
30282
30270
|
},
|
|
30283
30271
|
async (params) => {
|
|
30272
|
+
sendActivity(`Creating rectangle "${params.name}" (${params.width}\xD7${params.height})...`, "action");
|
|
30284
30273
|
const command = { type: "create-rectangle", ...params };
|
|
30285
30274
|
if (params.fillColor) {
|
|
30286
30275
|
command.fills = [{ type: "SOLID", color: params.fillColor }];
|
|
30287
30276
|
delete command.fillColor;
|
|
30288
30277
|
}
|
|
30289
30278
|
const result = await sendToFigma(command);
|
|
30290
|
-
|
|
30291
|
-
|
|
30292
|
-
};
|
|
30279
|
+
sendActivity(`Created rectangle "${params.name}"`, "success");
|
|
30280
|
+
return { content: [{ type: "text", text: `Rectangle created: ${JSON.stringify(result.node, null, 2)}` }] };
|
|
30293
30281
|
}
|
|
30294
30282
|
);
|
|
30295
30283
|
server.tool(
|
|
@@ -30305,15 +30293,15 @@ server.tool(
|
|
|
30305
30293
|
fillColor: external_exports3.object({ r: external_exports3.number(), g: external_exports3.number(), b: external_exports3.number(), a: external_exports3.number().default(1) }).optional().describe("Text color (RGBA 0-1)")
|
|
30306
30294
|
},
|
|
30307
30295
|
async (params) => {
|
|
30296
|
+
sendActivity(`Creating text "${params.characters.slice(0, 30)}${params.characters.length > 30 ? "..." : ""}"...`, "action");
|
|
30308
30297
|
const command = { type: "create-text", ...params };
|
|
30309
30298
|
if (params.fillColor) {
|
|
30310
30299
|
command.fills = [{ type: "SOLID", color: params.fillColor }];
|
|
30311
30300
|
delete command.fillColor;
|
|
30312
30301
|
}
|
|
30313
30302
|
const result = await sendToFigma(command);
|
|
30314
|
-
|
|
30315
|
-
|
|
30316
|
-
};
|
|
30303
|
+
sendActivity(`Created text "${params.name}"`, "success");
|
|
30304
|
+
return { content: [{ type: "text", text: `Text created: ${JSON.stringify(result.node, null, 2)}` }] };
|
|
30317
30305
|
}
|
|
30318
30306
|
);
|
|
30319
30307
|
server.tool(
|
|
@@ -30324,10 +30312,11 @@ server.tool(
|
|
|
30324
30312
|
properties: external_exports3.record(external_exports3.unknown()).describe("Object of properties to set (e.g. { x: 10, name: 'New Name', visible: false })")
|
|
30325
30313
|
},
|
|
30326
30314
|
async ({ nodeId, properties }) => {
|
|
30315
|
+
const propKeys = Object.keys(properties).join(", ");
|
|
30316
|
+
sendActivity(`Modifying node ${nodeId} (${propKeys})...`, "action");
|
|
30327
30317
|
const result = await sendToFigma({ type: "modify-node", nodeId, properties });
|
|
30328
|
-
|
|
30329
|
-
|
|
30330
|
-
};
|
|
30318
|
+
sendActivity(`Modified: ${result.node?.name || nodeId}`, "success");
|
|
30319
|
+
return { content: [{ type: "text", text: `Node modified: ${JSON.stringify(result.node, null, 2)}` }] };
|
|
30331
30320
|
}
|
|
30332
30321
|
);
|
|
30333
30322
|
server.tool(
|
|
@@ -30335,10 +30324,10 @@ server.tool(
|
|
|
30335
30324
|
"Delete a node from the Figma document.",
|
|
30336
30325
|
{ nodeId: external_exports3.string().describe("The node ID to delete") },
|
|
30337
30326
|
async ({ nodeId }) => {
|
|
30327
|
+
sendActivity(`Deleting node ${nodeId}...`, "action");
|
|
30338
30328
|
const result = await sendToFigma({ type: "delete-node", nodeId });
|
|
30339
|
-
|
|
30340
|
-
|
|
30341
|
-
};
|
|
30329
|
+
sendActivity(`Deleted node ${result.nodeId}`, "success");
|
|
30330
|
+
return { content: [{ type: "text", text: `Deleted node: ${result.nodeId}` }] };
|
|
30342
30331
|
}
|
|
30343
30332
|
);
|
|
30344
30333
|
server.tool(
|
|
@@ -30350,7 +30339,9 @@ server.tool(
|
|
|
30350
30339
|
scale: external_exports3.number().default(2).describe("Export scale factor")
|
|
30351
30340
|
},
|
|
30352
30341
|
async ({ nodeId, format, scale }) => {
|
|
30342
|
+
sendActivity(`Exporting node ${nodeId} as ${format}...`, "action");
|
|
30353
30343
|
const result = await sendToFigma({ type: "export-node", nodeId, format, scale });
|
|
30344
|
+
sendActivity(`Exported as ${format}`, "success");
|
|
30354
30345
|
if (format === "SVG") {
|
|
30355
30346
|
const text = new TextDecoder().decode(new Uint8Array(result.data));
|
|
30356
30347
|
return { content: [{ type: "text", text }] };
|
|
@@ -30368,37 +30359,15 @@ server.tool(
|
|
|
30368
30359
|
}
|
|
30369
30360
|
);
|
|
30370
30361
|
server.tool(
|
|
30371
|
-
"
|
|
30372
|
-
"
|
|
30373
|
-
{
|
|
30374
|
-
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30378
|
-
|
|
30379
|
-
}
|
|
30380
|
-
const messages = [...chatQueue];
|
|
30381
|
-
chatQueue.length = 0;
|
|
30382
|
-
return {
|
|
30383
|
-
content: [
|
|
30384
|
-
{
|
|
30385
|
-
type: "text",
|
|
30386
|
-
text: JSON.stringify(messages, null, 2)
|
|
30387
|
-
}
|
|
30388
|
-
]
|
|
30389
|
-
};
|
|
30390
|
-
}
|
|
30391
|
-
);
|
|
30392
|
-
server.tool(
|
|
30393
|
-
"send_chat_message",
|
|
30394
|
-
"Send a message back to the Figma plugin chat UI to communicate with the user.",
|
|
30395
|
-
{ text: external_exports3.string().describe("Message text to display in chat") },
|
|
30396
|
-
async ({ text }) => {
|
|
30397
|
-
if (figmaSocket && figmaSocket.readyState === import_ws.WebSocket.OPEN) {
|
|
30398
|
-
figmaSocket.send(JSON.stringify({ type: "chat-response", text }));
|
|
30399
|
-
return { content: [{ type: "text", text: `Message sent to Figma chat: "${text}"` }] };
|
|
30400
|
-
}
|
|
30401
|
-
return { content: [{ type: "text", text: "Error: Figma plugin is not connected" }] };
|
|
30362
|
+
"send_activity",
|
|
30363
|
+
"Send an activity/status message to the Figma plugin log panel.",
|
|
30364
|
+
{
|
|
30365
|
+
text: external_exports3.string().describe("Activity message to display"),
|
|
30366
|
+
level: external_exports3.enum(["status", "action", "success", "error"]).default("status").describe("Log level")
|
|
30367
|
+
},
|
|
30368
|
+
async ({ text, level }) => {
|
|
30369
|
+
sendActivity(text, level);
|
|
30370
|
+
return { content: [{ type: "text", text: `Activity logged: "${text}"` }] };
|
|
30402
30371
|
}
|
|
30403
30372
|
);
|
|
30404
30373
|
async function main() {
|