@autobest-ui/agent 1.0.0 → 1.0.1
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 +69 -10
- package/mcp/figma-mcp-bridge/LICENSE +21 -0
- package/mcp/figma-mcp-bridge/README.md +25 -0
- package/mcp/figma-mcp-bridge/config.toml.example +10 -0
- package/mcp/figma-mcp-bridge/index.test.js +47 -0
- package/mcp/figma-mcp-bridge/package.json +16 -0
- package/mcp/figma-mcp-bridge/skills/figma-bridge/SKILL.md +98 -0
- package/mcp/figma-mcp-bridge/src/index.js +68 -0
- package/mcp/figma-mcp-bridge/src/server.js +159 -0
- package/mcp/figma-mcp-bridge/src/tools/context.js +75 -0
- package/mcp/figma-mcp-bridge/src/tools/index.js +1727 -0
- package/mcp/figma-mcp-bridge/src/tools/mutations.js +4423 -0
- package/mcp/figma-mcp-bridge/src/tools/nodes.js +78 -0
- package/mcp/figma-mcp-bridge/src/tools/pages.js +55 -0
- package/mcp/figma-mcp-bridge/src/websocket.js +255 -0
- package/mcp/rag-mcp-bridge/README.md +24 -12
- package/package.json +9 -4
- package/plugins/figma-plugin/LICENSE +21 -0
- package/plugins/figma-plugin/README.md +25 -0
- package/plugins/figma-plugin/code.js +6608 -0
- package/plugins/figma-plugin/manifest.json +32 -0
- package/plugins/figma-plugin/scripts/setup.mjs +72 -0
- package/plugins/figma-plugin/scripts/setup.test.mjs +45 -0
- package/plugins/figma-plugin/ui.html +235 -0
|
@@ -0,0 +1,4423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation tools - tools that modify the Figma document
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { Buffer } from 'node:buffer';
|
|
9
|
+
|
|
10
|
+
// File extension per export format, for the default output path
|
|
11
|
+
const EXPORT_EXTENSIONS = { PNG: 'png', SVG: 'svg', JPG: 'jpg', PDF: 'pdf' };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build the default on-disk destination for an export.
|
|
15
|
+
* os.tmpdir()/figma-mcp-bridge/<sanitized-node-id>-<timestamp>.<ext>
|
|
16
|
+
*/
|
|
17
|
+
function defaultExportPath(nodeId, format) {
|
|
18
|
+
const ext = EXPORT_EXTENSIONS[format] || 'bin';
|
|
19
|
+
const safeId = String(nodeId).replace(/[^A-Za-z0-9._-]+/g, '-');
|
|
20
|
+
return path.join(os.tmpdir(), 'figma-mcp-bridge', `${safeId}-${Date.now()}.${ext}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Set fills on a node
|
|
25
|
+
*/
|
|
26
|
+
export async function handleSetFills(bridge, args) {
|
|
27
|
+
if (!bridge.isConnected()) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: JSON.stringify({
|
|
32
|
+
error: {
|
|
33
|
+
code: 'NOT_CONNECTED',
|
|
34
|
+
message: 'Figma plugin is not connected.'
|
|
35
|
+
}
|
|
36
|
+
}, null, 2)
|
|
37
|
+
}],
|
|
38
|
+
isError: true
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { nodeId, fills } = args;
|
|
43
|
+
|
|
44
|
+
if (!nodeId) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
error: {
|
|
50
|
+
code: 'INVALID_PARAMS',
|
|
51
|
+
message: 'nodeId is required'
|
|
52
|
+
}
|
|
53
|
+
}, null, 2)
|
|
54
|
+
}],
|
|
55
|
+
isError: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const result = await bridge.sendCommand('set_fills', { nodeId, fills });
|
|
61
|
+
return {
|
|
62
|
+
content: [{
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: JSON.stringify(result, null, 2)
|
|
65
|
+
}]
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: 'text',
|
|
71
|
+
text: JSON.stringify({
|
|
72
|
+
error: {
|
|
73
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
74
|
+
message: error.message
|
|
75
|
+
}
|
|
76
|
+
}, null, 2)
|
|
77
|
+
}],
|
|
78
|
+
isError: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Set strokes (colors and/or uniform + per-side weights) on a node
|
|
85
|
+
*/
|
|
86
|
+
export async function handleSetStrokes(bridge, args) {
|
|
87
|
+
if (!bridge.isConnected()) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{
|
|
90
|
+
type: 'text',
|
|
91
|
+
text: JSON.stringify({
|
|
92
|
+
error: {
|
|
93
|
+
code: 'NOT_CONNECTED',
|
|
94
|
+
message: 'Figma plugin is not connected.'
|
|
95
|
+
}
|
|
96
|
+
}, null, 2)
|
|
97
|
+
}],
|
|
98
|
+
isError: true
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { nodeId } = args;
|
|
103
|
+
|
|
104
|
+
if (!nodeId) {
|
|
105
|
+
return {
|
|
106
|
+
content: [{
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: JSON.stringify({
|
|
109
|
+
error: {
|
|
110
|
+
code: 'INVALID_PARAMS',
|
|
111
|
+
message: 'nodeId is required'
|
|
112
|
+
}
|
|
113
|
+
}, null, 2)
|
|
114
|
+
}],
|
|
115
|
+
isError: true
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Pass args through so strokes, strokeWeight and the four per-side weights all reach the plugin
|
|
121
|
+
const result = await bridge.sendCommand('set_strokes', args);
|
|
122
|
+
return {
|
|
123
|
+
content: [{
|
|
124
|
+
type: 'text',
|
|
125
|
+
text: JSON.stringify(result, null, 2)
|
|
126
|
+
}]
|
|
127
|
+
};
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
content: [{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: JSON.stringify({
|
|
133
|
+
error: {
|
|
134
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
135
|
+
message: error.message
|
|
136
|
+
}
|
|
137
|
+
}, null, 2)
|
|
138
|
+
}],
|
|
139
|
+
isError: true
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a rectangle
|
|
146
|
+
*/
|
|
147
|
+
export async function handleCreateRectangle(bridge, args) {
|
|
148
|
+
if (!bridge.isConnected()) {
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: 'text',
|
|
152
|
+
text: JSON.stringify({
|
|
153
|
+
error: {
|
|
154
|
+
code: 'NOT_CONNECTED',
|
|
155
|
+
message: 'Figma plugin is not connected.'
|
|
156
|
+
}
|
|
157
|
+
}, null, 2)
|
|
158
|
+
}],
|
|
159
|
+
isError: true
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const result = await bridge.sendCommand('create_rectangle', args);
|
|
165
|
+
return {
|
|
166
|
+
content: [{
|
|
167
|
+
type: 'text',
|
|
168
|
+
text: JSON.stringify(result, null, 2)
|
|
169
|
+
}]
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return {
|
|
173
|
+
content: [{
|
|
174
|
+
type: 'text',
|
|
175
|
+
text: JSON.stringify({
|
|
176
|
+
error: {
|
|
177
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
178
|
+
message: error.message
|
|
179
|
+
}
|
|
180
|
+
}, null, 2)
|
|
181
|
+
}],
|
|
182
|
+
isError: true
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Set text content
|
|
189
|
+
*/
|
|
190
|
+
export async function handleSetText(bridge, args) {
|
|
191
|
+
if (!bridge.isConnected()) {
|
|
192
|
+
return {
|
|
193
|
+
content: [{
|
|
194
|
+
type: 'text',
|
|
195
|
+
text: JSON.stringify({
|
|
196
|
+
error: {
|
|
197
|
+
code: 'NOT_CONNECTED',
|
|
198
|
+
message: 'Figma plugin is not connected.'
|
|
199
|
+
}
|
|
200
|
+
}, null, 2)
|
|
201
|
+
}],
|
|
202
|
+
isError: true
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const { nodeId, text } = args;
|
|
207
|
+
|
|
208
|
+
if (!nodeId || text === undefined) {
|
|
209
|
+
return {
|
|
210
|
+
content: [{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: JSON.stringify({
|
|
213
|
+
error: {
|
|
214
|
+
code: 'INVALID_PARAMS',
|
|
215
|
+
message: 'nodeId and text are required'
|
|
216
|
+
}
|
|
217
|
+
}, null, 2)
|
|
218
|
+
}],
|
|
219
|
+
isError: true
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const result = await bridge.sendCommand('set_text', { nodeId, text });
|
|
225
|
+
return {
|
|
226
|
+
content: [{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: JSON.stringify(result, null, 2)
|
|
229
|
+
}]
|
|
230
|
+
};
|
|
231
|
+
} catch (error) {
|
|
232
|
+
return {
|
|
233
|
+
content: [{
|
|
234
|
+
type: 'text',
|
|
235
|
+
text: JSON.stringify({
|
|
236
|
+
error: {
|
|
237
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
238
|
+
message: error.message
|
|
239
|
+
}
|
|
240
|
+
}, null, 2)
|
|
241
|
+
}],
|
|
242
|
+
isError: true
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Clone nodes
|
|
249
|
+
*/
|
|
250
|
+
export async function handleCloneNodes(bridge, args) {
|
|
251
|
+
if (!bridge.isConnected()) {
|
|
252
|
+
return {
|
|
253
|
+
content: [{
|
|
254
|
+
type: 'text',
|
|
255
|
+
text: JSON.stringify({
|
|
256
|
+
error: {
|
|
257
|
+
code: 'NOT_CONNECTED',
|
|
258
|
+
message: 'Figma plugin is not connected.'
|
|
259
|
+
}
|
|
260
|
+
}, null, 2)
|
|
261
|
+
}],
|
|
262
|
+
isError: true
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { nodeIds, parentId, offset } = args;
|
|
267
|
+
|
|
268
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
269
|
+
return {
|
|
270
|
+
content: [{
|
|
271
|
+
type: 'text',
|
|
272
|
+
text: JSON.stringify({
|
|
273
|
+
error: {
|
|
274
|
+
code: 'INVALID_PARAMS',
|
|
275
|
+
message: 'nodeIds must be a non-empty array'
|
|
276
|
+
}
|
|
277
|
+
}, null, 2)
|
|
278
|
+
}],
|
|
279
|
+
isError: true
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const result = await bridge.sendCommand('clone_nodes', { nodeIds, parentId, offset });
|
|
285
|
+
return {
|
|
286
|
+
content: [{
|
|
287
|
+
type: 'text',
|
|
288
|
+
text: JSON.stringify(result, null, 2)
|
|
289
|
+
}]
|
|
290
|
+
};
|
|
291
|
+
} catch (error) {
|
|
292
|
+
return {
|
|
293
|
+
content: [{
|
|
294
|
+
type: 'text',
|
|
295
|
+
text: JSON.stringify({
|
|
296
|
+
error: {
|
|
297
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
298
|
+
message: error.message
|
|
299
|
+
}
|
|
300
|
+
}, null, 2)
|
|
301
|
+
}],
|
|
302
|
+
isError: true
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Delete nodes
|
|
309
|
+
*/
|
|
310
|
+
export async function handleDeleteNodes(bridge, args) {
|
|
311
|
+
if (!bridge.isConnected()) {
|
|
312
|
+
return {
|
|
313
|
+
content: [{
|
|
314
|
+
type: 'text',
|
|
315
|
+
text: JSON.stringify({
|
|
316
|
+
error: {
|
|
317
|
+
code: 'NOT_CONNECTED',
|
|
318
|
+
message: 'Figma plugin is not connected.'
|
|
319
|
+
}
|
|
320
|
+
}, null, 2)
|
|
321
|
+
}],
|
|
322
|
+
isError: true
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const { nodeIds } = args;
|
|
327
|
+
|
|
328
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
329
|
+
return {
|
|
330
|
+
content: [{
|
|
331
|
+
type: 'text',
|
|
332
|
+
text: JSON.stringify({
|
|
333
|
+
error: {
|
|
334
|
+
code: 'INVALID_PARAMS',
|
|
335
|
+
message: 'nodeIds must be a non-empty array'
|
|
336
|
+
}
|
|
337
|
+
}, null, 2)
|
|
338
|
+
}],
|
|
339
|
+
isError: true
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const result = await bridge.sendCommand('delete_nodes', { nodeIds });
|
|
345
|
+
return {
|
|
346
|
+
content: [{
|
|
347
|
+
type: 'text',
|
|
348
|
+
text: JSON.stringify(result, null, 2)
|
|
349
|
+
}]
|
|
350
|
+
};
|
|
351
|
+
} catch (error) {
|
|
352
|
+
return {
|
|
353
|
+
content: [{
|
|
354
|
+
type: 'text',
|
|
355
|
+
text: JSON.stringify({
|
|
356
|
+
error: {
|
|
357
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
358
|
+
message: error.message
|
|
359
|
+
}
|
|
360
|
+
}, null, 2)
|
|
361
|
+
}],
|
|
362
|
+
isError: true
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Move nodes
|
|
369
|
+
*/
|
|
370
|
+
export async function handleMoveNodes(bridge, args) {
|
|
371
|
+
if (!bridge.isConnected()) {
|
|
372
|
+
return {
|
|
373
|
+
content: [{
|
|
374
|
+
type: 'text',
|
|
375
|
+
text: JSON.stringify({
|
|
376
|
+
error: {
|
|
377
|
+
code: 'NOT_CONNECTED',
|
|
378
|
+
message: 'Figma plugin is not connected.'
|
|
379
|
+
}
|
|
380
|
+
}, null, 2)
|
|
381
|
+
}],
|
|
382
|
+
isError: true
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const { nodeIds, x, y, relative } = args;
|
|
387
|
+
|
|
388
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
389
|
+
return {
|
|
390
|
+
content: [{
|
|
391
|
+
type: 'text',
|
|
392
|
+
text: JSON.stringify({
|
|
393
|
+
error: {
|
|
394
|
+
code: 'INVALID_PARAMS',
|
|
395
|
+
message: 'nodeIds must be a non-empty array'
|
|
396
|
+
}
|
|
397
|
+
}, null, 2)
|
|
398
|
+
}],
|
|
399
|
+
isError: true
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (x === undefined && y === undefined) {
|
|
404
|
+
return {
|
|
405
|
+
content: [{
|
|
406
|
+
type: 'text',
|
|
407
|
+
text: JSON.stringify({
|
|
408
|
+
error: {
|
|
409
|
+
code: 'INVALID_PARAMS',
|
|
410
|
+
message: 'At least one of x or y must be provided'
|
|
411
|
+
}
|
|
412
|
+
}, null, 2)
|
|
413
|
+
}],
|
|
414
|
+
isError: true
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const result = await bridge.sendCommand('move_nodes', { nodeIds, x, y, relative });
|
|
420
|
+
return {
|
|
421
|
+
content: [{
|
|
422
|
+
type: 'text',
|
|
423
|
+
text: JSON.stringify(result, null, 2)
|
|
424
|
+
}]
|
|
425
|
+
};
|
|
426
|
+
} catch (error) {
|
|
427
|
+
return {
|
|
428
|
+
content: [{
|
|
429
|
+
type: 'text',
|
|
430
|
+
text: JSON.stringify({
|
|
431
|
+
error: {
|
|
432
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
433
|
+
message: error.message
|
|
434
|
+
}
|
|
435
|
+
}, null, 2)
|
|
436
|
+
}],
|
|
437
|
+
isError: true
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Resize nodes
|
|
444
|
+
*/
|
|
445
|
+
export async function handleResizeNodes(bridge, args) {
|
|
446
|
+
if (!bridge.isConnected()) {
|
|
447
|
+
return {
|
|
448
|
+
content: [{
|
|
449
|
+
type: 'text',
|
|
450
|
+
text: JSON.stringify({
|
|
451
|
+
error: {
|
|
452
|
+
code: 'NOT_CONNECTED',
|
|
453
|
+
message: 'Figma plugin is not connected.'
|
|
454
|
+
}
|
|
455
|
+
}, null, 2)
|
|
456
|
+
}],
|
|
457
|
+
isError: true
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const { nodeIds, width, height } = args;
|
|
462
|
+
|
|
463
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
464
|
+
return {
|
|
465
|
+
content: [{
|
|
466
|
+
type: 'text',
|
|
467
|
+
text: JSON.stringify({
|
|
468
|
+
error: {
|
|
469
|
+
code: 'INVALID_PARAMS',
|
|
470
|
+
message: 'nodeIds must be a non-empty array'
|
|
471
|
+
}
|
|
472
|
+
}, null, 2)
|
|
473
|
+
}],
|
|
474
|
+
isError: true
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (width === undefined && height === undefined) {
|
|
479
|
+
return {
|
|
480
|
+
content: [{
|
|
481
|
+
type: 'text',
|
|
482
|
+
text: JSON.stringify({
|
|
483
|
+
error: {
|
|
484
|
+
code: 'INVALID_PARAMS',
|
|
485
|
+
message: 'At least one of width or height must be provided'
|
|
486
|
+
}
|
|
487
|
+
}, null, 2)
|
|
488
|
+
}],
|
|
489
|
+
isError: true
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const result = await bridge.sendCommand('resize_nodes', { nodeIds, width, height });
|
|
495
|
+
return {
|
|
496
|
+
content: [{
|
|
497
|
+
type: 'text',
|
|
498
|
+
text: JSON.stringify(result, null, 2)
|
|
499
|
+
}],
|
|
500
|
+
// The plugin verifies width/height by readback. A resize that silently did
|
|
501
|
+
// nothing comes back as success: false with an errors array — surface that
|
|
502
|
+
// as an error instead of letting the caller record work that never happened.
|
|
503
|
+
isError: !!(result && result.success === false)
|
|
504
|
+
};
|
|
505
|
+
} catch (error) {
|
|
506
|
+
return {
|
|
507
|
+
content: [{
|
|
508
|
+
type: 'text',
|
|
509
|
+
text: JSON.stringify({
|
|
510
|
+
error: {
|
|
511
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
512
|
+
message: error.message
|
|
513
|
+
}
|
|
514
|
+
}, null, 2)
|
|
515
|
+
}],
|
|
516
|
+
isError: true
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Set opacity
|
|
523
|
+
*/
|
|
524
|
+
export async function handleSetOpacity(bridge, args) {
|
|
525
|
+
if (!bridge.isConnected()) {
|
|
526
|
+
return {
|
|
527
|
+
content: [{
|
|
528
|
+
type: 'text',
|
|
529
|
+
text: JSON.stringify({
|
|
530
|
+
error: {
|
|
531
|
+
code: 'NOT_CONNECTED',
|
|
532
|
+
message: 'Figma plugin is not connected.'
|
|
533
|
+
}
|
|
534
|
+
}, null, 2)
|
|
535
|
+
}],
|
|
536
|
+
isError: true
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const { nodeId, opacity } = args;
|
|
541
|
+
|
|
542
|
+
if (!nodeId) {
|
|
543
|
+
return {
|
|
544
|
+
content: [{
|
|
545
|
+
type: 'text',
|
|
546
|
+
text: JSON.stringify({
|
|
547
|
+
error: {
|
|
548
|
+
code: 'INVALID_PARAMS',
|
|
549
|
+
message: 'nodeId is required'
|
|
550
|
+
}
|
|
551
|
+
}, null, 2)
|
|
552
|
+
}],
|
|
553
|
+
isError: true
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (opacity === undefined || opacity < 0 || opacity > 1) {
|
|
558
|
+
return {
|
|
559
|
+
content: [{
|
|
560
|
+
type: 'text',
|
|
561
|
+
text: JSON.stringify({
|
|
562
|
+
error: {
|
|
563
|
+
code: 'INVALID_PARAMS',
|
|
564
|
+
message: 'opacity must be a number between 0 and 1'
|
|
565
|
+
}
|
|
566
|
+
}, null, 2)
|
|
567
|
+
}],
|
|
568
|
+
isError: true
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
const result = await bridge.sendCommand('set_opacity', { nodeId, opacity });
|
|
574
|
+
return {
|
|
575
|
+
content: [{
|
|
576
|
+
type: 'text',
|
|
577
|
+
text: JSON.stringify(result, null, 2)
|
|
578
|
+
}]
|
|
579
|
+
};
|
|
580
|
+
} catch (error) {
|
|
581
|
+
return {
|
|
582
|
+
content: [{
|
|
583
|
+
type: 'text',
|
|
584
|
+
text: JSON.stringify({
|
|
585
|
+
error: {
|
|
586
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
587
|
+
message: error.message
|
|
588
|
+
}
|
|
589
|
+
}, null, 2)
|
|
590
|
+
}],
|
|
591
|
+
isError: true
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Set corner radius
|
|
598
|
+
*/
|
|
599
|
+
export async function handleSetCornerRadius(bridge, args) {
|
|
600
|
+
if (!bridge.isConnected()) {
|
|
601
|
+
return {
|
|
602
|
+
content: [{
|
|
603
|
+
type: 'text',
|
|
604
|
+
text: JSON.stringify({
|
|
605
|
+
error: {
|
|
606
|
+
code: 'NOT_CONNECTED',
|
|
607
|
+
message: 'Figma plugin is not connected.'
|
|
608
|
+
}
|
|
609
|
+
}, null, 2)
|
|
610
|
+
}],
|
|
611
|
+
isError: true
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const { nodeId, radius, topLeft, topRight, bottomLeft, bottomRight } = args;
|
|
616
|
+
|
|
617
|
+
if (!nodeId) {
|
|
618
|
+
return {
|
|
619
|
+
content: [{
|
|
620
|
+
type: 'text',
|
|
621
|
+
text: JSON.stringify({
|
|
622
|
+
error: {
|
|
623
|
+
code: 'INVALID_PARAMS',
|
|
624
|
+
message: 'nodeId is required'
|
|
625
|
+
}
|
|
626
|
+
}, null, 2)
|
|
627
|
+
}],
|
|
628
|
+
isError: true
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (radius === undefined && topLeft === undefined && topRight === undefined && bottomLeft === undefined && bottomRight === undefined) {
|
|
633
|
+
return {
|
|
634
|
+
content: [{
|
|
635
|
+
type: 'text',
|
|
636
|
+
text: JSON.stringify({
|
|
637
|
+
error: {
|
|
638
|
+
code: 'INVALID_PARAMS',
|
|
639
|
+
message: 'At least one radius value must be provided'
|
|
640
|
+
}
|
|
641
|
+
}, null, 2)
|
|
642
|
+
}],
|
|
643
|
+
isError: true
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
const result = await bridge.sendCommand('set_corner_radius', { nodeId, radius, topLeft, topRight, bottomLeft, bottomRight });
|
|
649
|
+
return {
|
|
650
|
+
content: [{
|
|
651
|
+
type: 'text',
|
|
652
|
+
text: JSON.stringify(result, null, 2)
|
|
653
|
+
}]
|
|
654
|
+
};
|
|
655
|
+
} catch (error) {
|
|
656
|
+
return {
|
|
657
|
+
content: [{
|
|
658
|
+
type: 'text',
|
|
659
|
+
text: JSON.stringify({
|
|
660
|
+
error: {
|
|
661
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
662
|
+
message: error.message
|
|
663
|
+
}
|
|
664
|
+
}, null, 2)
|
|
665
|
+
}],
|
|
666
|
+
isError: true
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Group nodes
|
|
673
|
+
*/
|
|
674
|
+
export async function handleGroupNodes(bridge, args) {
|
|
675
|
+
if (!bridge.isConnected()) {
|
|
676
|
+
return {
|
|
677
|
+
content: [{
|
|
678
|
+
type: 'text',
|
|
679
|
+
text: JSON.stringify({
|
|
680
|
+
error: {
|
|
681
|
+
code: 'NOT_CONNECTED',
|
|
682
|
+
message: 'Figma plugin is not connected.'
|
|
683
|
+
}
|
|
684
|
+
}, null, 2)
|
|
685
|
+
}],
|
|
686
|
+
isError: true
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const { nodeIds, name } = args;
|
|
691
|
+
|
|
692
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
693
|
+
return {
|
|
694
|
+
content: [{
|
|
695
|
+
type: 'text',
|
|
696
|
+
text: JSON.stringify({
|
|
697
|
+
error: {
|
|
698
|
+
code: 'INVALID_PARAMS',
|
|
699
|
+
message: 'nodeIds must be a non-empty array'
|
|
700
|
+
}
|
|
701
|
+
}, null, 2)
|
|
702
|
+
}],
|
|
703
|
+
isError: true
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
const result = await bridge.sendCommand('group_nodes', { nodeIds, name });
|
|
709
|
+
return {
|
|
710
|
+
content: [{
|
|
711
|
+
type: 'text',
|
|
712
|
+
text: JSON.stringify(result, null, 2)
|
|
713
|
+
}]
|
|
714
|
+
};
|
|
715
|
+
} catch (error) {
|
|
716
|
+
return {
|
|
717
|
+
content: [{
|
|
718
|
+
type: 'text',
|
|
719
|
+
text: JSON.stringify({
|
|
720
|
+
error: {
|
|
721
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
722
|
+
message: error.message
|
|
723
|
+
}
|
|
724
|
+
}, null, 2)
|
|
725
|
+
}],
|
|
726
|
+
isError: true
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Ungroup nodes
|
|
733
|
+
*/
|
|
734
|
+
export async function handleUngroupNodes(bridge, args) {
|
|
735
|
+
if (!bridge.isConnected()) {
|
|
736
|
+
return {
|
|
737
|
+
content: [{
|
|
738
|
+
type: 'text',
|
|
739
|
+
text: JSON.stringify({
|
|
740
|
+
error: {
|
|
741
|
+
code: 'NOT_CONNECTED',
|
|
742
|
+
message: 'Figma plugin is not connected.'
|
|
743
|
+
}
|
|
744
|
+
}, null, 2)
|
|
745
|
+
}],
|
|
746
|
+
isError: true
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const { nodeIds } = args;
|
|
751
|
+
|
|
752
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
753
|
+
return {
|
|
754
|
+
content: [{
|
|
755
|
+
type: 'text',
|
|
756
|
+
text: JSON.stringify({
|
|
757
|
+
error: {
|
|
758
|
+
code: 'INVALID_PARAMS',
|
|
759
|
+
message: 'nodeIds must be a non-empty array'
|
|
760
|
+
}
|
|
761
|
+
}, null, 2)
|
|
762
|
+
}],
|
|
763
|
+
isError: true
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
try {
|
|
768
|
+
const result = await bridge.sendCommand('ungroup_nodes', { nodeIds });
|
|
769
|
+
return {
|
|
770
|
+
content: [{
|
|
771
|
+
type: 'text',
|
|
772
|
+
text: JSON.stringify(result, null, 2)
|
|
773
|
+
}]
|
|
774
|
+
};
|
|
775
|
+
} catch (error) {
|
|
776
|
+
return {
|
|
777
|
+
content: [{
|
|
778
|
+
type: 'text',
|
|
779
|
+
text: JSON.stringify({
|
|
780
|
+
error: {
|
|
781
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
782
|
+
message: error.message
|
|
783
|
+
}
|
|
784
|
+
}, null, 2)
|
|
785
|
+
}],
|
|
786
|
+
isError: true
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Create a frame
|
|
793
|
+
*/
|
|
794
|
+
export async function handleCreateFrame(bridge, args) {
|
|
795
|
+
if (!bridge.isConnected()) {
|
|
796
|
+
return {
|
|
797
|
+
content: [{
|
|
798
|
+
type: 'text',
|
|
799
|
+
text: JSON.stringify({
|
|
800
|
+
error: {
|
|
801
|
+
code: 'NOT_CONNECTED',
|
|
802
|
+
message: 'Figma plugin is not connected.'
|
|
803
|
+
}
|
|
804
|
+
}, null, 2)
|
|
805
|
+
}],
|
|
806
|
+
isError: true
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
try {
|
|
811
|
+
const result = await bridge.sendCommand('create_frame', args);
|
|
812
|
+
return {
|
|
813
|
+
content: [{
|
|
814
|
+
type: 'text',
|
|
815
|
+
text: JSON.stringify(result, null, 2)
|
|
816
|
+
}]
|
|
817
|
+
};
|
|
818
|
+
} catch (error) {
|
|
819
|
+
return {
|
|
820
|
+
content: [{
|
|
821
|
+
type: 'text',
|
|
822
|
+
text: JSON.stringify({
|
|
823
|
+
error: {
|
|
824
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
825
|
+
message: error.message
|
|
826
|
+
}
|
|
827
|
+
}, null, 2)
|
|
828
|
+
}],
|
|
829
|
+
isError: true
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Create a text node
|
|
836
|
+
*/
|
|
837
|
+
export async function handleCreateText(bridge, args) {
|
|
838
|
+
if (!bridge.isConnected()) {
|
|
839
|
+
return {
|
|
840
|
+
content: [{
|
|
841
|
+
type: 'text',
|
|
842
|
+
text: JSON.stringify({
|
|
843
|
+
error: {
|
|
844
|
+
code: 'NOT_CONNECTED',
|
|
845
|
+
message: 'Figma plugin is not connected.'
|
|
846
|
+
}
|
|
847
|
+
}, null, 2)
|
|
848
|
+
}],
|
|
849
|
+
isError: true
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
try {
|
|
854
|
+
const result = await bridge.sendCommand('create_text', args);
|
|
855
|
+
return {
|
|
856
|
+
content: [{
|
|
857
|
+
type: 'text',
|
|
858
|
+
text: JSON.stringify(result, null, 2)
|
|
859
|
+
}]
|
|
860
|
+
};
|
|
861
|
+
} catch (error) {
|
|
862
|
+
return {
|
|
863
|
+
content: [{
|
|
864
|
+
type: 'text',
|
|
865
|
+
text: JSON.stringify({
|
|
866
|
+
error: {
|
|
867
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
868
|
+
message: error.message
|
|
869
|
+
}
|
|
870
|
+
}, null, 2)
|
|
871
|
+
}],
|
|
872
|
+
isError: true
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Set selection
|
|
879
|
+
*/
|
|
880
|
+
export async function handleSetSelection(bridge, args) {
|
|
881
|
+
if (!bridge.isConnected()) {
|
|
882
|
+
return {
|
|
883
|
+
content: [{
|
|
884
|
+
type: 'text',
|
|
885
|
+
text: JSON.stringify({
|
|
886
|
+
error: {
|
|
887
|
+
code: 'NOT_CONNECTED',
|
|
888
|
+
message: 'Figma plugin is not connected.'
|
|
889
|
+
}
|
|
890
|
+
}, null, 2)
|
|
891
|
+
}],
|
|
892
|
+
isError: true
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const { nodeIds } = args;
|
|
897
|
+
|
|
898
|
+
if (!nodeIds || !Array.isArray(nodeIds)) {
|
|
899
|
+
return {
|
|
900
|
+
content: [{
|
|
901
|
+
type: 'text',
|
|
902
|
+
text: JSON.stringify({
|
|
903
|
+
error: {
|
|
904
|
+
code: 'INVALID_PARAMS',
|
|
905
|
+
message: 'nodeIds must be an array'
|
|
906
|
+
}
|
|
907
|
+
}, null, 2)
|
|
908
|
+
}],
|
|
909
|
+
isError: true
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
try {
|
|
914
|
+
const result = await bridge.sendCommand('set_selection', { nodeIds });
|
|
915
|
+
return {
|
|
916
|
+
content: [{
|
|
917
|
+
type: 'text',
|
|
918
|
+
text: JSON.stringify(result, null, 2)
|
|
919
|
+
}]
|
|
920
|
+
};
|
|
921
|
+
} catch (error) {
|
|
922
|
+
return {
|
|
923
|
+
content: [{
|
|
924
|
+
type: 'text',
|
|
925
|
+
text: JSON.stringify({
|
|
926
|
+
error: {
|
|
927
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
928
|
+
message: error.message
|
|
929
|
+
}
|
|
930
|
+
}, null, 2)
|
|
931
|
+
}],
|
|
932
|
+
isError: true
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Set current page
|
|
939
|
+
*/
|
|
940
|
+
export async function handleSetCurrentPage(bridge, args) {
|
|
941
|
+
if (!bridge.isConnected()) {
|
|
942
|
+
return {
|
|
943
|
+
content: [{
|
|
944
|
+
type: 'text',
|
|
945
|
+
text: JSON.stringify({
|
|
946
|
+
error: {
|
|
947
|
+
code: 'NOT_CONNECTED',
|
|
948
|
+
message: 'Figma plugin is not connected.'
|
|
949
|
+
}
|
|
950
|
+
}, null, 2)
|
|
951
|
+
}],
|
|
952
|
+
isError: true
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const { pageId } = args;
|
|
957
|
+
|
|
958
|
+
if (!pageId) {
|
|
959
|
+
return {
|
|
960
|
+
content: [{
|
|
961
|
+
type: 'text',
|
|
962
|
+
text: JSON.stringify({
|
|
963
|
+
error: {
|
|
964
|
+
code: 'INVALID_PARAMS',
|
|
965
|
+
message: 'pageId is required'
|
|
966
|
+
}
|
|
967
|
+
}, null, 2)
|
|
968
|
+
}],
|
|
969
|
+
isError: true
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
try {
|
|
974
|
+
const result = await bridge.sendCommand('set_current_page', { pageId });
|
|
975
|
+
return {
|
|
976
|
+
content: [{
|
|
977
|
+
type: 'text',
|
|
978
|
+
text: JSON.stringify(result, null, 2)
|
|
979
|
+
}]
|
|
980
|
+
};
|
|
981
|
+
} catch (error) {
|
|
982
|
+
return {
|
|
983
|
+
content: [{
|
|
984
|
+
type: 'text',
|
|
985
|
+
text: JSON.stringify({
|
|
986
|
+
error: {
|
|
987
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
988
|
+
message: error.message
|
|
989
|
+
}
|
|
990
|
+
}, null, 2)
|
|
991
|
+
}],
|
|
992
|
+
isError: true
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Export node as image
|
|
999
|
+
*/
|
|
1000
|
+
export async function handleExportNode(bridge, args) {
|
|
1001
|
+
if (!bridge.isConnected()) {
|
|
1002
|
+
return {
|
|
1003
|
+
content: [{
|
|
1004
|
+
type: 'text',
|
|
1005
|
+
text: JSON.stringify({
|
|
1006
|
+
error: {
|
|
1007
|
+
code: 'NOT_CONNECTED',
|
|
1008
|
+
message: 'Figma plugin is not connected.'
|
|
1009
|
+
}
|
|
1010
|
+
}, null, 2)
|
|
1011
|
+
}],
|
|
1012
|
+
isError: true
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const { nodeId, format, scale, outputPath, returnBase64 } = args;
|
|
1017
|
+
|
|
1018
|
+
if (!nodeId) {
|
|
1019
|
+
return {
|
|
1020
|
+
content: [{
|
|
1021
|
+
type: 'text',
|
|
1022
|
+
text: JSON.stringify({
|
|
1023
|
+
error: {
|
|
1024
|
+
code: 'INVALID_PARAMS',
|
|
1025
|
+
message: 'nodeId is required'
|
|
1026
|
+
}
|
|
1027
|
+
}, null, 2)
|
|
1028
|
+
}],
|
|
1029
|
+
isError: true
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (outputPath !== undefined && outputPath !== null && !path.isAbsolute(outputPath)) {
|
|
1034
|
+
return {
|
|
1035
|
+
content: [{
|
|
1036
|
+
type: 'text',
|
|
1037
|
+
text: JSON.stringify({
|
|
1038
|
+
error: {
|
|
1039
|
+
code: 'INVALID_PARAMS',
|
|
1040
|
+
message: `outputPath must be an absolute file path. Got: ${outputPath}`
|
|
1041
|
+
}
|
|
1042
|
+
}, null, 2)
|
|
1043
|
+
}],
|
|
1044
|
+
isError: true
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
try {
|
|
1049
|
+
const result = await bridge.sendCommand('export_node', { nodeId, format, scale });
|
|
1050
|
+
|
|
1051
|
+
// Escape hatch: callers that genuinely want the bytes inline.
|
|
1052
|
+
if (returnBase64) {
|
|
1053
|
+
return {
|
|
1054
|
+
content: [{
|
|
1055
|
+
type: 'text',
|
|
1056
|
+
text: JSON.stringify(result, null, 2)
|
|
1057
|
+
}]
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (!result || typeof result.data !== 'string' || result.data.length === 0) {
|
|
1062
|
+
return {
|
|
1063
|
+
content: [{
|
|
1064
|
+
type: 'text',
|
|
1065
|
+
text: JSON.stringify({
|
|
1066
|
+
error: {
|
|
1067
|
+
code: 'EXPORT_NO_DATA',
|
|
1068
|
+
message: `Figma returned no image data for node ${nodeId}. Nothing was written to disk.`
|
|
1069
|
+
}
|
|
1070
|
+
}, null, 2)
|
|
1071
|
+
}],
|
|
1072
|
+
isError: true
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// File-first: decode and write, return the path. Images cannot be viewed
|
|
1077
|
+
// inline as base64, so a path the caller can Read is the useful result.
|
|
1078
|
+
const resolvedFormat = String(format || result.format || 'PNG').toUpperCase();
|
|
1079
|
+
const targetPath = outputPath || defaultExportPath(result.nodeId || nodeId, resolvedFormat);
|
|
1080
|
+
const buffer = Buffer.from(result.data, 'base64');
|
|
1081
|
+
|
|
1082
|
+
try {
|
|
1083
|
+
const dir = path.dirname(targetPath);
|
|
1084
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1085
|
+
fs.writeFileSync(targetPath, buffer);
|
|
1086
|
+
} catch (writeError) {
|
|
1087
|
+
return {
|
|
1088
|
+
content: [{
|
|
1089
|
+
type: 'text',
|
|
1090
|
+
text: JSON.stringify({
|
|
1091
|
+
error: {
|
|
1092
|
+
code: 'EXPORT_WRITE_FAILED',
|
|
1093
|
+
message: `Exported node ${nodeId} but could not write the image to ${targetPath}: ${writeError.message}. ` +
|
|
1094
|
+
'Pass a writable absolute outputPath, or returnBase64: true to get the data inline instead.',
|
|
1095
|
+
path: targetPath
|
|
1096
|
+
}
|
|
1097
|
+
}, null, 2)
|
|
1098
|
+
}],
|
|
1099
|
+
isError: true
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
return {
|
|
1104
|
+
content: [{
|
|
1105
|
+
type: 'text',
|
|
1106
|
+
text: JSON.stringify({
|
|
1107
|
+
success: true,
|
|
1108
|
+
nodeId: result.nodeId || nodeId,
|
|
1109
|
+
path: targetPath,
|
|
1110
|
+
format: resolvedFormat,
|
|
1111
|
+
scale: result.scale !== undefined ? result.scale : scale,
|
|
1112
|
+
bytes: buffer.length,
|
|
1113
|
+
message: 'Image written to disk. Read the file at `path` to view it.'
|
|
1114
|
+
}, null, 2)
|
|
1115
|
+
}]
|
|
1116
|
+
};
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
return {
|
|
1119
|
+
content: [{
|
|
1120
|
+
type: 'text',
|
|
1121
|
+
text: JSON.stringify({
|
|
1122
|
+
error: {
|
|
1123
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1124
|
+
message: error.message
|
|
1125
|
+
}
|
|
1126
|
+
}, null, 2)
|
|
1127
|
+
}],
|
|
1128
|
+
isError: true
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// ============================================================
|
|
1134
|
+
// New Commands (Phase 2)
|
|
1135
|
+
// ============================================================
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Create an ellipse
|
|
1139
|
+
*/
|
|
1140
|
+
export async function handleCreateEllipse(bridge, args) {
|
|
1141
|
+
if (!bridge.isConnected()) {
|
|
1142
|
+
return {
|
|
1143
|
+
content: [{
|
|
1144
|
+
type: 'text',
|
|
1145
|
+
text: JSON.stringify({
|
|
1146
|
+
error: {
|
|
1147
|
+
code: 'NOT_CONNECTED',
|
|
1148
|
+
message: 'Figma plugin is not connected.'
|
|
1149
|
+
}
|
|
1150
|
+
}, null, 2)
|
|
1151
|
+
}],
|
|
1152
|
+
isError: true
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
try {
|
|
1157
|
+
const result = await bridge.sendCommand('create_ellipse', args);
|
|
1158
|
+
return {
|
|
1159
|
+
content: [{
|
|
1160
|
+
type: 'text',
|
|
1161
|
+
text: JSON.stringify(result, null, 2)
|
|
1162
|
+
}]
|
|
1163
|
+
};
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
return {
|
|
1166
|
+
content: [{
|
|
1167
|
+
type: 'text',
|
|
1168
|
+
text: JSON.stringify({
|
|
1169
|
+
error: {
|
|
1170
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1171
|
+
message: error.message
|
|
1172
|
+
}
|
|
1173
|
+
}, null, 2)
|
|
1174
|
+
}],
|
|
1175
|
+
isError: true
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Set effects on a node
|
|
1182
|
+
*/
|
|
1183
|
+
export async function handleSetEffects(bridge, args) {
|
|
1184
|
+
if (!bridge.isConnected()) {
|
|
1185
|
+
return {
|
|
1186
|
+
content: [{
|
|
1187
|
+
type: 'text',
|
|
1188
|
+
text: JSON.stringify({
|
|
1189
|
+
error: {
|
|
1190
|
+
code: 'NOT_CONNECTED',
|
|
1191
|
+
message: 'Figma plugin is not connected.'
|
|
1192
|
+
}
|
|
1193
|
+
}, null, 2)
|
|
1194
|
+
}],
|
|
1195
|
+
isError: true
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const { nodeId, effects } = args;
|
|
1200
|
+
|
|
1201
|
+
if (!nodeId) {
|
|
1202
|
+
return {
|
|
1203
|
+
content: [{
|
|
1204
|
+
type: 'text',
|
|
1205
|
+
text: JSON.stringify({
|
|
1206
|
+
error: {
|
|
1207
|
+
code: 'INVALID_PARAMS',
|
|
1208
|
+
message: 'nodeId is required'
|
|
1209
|
+
}
|
|
1210
|
+
}, null, 2)
|
|
1211
|
+
}],
|
|
1212
|
+
isError: true
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (!effects || !Array.isArray(effects)) {
|
|
1217
|
+
return {
|
|
1218
|
+
content: [{
|
|
1219
|
+
type: 'text',
|
|
1220
|
+
text: JSON.stringify({
|
|
1221
|
+
error: {
|
|
1222
|
+
code: 'INVALID_PARAMS',
|
|
1223
|
+
message: 'effects must be an array'
|
|
1224
|
+
}
|
|
1225
|
+
}, null, 2)
|
|
1226
|
+
}],
|
|
1227
|
+
isError: true
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
try {
|
|
1232
|
+
const result = await bridge.sendCommand('set_effects', { nodeId, effects });
|
|
1233
|
+
return {
|
|
1234
|
+
content: [{
|
|
1235
|
+
type: 'text',
|
|
1236
|
+
text: JSON.stringify(result, null, 2)
|
|
1237
|
+
}]
|
|
1238
|
+
};
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
return {
|
|
1241
|
+
content: [{
|
|
1242
|
+
type: 'text',
|
|
1243
|
+
text: JSON.stringify({
|
|
1244
|
+
error: {
|
|
1245
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1246
|
+
message: error.message
|
|
1247
|
+
}
|
|
1248
|
+
}, null, 2)
|
|
1249
|
+
}],
|
|
1250
|
+
isError: true
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Set auto-layout on a frame
|
|
1257
|
+
*/
|
|
1258
|
+
export async function handleSetAutoLayout(bridge, args) {
|
|
1259
|
+
if (!bridge.isConnected()) {
|
|
1260
|
+
return {
|
|
1261
|
+
content: [{
|
|
1262
|
+
type: 'text',
|
|
1263
|
+
text: JSON.stringify({
|
|
1264
|
+
error: {
|
|
1265
|
+
code: 'NOT_CONNECTED',
|
|
1266
|
+
message: 'Figma plugin is not connected.'
|
|
1267
|
+
}
|
|
1268
|
+
}, null, 2)
|
|
1269
|
+
}],
|
|
1270
|
+
isError: true
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const { nodeId } = args;
|
|
1275
|
+
|
|
1276
|
+
if (!nodeId) {
|
|
1277
|
+
return {
|
|
1278
|
+
content: [{
|
|
1279
|
+
type: 'text',
|
|
1280
|
+
text: JSON.stringify({
|
|
1281
|
+
error: {
|
|
1282
|
+
code: 'INVALID_PARAMS',
|
|
1283
|
+
message: 'nodeId is required'
|
|
1284
|
+
}
|
|
1285
|
+
}, null, 2)
|
|
1286
|
+
}],
|
|
1287
|
+
isError: true
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
try {
|
|
1292
|
+
const result = await bridge.sendCommand('set_auto_layout', args);
|
|
1293
|
+
return {
|
|
1294
|
+
content: [{
|
|
1295
|
+
type: 'text',
|
|
1296
|
+
text: JSON.stringify(result, null, 2)
|
|
1297
|
+
}]
|
|
1298
|
+
};
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
return {
|
|
1301
|
+
content: [{
|
|
1302
|
+
type: 'text',
|
|
1303
|
+
text: JSON.stringify({
|
|
1304
|
+
error: {
|
|
1305
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1306
|
+
message: error.message
|
|
1307
|
+
}
|
|
1308
|
+
}, null, 2)
|
|
1309
|
+
}],
|
|
1310
|
+
isError: true
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Get local styles
|
|
1317
|
+
*/
|
|
1318
|
+
export async function handleGetLocalStyles(bridge, args) {
|
|
1319
|
+
if (!bridge.isConnected()) {
|
|
1320
|
+
return {
|
|
1321
|
+
content: [{
|
|
1322
|
+
type: 'text',
|
|
1323
|
+
text: JSON.stringify({
|
|
1324
|
+
error: {
|
|
1325
|
+
code: 'NOT_CONNECTED',
|
|
1326
|
+
message: 'Figma plugin is not connected.'
|
|
1327
|
+
}
|
|
1328
|
+
}, null, 2)
|
|
1329
|
+
}],
|
|
1330
|
+
isError: true
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
try {
|
|
1335
|
+
const result = await bridge.sendCommand('get_local_styles', args);
|
|
1336
|
+
return {
|
|
1337
|
+
content: [{
|
|
1338
|
+
type: 'text',
|
|
1339
|
+
text: JSON.stringify(result, null, 2)
|
|
1340
|
+
}]
|
|
1341
|
+
};
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
return {
|
|
1344
|
+
content: [{
|
|
1345
|
+
type: 'text',
|
|
1346
|
+
text: JSON.stringify({
|
|
1347
|
+
error: {
|
|
1348
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1349
|
+
message: error.message
|
|
1350
|
+
}
|
|
1351
|
+
}, null, 2)
|
|
1352
|
+
}],
|
|
1353
|
+
isError: true
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Apply a style to a node
|
|
1360
|
+
*/
|
|
1361
|
+
export async function handleApplyStyle(bridge, args) {
|
|
1362
|
+
if (!bridge.isConnected()) {
|
|
1363
|
+
return {
|
|
1364
|
+
content: [{
|
|
1365
|
+
type: 'text',
|
|
1366
|
+
text: JSON.stringify({
|
|
1367
|
+
error: {
|
|
1368
|
+
code: 'NOT_CONNECTED',
|
|
1369
|
+
message: 'Figma plugin is not connected.'
|
|
1370
|
+
}
|
|
1371
|
+
}, null, 2)
|
|
1372
|
+
}],
|
|
1373
|
+
isError: true
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const { nodeId, styleId, property } = args;
|
|
1378
|
+
|
|
1379
|
+
if (!nodeId || !styleId || !property) {
|
|
1380
|
+
return {
|
|
1381
|
+
content: [{
|
|
1382
|
+
type: 'text',
|
|
1383
|
+
text: JSON.stringify({
|
|
1384
|
+
error: {
|
|
1385
|
+
code: 'INVALID_PARAMS',
|
|
1386
|
+
message: 'nodeId, styleId, and property are required'
|
|
1387
|
+
}
|
|
1388
|
+
}, null, 2)
|
|
1389
|
+
}],
|
|
1390
|
+
isError: true
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
try {
|
|
1395
|
+
const result = await bridge.sendCommand('apply_style', { nodeId, styleId, property });
|
|
1396
|
+
return {
|
|
1397
|
+
content: [{
|
|
1398
|
+
type: 'text',
|
|
1399
|
+
text: JSON.stringify(result, null, 2)
|
|
1400
|
+
}]
|
|
1401
|
+
};
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
return {
|
|
1404
|
+
content: [{
|
|
1405
|
+
type: 'text',
|
|
1406
|
+
text: JSON.stringify({
|
|
1407
|
+
error: {
|
|
1408
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1409
|
+
message: error.message
|
|
1410
|
+
}
|
|
1411
|
+
}, null, 2)
|
|
1412
|
+
}],
|
|
1413
|
+
isError: true
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* Create a component
|
|
1420
|
+
*/
|
|
1421
|
+
export async function handleCreateComponent(bridge, args) {
|
|
1422
|
+
if (!bridge.isConnected()) {
|
|
1423
|
+
return {
|
|
1424
|
+
content: [{
|
|
1425
|
+
type: 'text',
|
|
1426
|
+
text: JSON.stringify({
|
|
1427
|
+
error: {
|
|
1428
|
+
code: 'NOT_CONNECTED',
|
|
1429
|
+
message: 'Figma plugin is not connected.'
|
|
1430
|
+
}
|
|
1431
|
+
}, null, 2)
|
|
1432
|
+
}],
|
|
1433
|
+
isError: true
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
try {
|
|
1438
|
+
const result = await bridge.sendCommand('create_component', args);
|
|
1439
|
+
return {
|
|
1440
|
+
content: [{
|
|
1441
|
+
type: 'text',
|
|
1442
|
+
text: JSON.stringify(result, null, 2)
|
|
1443
|
+
}]
|
|
1444
|
+
};
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
return {
|
|
1447
|
+
content: [{
|
|
1448
|
+
type: 'text',
|
|
1449
|
+
text: JSON.stringify({
|
|
1450
|
+
error: {
|
|
1451
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1452
|
+
message: error.message
|
|
1453
|
+
}
|
|
1454
|
+
}, null, 2)
|
|
1455
|
+
}],
|
|
1456
|
+
isError: true
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* Create an instance of a component
|
|
1463
|
+
*/
|
|
1464
|
+
export async function handleCreateInstance(bridge, args) {
|
|
1465
|
+
if (!bridge.isConnected()) {
|
|
1466
|
+
return {
|
|
1467
|
+
content: [{
|
|
1468
|
+
type: 'text',
|
|
1469
|
+
text: JSON.stringify({
|
|
1470
|
+
error: {
|
|
1471
|
+
code: 'NOT_CONNECTED',
|
|
1472
|
+
message: 'Figma plugin is not connected.'
|
|
1473
|
+
}
|
|
1474
|
+
}, null, 2)
|
|
1475
|
+
}],
|
|
1476
|
+
isError: true
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const { componentId } = args;
|
|
1481
|
+
|
|
1482
|
+
if (!componentId) {
|
|
1483
|
+
return {
|
|
1484
|
+
content: [{
|
|
1485
|
+
type: 'text',
|
|
1486
|
+
text: JSON.stringify({
|
|
1487
|
+
error: {
|
|
1488
|
+
code: 'INVALID_PARAMS',
|
|
1489
|
+
message: 'componentId is required'
|
|
1490
|
+
}
|
|
1491
|
+
}, null, 2)
|
|
1492
|
+
}],
|
|
1493
|
+
isError: true
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
try {
|
|
1498
|
+
const result = await bridge.sendCommand('create_instance', args);
|
|
1499
|
+
return {
|
|
1500
|
+
content: [{
|
|
1501
|
+
type: 'text',
|
|
1502
|
+
text: JSON.stringify(result, null, 2)
|
|
1503
|
+
}]
|
|
1504
|
+
};
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
return {
|
|
1507
|
+
content: [{
|
|
1508
|
+
type: 'text',
|
|
1509
|
+
text: JSON.stringify({
|
|
1510
|
+
error: {
|
|
1511
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1512
|
+
message: error.message
|
|
1513
|
+
}
|
|
1514
|
+
}, null, 2)
|
|
1515
|
+
}],
|
|
1516
|
+
isError: true
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// ============================================================
|
|
1522
|
+
// Phase 3 Commands: Variables, Lines, Constraints
|
|
1523
|
+
// ============================================================
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* Get local variables and variable collections
|
|
1527
|
+
*/
|
|
1528
|
+
export async function handleGetLocalVariables(bridge, args) {
|
|
1529
|
+
if (!bridge.isConnected()) {
|
|
1530
|
+
return {
|
|
1531
|
+
content: [{
|
|
1532
|
+
type: 'text',
|
|
1533
|
+
text: JSON.stringify({
|
|
1534
|
+
error: {
|
|
1535
|
+
code: 'NOT_CONNECTED',
|
|
1536
|
+
message: 'Figma plugin is not connected.'
|
|
1537
|
+
}
|
|
1538
|
+
}, null, 2)
|
|
1539
|
+
}],
|
|
1540
|
+
isError: true
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
try {
|
|
1545
|
+
const result = await bridge.sendCommand('get_local_variables', args);
|
|
1546
|
+
return {
|
|
1547
|
+
content: [{
|
|
1548
|
+
type: 'text',
|
|
1549
|
+
text: JSON.stringify(result, null, 2)
|
|
1550
|
+
}]
|
|
1551
|
+
};
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
return {
|
|
1554
|
+
content: [{
|
|
1555
|
+
type: 'text',
|
|
1556
|
+
text: JSON.stringify({
|
|
1557
|
+
error: {
|
|
1558
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1559
|
+
message: error.message
|
|
1560
|
+
}
|
|
1561
|
+
}, null, 2)
|
|
1562
|
+
}],
|
|
1563
|
+
isError: true
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Search variables with filtering (optimized for reduced token usage)
|
|
1570
|
+
*/
|
|
1571
|
+
export async function handleSearchVariables(bridge, args) {
|
|
1572
|
+
if (!bridge.isConnected()) {
|
|
1573
|
+
return {
|
|
1574
|
+
content: [{
|
|
1575
|
+
type: 'text',
|
|
1576
|
+
text: JSON.stringify({
|
|
1577
|
+
error: {
|
|
1578
|
+
code: 'NOT_CONNECTED',
|
|
1579
|
+
message: 'Figma plugin is not connected.'
|
|
1580
|
+
}
|
|
1581
|
+
}, null, 2)
|
|
1582
|
+
}],
|
|
1583
|
+
isError: true
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
try {
|
|
1588
|
+
const result = await bridge.sendCommand('search_variables', args);
|
|
1589
|
+
return {
|
|
1590
|
+
content: [{
|
|
1591
|
+
type: 'text',
|
|
1592
|
+
text: JSON.stringify(result, null, 2)
|
|
1593
|
+
}]
|
|
1594
|
+
};
|
|
1595
|
+
} catch (error) {
|
|
1596
|
+
return {
|
|
1597
|
+
content: [{
|
|
1598
|
+
type: 'text',
|
|
1599
|
+
text: JSON.stringify({
|
|
1600
|
+
error: {
|
|
1601
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1602
|
+
message: error.message
|
|
1603
|
+
}
|
|
1604
|
+
}, null, 2)
|
|
1605
|
+
}],
|
|
1606
|
+
isError: true
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/**
|
|
1612
|
+
* Set variable value, or bind variable to a node or style property
|
|
1613
|
+
*/
|
|
1614
|
+
export async function handleSetVariable(bridge, args) {
|
|
1615
|
+
if (!bridge.isConnected()) {
|
|
1616
|
+
return {
|
|
1617
|
+
content: [{
|
|
1618
|
+
type: 'text',
|
|
1619
|
+
text: JSON.stringify({
|
|
1620
|
+
error: {
|
|
1621
|
+
code: 'NOT_CONNECTED',
|
|
1622
|
+
message: 'Figma plugin is not connected.'
|
|
1623
|
+
}
|
|
1624
|
+
}, null, 2)
|
|
1625
|
+
}],
|
|
1626
|
+
isError: true
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const { variableId, modeId, value, nodeId, styleId, field } = args;
|
|
1631
|
+
|
|
1632
|
+
if (!variableId) {
|
|
1633
|
+
return {
|
|
1634
|
+
content: [{
|
|
1635
|
+
type: 'text',
|
|
1636
|
+
text: JSON.stringify({
|
|
1637
|
+
error: {
|
|
1638
|
+
code: 'INVALID_PARAMS',
|
|
1639
|
+
message: 'variableId is required'
|
|
1640
|
+
}
|
|
1641
|
+
}, null, 2)
|
|
1642
|
+
}],
|
|
1643
|
+
isError: true
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// Validate: either (modeId + value) for setting, or (nodeId + field) for binding
|
|
1648
|
+
if (value !== undefined && !modeId) {
|
|
1649
|
+
return {
|
|
1650
|
+
content: [{
|
|
1651
|
+
type: 'text',
|
|
1652
|
+
text: JSON.stringify({
|
|
1653
|
+
error: {
|
|
1654
|
+
code: 'INVALID_PARAMS',
|
|
1655
|
+
message: 'modeId is required when setting a value'
|
|
1656
|
+
}
|
|
1657
|
+
}, null, 2)
|
|
1658
|
+
}],
|
|
1659
|
+
isError: true
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
if ((nodeId || styleId) && !field) {
|
|
1664
|
+
return {
|
|
1665
|
+
content: [{
|
|
1666
|
+
type: 'text',
|
|
1667
|
+
text: JSON.stringify({
|
|
1668
|
+
error: {
|
|
1669
|
+
code: 'INVALID_PARAMS',
|
|
1670
|
+
message: 'field is required when binding to a node or style'
|
|
1671
|
+
}
|
|
1672
|
+
}, null, 2)
|
|
1673
|
+
}],
|
|
1674
|
+
isError: true
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
try {
|
|
1679
|
+
const result = await bridge.sendCommand('set_variable', args);
|
|
1680
|
+
return {
|
|
1681
|
+
content: [{
|
|
1682
|
+
type: 'text',
|
|
1683
|
+
text: JSON.stringify(result, null, 2)
|
|
1684
|
+
}]
|
|
1685
|
+
};
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
return {
|
|
1688
|
+
content: [{
|
|
1689
|
+
type: 'text',
|
|
1690
|
+
text: JSON.stringify({
|
|
1691
|
+
error: {
|
|
1692
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1693
|
+
message: error.message
|
|
1694
|
+
}
|
|
1695
|
+
}, null, 2)
|
|
1696
|
+
}],
|
|
1697
|
+
isError: true
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
/**
|
|
1703
|
+
* Create a line
|
|
1704
|
+
*/
|
|
1705
|
+
export async function handleCreateLine(bridge, args) {
|
|
1706
|
+
if (!bridge.isConnected()) {
|
|
1707
|
+
return {
|
|
1708
|
+
content: [{
|
|
1709
|
+
type: 'text',
|
|
1710
|
+
text: JSON.stringify({
|
|
1711
|
+
error: {
|
|
1712
|
+
code: 'NOT_CONNECTED',
|
|
1713
|
+
message: 'Figma plugin is not connected.'
|
|
1714
|
+
}
|
|
1715
|
+
}, null, 2)
|
|
1716
|
+
}],
|
|
1717
|
+
isError: true
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
try {
|
|
1722
|
+
const result = await bridge.sendCommand('create_line', args);
|
|
1723
|
+
return {
|
|
1724
|
+
content: [{
|
|
1725
|
+
type: 'text',
|
|
1726
|
+
text: JSON.stringify(result, null, 2)
|
|
1727
|
+
}]
|
|
1728
|
+
};
|
|
1729
|
+
} catch (error) {
|
|
1730
|
+
return {
|
|
1731
|
+
content: [{
|
|
1732
|
+
type: 'text',
|
|
1733
|
+
text: JSON.stringify({
|
|
1734
|
+
error: {
|
|
1735
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1736
|
+
message: error.message
|
|
1737
|
+
}
|
|
1738
|
+
}, null, 2)
|
|
1739
|
+
}],
|
|
1740
|
+
isError: true
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/**
|
|
1746
|
+
* Set constraints on a node
|
|
1747
|
+
*/
|
|
1748
|
+
export async function handleSetConstraints(bridge, args) {
|
|
1749
|
+
if (!bridge.isConnected()) {
|
|
1750
|
+
return {
|
|
1751
|
+
content: [{
|
|
1752
|
+
type: 'text',
|
|
1753
|
+
text: JSON.stringify({
|
|
1754
|
+
error: {
|
|
1755
|
+
code: 'NOT_CONNECTED',
|
|
1756
|
+
message: 'Figma plugin is not connected.'
|
|
1757
|
+
}
|
|
1758
|
+
}, null, 2)
|
|
1759
|
+
}],
|
|
1760
|
+
isError: true
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
const { nodeId, horizontal, vertical } = args;
|
|
1765
|
+
|
|
1766
|
+
if (!nodeId) {
|
|
1767
|
+
return {
|
|
1768
|
+
content: [{
|
|
1769
|
+
type: 'text',
|
|
1770
|
+
text: JSON.stringify({
|
|
1771
|
+
error: {
|
|
1772
|
+
code: 'INVALID_PARAMS',
|
|
1773
|
+
message: 'nodeId is required'
|
|
1774
|
+
}
|
|
1775
|
+
}, null, 2)
|
|
1776
|
+
}],
|
|
1777
|
+
isError: true
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
if (horizontal === undefined && vertical === undefined) {
|
|
1782
|
+
return {
|
|
1783
|
+
content: [{
|
|
1784
|
+
type: 'text',
|
|
1785
|
+
text: JSON.stringify({
|
|
1786
|
+
error: {
|
|
1787
|
+
code: 'INVALID_PARAMS',
|
|
1788
|
+
message: 'At least one of horizontal or vertical must be provided'
|
|
1789
|
+
}
|
|
1790
|
+
}, null, 2)
|
|
1791
|
+
}],
|
|
1792
|
+
isError: true
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
try {
|
|
1797
|
+
const result = await bridge.sendCommand('set_constraints', { nodeId, horizontal, vertical });
|
|
1798
|
+
return {
|
|
1799
|
+
content: [{
|
|
1800
|
+
type: 'text',
|
|
1801
|
+
text: JSON.stringify(result, null, 2)
|
|
1802
|
+
}]
|
|
1803
|
+
};
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
return {
|
|
1806
|
+
content: [{
|
|
1807
|
+
type: 'text',
|
|
1808
|
+
text: JSON.stringify({
|
|
1809
|
+
error: {
|
|
1810
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1811
|
+
message: error.message
|
|
1812
|
+
}
|
|
1813
|
+
}, null, 2)
|
|
1814
|
+
}],
|
|
1815
|
+
isError: true
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// ============================================================
|
|
1821
|
+
// Phase 4 Commands: Polygons, Boolean Operations, Viewport, Blend Mode, Detach
|
|
1822
|
+
// ============================================================
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* Create a polygon or star
|
|
1826
|
+
*/
|
|
1827
|
+
export async function handleCreatePolygon(bridge, args) {
|
|
1828
|
+
if (!bridge.isConnected()) {
|
|
1829
|
+
return {
|
|
1830
|
+
content: [{
|
|
1831
|
+
type: 'text',
|
|
1832
|
+
text: JSON.stringify({
|
|
1833
|
+
error: {
|
|
1834
|
+
code: 'NOT_CONNECTED',
|
|
1835
|
+
message: 'Figma plugin is not connected.'
|
|
1836
|
+
}
|
|
1837
|
+
}, null, 2)
|
|
1838
|
+
}],
|
|
1839
|
+
isError: true
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
try {
|
|
1844
|
+
const result = await bridge.sendCommand('create_polygon', args);
|
|
1845
|
+
return {
|
|
1846
|
+
content: [{
|
|
1847
|
+
type: 'text',
|
|
1848
|
+
text: JSON.stringify(result, null, 2)
|
|
1849
|
+
}]
|
|
1850
|
+
};
|
|
1851
|
+
} catch (error) {
|
|
1852
|
+
return {
|
|
1853
|
+
content: [{
|
|
1854
|
+
type: 'text',
|
|
1855
|
+
text: JSON.stringify({
|
|
1856
|
+
error: {
|
|
1857
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1858
|
+
message: error.message
|
|
1859
|
+
}
|
|
1860
|
+
}, null, 2)
|
|
1861
|
+
}],
|
|
1862
|
+
isError: true
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
/**
|
|
1868
|
+
* Perform boolean operation on nodes
|
|
1869
|
+
*/
|
|
1870
|
+
export async function handleBooleanOperation(bridge, args) {
|
|
1871
|
+
if (!bridge.isConnected()) {
|
|
1872
|
+
return {
|
|
1873
|
+
content: [{
|
|
1874
|
+
type: 'text',
|
|
1875
|
+
text: JSON.stringify({
|
|
1876
|
+
error: {
|
|
1877
|
+
code: 'NOT_CONNECTED',
|
|
1878
|
+
message: 'Figma plugin is not connected.'
|
|
1879
|
+
}
|
|
1880
|
+
}, null, 2)
|
|
1881
|
+
}],
|
|
1882
|
+
isError: true
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
const { operation, nodeIds } = args;
|
|
1887
|
+
|
|
1888
|
+
if (!operation) {
|
|
1889
|
+
return {
|
|
1890
|
+
content: [{
|
|
1891
|
+
type: 'text',
|
|
1892
|
+
text: JSON.stringify({
|
|
1893
|
+
error: {
|
|
1894
|
+
code: 'INVALID_PARAMS',
|
|
1895
|
+
message: 'operation is required'
|
|
1896
|
+
}
|
|
1897
|
+
}, null, 2)
|
|
1898
|
+
}],
|
|
1899
|
+
isError: true
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length < 2) {
|
|
1904
|
+
return {
|
|
1905
|
+
content: [{
|
|
1906
|
+
type: 'text',
|
|
1907
|
+
text: JSON.stringify({
|
|
1908
|
+
error: {
|
|
1909
|
+
code: 'INVALID_PARAMS',
|
|
1910
|
+
message: 'nodeIds must be an array with at least 2 nodes'
|
|
1911
|
+
}
|
|
1912
|
+
}, null, 2)
|
|
1913
|
+
}],
|
|
1914
|
+
isError: true
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
try {
|
|
1919
|
+
const result = await bridge.sendCommand('boolean_operation', args);
|
|
1920
|
+
return {
|
|
1921
|
+
content: [{
|
|
1922
|
+
type: 'text',
|
|
1923
|
+
text: JSON.stringify(result, null, 2)
|
|
1924
|
+
}]
|
|
1925
|
+
};
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
return {
|
|
1928
|
+
content: [{
|
|
1929
|
+
type: 'text',
|
|
1930
|
+
text: JSON.stringify({
|
|
1931
|
+
error: {
|
|
1932
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1933
|
+
message: error.message
|
|
1934
|
+
}
|
|
1935
|
+
}, null, 2)
|
|
1936
|
+
}],
|
|
1937
|
+
isError: true
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
/**
|
|
1943
|
+
* Zoom viewport to node(s)
|
|
1944
|
+
*/
|
|
1945
|
+
export async function handleZoomToNode(bridge, args) {
|
|
1946
|
+
if (!bridge.isConnected()) {
|
|
1947
|
+
return {
|
|
1948
|
+
content: [{
|
|
1949
|
+
type: 'text',
|
|
1950
|
+
text: JSON.stringify({
|
|
1951
|
+
error: {
|
|
1952
|
+
code: 'NOT_CONNECTED',
|
|
1953
|
+
message: 'Figma plugin is not connected.'
|
|
1954
|
+
}
|
|
1955
|
+
}, null, 2)
|
|
1956
|
+
}],
|
|
1957
|
+
isError: true
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
const { nodeIds } = args;
|
|
1962
|
+
|
|
1963
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
1964
|
+
return {
|
|
1965
|
+
content: [{
|
|
1966
|
+
type: 'text',
|
|
1967
|
+
text: JSON.stringify({
|
|
1968
|
+
error: {
|
|
1969
|
+
code: 'INVALID_PARAMS',
|
|
1970
|
+
message: 'nodeIds must be a non-empty array'
|
|
1971
|
+
}
|
|
1972
|
+
}, null, 2)
|
|
1973
|
+
}],
|
|
1974
|
+
isError: true
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
try {
|
|
1979
|
+
const result = await bridge.sendCommand('zoom_to_node', { nodeIds });
|
|
1980
|
+
return {
|
|
1981
|
+
content: [{
|
|
1982
|
+
type: 'text',
|
|
1983
|
+
text: JSON.stringify(result, null, 2)
|
|
1984
|
+
}]
|
|
1985
|
+
};
|
|
1986
|
+
} catch (error) {
|
|
1987
|
+
return {
|
|
1988
|
+
content: [{
|
|
1989
|
+
type: 'text',
|
|
1990
|
+
text: JSON.stringify({
|
|
1991
|
+
error: {
|
|
1992
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
1993
|
+
message: error.message
|
|
1994
|
+
}
|
|
1995
|
+
}, null, 2)
|
|
1996
|
+
}],
|
|
1997
|
+
isError: true
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/**
|
|
2003
|
+
* Set blend mode on a node
|
|
2004
|
+
*/
|
|
2005
|
+
export async function handleSetBlendMode(bridge, args) {
|
|
2006
|
+
if (!bridge.isConnected()) {
|
|
2007
|
+
return {
|
|
2008
|
+
content: [{
|
|
2009
|
+
type: 'text',
|
|
2010
|
+
text: JSON.stringify({
|
|
2011
|
+
error: {
|
|
2012
|
+
code: 'NOT_CONNECTED',
|
|
2013
|
+
message: 'Figma plugin is not connected.'
|
|
2014
|
+
}
|
|
2015
|
+
}, null, 2)
|
|
2016
|
+
}],
|
|
2017
|
+
isError: true
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
const { nodeId, blendMode } = args;
|
|
2022
|
+
|
|
2023
|
+
if (!nodeId) {
|
|
2024
|
+
return {
|
|
2025
|
+
content: [{
|
|
2026
|
+
type: 'text',
|
|
2027
|
+
text: JSON.stringify({
|
|
2028
|
+
error: {
|
|
2029
|
+
code: 'INVALID_PARAMS',
|
|
2030
|
+
message: 'nodeId is required'
|
|
2031
|
+
}
|
|
2032
|
+
}, null, 2)
|
|
2033
|
+
}],
|
|
2034
|
+
isError: true
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
if (!blendMode) {
|
|
2039
|
+
return {
|
|
2040
|
+
content: [{
|
|
2041
|
+
type: 'text',
|
|
2042
|
+
text: JSON.stringify({
|
|
2043
|
+
error: {
|
|
2044
|
+
code: 'INVALID_PARAMS',
|
|
2045
|
+
message: 'blendMode is required'
|
|
2046
|
+
}
|
|
2047
|
+
}, null, 2)
|
|
2048
|
+
}],
|
|
2049
|
+
isError: true
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
try {
|
|
2054
|
+
const result = await bridge.sendCommand('set_blend_mode', { nodeId, blendMode });
|
|
2055
|
+
return {
|
|
2056
|
+
content: [{
|
|
2057
|
+
type: 'text',
|
|
2058
|
+
text: JSON.stringify(result, null, 2)
|
|
2059
|
+
}]
|
|
2060
|
+
};
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
return {
|
|
2063
|
+
content: [{
|
|
2064
|
+
type: 'text',
|
|
2065
|
+
text: JSON.stringify({
|
|
2066
|
+
error: {
|
|
2067
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2068
|
+
message: error.message
|
|
2069
|
+
}
|
|
2070
|
+
}, null, 2)
|
|
2071
|
+
}],
|
|
2072
|
+
isError: true
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
/**
|
|
2078
|
+
* Detach instance from component
|
|
2079
|
+
*/
|
|
2080
|
+
export async function handleDetachInstance(bridge, args) {
|
|
2081
|
+
if (!bridge.isConnected()) {
|
|
2082
|
+
return {
|
|
2083
|
+
content: [{
|
|
2084
|
+
type: 'text',
|
|
2085
|
+
text: JSON.stringify({
|
|
2086
|
+
error: {
|
|
2087
|
+
code: 'NOT_CONNECTED',
|
|
2088
|
+
message: 'Figma plugin is not connected.'
|
|
2089
|
+
}
|
|
2090
|
+
}, null, 2)
|
|
2091
|
+
}],
|
|
2092
|
+
isError: true
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
const { nodeId } = args;
|
|
2097
|
+
|
|
2098
|
+
if (!nodeId) {
|
|
2099
|
+
return {
|
|
2100
|
+
content: [{
|
|
2101
|
+
type: 'text',
|
|
2102
|
+
text: JSON.stringify({
|
|
2103
|
+
error: {
|
|
2104
|
+
code: 'INVALID_PARAMS',
|
|
2105
|
+
message: 'nodeId is required'
|
|
2106
|
+
}
|
|
2107
|
+
}, null, 2)
|
|
2108
|
+
}],
|
|
2109
|
+
isError: true
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
try {
|
|
2114
|
+
const result = await bridge.sendCommand('detach_instance', { nodeId });
|
|
2115
|
+
return {
|
|
2116
|
+
content: [{
|
|
2117
|
+
type: 'text',
|
|
2118
|
+
text: JSON.stringify(result, null, 2)
|
|
2119
|
+
}]
|
|
2120
|
+
};
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
return {
|
|
2123
|
+
content: [{
|
|
2124
|
+
type: 'text',
|
|
2125
|
+
text: JSON.stringify({
|
|
2126
|
+
error: {
|
|
2127
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2128
|
+
message: error.message
|
|
2129
|
+
}
|
|
2130
|
+
}, null, 2)
|
|
2131
|
+
}],
|
|
2132
|
+
isError: true
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// ============================================================
|
|
2138
|
+
// Phase 5 Commands: Layout Align, Vector, Rename, Reorder
|
|
2139
|
+
// ============================================================
|
|
2140
|
+
|
|
2141
|
+
/**
|
|
2142
|
+
* Set layout align properties on a node (for auto-layout children)
|
|
2143
|
+
*/
|
|
2144
|
+
export async function handleSetLayoutAlign(bridge, args) {
|
|
2145
|
+
if (!bridge.isConnected()) {
|
|
2146
|
+
return {
|
|
2147
|
+
content: [{
|
|
2148
|
+
type: 'text',
|
|
2149
|
+
text: JSON.stringify({
|
|
2150
|
+
error: {
|
|
2151
|
+
code: 'NOT_CONNECTED',
|
|
2152
|
+
message: 'Figma plugin is not connected.'
|
|
2153
|
+
}
|
|
2154
|
+
}, null, 2)
|
|
2155
|
+
}],
|
|
2156
|
+
isError: true
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
const { nodeId } = args;
|
|
2161
|
+
|
|
2162
|
+
if (!nodeId) {
|
|
2163
|
+
return {
|
|
2164
|
+
content: [{
|
|
2165
|
+
type: 'text',
|
|
2166
|
+
text: JSON.stringify({
|
|
2167
|
+
error: {
|
|
2168
|
+
code: 'INVALID_PARAMS',
|
|
2169
|
+
message: 'nodeId is required'
|
|
2170
|
+
}
|
|
2171
|
+
}, null, 2)
|
|
2172
|
+
}],
|
|
2173
|
+
isError: true
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
try {
|
|
2178
|
+
const result = await bridge.sendCommand('set_layout_align', args);
|
|
2179
|
+
return {
|
|
2180
|
+
content: [{
|
|
2181
|
+
type: 'text',
|
|
2182
|
+
text: JSON.stringify(result, null, 2)
|
|
2183
|
+
}]
|
|
2184
|
+
};
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
return {
|
|
2187
|
+
content: [{
|
|
2188
|
+
type: 'text',
|
|
2189
|
+
text: JSON.stringify({
|
|
2190
|
+
error: {
|
|
2191
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2192
|
+
message: error.message
|
|
2193
|
+
}
|
|
2194
|
+
}, null, 2)
|
|
2195
|
+
}],
|
|
2196
|
+
isError: true
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
/**
|
|
2202
|
+
* Create a vector with custom path data
|
|
2203
|
+
*/
|
|
2204
|
+
export async function handleCreateVector(bridge, args) {
|
|
2205
|
+
if (!bridge.isConnected()) {
|
|
2206
|
+
return {
|
|
2207
|
+
content: [{
|
|
2208
|
+
type: 'text',
|
|
2209
|
+
text: JSON.stringify({
|
|
2210
|
+
error: {
|
|
2211
|
+
code: 'NOT_CONNECTED',
|
|
2212
|
+
message: 'Figma plugin is not connected.'
|
|
2213
|
+
}
|
|
2214
|
+
}, null, 2)
|
|
2215
|
+
}],
|
|
2216
|
+
isError: true
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
const { data } = args;
|
|
2221
|
+
|
|
2222
|
+
if (!data) {
|
|
2223
|
+
return {
|
|
2224
|
+
content: [{
|
|
2225
|
+
type: 'text',
|
|
2226
|
+
text: JSON.stringify({
|
|
2227
|
+
error: {
|
|
2228
|
+
code: 'INVALID_PARAMS',
|
|
2229
|
+
message: 'data (SVG path string) is required'
|
|
2230
|
+
}
|
|
2231
|
+
}, null, 2)
|
|
2232
|
+
}],
|
|
2233
|
+
isError: true
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
try {
|
|
2238
|
+
const result = await bridge.sendCommand('create_vector', args);
|
|
2239
|
+
return {
|
|
2240
|
+
content: [{
|
|
2241
|
+
type: 'text',
|
|
2242
|
+
text: JSON.stringify(result, null, 2)
|
|
2243
|
+
}]
|
|
2244
|
+
};
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
return {
|
|
2247
|
+
content: [{
|
|
2248
|
+
type: 'text',
|
|
2249
|
+
text: JSON.stringify({
|
|
2250
|
+
error: {
|
|
2251
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2252
|
+
message: error.message
|
|
2253
|
+
}
|
|
2254
|
+
}, null, 2)
|
|
2255
|
+
}],
|
|
2256
|
+
isError: true
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/**
|
|
2262
|
+
* Rename one or more nodes
|
|
2263
|
+
*/
|
|
2264
|
+
export async function handleRenameNode(bridge, args) {
|
|
2265
|
+
if (!bridge.isConnected()) {
|
|
2266
|
+
return {
|
|
2267
|
+
content: [{
|
|
2268
|
+
type: 'text',
|
|
2269
|
+
text: JSON.stringify({
|
|
2270
|
+
error: {
|
|
2271
|
+
code: 'NOT_CONNECTED',
|
|
2272
|
+
message: 'Figma plugin is not connected.'
|
|
2273
|
+
}
|
|
2274
|
+
}, null, 2)
|
|
2275
|
+
}],
|
|
2276
|
+
isError: true
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
const { nodeId, nodeIds, name } = args;
|
|
2281
|
+
|
|
2282
|
+
if (!nodeId && (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0)) {
|
|
2283
|
+
return {
|
|
2284
|
+
content: [{
|
|
2285
|
+
type: 'text',
|
|
2286
|
+
text: JSON.stringify({
|
|
2287
|
+
error: {
|
|
2288
|
+
code: 'INVALID_PARAMS',
|
|
2289
|
+
message: 'nodeId or nodeIds is required'
|
|
2290
|
+
}
|
|
2291
|
+
}, null, 2)
|
|
2292
|
+
}],
|
|
2293
|
+
isError: true
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
if (!name) {
|
|
2298
|
+
return {
|
|
2299
|
+
content: [{
|
|
2300
|
+
type: 'text',
|
|
2301
|
+
text: JSON.stringify({
|
|
2302
|
+
error: {
|
|
2303
|
+
code: 'INVALID_PARAMS',
|
|
2304
|
+
message: 'name is required'
|
|
2305
|
+
}
|
|
2306
|
+
}, null, 2)
|
|
2307
|
+
}],
|
|
2308
|
+
isError: true
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
try {
|
|
2313
|
+
const result = await bridge.sendCommand('rename_node', args);
|
|
2314
|
+
return {
|
|
2315
|
+
content: [{
|
|
2316
|
+
type: 'text',
|
|
2317
|
+
text: JSON.stringify(result, null, 2)
|
|
2318
|
+
}]
|
|
2319
|
+
};
|
|
2320
|
+
} catch (error) {
|
|
2321
|
+
return {
|
|
2322
|
+
content: [{
|
|
2323
|
+
type: 'text',
|
|
2324
|
+
text: JSON.stringify({
|
|
2325
|
+
error: {
|
|
2326
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2327
|
+
message: error.message
|
|
2328
|
+
}
|
|
2329
|
+
}, null, 2)
|
|
2330
|
+
}],
|
|
2331
|
+
isError: true
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
/**
|
|
2337
|
+
* Reorder a node (change z-order)
|
|
2338
|
+
*/
|
|
2339
|
+
export async function handleReorderNode(bridge, args) {
|
|
2340
|
+
if (!bridge.isConnected()) {
|
|
2341
|
+
return {
|
|
2342
|
+
content: [{
|
|
2343
|
+
type: 'text',
|
|
2344
|
+
text: JSON.stringify({
|
|
2345
|
+
error: {
|
|
2346
|
+
code: 'NOT_CONNECTED',
|
|
2347
|
+
message: 'Figma plugin is not connected.'
|
|
2348
|
+
}
|
|
2349
|
+
}, null, 2)
|
|
2350
|
+
}],
|
|
2351
|
+
isError: true
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
const { nodeId, position } = args;
|
|
2356
|
+
|
|
2357
|
+
if (!nodeId) {
|
|
2358
|
+
return {
|
|
2359
|
+
content: [{
|
|
2360
|
+
type: 'text',
|
|
2361
|
+
text: JSON.stringify({
|
|
2362
|
+
error: {
|
|
2363
|
+
code: 'INVALID_PARAMS',
|
|
2364
|
+
message: 'nodeId is required'
|
|
2365
|
+
}
|
|
2366
|
+
}, null, 2)
|
|
2367
|
+
}],
|
|
2368
|
+
isError: true
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (position === undefined) {
|
|
2373
|
+
return {
|
|
2374
|
+
content: [{
|
|
2375
|
+
type: 'text',
|
|
2376
|
+
text: JSON.stringify({
|
|
2377
|
+
error: {
|
|
2378
|
+
code: 'INVALID_PARAMS',
|
|
2379
|
+
message: 'position is required (front, back, or index number)'
|
|
2380
|
+
}
|
|
2381
|
+
}, null, 2)
|
|
2382
|
+
}],
|
|
2383
|
+
isError: true
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
try {
|
|
2388
|
+
const result = await bridge.sendCommand('reorder_node', args);
|
|
2389
|
+
return {
|
|
2390
|
+
content: [{
|
|
2391
|
+
type: 'text',
|
|
2392
|
+
text: JSON.stringify(result, null, 2)
|
|
2393
|
+
}]
|
|
2394
|
+
};
|
|
2395
|
+
} catch (error) {
|
|
2396
|
+
return {
|
|
2397
|
+
content: [{
|
|
2398
|
+
type: 'text',
|
|
2399
|
+
text: JSON.stringify({
|
|
2400
|
+
error: {
|
|
2401
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2402
|
+
message: error.message
|
|
2403
|
+
}
|
|
2404
|
+
}, null, 2)
|
|
2405
|
+
}],
|
|
2406
|
+
isError: true
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
// ============================================================
|
|
2412
|
+
// Smart Query Commands (token-efficient search)
|
|
2413
|
+
// ============================================================
|
|
2414
|
+
|
|
2415
|
+
/**
|
|
2416
|
+
* Search nodes by name within a scope
|
|
2417
|
+
*/
|
|
2418
|
+
export async function handleSearchNodes(bridge, args) {
|
|
2419
|
+
if (!bridge.isConnected()) {
|
|
2420
|
+
return {
|
|
2421
|
+
content: [{
|
|
2422
|
+
type: 'text',
|
|
2423
|
+
text: JSON.stringify({
|
|
2424
|
+
error: {
|
|
2425
|
+
code: 'NOT_CONNECTED',
|
|
2426
|
+
message: 'Figma plugin is not connected.'
|
|
2427
|
+
}
|
|
2428
|
+
}, null, 2)
|
|
2429
|
+
}],
|
|
2430
|
+
isError: true
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
const { parentId } = args;
|
|
2435
|
+
|
|
2436
|
+
if (!parentId) {
|
|
2437
|
+
return {
|
|
2438
|
+
content: [{
|
|
2439
|
+
type: 'text',
|
|
2440
|
+
text: JSON.stringify({
|
|
2441
|
+
error: {
|
|
2442
|
+
code: 'INVALID_PARAMS',
|
|
2443
|
+
message: 'parentId is required to scope the search'
|
|
2444
|
+
}
|
|
2445
|
+
}, null, 2)
|
|
2446
|
+
}],
|
|
2447
|
+
isError: true
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
try {
|
|
2452
|
+
const result = await bridge.sendCommand('search_nodes', args);
|
|
2453
|
+
return {
|
|
2454
|
+
content: [{
|
|
2455
|
+
type: 'text',
|
|
2456
|
+
text: JSON.stringify(result, null, 2)
|
|
2457
|
+
}]
|
|
2458
|
+
};
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
return {
|
|
2461
|
+
content: [{
|
|
2462
|
+
type: 'text',
|
|
2463
|
+
text: JSON.stringify({
|
|
2464
|
+
error: {
|
|
2465
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2466
|
+
message: error.message
|
|
2467
|
+
}
|
|
2468
|
+
}, null, 2)
|
|
2469
|
+
}],
|
|
2470
|
+
isError: true
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/**
|
|
2476
|
+
* Search local components by name
|
|
2477
|
+
*/
|
|
2478
|
+
export async function handleSearchComponents(bridge, args) {
|
|
2479
|
+
if (!bridge.isConnected()) {
|
|
2480
|
+
return {
|
|
2481
|
+
content: [{
|
|
2482
|
+
type: 'text',
|
|
2483
|
+
text: JSON.stringify({
|
|
2484
|
+
error: {
|
|
2485
|
+
code: 'NOT_CONNECTED',
|
|
2486
|
+
message: 'Figma plugin is not connected.'
|
|
2487
|
+
}
|
|
2488
|
+
}, null, 2)
|
|
2489
|
+
}],
|
|
2490
|
+
isError: true
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
try {
|
|
2495
|
+
const result = await bridge.sendCommand('search_components', args);
|
|
2496
|
+
return {
|
|
2497
|
+
content: [{
|
|
2498
|
+
type: 'text',
|
|
2499
|
+
text: JSON.stringify(result, null, 2)
|
|
2500
|
+
}]
|
|
2501
|
+
};
|
|
2502
|
+
} catch (error) {
|
|
2503
|
+
return {
|
|
2504
|
+
content: [{
|
|
2505
|
+
type: 'text',
|
|
2506
|
+
text: JSON.stringify({
|
|
2507
|
+
error: {
|
|
2508
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2509
|
+
message: error.message
|
|
2510
|
+
}
|
|
2511
|
+
}, null, 2)
|
|
2512
|
+
}],
|
|
2513
|
+
isError: true
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
/**
|
|
2519
|
+
* Search local styles by name
|
|
2520
|
+
*/
|
|
2521
|
+
export async function handleSearchStyles(bridge, args) {
|
|
2522
|
+
if (!bridge.isConnected()) {
|
|
2523
|
+
return {
|
|
2524
|
+
content: [{
|
|
2525
|
+
type: 'text',
|
|
2526
|
+
text: JSON.stringify({
|
|
2527
|
+
error: {
|
|
2528
|
+
code: 'NOT_CONNECTED',
|
|
2529
|
+
message: 'Figma plugin is not connected.'
|
|
2530
|
+
}
|
|
2531
|
+
}, null, 2)
|
|
2532
|
+
}],
|
|
2533
|
+
isError: true
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
try {
|
|
2538
|
+
const result = await bridge.sendCommand('search_styles', args);
|
|
2539
|
+
return {
|
|
2540
|
+
content: [{
|
|
2541
|
+
type: 'text',
|
|
2542
|
+
text: JSON.stringify(result, null, 2)
|
|
2543
|
+
}]
|
|
2544
|
+
};
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
return {
|
|
2547
|
+
content: [{
|
|
2548
|
+
type: 'text',
|
|
2549
|
+
text: JSON.stringify({
|
|
2550
|
+
error: {
|
|
2551
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2552
|
+
message: error.message
|
|
2553
|
+
}
|
|
2554
|
+
}, null, 2)
|
|
2555
|
+
}],
|
|
2556
|
+
isError: true
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
/**
|
|
2562
|
+
* Get immediate children of a node
|
|
2563
|
+
*/
|
|
2564
|
+
export async function handleGetChildren(bridge, args) {
|
|
2565
|
+
if (!bridge.isConnected()) {
|
|
2566
|
+
return {
|
|
2567
|
+
content: [{
|
|
2568
|
+
type: 'text',
|
|
2569
|
+
text: JSON.stringify({
|
|
2570
|
+
error: {
|
|
2571
|
+
code: 'NOT_CONNECTED',
|
|
2572
|
+
message: 'Figma plugin is not connected.'
|
|
2573
|
+
}
|
|
2574
|
+
}, null, 2)
|
|
2575
|
+
}],
|
|
2576
|
+
isError: true
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
const { parentId } = args;
|
|
2581
|
+
|
|
2582
|
+
if (!parentId) {
|
|
2583
|
+
return {
|
|
2584
|
+
content: [{
|
|
2585
|
+
type: 'text',
|
|
2586
|
+
text: JSON.stringify({
|
|
2587
|
+
error: {
|
|
2588
|
+
code: 'INVALID_PARAMS',
|
|
2589
|
+
message: 'parentId is required'
|
|
2590
|
+
}
|
|
2591
|
+
}, null, 2)
|
|
2592
|
+
}],
|
|
2593
|
+
isError: true
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
try {
|
|
2598
|
+
const result = await bridge.sendCommand('get_children', args);
|
|
2599
|
+
return {
|
|
2600
|
+
content: [{
|
|
2601
|
+
type: 'text',
|
|
2602
|
+
text: JSON.stringify(result, null, 2)
|
|
2603
|
+
}]
|
|
2604
|
+
};
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
return {
|
|
2607
|
+
content: [{
|
|
2608
|
+
type: 'text',
|
|
2609
|
+
text: JSON.stringify({
|
|
2610
|
+
error: {
|
|
2611
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2612
|
+
message: error.message
|
|
2613
|
+
}
|
|
2614
|
+
}, null, 2)
|
|
2615
|
+
}],
|
|
2616
|
+
isError: true
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
/**
|
|
2622
|
+
* Set text style properties on an existing text node
|
|
2623
|
+
*/
|
|
2624
|
+
export async function handleSetTextStyle(bridge, args) {
|
|
2625
|
+
if (!bridge.isConnected()) {
|
|
2626
|
+
return {
|
|
2627
|
+
content: [{
|
|
2628
|
+
type: 'text',
|
|
2629
|
+
text: JSON.stringify({
|
|
2630
|
+
error: {
|
|
2631
|
+
code: 'NOT_CONNECTED',
|
|
2632
|
+
message: 'Figma plugin is not connected.'
|
|
2633
|
+
}
|
|
2634
|
+
}, null, 2)
|
|
2635
|
+
}],
|
|
2636
|
+
isError: true
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
const { nodeId } = args;
|
|
2641
|
+
|
|
2642
|
+
if (!nodeId) {
|
|
2643
|
+
return {
|
|
2644
|
+
content: [{
|
|
2645
|
+
type: 'text',
|
|
2646
|
+
text: JSON.stringify({
|
|
2647
|
+
error: {
|
|
2648
|
+
code: 'INVALID_PARAMS',
|
|
2649
|
+
message: 'nodeId is required'
|
|
2650
|
+
}
|
|
2651
|
+
}, null, 2)
|
|
2652
|
+
}],
|
|
2653
|
+
isError: true
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
try {
|
|
2658
|
+
const result = await bridge.sendCommand('set_text_style', args);
|
|
2659
|
+
return {
|
|
2660
|
+
content: [{
|
|
2661
|
+
type: 'text',
|
|
2662
|
+
text: JSON.stringify(result, null, 2)
|
|
2663
|
+
}]
|
|
2664
|
+
};
|
|
2665
|
+
} catch (error) {
|
|
2666
|
+
return {
|
|
2667
|
+
content: [{
|
|
2668
|
+
type: 'text',
|
|
2669
|
+
text: JSON.stringify({
|
|
2670
|
+
error: {
|
|
2671
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2672
|
+
message: error.message
|
|
2673
|
+
}
|
|
2674
|
+
}, null, 2)
|
|
2675
|
+
}],
|
|
2676
|
+
isError: true
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
/**
|
|
2682
|
+
* Create a new local paint style
|
|
2683
|
+
*/
|
|
2684
|
+
export async function handleCreatePaintStyle(bridge, args) {
|
|
2685
|
+
if (!bridge.isConnected()) {
|
|
2686
|
+
return {
|
|
2687
|
+
content: [{
|
|
2688
|
+
type: 'text',
|
|
2689
|
+
text: JSON.stringify({
|
|
2690
|
+
error: {
|
|
2691
|
+
code: 'NOT_CONNECTED',
|
|
2692
|
+
message: 'Figma plugin is not connected.'
|
|
2693
|
+
}
|
|
2694
|
+
}, null, 2)
|
|
2695
|
+
}],
|
|
2696
|
+
isError: true
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
const { name, fills } = args;
|
|
2701
|
+
|
|
2702
|
+
if (!name || !fills) {
|
|
2703
|
+
return {
|
|
2704
|
+
content: [{
|
|
2705
|
+
type: 'text',
|
|
2706
|
+
text: JSON.stringify({
|
|
2707
|
+
error: {
|
|
2708
|
+
code: 'INVALID_PARAMS',
|
|
2709
|
+
message: 'name and fills are required'
|
|
2710
|
+
}
|
|
2711
|
+
}, null, 2)
|
|
2712
|
+
}],
|
|
2713
|
+
isError: true
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
try {
|
|
2718
|
+
const result = await bridge.sendCommand('create_paint_style', args);
|
|
2719
|
+
return {
|
|
2720
|
+
content: [{
|
|
2721
|
+
type: 'text',
|
|
2722
|
+
text: JSON.stringify(result, null, 2)
|
|
2723
|
+
}]
|
|
2724
|
+
};
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
return {
|
|
2727
|
+
content: [{
|
|
2728
|
+
type: 'text',
|
|
2729
|
+
text: JSON.stringify({
|
|
2730
|
+
error: {
|
|
2731
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2732
|
+
message: error.message
|
|
2733
|
+
}
|
|
2734
|
+
}, null, 2)
|
|
2735
|
+
}],
|
|
2736
|
+
isError: true
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
/**
|
|
2742
|
+
* Create a new local text style
|
|
2743
|
+
*/
|
|
2744
|
+
export async function handleCreateTextStyle(bridge, args) {
|
|
2745
|
+
if (!bridge.isConnected()) {
|
|
2746
|
+
return {
|
|
2747
|
+
content: [{
|
|
2748
|
+
type: 'text',
|
|
2749
|
+
text: JSON.stringify({
|
|
2750
|
+
error: {
|
|
2751
|
+
code: 'NOT_CONNECTED',
|
|
2752
|
+
message: 'Figma plugin is not connected.'
|
|
2753
|
+
}
|
|
2754
|
+
}, null, 2)
|
|
2755
|
+
}],
|
|
2756
|
+
isError: true
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
const { name } = args;
|
|
2761
|
+
|
|
2762
|
+
if (!name) {
|
|
2763
|
+
return {
|
|
2764
|
+
content: [{
|
|
2765
|
+
type: 'text',
|
|
2766
|
+
text: JSON.stringify({
|
|
2767
|
+
error: {
|
|
2768
|
+
code: 'INVALID_PARAMS',
|
|
2769
|
+
message: 'name is required'
|
|
2770
|
+
}
|
|
2771
|
+
}, null, 2)
|
|
2772
|
+
}],
|
|
2773
|
+
isError: true
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
try {
|
|
2778
|
+
const result = await bridge.sendCommand('create_text_style', args);
|
|
2779
|
+
return {
|
|
2780
|
+
content: [{
|
|
2781
|
+
type: 'text',
|
|
2782
|
+
text: JSON.stringify(result, null, 2)
|
|
2783
|
+
}]
|
|
2784
|
+
};
|
|
2785
|
+
} catch (error) {
|
|
2786
|
+
return {
|
|
2787
|
+
content: [{
|
|
2788
|
+
type: 'text',
|
|
2789
|
+
text: JSON.stringify({
|
|
2790
|
+
error: {
|
|
2791
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2792
|
+
message: error.message
|
|
2793
|
+
}
|
|
2794
|
+
}, null, 2)
|
|
2795
|
+
}],
|
|
2796
|
+
isError: true
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
/**
|
|
2802
|
+
* Create a new variable collection
|
|
2803
|
+
*/
|
|
2804
|
+
export async function handleCreateVariableCollection(bridge, args) {
|
|
2805
|
+
if (!bridge.isConnected()) {
|
|
2806
|
+
return {
|
|
2807
|
+
content: [{
|
|
2808
|
+
type: 'text',
|
|
2809
|
+
text: JSON.stringify({
|
|
2810
|
+
error: {
|
|
2811
|
+
code: 'NOT_CONNECTED',
|
|
2812
|
+
message: 'Figma plugin is not connected.'
|
|
2813
|
+
}
|
|
2814
|
+
}, null, 2)
|
|
2815
|
+
}],
|
|
2816
|
+
isError: true
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
const { name } = args;
|
|
2821
|
+
|
|
2822
|
+
if (!name) {
|
|
2823
|
+
return {
|
|
2824
|
+
content: [{
|
|
2825
|
+
type: 'text',
|
|
2826
|
+
text: JSON.stringify({
|
|
2827
|
+
error: {
|
|
2828
|
+
code: 'INVALID_PARAMS',
|
|
2829
|
+
message: 'name is required'
|
|
2830
|
+
}
|
|
2831
|
+
}, null, 2)
|
|
2832
|
+
}],
|
|
2833
|
+
isError: true
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
try {
|
|
2838
|
+
const result = await bridge.sendCommand('create_variable_collection', args);
|
|
2839
|
+
return {
|
|
2840
|
+
content: [{
|
|
2841
|
+
type: 'text',
|
|
2842
|
+
text: JSON.stringify(result, null, 2)
|
|
2843
|
+
}]
|
|
2844
|
+
};
|
|
2845
|
+
} catch (error) {
|
|
2846
|
+
return {
|
|
2847
|
+
content: [{
|
|
2848
|
+
type: 'text',
|
|
2849
|
+
text: JSON.stringify({
|
|
2850
|
+
error: {
|
|
2851
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2852
|
+
message: error.message
|
|
2853
|
+
}
|
|
2854
|
+
}, null, 2)
|
|
2855
|
+
}],
|
|
2856
|
+
isError: true
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
/**
|
|
2862
|
+
* Create a new variable in a collection
|
|
2863
|
+
*/
|
|
2864
|
+
export async function handleCreateVariable(bridge, args) {
|
|
2865
|
+
if (!bridge.isConnected()) {
|
|
2866
|
+
return {
|
|
2867
|
+
content: [{
|
|
2868
|
+
type: 'text',
|
|
2869
|
+
text: JSON.stringify({
|
|
2870
|
+
error: {
|
|
2871
|
+
code: 'NOT_CONNECTED',
|
|
2872
|
+
message: 'Figma plugin is not connected.'
|
|
2873
|
+
}
|
|
2874
|
+
}, null, 2)
|
|
2875
|
+
}],
|
|
2876
|
+
isError: true
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
const { collectionId, name, type } = args;
|
|
2881
|
+
|
|
2882
|
+
if (!collectionId || !name || !type) {
|
|
2883
|
+
return {
|
|
2884
|
+
content: [{
|
|
2885
|
+
type: 'text',
|
|
2886
|
+
text: JSON.stringify({
|
|
2887
|
+
error: {
|
|
2888
|
+
code: 'INVALID_PARAMS',
|
|
2889
|
+
message: 'collectionId, name, and type are required'
|
|
2890
|
+
}
|
|
2891
|
+
}, null, 2)
|
|
2892
|
+
}],
|
|
2893
|
+
isError: true
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
try {
|
|
2898
|
+
const result = await bridge.sendCommand('create_variable', args);
|
|
2899
|
+
return {
|
|
2900
|
+
content: [{
|
|
2901
|
+
type: 'text',
|
|
2902
|
+
text: JSON.stringify(result, null, 2)
|
|
2903
|
+
}]
|
|
2904
|
+
};
|
|
2905
|
+
} catch (error) {
|
|
2906
|
+
return {
|
|
2907
|
+
content: [{
|
|
2908
|
+
type: 'text',
|
|
2909
|
+
text: JSON.stringify({
|
|
2910
|
+
error: {
|
|
2911
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2912
|
+
message: error.message
|
|
2913
|
+
}
|
|
2914
|
+
}, null, 2)
|
|
2915
|
+
}],
|
|
2916
|
+
isError: true
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
/**
|
|
2922
|
+
* Rename an existing variable
|
|
2923
|
+
*/
|
|
2924
|
+
export async function handleRenameVariable(bridge, args) {
|
|
2925
|
+
if (!bridge.isConnected()) {
|
|
2926
|
+
return {
|
|
2927
|
+
content: [{
|
|
2928
|
+
type: 'text',
|
|
2929
|
+
text: JSON.stringify({
|
|
2930
|
+
error: {
|
|
2931
|
+
code: 'NOT_CONNECTED',
|
|
2932
|
+
message: 'Figma plugin is not connected.'
|
|
2933
|
+
}
|
|
2934
|
+
}, null, 2)
|
|
2935
|
+
}],
|
|
2936
|
+
isError: true
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
const { variableId, name } = args;
|
|
2941
|
+
|
|
2942
|
+
if (!variableId || !name) {
|
|
2943
|
+
return {
|
|
2944
|
+
content: [{
|
|
2945
|
+
type: 'text',
|
|
2946
|
+
text: JSON.stringify({
|
|
2947
|
+
error: {
|
|
2948
|
+
code: 'INVALID_PARAMS',
|
|
2949
|
+
message: 'variableId and name are required'
|
|
2950
|
+
}
|
|
2951
|
+
}, null, 2)
|
|
2952
|
+
}],
|
|
2953
|
+
isError: true
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
try {
|
|
2958
|
+
const result = await bridge.sendCommand('rename_variable', args);
|
|
2959
|
+
return {
|
|
2960
|
+
content: [{
|
|
2961
|
+
type: 'text',
|
|
2962
|
+
text: JSON.stringify(result, null, 2)
|
|
2963
|
+
}]
|
|
2964
|
+
};
|
|
2965
|
+
} catch (error) {
|
|
2966
|
+
return {
|
|
2967
|
+
content: [{
|
|
2968
|
+
type: 'text',
|
|
2969
|
+
text: JSON.stringify({
|
|
2970
|
+
error: {
|
|
2971
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
2972
|
+
message: error.message
|
|
2973
|
+
}
|
|
2974
|
+
}, null, 2)
|
|
2975
|
+
}],
|
|
2976
|
+
isError: true
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
/**
|
|
2982
|
+
* Delete one or more variables
|
|
2983
|
+
*/
|
|
2984
|
+
export async function handleDeleteVariables(bridge, args) {
|
|
2985
|
+
if (!bridge.isConnected()) {
|
|
2986
|
+
return {
|
|
2987
|
+
content: [{
|
|
2988
|
+
type: 'text',
|
|
2989
|
+
text: JSON.stringify({
|
|
2990
|
+
error: {
|
|
2991
|
+
code: 'NOT_CONNECTED',
|
|
2992
|
+
message: 'Figma plugin is not connected.'
|
|
2993
|
+
}
|
|
2994
|
+
}, null, 2)
|
|
2995
|
+
}],
|
|
2996
|
+
isError: true
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
const { variableIds } = args;
|
|
3001
|
+
|
|
3002
|
+
if (!variableIds || !Array.isArray(variableIds) || variableIds.length === 0) {
|
|
3003
|
+
return {
|
|
3004
|
+
content: [{
|
|
3005
|
+
type: 'text',
|
|
3006
|
+
text: JSON.stringify({
|
|
3007
|
+
error: {
|
|
3008
|
+
code: 'INVALID_PARAMS',
|
|
3009
|
+
message: 'variableIds array is required'
|
|
3010
|
+
}
|
|
3011
|
+
}, null, 2)
|
|
3012
|
+
}],
|
|
3013
|
+
isError: true
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
try {
|
|
3018
|
+
const result = await bridge.sendCommand('delete_variables', args);
|
|
3019
|
+
return {
|
|
3020
|
+
content: [{
|
|
3021
|
+
type: 'text',
|
|
3022
|
+
text: JSON.stringify(result, null, 2)
|
|
3023
|
+
}]
|
|
3024
|
+
};
|
|
3025
|
+
} catch (error) {
|
|
3026
|
+
return {
|
|
3027
|
+
content: [{
|
|
3028
|
+
type: 'text',
|
|
3029
|
+
text: JSON.stringify({
|
|
3030
|
+
error: {
|
|
3031
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3032
|
+
message: error.message
|
|
3033
|
+
}
|
|
3034
|
+
}, null, 2)
|
|
3035
|
+
}],
|
|
3036
|
+
isError: true
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
/**
|
|
3042
|
+
* Delete a variable collection
|
|
3043
|
+
*/
|
|
3044
|
+
export async function handleDeleteVariableCollection(bridge, args) {
|
|
3045
|
+
if (!bridge.isConnected()) {
|
|
3046
|
+
return {
|
|
3047
|
+
content: [{
|
|
3048
|
+
type: 'text',
|
|
3049
|
+
text: JSON.stringify({
|
|
3050
|
+
error: {
|
|
3051
|
+
code: 'NOT_CONNECTED',
|
|
3052
|
+
message: 'Figma plugin is not connected.'
|
|
3053
|
+
}
|
|
3054
|
+
}, null, 2)
|
|
3055
|
+
}],
|
|
3056
|
+
isError: true
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
const { collectionId } = args;
|
|
3061
|
+
|
|
3062
|
+
if (!collectionId) {
|
|
3063
|
+
return {
|
|
3064
|
+
content: [{
|
|
3065
|
+
type: 'text',
|
|
3066
|
+
text: JSON.stringify({
|
|
3067
|
+
error: {
|
|
3068
|
+
code: 'INVALID_PARAMS',
|
|
3069
|
+
message: 'collectionId is required'
|
|
3070
|
+
}
|
|
3071
|
+
}, null, 2)
|
|
3072
|
+
}],
|
|
3073
|
+
isError: true
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
try {
|
|
3078
|
+
const result = await bridge.sendCommand('delete_variable_collection', args);
|
|
3079
|
+
return {
|
|
3080
|
+
content: [{
|
|
3081
|
+
type: 'text',
|
|
3082
|
+
text: JSON.stringify(result, null, 2)
|
|
3083
|
+
}]
|
|
3084
|
+
};
|
|
3085
|
+
} catch (error) {
|
|
3086
|
+
return {
|
|
3087
|
+
content: [{
|
|
3088
|
+
type: 'text',
|
|
3089
|
+
text: JSON.stringify({
|
|
3090
|
+
error: {
|
|
3091
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3092
|
+
message: error.message
|
|
3093
|
+
}
|
|
3094
|
+
}, null, 2)
|
|
3095
|
+
}],
|
|
3096
|
+
isError: true
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
/**
|
|
3102
|
+
* Rename a variable collection
|
|
3103
|
+
*/
|
|
3104
|
+
export async function handleRenameVariableCollection(bridge, args) {
|
|
3105
|
+
if (!bridge.isConnected()) {
|
|
3106
|
+
return {
|
|
3107
|
+
content: [{
|
|
3108
|
+
type: 'text',
|
|
3109
|
+
text: JSON.stringify({
|
|
3110
|
+
error: {
|
|
3111
|
+
code: 'NOT_CONNECTED',
|
|
3112
|
+
message: 'Figma plugin is not connected.'
|
|
3113
|
+
}
|
|
3114
|
+
}, null, 2)
|
|
3115
|
+
}],
|
|
3116
|
+
isError: true
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
const { collectionId, name } = args;
|
|
3121
|
+
|
|
3122
|
+
if (!collectionId || !name) {
|
|
3123
|
+
return {
|
|
3124
|
+
content: [{
|
|
3125
|
+
type: 'text',
|
|
3126
|
+
text: JSON.stringify({
|
|
3127
|
+
error: {
|
|
3128
|
+
code: 'INVALID_PARAMS',
|
|
3129
|
+
message: 'collectionId and name are required'
|
|
3130
|
+
}
|
|
3131
|
+
}, null, 2)
|
|
3132
|
+
}],
|
|
3133
|
+
isError: true
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
try {
|
|
3138
|
+
const result = await bridge.sendCommand('rename_variable_collection', args);
|
|
3139
|
+
return {
|
|
3140
|
+
content: [{
|
|
3141
|
+
type: 'text',
|
|
3142
|
+
text: JSON.stringify(result, null, 2)
|
|
3143
|
+
}]
|
|
3144
|
+
};
|
|
3145
|
+
} catch (error) {
|
|
3146
|
+
return {
|
|
3147
|
+
content: [{
|
|
3148
|
+
type: 'text',
|
|
3149
|
+
text: JSON.stringify({
|
|
3150
|
+
error: {
|
|
3151
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3152
|
+
message: error.message
|
|
3153
|
+
}
|
|
3154
|
+
}, null, 2)
|
|
3155
|
+
}],
|
|
3156
|
+
isError: true
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
/**
|
|
3162
|
+
* Rename a mode in a variable collection
|
|
3163
|
+
*/
|
|
3164
|
+
export async function handleRenameMode(bridge, args) {
|
|
3165
|
+
if (!bridge.isConnected()) {
|
|
3166
|
+
return {
|
|
3167
|
+
content: [{
|
|
3168
|
+
type: 'text',
|
|
3169
|
+
text: JSON.stringify({
|
|
3170
|
+
error: {
|
|
3171
|
+
code: 'NOT_CONNECTED',
|
|
3172
|
+
message: 'Figma plugin is not connected.'
|
|
3173
|
+
}
|
|
3174
|
+
}, null, 2)
|
|
3175
|
+
}],
|
|
3176
|
+
isError: true
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
const { collectionId, modeId, name } = args;
|
|
3181
|
+
|
|
3182
|
+
if (!collectionId || !modeId || !name) {
|
|
3183
|
+
return {
|
|
3184
|
+
content: [{
|
|
3185
|
+
type: 'text',
|
|
3186
|
+
text: JSON.stringify({
|
|
3187
|
+
error: {
|
|
3188
|
+
code: 'INVALID_PARAMS',
|
|
3189
|
+
message: 'collectionId, modeId, and name are required'
|
|
3190
|
+
}
|
|
3191
|
+
}, null, 2)
|
|
3192
|
+
}],
|
|
3193
|
+
isError: true
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
try {
|
|
3198
|
+
const result = await bridge.sendCommand('rename_mode', args);
|
|
3199
|
+
return {
|
|
3200
|
+
content: [{
|
|
3201
|
+
type: 'text',
|
|
3202
|
+
text: JSON.stringify(result, null, 2)
|
|
3203
|
+
}]
|
|
3204
|
+
};
|
|
3205
|
+
} catch (error) {
|
|
3206
|
+
return {
|
|
3207
|
+
content: [{
|
|
3208
|
+
type: 'text',
|
|
3209
|
+
text: JSON.stringify({
|
|
3210
|
+
error: {
|
|
3211
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3212
|
+
message: error.message
|
|
3213
|
+
}
|
|
3214
|
+
}, null, 2)
|
|
3215
|
+
}],
|
|
3216
|
+
isError: true
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
/**
|
|
3222
|
+
* Add a mode to a variable collection
|
|
3223
|
+
*/
|
|
3224
|
+
export async function handleAddMode(bridge, args) {
|
|
3225
|
+
if (!bridge.isConnected()) {
|
|
3226
|
+
return {
|
|
3227
|
+
content: [{
|
|
3228
|
+
type: 'text',
|
|
3229
|
+
text: JSON.stringify({
|
|
3230
|
+
error: {
|
|
3231
|
+
code: 'NOT_CONNECTED',
|
|
3232
|
+
message: 'Figma plugin is not connected.'
|
|
3233
|
+
}
|
|
3234
|
+
}, null, 2)
|
|
3235
|
+
}],
|
|
3236
|
+
isError: true
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
const { collectionId, name } = args;
|
|
3241
|
+
|
|
3242
|
+
if (!collectionId || !name) {
|
|
3243
|
+
return {
|
|
3244
|
+
content: [{
|
|
3245
|
+
type: 'text',
|
|
3246
|
+
text: JSON.stringify({
|
|
3247
|
+
error: {
|
|
3248
|
+
code: 'INVALID_PARAMS',
|
|
3249
|
+
message: 'collectionId and name are required'
|
|
3250
|
+
}
|
|
3251
|
+
}, null, 2)
|
|
3252
|
+
}],
|
|
3253
|
+
isError: true
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
try {
|
|
3258
|
+
const result = await bridge.sendCommand('add_mode', args);
|
|
3259
|
+
return {
|
|
3260
|
+
content: [{
|
|
3261
|
+
type: 'text',
|
|
3262
|
+
text: JSON.stringify(result, null, 2)
|
|
3263
|
+
}]
|
|
3264
|
+
};
|
|
3265
|
+
} catch (error) {
|
|
3266
|
+
return {
|
|
3267
|
+
content: [{
|
|
3268
|
+
type: 'text',
|
|
3269
|
+
text: JSON.stringify({
|
|
3270
|
+
error: {
|
|
3271
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3272
|
+
message: error.message
|
|
3273
|
+
}
|
|
3274
|
+
}, null, 2)
|
|
3275
|
+
}],
|
|
3276
|
+
isError: true
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
/**
|
|
3282
|
+
* Delete a mode from a variable collection
|
|
3283
|
+
*/
|
|
3284
|
+
export async function handleDeleteMode(bridge, args) {
|
|
3285
|
+
if (!bridge.isConnected()) {
|
|
3286
|
+
return {
|
|
3287
|
+
content: [{
|
|
3288
|
+
type: 'text',
|
|
3289
|
+
text: JSON.stringify({
|
|
3290
|
+
error: {
|
|
3291
|
+
code: 'NOT_CONNECTED',
|
|
3292
|
+
message: 'Figma plugin is not connected.'
|
|
3293
|
+
}
|
|
3294
|
+
}, null, 2)
|
|
3295
|
+
}],
|
|
3296
|
+
isError: true
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
const { collectionId, modeId } = args;
|
|
3301
|
+
|
|
3302
|
+
if (!collectionId || !modeId) {
|
|
3303
|
+
return {
|
|
3304
|
+
content: [{
|
|
3305
|
+
type: 'text',
|
|
3306
|
+
text: JSON.stringify({
|
|
3307
|
+
error: {
|
|
3308
|
+
code: 'INVALID_PARAMS',
|
|
3309
|
+
message: 'collectionId and modeId are required'
|
|
3310
|
+
}
|
|
3311
|
+
}, null, 2)
|
|
3312
|
+
}],
|
|
3313
|
+
isError: true
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
try {
|
|
3318
|
+
const result = await bridge.sendCommand('delete_mode', args);
|
|
3319
|
+
return {
|
|
3320
|
+
content: [{
|
|
3321
|
+
type: 'text',
|
|
3322
|
+
text: JSON.stringify(result, null, 2)
|
|
3323
|
+
}]
|
|
3324
|
+
};
|
|
3325
|
+
} catch (error) {
|
|
3326
|
+
return {
|
|
3327
|
+
content: [{
|
|
3328
|
+
type: 'text',
|
|
3329
|
+
text: JSON.stringify({
|
|
3330
|
+
error: {
|
|
3331
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3332
|
+
message: error.message
|
|
3333
|
+
}
|
|
3334
|
+
}, null, 2)
|
|
3335
|
+
}],
|
|
3336
|
+
isError: true
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
/**
|
|
3342
|
+
* Unbind a variable from a node property
|
|
3343
|
+
*/
|
|
3344
|
+
export async function handleUnbindVariable(bridge, args) {
|
|
3345
|
+
if (!bridge.isConnected()) {
|
|
3346
|
+
return {
|
|
3347
|
+
content: [{
|
|
3348
|
+
type: 'text',
|
|
3349
|
+
text: JSON.stringify({
|
|
3350
|
+
error: {
|
|
3351
|
+
code: 'NOT_CONNECTED',
|
|
3352
|
+
message: 'Figma plugin is not connected.'
|
|
3353
|
+
}
|
|
3354
|
+
}, null, 2)
|
|
3355
|
+
}],
|
|
3356
|
+
isError: true
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
const { nodeId, field } = args;
|
|
3361
|
+
|
|
3362
|
+
if (!nodeId || !field) {
|
|
3363
|
+
return {
|
|
3364
|
+
content: [{
|
|
3365
|
+
type: 'text',
|
|
3366
|
+
text: JSON.stringify({
|
|
3367
|
+
error: {
|
|
3368
|
+
code: 'INVALID_PARAMS',
|
|
3369
|
+
message: 'nodeId and field are required'
|
|
3370
|
+
}
|
|
3371
|
+
}, null, 2)
|
|
3372
|
+
}],
|
|
3373
|
+
isError: true
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
try {
|
|
3378
|
+
const result = await bridge.sendCommand('unbind_variable', args);
|
|
3379
|
+
return {
|
|
3380
|
+
content: [{
|
|
3381
|
+
type: 'text',
|
|
3382
|
+
text: JSON.stringify(result, null, 2)
|
|
3383
|
+
}]
|
|
3384
|
+
};
|
|
3385
|
+
} catch (error) {
|
|
3386
|
+
return {
|
|
3387
|
+
content: [{
|
|
3388
|
+
type: 'text',
|
|
3389
|
+
text: JSON.stringify({
|
|
3390
|
+
error: {
|
|
3391
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3392
|
+
message: error.message
|
|
3393
|
+
}
|
|
3394
|
+
}, null, 2)
|
|
3395
|
+
}],
|
|
3396
|
+
isError: true
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// ============================================================
|
|
3402
|
+
// Page Management Commands
|
|
3403
|
+
// ============================================================
|
|
3404
|
+
|
|
3405
|
+
/**
|
|
3406
|
+
* Create a new page
|
|
3407
|
+
*/
|
|
3408
|
+
export async function handleCreatePage(bridge, args) {
|
|
3409
|
+
if (!bridge.isConnected()) {
|
|
3410
|
+
return {
|
|
3411
|
+
content: [{
|
|
3412
|
+
type: 'text',
|
|
3413
|
+
text: JSON.stringify({
|
|
3414
|
+
error: {
|
|
3415
|
+
code: 'NOT_CONNECTED',
|
|
3416
|
+
message: 'Figma plugin is not connected.'
|
|
3417
|
+
}
|
|
3418
|
+
}, null, 2)
|
|
3419
|
+
}],
|
|
3420
|
+
isError: true
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
const { name } = args;
|
|
3425
|
+
|
|
3426
|
+
if (!name) {
|
|
3427
|
+
return {
|
|
3428
|
+
content: [{
|
|
3429
|
+
type: 'text',
|
|
3430
|
+
text: JSON.stringify({
|
|
3431
|
+
error: {
|
|
3432
|
+
code: 'INVALID_PARAMS',
|
|
3433
|
+
message: 'name is required'
|
|
3434
|
+
}
|
|
3435
|
+
}, null, 2)
|
|
3436
|
+
}],
|
|
3437
|
+
isError: true
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
try {
|
|
3442
|
+
const result = await bridge.sendCommand('create_page', args);
|
|
3443
|
+
return {
|
|
3444
|
+
content: [{
|
|
3445
|
+
type: 'text',
|
|
3446
|
+
text: JSON.stringify(result, null, 2)
|
|
3447
|
+
}]
|
|
3448
|
+
};
|
|
3449
|
+
} catch (error) {
|
|
3450
|
+
return {
|
|
3451
|
+
content: [{
|
|
3452
|
+
type: 'text',
|
|
3453
|
+
text: JSON.stringify({
|
|
3454
|
+
error: {
|
|
3455
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3456
|
+
message: error.message
|
|
3457
|
+
}
|
|
3458
|
+
}, null, 2)
|
|
3459
|
+
}],
|
|
3460
|
+
isError: true
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
/**
|
|
3466
|
+
* Rename a page
|
|
3467
|
+
*/
|
|
3468
|
+
export async function handleRenamePage(bridge, args) {
|
|
3469
|
+
if (!bridge.isConnected()) {
|
|
3470
|
+
return {
|
|
3471
|
+
content: [{
|
|
3472
|
+
type: 'text',
|
|
3473
|
+
text: JSON.stringify({
|
|
3474
|
+
error: {
|
|
3475
|
+
code: 'NOT_CONNECTED',
|
|
3476
|
+
message: 'Figma plugin is not connected.'
|
|
3477
|
+
}
|
|
3478
|
+
}, null, 2)
|
|
3479
|
+
}],
|
|
3480
|
+
isError: true
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
const { pageId, name } = args;
|
|
3485
|
+
|
|
3486
|
+
if (!pageId || !name) {
|
|
3487
|
+
return {
|
|
3488
|
+
content: [{
|
|
3489
|
+
type: 'text',
|
|
3490
|
+
text: JSON.stringify({
|
|
3491
|
+
error: {
|
|
3492
|
+
code: 'INVALID_PARAMS',
|
|
3493
|
+
message: 'pageId and name are required'
|
|
3494
|
+
}
|
|
3495
|
+
}, null, 2)
|
|
3496
|
+
}],
|
|
3497
|
+
isError: true
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
try {
|
|
3502
|
+
const result = await bridge.sendCommand('rename_page', args);
|
|
3503
|
+
return {
|
|
3504
|
+
content: [{
|
|
3505
|
+
type: 'text',
|
|
3506
|
+
text: JSON.stringify(result, null, 2)
|
|
3507
|
+
}]
|
|
3508
|
+
};
|
|
3509
|
+
} catch (error) {
|
|
3510
|
+
return {
|
|
3511
|
+
content: [{
|
|
3512
|
+
type: 'text',
|
|
3513
|
+
text: JSON.stringify({
|
|
3514
|
+
error: {
|
|
3515
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3516
|
+
message: error.message
|
|
3517
|
+
}
|
|
3518
|
+
}, null, 2)
|
|
3519
|
+
}],
|
|
3520
|
+
isError: true
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
/**
|
|
3526
|
+
* Delete a page
|
|
3527
|
+
*/
|
|
3528
|
+
export async function handleDeletePage(bridge, args) {
|
|
3529
|
+
if (!bridge.isConnected()) {
|
|
3530
|
+
return {
|
|
3531
|
+
content: [{
|
|
3532
|
+
type: 'text',
|
|
3533
|
+
text: JSON.stringify({
|
|
3534
|
+
error: {
|
|
3535
|
+
code: 'NOT_CONNECTED',
|
|
3536
|
+
message: 'Figma plugin is not connected.'
|
|
3537
|
+
}
|
|
3538
|
+
}, null, 2)
|
|
3539
|
+
}],
|
|
3540
|
+
isError: true
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
const { pageId } = args;
|
|
3545
|
+
|
|
3546
|
+
if (!pageId) {
|
|
3547
|
+
return {
|
|
3548
|
+
content: [{
|
|
3549
|
+
type: 'text',
|
|
3550
|
+
text: JSON.stringify({
|
|
3551
|
+
error: {
|
|
3552
|
+
code: 'INVALID_PARAMS',
|
|
3553
|
+
message: 'pageId is required'
|
|
3554
|
+
}
|
|
3555
|
+
}, null, 2)
|
|
3556
|
+
}],
|
|
3557
|
+
isError: true
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
try {
|
|
3562
|
+
const result = await bridge.sendCommand('delete_page', args);
|
|
3563
|
+
return {
|
|
3564
|
+
content: [{
|
|
3565
|
+
type: 'text',
|
|
3566
|
+
text: JSON.stringify(result, null, 2)
|
|
3567
|
+
}]
|
|
3568
|
+
};
|
|
3569
|
+
} catch (error) {
|
|
3570
|
+
return {
|
|
3571
|
+
content: [{
|
|
3572
|
+
type: 'text',
|
|
3573
|
+
text: JSON.stringify({
|
|
3574
|
+
error: {
|
|
3575
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3576
|
+
message: error.message
|
|
3577
|
+
}
|
|
3578
|
+
}, null, 2)
|
|
3579
|
+
}],
|
|
3580
|
+
isError: true
|
|
3581
|
+
};
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
/**
|
|
3586
|
+
* Reorder a page (change its position in the page list)
|
|
3587
|
+
*/
|
|
3588
|
+
export async function handleReorderPage(bridge, args) {
|
|
3589
|
+
if (!bridge.isConnected()) {
|
|
3590
|
+
return {
|
|
3591
|
+
content: [{
|
|
3592
|
+
type: 'text',
|
|
3593
|
+
text: JSON.stringify({
|
|
3594
|
+
error: {
|
|
3595
|
+
code: 'NOT_CONNECTED',
|
|
3596
|
+
message: 'Figma plugin is not connected.'
|
|
3597
|
+
}
|
|
3598
|
+
}, null, 2)
|
|
3599
|
+
}],
|
|
3600
|
+
isError: true
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
const { pageId, index } = args;
|
|
3605
|
+
|
|
3606
|
+
if (!pageId || index === undefined) {
|
|
3607
|
+
return {
|
|
3608
|
+
content: [{
|
|
3609
|
+
type: 'text',
|
|
3610
|
+
text: JSON.stringify({
|
|
3611
|
+
error: {
|
|
3612
|
+
code: 'INVALID_PARAMS',
|
|
3613
|
+
message: 'pageId and index are required'
|
|
3614
|
+
}
|
|
3615
|
+
}, null, 2)
|
|
3616
|
+
}],
|
|
3617
|
+
isError: true
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
try {
|
|
3622
|
+
const result = await bridge.sendCommand('reorder_page', args);
|
|
3623
|
+
return {
|
|
3624
|
+
content: [{
|
|
3625
|
+
type: 'text',
|
|
3626
|
+
text: JSON.stringify(result, null, 2)
|
|
3627
|
+
}]
|
|
3628
|
+
};
|
|
3629
|
+
} catch (error) {
|
|
3630
|
+
return {
|
|
3631
|
+
content: [{
|
|
3632
|
+
type: 'text',
|
|
3633
|
+
text: JSON.stringify({
|
|
3634
|
+
error: {
|
|
3635
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3636
|
+
message: error.message
|
|
3637
|
+
}
|
|
3638
|
+
}, null, 2)
|
|
3639
|
+
}],
|
|
3640
|
+
isError: true
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
// ============================================================
|
|
3646
|
+
// Node Structure Commands
|
|
3647
|
+
// ============================================================
|
|
3648
|
+
|
|
3649
|
+
/**
|
|
3650
|
+
* Reparent nodes - move nodes to a different parent
|
|
3651
|
+
*/
|
|
3652
|
+
export async function handleReparentNodes(bridge, args) {
|
|
3653
|
+
if (!bridge.isConnected()) {
|
|
3654
|
+
return {
|
|
3655
|
+
content: [{
|
|
3656
|
+
type: 'text',
|
|
3657
|
+
text: JSON.stringify({
|
|
3658
|
+
error: {
|
|
3659
|
+
code: 'NOT_CONNECTED',
|
|
3660
|
+
message: 'Figma plugin is not connected.'
|
|
3661
|
+
}
|
|
3662
|
+
}, null, 2)
|
|
3663
|
+
}],
|
|
3664
|
+
isError: true
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
const { nodeIds, newParentId } = args;
|
|
3669
|
+
|
|
3670
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
3671
|
+
return {
|
|
3672
|
+
content: [{
|
|
3673
|
+
type: 'text',
|
|
3674
|
+
text: JSON.stringify({
|
|
3675
|
+
error: {
|
|
3676
|
+
code: 'INVALID_PARAMS',
|
|
3677
|
+
message: 'nodeIds must be a non-empty array'
|
|
3678
|
+
}
|
|
3679
|
+
}, null, 2)
|
|
3680
|
+
}],
|
|
3681
|
+
isError: true
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
if (!newParentId) {
|
|
3686
|
+
return {
|
|
3687
|
+
content: [{
|
|
3688
|
+
type: 'text',
|
|
3689
|
+
text: JSON.stringify({
|
|
3690
|
+
error: {
|
|
3691
|
+
code: 'INVALID_PARAMS',
|
|
3692
|
+
message: 'newParentId is required'
|
|
3693
|
+
}
|
|
3694
|
+
}, null, 2)
|
|
3695
|
+
}],
|
|
3696
|
+
isError: true
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
try {
|
|
3701
|
+
const result = await bridge.sendCommand('reparent_nodes', args);
|
|
3702
|
+
return {
|
|
3703
|
+
content: [{
|
|
3704
|
+
type: 'text',
|
|
3705
|
+
text: JSON.stringify(result, null, 2)
|
|
3706
|
+
}]
|
|
3707
|
+
};
|
|
3708
|
+
} catch (error) {
|
|
3709
|
+
return {
|
|
3710
|
+
content: [{
|
|
3711
|
+
type: 'text',
|
|
3712
|
+
text: JSON.stringify({
|
|
3713
|
+
error: {
|
|
3714
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3715
|
+
message: error.message
|
|
3716
|
+
}
|
|
3717
|
+
}, null, 2)
|
|
3718
|
+
}],
|
|
3719
|
+
isError: true
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
/**
|
|
3725
|
+
* Move nodes to a different page
|
|
3726
|
+
*/
|
|
3727
|
+
export async function handleMoveToPage(bridge, args) {
|
|
3728
|
+
if (!bridge.isConnected()) {
|
|
3729
|
+
return {
|
|
3730
|
+
content: [{
|
|
3731
|
+
type: 'text',
|
|
3732
|
+
text: JSON.stringify({
|
|
3733
|
+
error: {
|
|
3734
|
+
code: 'NOT_CONNECTED',
|
|
3735
|
+
message: 'Figma plugin is not connected.'
|
|
3736
|
+
}
|
|
3737
|
+
}, null, 2)
|
|
3738
|
+
}],
|
|
3739
|
+
isError: true
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
const { nodeIds, targetPageId } = args;
|
|
3744
|
+
|
|
3745
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
3746
|
+
return {
|
|
3747
|
+
content: [{
|
|
3748
|
+
type: 'text',
|
|
3749
|
+
text: JSON.stringify({
|
|
3750
|
+
error: {
|
|
3751
|
+
code: 'INVALID_PARAMS',
|
|
3752
|
+
message: 'nodeIds must be a non-empty array'
|
|
3753
|
+
}
|
|
3754
|
+
}, null, 2)
|
|
3755
|
+
}],
|
|
3756
|
+
isError: true
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
if (!targetPageId) {
|
|
3761
|
+
return {
|
|
3762
|
+
content: [{
|
|
3763
|
+
type: 'text',
|
|
3764
|
+
text: JSON.stringify({
|
|
3765
|
+
error: {
|
|
3766
|
+
code: 'INVALID_PARAMS',
|
|
3767
|
+
message: 'targetPageId is required'
|
|
3768
|
+
}
|
|
3769
|
+
}, null, 2)
|
|
3770
|
+
}],
|
|
3771
|
+
isError: true
|
|
3772
|
+
};
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
try {
|
|
3776
|
+
const result = await bridge.sendCommand('move_to_page', args);
|
|
3777
|
+
return {
|
|
3778
|
+
content: [{
|
|
3779
|
+
type: 'text',
|
|
3780
|
+
text: JSON.stringify(result, null, 2)
|
|
3781
|
+
}]
|
|
3782
|
+
};
|
|
3783
|
+
} catch (error) {
|
|
3784
|
+
return {
|
|
3785
|
+
content: [{
|
|
3786
|
+
type: 'text',
|
|
3787
|
+
text: JSON.stringify({
|
|
3788
|
+
error: {
|
|
3789
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3790
|
+
message: error.message
|
|
3791
|
+
}
|
|
3792
|
+
}, null, 2)
|
|
3793
|
+
}],
|
|
3794
|
+
isError: true
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
// ============================================================
|
|
3800
|
+
// Component Instance Commands
|
|
3801
|
+
// ============================================================
|
|
3802
|
+
|
|
3803
|
+
/**
|
|
3804
|
+
* Swap an instance to use a different component
|
|
3805
|
+
*/
|
|
3806
|
+
export async function handleSwapInstance(bridge, args) {
|
|
3807
|
+
if (!bridge.isConnected()) {
|
|
3808
|
+
return {
|
|
3809
|
+
content: [{
|
|
3810
|
+
type: 'text',
|
|
3811
|
+
text: JSON.stringify({
|
|
3812
|
+
error: {
|
|
3813
|
+
code: 'NOT_CONNECTED',
|
|
3814
|
+
message: 'Figma plugin is not connected.'
|
|
3815
|
+
}
|
|
3816
|
+
}, null, 2)
|
|
3817
|
+
}],
|
|
3818
|
+
isError: true
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
const { instanceId, newComponentId } = args;
|
|
3823
|
+
|
|
3824
|
+
if (!instanceId) {
|
|
3825
|
+
return {
|
|
3826
|
+
content: [{
|
|
3827
|
+
type: 'text',
|
|
3828
|
+
text: JSON.stringify({
|
|
3829
|
+
error: {
|
|
3830
|
+
code: 'INVALID_PARAMS',
|
|
3831
|
+
message: 'instanceId is required'
|
|
3832
|
+
}
|
|
3833
|
+
}, null, 2)
|
|
3834
|
+
}],
|
|
3835
|
+
isError: true
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
if (!newComponentId) {
|
|
3840
|
+
return {
|
|
3841
|
+
content: [{
|
|
3842
|
+
type: 'text',
|
|
3843
|
+
text: JSON.stringify({
|
|
3844
|
+
error: {
|
|
3845
|
+
code: 'INVALID_PARAMS',
|
|
3846
|
+
message: 'newComponentId is required'
|
|
3847
|
+
}
|
|
3848
|
+
}, null, 2)
|
|
3849
|
+
}],
|
|
3850
|
+
isError: true
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
try {
|
|
3855
|
+
const result = await bridge.sendCommand('swap_instance', args);
|
|
3856
|
+
return {
|
|
3857
|
+
content: [{
|
|
3858
|
+
type: 'text',
|
|
3859
|
+
text: JSON.stringify(result, null, 2)
|
|
3860
|
+
}]
|
|
3861
|
+
};
|
|
3862
|
+
} catch (error) {
|
|
3863
|
+
return {
|
|
3864
|
+
content: [{
|
|
3865
|
+
type: 'text',
|
|
3866
|
+
text: JSON.stringify({
|
|
3867
|
+
error: {
|
|
3868
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3869
|
+
message: error.message
|
|
3870
|
+
}
|
|
3871
|
+
}, null, 2)
|
|
3872
|
+
}],
|
|
3873
|
+
isError: true
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
// ============================================================
|
|
3879
|
+
// Additional Commands
|
|
3880
|
+
// ============================================================
|
|
3881
|
+
|
|
3882
|
+
/**
|
|
3883
|
+
* Duplicate a page - clone entire page with all contents
|
|
3884
|
+
*/
|
|
3885
|
+
export async function handleDuplicatePage(bridge, args) {
|
|
3886
|
+
if (!bridge.isConnected()) {
|
|
3887
|
+
return {
|
|
3888
|
+
content: [{
|
|
3889
|
+
type: 'text',
|
|
3890
|
+
text: JSON.stringify({
|
|
3891
|
+
error: {
|
|
3892
|
+
code: 'NOT_CONNECTED',
|
|
3893
|
+
message: 'Figma plugin is not connected.'
|
|
3894
|
+
}
|
|
3895
|
+
}, null, 2)
|
|
3896
|
+
}],
|
|
3897
|
+
isError: true
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
const { pageId } = args;
|
|
3902
|
+
|
|
3903
|
+
if (!pageId) {
|
|
3904
|
+
return {
|
|
3905
|
+
content: [{
|
|
3906
|
+
type: 'text',
|
|
3907
|
+
text: JSON.stringify({
|
|
3908
|
+
error: {
|
|
3909
|
+
code: 'INVALID_PARAMS',
|
|
3910
|
+
message: 'pageId is required'
|
|
3911
|
+
}
|
|
3912
|
+
}, null, 2)
|
|
3913
|
+
}],
|
|
3914
|
+
isError: true
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
try {
|
|
3919
|
+
const result = await bridge.sendCommand('duplicate_page', args);
|
|
3920
|
+
return {
|
|
3921
|
+
content: [{
|
|
3922
|
+
type: 'text',
|
|
3923
|
+
text: JSON.stringify(result, null, 2)
|
|
3924
|
+
}]
|
|
3925
|
+
};
|
|
3926
|
+
} catch (error) {
|
|
3927
|
+
return {
|
|
3928
|
+
content: [{
|
|
3929
|
+
type: 'text',
|
|
3930
|
+
text: JSON.stringify({
|
|
3931
|
+
error: {
|
|
3932
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
3933
|
+
message: error.message
|
|
3934
|
+
}
|
|
3935
|
+
}, null, 2)
|
|
3936
|
+
}],
|
|
3937
|
+
isError: true
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
/**
|
|
3943
|
+
* Set rotation on nodes
|
|
3944
|
+
*/
|
|
3945
|
+
export async function handleSetRotation(bridge, args) {
|
|
3946
|
+
if (!bridge.isConnected()) {
|
|
3947
|
+
return {
|
|
3948
|
+
content: [{
|
|
3949
|
+
type: 'text',
|
|
3950
|
+
text: JSON.stringify({
|
|
3951
|
+
error: {
|
|
3952
|
+
code: 'NOT_CONNECTED',
|
|
3953
|
+
message: 'Figma plugin is not connected.'
|
|
3954
|
+
}
|
|
3955
|
+
}, null, 2)
|
|
3956
|
+
}],
|
|
3957
|
+
isError: true
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
const { nodeIds, rotation, pivot } = args;
|
|
3962
|
+
|
|
3963
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
3964
|
+
return {
|
|
3965
|
+
content: [{
|
|
3966
|
+
type: 'text',
|
|
3967
|
+
text: JSON.stringify({
|
|
3968
|
+
error: {
|
|
3969
|
+
code: 'INVALID_PARAMS',
|
|
3970
|
+
message: 'nodeIds must be a non-empty array'
|
|
3971
|
+
}
|
|
3972
|
+
}, null, 2)
|
|
3973
|
+
}],
|
|
3974
|
+
isError: true
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
if (pivot !== undefined && pivot !== 'center' && pivot !== 'top-left') {
|
|
3979
|
+
return invalidParams(`pivot must be "center" or "top-left", got "${pivot}"`);
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
if (rotation === undefined) {
|
|
3983
|
+
return {
|
|
3984
|
+
content: [{
|
|
3985
|
+
type: 'text',
|
|
3986
|
+
text: JSON.stringify({
|
|
3987
|
+
error: {
|
|
3988
|
+
code: 'INVALID_PARAMS',
|
|
3989
|
+
message: 'rotation is required'
|
|
3990
|
+
}
|
|
3991
|
+
}, null, 2)
|
|
3992
|
+
}],
|
|
3993
|
+
isError: true
|
|
3994
|
+
};
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
try {
|
|
3998
|
+
const result = await bridge.sendCommand('set_rotation', args);
|
|
3999
|
+
return {
|
|
4000
|
+
content: [{
|
|
4001
|
+
type: 'text',
|
|
4002
|
+
text: JSON.stringify(result, null, 2)
|
|
4003
|
+
}]
|
|
4004
|
+
};
|
|
4005
|
+
} catch (error) {
|
|
4006
|
+
return {
|
|
4007
|
+
content: [{
|
|
4008
|
+
type: 'text',
|
|
4009
|
+
text: JSON.stringify({
|
|
4010
|
+
error: {
|
|
4011
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
4012
|
+
message: error.message
|
|
4013
|
+
}
|
|
4014
|
+
}, null, 2)
|
|
4015
|
+
}],
|
|
4016
|
+
isError: true
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
/**
|
|
4022
|
+
* Set layout grids on a frame
|
|
4023
|
+
*/
|
|
4024
|
+
export async function handleSetLayoutGrids(bridge, args) {
|
|
4025
|
+
if (!bridge.isConnected()) {
|
|
4026
|
+
return {
|
|
4027
|
+
content: [{
|
|
4028
|
+
type: 'text',
|
|
4029
|
+
text: JSON.stringify({
|
|
4030
|
+
error: {
|
|
4031
|
+
code: 'NOT_CONNECTED',
|
|
4032
|
+
message: 'Figma plugin is not connected.'
|
|
4033
|
+
}
|
|
4034
|
+
}, null, 2)
|
|
4035
|
+
}],
|
|
4036
|
+
isError: true
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
const { nodeId, layoutGrids } = args;
|
|
4041
|
+
|
|
4042
|
+
if (!nodeId) {
|
|
4043
|
+
return {
|
|
4044
|
+
content: [{
|
|
4045
|
+
type: 'text',
|
|
4046
|
+
text: JSON.stringify({
|
|
4047
|
+
error: {
|
|
4048
|
+
code: 'INVALID_PARAMS',
|
|
4049
|
+
message: 'nodeId is required'
|
|
4050
|
+
}
|
|
4051
|
+
}, null, 2)
|
|
4052
|
+
}],
|
|
4053
|
+
isError: true
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
if (!layoutGrids || !Array.isArray(layoutGrids)) {
|
|
4058
|
+
return {
|
|
4059
|
+
content: [{
|
|
4060
|
+
type: 'text',
|
|
4061
|
+
text: JSON.stringify({
|
|
4062
|
+
error: {
|
|
4063
|
+
code: 'INVALID_PARAMS',
|
|
4064
|
+
message: 'layoutGrids must be an array'
|
|
4065
|
+
}
|
|
4066
|
+
}, null, 2)
|
|
4067
|
+
}],
|
|
4068
|
+
isError: true
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
|
|
4072
|
+
try {
|
|
4073
|
+
const result = await bridge.sendCommand('set_layout_grids', args);
|
|
4074
|
+
return {
|
|
4075
|
+
content: [{
|
|
4076
|
+
type: 'text',
|
|
4077
|
+
text: JSON.stringify(result, null, 2)
|
|
4078
|
+
}]
|
|
4079
|
+
};
|
|
4080
|
+
} catch (error) {
|
|
4081
|
+
return {
|
|
4082
|
+
content: [{
|
|
4083
|
+
type: 'text',
|
|
4084
|
+
text: JSON.stringify({
|
|
4085
|
+
error: {
|
|
4086
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
4087
|
+
message: error.message
|
|
4088
|
+
}
|
|
4089
|
+
}, null, 2)
|
|
4090
|
+
}],
|
|
4091
|
+
isError: true
|
|
4092
|
+
};
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
|
|
4096
|
+
// ============================================================
|
|
4097
|
+
// FigJam handlers
|
|
4098
|
+
// ============================================================
|
|
4099
|
+
|
|
4100
|
+
/**
|
|
4101
|
+
* Standard MCP-shaped wrapper: connection guard + sendCommand + error handling.
|
|
4102
|
+
* Thin handlers that have no extra param validation use this directly.
|
|
4103
|
+
*/
|
|
4104
|
+
async function runCommand(bridge, command, args) {
|
|
4105
|
+
if (!bridge.isConnected()) {
|
|
4106
|
+
return {
|
|
4107
|
+
content: [{
|
|
4108
|
+
type: 'text',
|
|
4109
|
+
text: JSON.stringify({
|
|
4110
|
+
error: { code: 'NOT_CONNECTED', message: 'Figma plugin is not connected.' }
|
|
4111
|
+
}, null, 2)
|
|
4112
|
+
}],
|
|
4113
|
+
isError: true
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
try {
|
|
4117
|
+
const result = await bridge.sendCommand(command, args);
|
|
4118
|
+
return {
|
|
4119
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
4120
|
+
};
|
|
4121
|
+
} catch (error) {
|
|
4122
|
+
return {
|
|
4123
|
+
content: [{
|
|
4124
|
+
type: 'text',
|
|
4125
|
+
text: JSON.stringify({
|
|
4126
|
+
error: { code: error.code || 'UNKNOWN_ERROR', message: error.message }
|
|
4127
|
+
}, null, 2)
|
|
4128
|
+
}],
|
|
4129
|
+
isError: true
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
export async function handleCreateSticky(bridge, args) {
|
|
4135
|
+
return runCommand(bridge, 'create_sticky', args);
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
export async function handleSetSticky(bridge, args) {
|
|
4139
|
+
return runCommand(bridge, 'set_sticky', args);
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
export async function handleCreateShapeWithText(bridge, args) {
|
|
4143
|
+
return runCommand(bridge, 'create_shape_with_text', args);
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
export async function handleSetShapeType(bridge, args) {
|
|
4147
|
+
return runCommand(bridge, 'set_shape_type', args);
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
export async function handleCreateConnector(bridge, args) {
|
|
4151
|
+
return runCommand(bridge, 'create_connector', args);
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
export async function handleSetConnector(bridge, args) {
|
|
4155
|
+
return runCommand(bridge, 'set_connector', args);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
export async function handleCreateSection(bridge, args) {
|
|
4159
|
+
return runCommand(bridge, 'create_section', args);
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
export async function handleSetSection(bridge, args) {
|
|
4163
|
+
return runCommand(bridge, 'set_section', args);
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
export async function handleCreateTable(bridge, args) {
|
|
4167
|
+
return runCommand(bridge, 'create_table', args);
|
|
4168
|
+
}
|
|
4169
|
+
|
|
4170
|
+
export async function handleSetTableCell(bridge, args) {
|
|
4171
|
+
return runCommand(bridge, 'set_table_cell', args);
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4174
|
+
export async function handleInsertTableRow(bridge, args) {
|
|
4175
|
+
return runCommand(bridge, 'insert_table_row', args);
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
export async function handleInsertTableColumn(bridge, args) {
|
|
4179
|
+
return runCommand(bridge, 'insert_table_column', args);
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
export async function handleRemoveTableRow(bridge, args) {
|
|
4183
|
+
return runCommand(bridge, 'remove_table_row', args);
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
export async function handleRemoveTableColumn(bridge, args) {
|
|
4187
|
+
return runCommand(bridge, 'remove_table_column', args);
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
export async function handleResizeTableRow(bridge, args) {
|
|
4191
|
+
return runCommand(bridge, 'resize_table_row', args);
|
|
4192
|
+
}
|
|
4193
|
+
|
|
4194
|
+
export async function handleResizeTableColumn(bridge, args) {
|
|
4195
|
+
return runCommand(bridge, 'resize_table_column', args);
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
export async function handleMoveTableRow(bridge, args) {
|
|
4199
|
+
return runCommand(bridge, 'move_table_row', args);
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
export async function handleMoveTableColumn(bridge, args) {
|
|
4203
|
+
return runCommand(bridge, 'move_table_column', args);
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
export async function handleCreateCodeBlock(bridge, args) {
|
|
4207
|
+
return runCommand(bridge, 'create_code_block', args);
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
export async function handleSetCodeBlock(bridge, args) {
|
|
4211
|
+
return runCommand(bridge, 'set_code_block', args);
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
export async function handleCreateLinkPreview(bridge, args) {
|
|
4215
|
+
return runCommand(bridge, 'create_link_preview', args);
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
/**
|
|
4219
|
+
* Combine components as variants into a component set
|
|
4220
|
+
*/
|
|
4221
|
+
export async function handleCombineAsVariants(bridge, args) {
|
|
4222
|
+
if (!bridge.isConnected()) {
|
|
4223
|
+
return {
|
|
4224
|
+
content: [{
|
|
4225
|
+
type: 'text',
|
|
4226
|
+
text: JSON.stringify({
|
|
4227
|
+
error: {
|
|
4228
|
+
code: 'NOT_CONNECTED',
|
|
4229
|
+
message: 'Figma plugin is not connected.'
|
|
4230
|
+
}
|
|
4231
|
+
}, null, 2)
|
|
4232
|
+
}],
|
|
4233
|
+
isError: true
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
const { componentIds } = args;
|
|
4238
|
+
|
|
4239
|
+
if (!componentIds || !Array.isArray(componentIds) || componentIds.length < 2) {
|
|
4240
|
+
return {
|
|
4241
|
+
content: [{
|
|
4242
|
+
type: 'text',
|
|
4243
|
+
text: JSON.stringify({
|
|
4244
|
+
error: {
|
|
4245
|
+
code: 'INVALID_PARAMS',
|
|
4246
|
+
message: 'componentIds must be an array with at least 2 component IDs'
|
|
4247
|
+
}
|
|
4248
|
+
}, null, 2)
|
|
4249
|
+
}],
|
|
4250
|
+
isError: true
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
|
|
4254
|
+
try {
|
|
4255
|
+
const result = await bridge.sendCommand('combine_as_variants', args);
|
|
4256
|
+
return {
|
|
4257
|
+
content: [{
|
|
4258
|
+
type: 'text',
|
|
4259
|
+
text: JSON.stringify(result, null, 2)
|
|
4260
|
+
}]
|
|
4261
|
+
};
|
|
4262
|
+
} catch (error) {
|
|
4263
|
+
return {
|
|
4264
|
+
content: [{
|
|
4265
|
+
type: 'text',
|
|
4266
|
+
text: JSON.stringify({
|
|
4267
|
+
error: {
|
|
4268
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
4269
|
+
message: error.message
|
|
4270
|
+
}
|
|
4271
|
+
}, null, 2)
|
|
4272
|
+
}],
|
|
4273
|
+
isError: true
|
|
4274
|
+
};
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
// ============================================================
|
|
4279
|
+
// Prototype tools
|
|
4280
|
+
// ============================================================
|
|
4281
|
+
|
|
4282
|
+
export async function handleGetReactions(bridge, args) {
|
|
4283
|
+
return runCommand(bridge, 'get_reactions', args);
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
export async function handleAddReaction(bridge, args) {
|
|
4287
|
+
return runCommand(bridge, 'add_reaction', args);
|
|
4288
|
+
}
|
|
4289
|
+
|
|
4290
|
+
export async function handleRemoveReaction(bridge, args) {
|
|
4291
|
+
return runCommand(bridge, 'remove_reaction', args);
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4294
|
+
export async function handleSetFlowStartingPoint(bridge, args) {
|
|
4295
|
+
return runCommand(bridge, 'set_flow_starting_point', args);
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
// ============================================================
|
|
4299
|
+
// Visibility, clipping, style deletion, variable modes
|
|
4300
|
+
// ============================================================
|
|
4301
|
+
|
|
4302
|
+
/**
|
|
4303
|
+
* Invalid-params response in the standard MCP shape
|
|
4304
|
+
*/
|
|
4305
|
+
function invalidParams(message) {
|
|
4306
|
+
return {
|
|
4307
|
+
content: [{
|
|
4308
|
+
type: 'text',
|
|
4309
|
+
text: JSON.stringify({
|
|
4310
|
+
error: { code: 'INVALID_PARAMS', message }
|
|
4311
|
+
}, null, 2)
|
|
4312
|
+
}],
|
|
4313
|
+
isError: true
|
|
4314
|
+
};
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
/**
|
|
4318
|
+
* Show or hide nodes
|
|
4319
|
+
*/
|
|
4320
|
+
export async function handleSetVisible(bridge, args) {
|
|
4321
|
+
const { nodeIds } = args;
|
|
4322
|
+
|
|
4323
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
4324
|
+
return invalidParams('nodeIds must be a non-empty array of node IDs');
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
return runCommand(bridge, 'set_visible', args);
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
/**
|
|
4331
|
+
* Set clipsContent on frame-like nodes
|
|
4332
|
+
*/
|
|
4333
|
+
export async function handleSetClipsContent(bridge, args) {
|
|
4334
|
+
const { nodeIds } = args;
|
|
4335
|
+
|
|
4336
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
4337
|
+
return invalidParams('nodeIds must be a non-empty array of node IDs');
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
return runCommand(bridge, 'set_clips_content', args);
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
/**
|
|
4344
|
+
* Set or clear min/max size limits on nodes.
|
|
4345
|
+
*
|
|
4346
|
+
* `null` is a meaningful value here (it clears the limit), so the presence check
|
|
4347
|
+
* uses hasOwnProperty rather than a truthiness test.
|
|
4348
|
+
*/
|
|
4349
|
+
export async function handleSetSizeLimits(bridge, args) {
|
|
4350
|
+
const { nodeIds } = args;
|
|
4351
|
+
|
|
4352
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
4353
|
+
return invalidParams('nodeIds must be a non-empty array of node IDs');
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
const limitFields = ['minWidth', 'maxWidth', 'minHeight', 'maxHeight'];
|
|
4357
|
+
const provided = limitFields.filter(
|
|
4358
|
+
(field) => Object.prototype.hasOwnProperty.call(args, field) && args[field] !== undefined
|
|
4359
|
+
);
|
|
4360
|
+
|
|
4361
|
+
if (provided.length === 0) {
|
|
4362
|
+
return invalidParams(
|
|
4363
|
+
'At least one of minWidth, maxWidth, minHeight, maxHeight must be provided. ' +
|
|
4364
|
+
'Pass a number to set a limit, or null to clear it.'
|
|
4365
|
+
);
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
for (const field of provided) {
|
|
4369
|
+
const value = args[field];
|
|
4370
|
+
if (value !== null && (typeof value !== 'number' || !(value > 0))) {
|
|
4371
|
+
return invalidParams(`${field} must be a positive number, or null to clear it (got ${JSON.stringify(value)})`);
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
const result = await runCommand(bridge, 'set_size_limits', args);
|
|
4376
|
+
if (result.isError) return result;
|
|
4377
|
+
|
|
4378
|
+
// The plugin verifies every write by readback; a limit that did not land comes
|
|
4379
|
+
// back as success: false with an errors array.
|
|
4380
|
+
try {
|
|
4381
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
4382
|
+
if (parsed && parsed.success === false) {
|
|
4383
|
+
return Object.assign({}, result, { isError: true });
|
|
4384
|
+
}
|
|
4385
|
+
} catch (_) {
|
|
4386
|
+
// Non-JSON payload — leave the response as-is.
|
|
4387
|
+
}
|
|
4388
|
+
return result;
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
/**
|
|
4392
|
+
* Delete a local style
|
|
4393
|
+
*/
|
|
4394
|
+
export async function handleDeleteStyle(bridge, args) {
|
|
4395
|
+
const { styleId } = args;
|
|
4396
|
+
|
|
4397
|
+
if (!styleId) {
|
|
4398
|
+
return invalidParams('styleId is required');
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
return runCommand(bridge, 'delete_style', args);
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
/**
|
|
4405
|
+
* Pin or unpin an explicit variable mode on nodes/pages
|
|
4406
|
+
*/
|
|
4407
|
+
export async function handleSetVariableMode(bridge, args) {
|
|
4408
|
+
const { nodeIds, collectionId, modeId, clear } = args;
|
|
4409
|
+
|
|
4410
|
+
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
4411
|
+
return invalidParams('nodeIds must be a non-empty array of node or page IDs');
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
if (!collectionId) {
|
|
4415
|
+
return invalidParams('collectionId is required');
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
if (!clear && !modeId) {
|
|
4419
|
+
return invalidParams('modeId is required unless clear is true');
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
return runCommand(bridge, 'set_variable_mode', args);
|
|
4423
|
+
}
|