figma-console-mcp 1.44.2 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,621 @@
1
+ /**
2
+ * Figma Motion tools (Plugin API Motion, beta: Updates 130–140).
3
+ *
4
+ * Read and write animation styles (Figma presets and local custom styles),
5
+ * manual keyframe tracks, and export animated frames as MP4/GIF/WebM.
6
+ * Easing and duration can be bound to EASING/TIMING variables.
7
+ *
8
+ * Every tool builds a Plugin API script here and runs it through
9
+ * executeCodeViaUI, so no Desktop Bridge plugin change (or re-import) is
10
+ * needed. Each script checks the API exists first, so an older Figma Desktop
11
+ * gets a clear message instead of a TypeError.
12
+ */
13
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { dirname, isAbsolute, join, resolve } from "node:path";
16
+ import { z } from "zod";
17
+ import { createChildLogger } from "./logger.js";
18
+ const logger = createChildLogger({ component: "motion-tools" });
19
+ export const EASING_TYPES = [
20
+ "EASE_IN",
21
+ "EASE_OUT",
22
+ "EASE_IN_AND_OUT",
23
+ "LINEAR",
24
+ "EASE_IN_BACK",
25
+ "EASE_OUT_BACK",
26
+ "EASE_IN_AND_OUT_BACK",
27
+ "CUSTOM_CUBIC_BEZIER",
28
+ "GENTLE",
29
+ "QUICK",
30
+ "BOUNCY",
31
+ "SLOW",
32
+ "CUSTOM_SPRING",
33
+ "HOLD",
34
+ ];
35
+ export const KEYFRAME_PROPERTIES = [
36
+ "OPACITY",
37
+ "TRANSLATION_X",
38
+ "TRANSLATION_Y",
39
+ "TRANSLATION_XY",
40
+ "ROTATION",
41
+ "SCALE_X",
42
+ "SCALE_Y",
43
+ "SCALE_XY",
44
+ "WIDTH",
45
+ "HEIGHT",
46
+ "CORNER_RADIUS",
47
+ "RECTANGLE_TOP_LEFT_CORNER_RADIUS",
48
+ "RECTANGLE_TOP_RIGHT_CORNER_RADIUS",
49
+ "RECTANGLE_BOTTOM_LEFT_CORNER_RADIUS",
50
+ "RECTANGLE_BOTTOM_RIGHT_CORNER_RADIUS",
51
+ "STROKE_WEIGHT",
52
+ "BORDER_TOP_WEIGHT",
53
+ "BORDER_BOTTOM_WEIGHT",
54
+ "BORDER_LEFT_WEIGHT",
55
+ "BORDER_RIGHT_WEIGHT",
56
+ "STACK_SPACING",
57
+ "STACK_COUNTER_SPACING",
58
+ "STACK_PADDING_LEFT",
59
+ "STACK_PADDING_TOP",
60
+ "STACK_PADDING_RIGHT",
61
+ "STACK_PADDING_BOTTOM",
62
+ "GRID_ROW_GAP",
63
+ "GRID_COLUMN_GAP",
64
+ "PATH_TRIM_START",
65
+ "PATH_TRIM_END",
66
+ ];
67
+ export const EFFECT_KEYFRAME_FIELDS = [
68
+ "OFFSET_X",
69
+ "OFFSET_Y",
70
+ "RADIUS",
71
+ "SPREAD",
72
+ "COLOR",
73
+ "SECONDARY_COLOR",
74
+ "EFFECT_OPACITY",
75
+ "REFRACTION_RADIUS",
76
+ "REFRACTION_INTENSITY",
77
+ "SPECULAR_ANGLE",
78
+ "SPECULAR_INTENSITY",
79
+ "CHROMATIC_ABERRATION",
80
+ "SPLAY",
81
+ "START_RADIUS",
82
+ "NOISE_SIZE_X",
83
+ "NOISE_SIZE_Y",
84
+ "DENSITY",
85
+ ];
86
+ const MP4_WEBM_FPS = [12, 24, 30, 60];
87
+ const GIF_FPS = [8, 12, 15, 24, 30];
88
+ const VIDEO_SCALES = [0.5, 0.75, 1, 1.5, 2, 3, 4];
89
+ /** Inline (base64) exports above this many bytes must be written to disk instead. */
90
+ export const MAX_INLINE_EXPORT_BYTES = 5 * 1024 * 1024;
91
+ const fileKeySchema = z
92
+ .string()
93
+ .optional()
94
+ .describe("Local Mode only. Run against this specific connected file instead of the active file. Get connected fileKeys from figma_list_open_files. Rejected in Cloud Mode, which pairs with a single plugin instance.");
95
+ const easingSchema = z
96
+ .object({
97
+ type: z
98
+ .enum(EASING_TYPES)
99
+ .optional()
100
+ .describe("Easing curve. CUSTOM_CUBIC_BEZIER needs `bezier`; CUSTOM_SPRING needs `bounce`."),
101
+ bezier: z
102
+ .object({ x1: z.number(), y1: z.number(), x2: z.number(), y2: z.number() })
103
+ .optional()
104
+ .describe("Control points for CUSTOM_CUBIC_BEZIER, as in CSS cubic-bezier(x1, y1, x2, y2)"),
105
+ bounce: z.number().min(0).max(1).optional().describe("Spring bounce (0–1) for CUSTOM_SPRING"),
106
+ variableId: z
107
+ .string()
108
+ .optional()
109
+ .describe("Bind an EASING variable instead of a literal curve (the other fields are then ignored)"),
110
+ })
111
+ .describe("Easing: a literal curve, or an EASING variable to bind");
112
+ const keyframeValueSchema = z
113
+ .union([
114
+ z.number(),
115
+ z.object({ x: z.number(), y: z.number() }),
116
+ z.object({ r: z.number(), g: z.number(), b: z.number(), a: z.number().optional() }),
117
+ ])
118
+ .describe("A number (most properties), {x,y} for TRANSLATION_XY / SCALE_XY, or {r,g,b,a} (0–1) for colors");
119
+ const trackTargetShape = {
120
+ property: z
121
+ .enum(KEYFRAME_PROPERTIES)
122
+ .optional()
123
+ .describe("Node property to animate. TRANSLATION_* and ROTATION are additive offsets; SCALE_* are multipliers"),
124
+ paint: z
125
+ .object({ collection: z.enum(["fills", "strokes"]), index: z.number().int().min(0) })
126
+ .optional()
127
+ .describe("Animate the color of fills[index] or strokes[index] (values are {r,g,b,a})"),
128
+ effect: z
129
+ .object({ index: z.number().int().min(0), field: z.enum(EFFECT_KEYFRAME_FIELDS) })
130
+ .optional()
131
+ .describe("Animate a field of effects[index]"),
132
+ };
133
+ const keyframeTrackSchema = z.object({
134
+ ...trackTargetShape,
135
+ baseValue: keyframeValueSchema.optional().describe("Value outside the keyframed range (optional)"),
136
+ keyframes: z
137
+ .array(z.object({
138
+ time: z.number().min(0).describe("Position on the timeline in seconds"),
139
+ value: keyframeValueSchema,
140
+ easing: easingSchema.optional().describe("Easing INTO this keyframe"),
141
+ }))
142
+ .min(1)
143
+ .describe("Keyframes in time order"),
144
+ });
145
+ const keyframeTargetSchema = z
146
+ .object(trackTargetShape)
147
+ .describe("Which keyframe track: exactly one of property, paint or effect");
148
+ const propsSchema = z
149
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
150
+ .optional()
151
+ .describe("Preset-specific options (e.g. {type:'fadeIn'}, {direction:'right', distance:120}). Valid keys and defaults per preset: figma_manage_animation_styles action 'list_presets'.");
152
+ /**
153
+ * Plugin-side helpers shared by every motion script: availability check,
154
+ * easing/value conversion in both directions, and a normalized read of a
155
+ * node's animations. Kept as one string so each script is self-contained.
156
+ */
157
+ export const MOTION_HELPERS = `
158
+ const MOTION_UNAVAILABLE = 'The Figma Motion API is not available in this Figma Desktop. Update Figma Desktop (Motion shipped in Plugin API Update 130, June 2026).';
159
+ function assertMotion(node) {
160
+ if (typeof figma.motion === 'undefined' || (node && typeof node.applyAnimationStyle !== 'function')) {
161
+ throw new Error(MOTION_UNAVAILABLE);
162
+ }
163
+ }
164
+ async function getTarget(id) {
165
+ const node = id ? await figma.getNodeByIdAsync(id) : figma.currentPage.selection[0];
166
+ if (!node) throw new Error(id ? 'Node not found: ' + id : 'No nodeId given and nothing is selected');
167
+ if (!('animationStyles' in node)) throw new Error('Node ' + node.id + ' (' + node.type + ') cannot be animated');
168
+ return node;
169
+ }
170
+ function assertWritable(node) {
171
+ if (node.type === 'COMPONENT' || node.type === 'COMPONENT_SET') {
172
+ throw new Error('Figma does not allow writing animations to a main component through the Plugin API. Apply them to an instance or a frame instead.');
173
+ }
174
+ }
175
+ async function toEasing(e) {
176
+ if (!e) return undefined;
177
+ if (e.variableId) {
178
+ const v = await figma.variables.getVariableByIdAsync(e.variableId);
179
+ if (!v) throw new Error('EASING variable not found: ' + e.variableId);
180
+ if (v.resolvedType !== 'EASING') throw new Error('Variable ' + v.name + ' is ' + v.resolvedType + ', not EASING');
181
+ return figma.variables.createVariableAlias(v);
182
+ }
183
+ if (!e.type) throw new Error('easing needs a type or a variableId');
184
+ const out = { type: e.type };
185
+ if (e.type === 'CUSTOM_CUBIC_BEZIER') {
186
+ if (!e.bezier) throw new Error('CUSTOM_CUBIC_BEZIER easing needs bezier {x1,y1,x2,y2}');
187
+ out.easingFunctionCubicBezier = e.bezier;
188
+ }
189
+ if (e.type === 'CUSTOM_SPRING') out.easingFunctionSpring = { bounce: e.bounce === undefined ? 0.3 : e.bounce };
190
+ return out;
191
+ }
192
+ async function timingAlias(id) {
193
+ const v = await figma.variables.getVariableByIdAsync(id);
194
+ if (!v) throw new Error('TIMING variable not found: ' + id);
195
+ if (v.resolvedType !== 'TIMING') throw new Error('Variable ' + v.name + ' is ' + v.resolvedType + ', not TIMING');
196
+ return figma.variables.createVariableAlias(v);
197
+ }
198
+ function toKeyframeValue(v) {
199
+ if (typeof v === 'number') return { type: 'FLOAT', value: v };
200
+ if (v && typeof v === 'object' && 'r' in v) return { type: 'COLOR', value: { r: v.r, g: v.g, b: v.b, a: v.a === undefined ? 1 : v.a } };
201
+ if (v && typeof v === 'object' && 'x' in v) return { type: 'VECTOR', value: { x: v.x, y: v.y } };
202
+ throw new Error('Unsupported keyframe value: ' + JSON.stringify(v));
203
+ }
204
+ function toField(t) {
205
+ const n = (t.property ? 1 : 0) + (t.paint ? 1 : 0) + (t.effect ? 1 : 0);
206
+ if (n !== 1) throw new Error('Each keyframe track needs exactly one of property, paint or effect');
207
+ if (t.property) return { type: 'PROPERTY', name: t.property };
208
+ if (t.paint) return { type: 'INDEXED_ITEM', collection: t.paint.collection, index: t.paint.index };
209
+ return { type: 'INDEXED_ITEM', collection: 'effects', index: t.effect.index, field: t.effect.field };
210
+ }
211
+ const r3 = (n) => Math.round(n * 1000) / 1000;
212
+ async function readEasing(e) {
213
+ if (!e) return undefined;
214
+ if (e.type === 'VARIABLE_ALIAS') {
215
+ const v = await figma.variables.getVariableByIdAsync(e.id);
216
+ return { variableId: e.id, variableName: v ? v.name : undefined };
217
+ }
218
+ const out = { type: e.type };
219
+ const b = e.easingFunctionCubicBezier;
220
+ if (b) { out.bezier = { x1: r3(b.x1), y1: r3(b.y1), x2: r3(b.x2), y2: r3(b.y2) }; out.css = 'cubic-bezier(' + [b.x1, b.y1, b.x2, b.y2].map(r3).join(', ') + ')'; }
221
+ if (e.easingFunctionSpring) out.bounce = r3(e.easingFunctionSpring.bounce);
222
+ if (e.type === 'LINEAR') out.css = 'linear';
223
+ if (e.type === 'HOLD') out.css = 'steps(1, end)';
224
+ return out;
225
+ }
226
+ async function readProps(props) {
227
+ if (!props) return undefined;
228
+ const out = {};
229
+ for (const k of Object.keys(props)) {
230
+ const v = props[k];
231
+ if (v && typeof v === 'object' && v.type === 'VARIABLE_ALIAS') {
232
+ const variable = await figma.variables.getVariableByIdAsync(v.id);
233
+ out[k] = { variableId: v.id, variableName: variable ? variable.name : undefined };
234
+ } else if (k === 'easing' && v && typeof v === 'object') {
235
+ out[k] = await readEasing(v);
236
+ } else out[k] = v;
237
+ }
238
+ return out;
239
+ }
240
+ function readValue(v) { return v ? v.value : undefined; }
241
+ async function readBinding(target, b) {
242
+ const kfs = [];
243
+ for (const k of b.keyframes || []) kfs.push({ time: r3(k.timelinePosition), value: readValue(k.value), easing: await readEasing(k.easing) });
244
+ return { target, baseValue: readValue(b.baseValue), keyframes: kfs };
245
+ }
246
+ async function readTracks(node) {
247
+ const mk = node.manualKeyframeTracks || {};
248
+ const tracks = [];
249
+ for (const key of Object.keys(mk)) {
250
+ const entry = mk[key];
251
+ if (!entry) continue;
252
+ if (key === 'fills' || key === 'strokes') {
253
+ for (const idx of Object.keys(entry)) {
254
+ const b = entry[idx];
255
+ if (b && b.keyframes) tracks.push(await readBinding({ paint: { collection: key, index: Number(idx) } }, b));
256
+ }
257
+ } else if (key === 'effects') {
258
+ for (const idx of Object.keys(entry)) {
259
+ const fx = entry[idx] || {};
260
+ for (const field of Object.keys(fx)) {
261
+ if (field !== 'properties' && fx[field] && fx[field].keyframes) tracks.push(await readBinding({ effect: { index: Number(idx), field } }, fx[field]));
262
+ }
263
+ }
264
+ } else if (entry.keyframes) {
265
+ tracks.push(await readBinding({ property: key }, entry));
266
+ }
267
+ }
268
+ return tracks;
269
+ }
270
+ async function readStyle(a) {
271
+ const out = { kind: a.type === 'CUSTOM' ? 'custom' : 'preset', appliedId: a.id, styleId: a.styleId, name: a.name };
272
+ if (a.duration !== undefined) out.duration = r3(a.duration);
273
+ if (a.timelineOffset !== undefined) out.timelineOffset = r3(a.timelineOffset);
274
+ if (a.props) out.props = await readProps(a.props);
275
+ if (a.type === 'CUSTOM') {
276
+ try {
277
+ const style = await figma.getStyleByIdAsync(a.styleId);
278
+ if (style && style.animationEntries) out.entries = await Promise.all(style.animationEntries.map(readStyle));
279
+ } catch (e) { /* remote or missing style */ }
280
+ }
281
+ return out;
282
+ }
283
+ async function readAnimations(node) {
284
+ const styles = [];
285
+ for (const a of node.animationStyles || []) styles.push(await readStyle(a));
286
+ return {
287
+ id: node.id, name: node.name, type: node.type,
288
+ timelines: (node.timelines || []).map(t => ({ id: t.id, duration: r3(t.duration) })),
289
+ animationStyles: styles,
290
+ keyframeTracks: await readTracks(node),
291
+ };
292
+ }
293
+ function hasAnimations(node) {
294
+ if (!('animationStyles' in node)) return false;
295
+ if ((node.animationStyles || []).length) return true;
296
+ return Object.keys(node.manualKeyframeTracks || {}).length > 0;
297
+ }
298
+ async function readCustomStyle(s) {
299
+ return { id: s.id, name: s.name, description: s.description, remote: s.remote, entries: await Promise.all(s.animationEntries.map(readStyle)) };
300
+ }
301
+ `;
302
+ /** Wrap a script body with the shared helpers and a JSON payload `P`. */
303
+ export function motionScript(payload, body) {
304
+ return `${MOTION_HELPERS}\nconst P = ${JSON.stringify(payload)};\n${body}`;
305
+ }
306
+ export const GET_ANIMATIONS_BODY = `
307
+ const node = await getTarget(P.nodeId);
308
+ assertMotion(node);
309
+ const out = { node: await readAnimations(node), playheadPosition: figma.motion.playheadPosition };
310
+ if (P.includeDescendants && 'findAll' in node) {
311
+ const found = node.findAll(n => hasAnimations(n)).slice(0, P.limit);
312
+ out.descendants = [];
313
+ for (const n of found) out.descendants.push(await readAnimations(n));
314
+ }
315
+ return out;
316
+ `;
317
+ export const APPLY_ANIMATION_BODY = `
318
+ const node = await getTarget(P.nodeId);
319
+ assertMotion(node);
320
+ assertWritable(node);
321
+ const applied = [];
322
+ if (P.preset || P.customStyleId) {
323
+ if (P.customStyleId) {
324
+ const config = { type: 'CUSTOM' };
325
+ if (P.timelineOffset !== undefined) config.timelineOffset = P.timelineOffset;
326
+ applied.push(node.applyAnimationStyle(P.customStyleId, config));
327
+ } else {
328
+ const presets = figma.motion.figmaAnimationStyles().map(s => s.styleId);
329
+ if (presets.indexOf(P.preset) === -1) throw new Error('Unknown preset "' + P.preset + '". Available: ' + presets.join(', '));
330
+ const config = {};
331
+ if (P.duration !== undefined) config.duration = P.duration;
332
+ if (P.timelineOffset !== undefined) config.timelineOffset = P.timelineOffset;
333
+ const props = Object.assign({}, P.props || {});
334
+ const easing = await toEasing(P.easing);
335
+ if (easing) props.easing = easing;
336
+ if (P.durationVariableId) props.duration = await timingAlias(P.durationVariableId);
337
+ if (Object.keys(props).length) config.props = props;
338
+ applied.push(node.applyAnimationStyle(P.preset, config));
339
+ }
340
+ }
341
+ for (const t of P.keyframeTracks || []) {
342
+ const keyframes = [];
343
+ for (const k of t.keyframes) {
344
+ const kf = { timelinePosition: k.time, value: toKeyframeValue(k.value) };
345
+ const easing = await toEasing(k.easing);
346
+ if (easing) kf.easing = easing;
347
+ keyframes.push(kf);
348
+ }
349
+ const track = { keyframes };
350
+ if (t.baseValue !== undefined) track.baseValue = toKeyframeValue(t.baseValue);
351
+ node.applyManualKeyframeTrack(toField(t), track);
352
+ applied.push(t.property || (t.paint ? t.paint.collection + '[' + t.paint.index + ']' : 'effects[' + t.effect.index + '].' + t.effect.field));
353
+ }
354
+ return { applied, node: await readAnimations(node) };
355
+ `;
356
+ export const REMOVE_ANIMATION_BODY = `
357
+ const node = await getTarget(P.nodeId);
358
+ assertMotion(node);
359
+ assertWritable(node);
360
+ const removed = [];
361
+ if (P.all) {
362
+ for (const a of (node.animationStyles || []).slice()) { node.removeAnimationStyle(a.id); removed.push(a.id); }
363
+ for (const t of await readTracks(node)) { node.removeManualKeyframeTrack(toField(t.target)); removed.push(JSON.stringify(t.target)); }
364
+ } else {
365
+ if (P.appliedId) {
366
+ if (!(node.animationStyles || []).some(a => a.id === P.appliedId)) {
367
+ throw new Error('No applied animation style with id ' + P.appliedId + ' on this node. Read appliedId values with figma_get_animations.');
368
+ }
369
+ node.removeAnimationStyle(P.appliedId);
370
+ removed.push(P.appliedId);
371
+ }
372
+ if (P.keyframeTrack) { node.removeManualKeyframeTrack(toField(P.keyframeTrack)); removed.push(JSON.stringify(P.keyframeTrack)); }
373
+ }
374
+ return { removed, node: await readAnimations(node) };
375
+ `;
376
+ export const MANAGE_STYLES_BODY = `
377
+ assertMotion(null);
378
+ if (P.action === 'list_presets') {
379
+ return { presets: figma.motion.figmaAnimationStyles().map(s => ({ styleId: s.styleId, name: s.name, description: s.description, props: s.props })) };
380
+ }
381
+ if (typeof figma.createCustomAnimationStyle !== 'function') {
382
+ throw new Error('Custom animation styles need a newer Figma Desktop (Plugin API Update 140, September 2026).');
383
+ }
384
+ async function getStyle() {
385
+ if (!P.styleId) throw new Error('styleId is required for ' + P.action);
386
+ const s = await figma.getStyleByIdAsync(P.styleId);
387
+ if (!s || s.type !== 'CUSTOM_ANIMATION') throw new Error('No custom animation style with id ' + P.styleId);
388
+ return s;
389
+ }
390
+ async function entryProps(e) {
391
+ const props = Object.assign({}, e.props || {});
392
+ if (e.duration !== undefined) props.duration = e.duration;
393
+ if (e.timelineOffset !== undefined) props.timelineOffset = e.timelineOffset;
394
+ const easing = await toEasing(e.easing);
395
+ if (easing) props.easing = easing;
396
+ if (e.durationVariableId) props.duration = await timingAlias(e.durationVariableId);
397
+ return props;
398
+ }
399
+ async function addEntries(style, entries) {
400
+ const presets = figma.motion.figmaAnimationStyles().map(s => s.styleId);
401
+ for (const e of entries || []) {
402
+ if (presets.indexOf(e.preset) === -1) throw new Error('Unknown preset "' + e.preset + '". Available: ' + presets.join(', '));
403
+ await style.addAnimationEntryAsync(e.preset, await entryProps(e));
404
+ }
405
+ }
406
+ if (P.action === 'list') {
407
+ const styles = await figma.getLocalCustomAnimationStylesAsync();
408
+ return { styles: await Promise.all(styles.map(readCustomStyle)) };
409
+ }
410
+ if (P.action === 'create') {
411
+ if (!P.name) throw new Error('name is required to create a style');
412
+ const style = figma.createCustomAnimationStyle();
413
+ style.name = P.name;
414
+ if (P.description !== undefined) style.description = P.description;
415
+ try { await addEntries(style, P.entries); } catch (e) { style.remove(); throw e; }
416
+ return { created: await readCustomStyle(style) };
417
+ }
418
+ const style = await getStyle();
419
+ if (P.action === 'update') {
420
+ if (P.name !== undefined) style.name = P.name;
421
+ if (P.description !== undefined) style.description = P.description;
422
+ await addEntries(style, P.entries);
423
+ return { updated: await readCustomStyle(style) };
424
+ }
425
+ if (P.action === 'remove_entry') {
426
+ if (!P.entryId) throw new Error('entryId is required for remove_entry');
427
+ style.removeAnimationEntry(P.entryId);
428
+ return { updated: await readCustomStyle(style) };
429
+ }
430
+ if (P.action === 'delete') {
431
+ const name = style.name;
432
+ style.remove();
433
+ return { deleted: { id: P.styleId, name } };
434
+ }
435
+ throw new Error('Unknown action ' + P.action);
436
+ `;
437
+ export const EXPORT_ANIMATION_BODY = `
438
+ const node = P.nodeId ? await figma.getNodeByIdAsync(P.nodeId) : figma.currentPage.selection[0];
439
+ if (!node) throw new Error(P.nodeId ? 'Node not found: ' + P.nodeId : 'No nodeId given and nothing is selected');
440
+ if (typeof figma.motion === 'undefined') throw new Error(MOTION_UNAVAILABLE);
441
+ const settings = { format: P.format };
442
+ if (P.fps !== undefined) settings.fps = P.fps;
443
+ if (P.quality !== undefined && P.format !== 'GIF') settings.quality = P.quality;
444
+ if (P.loopCount !== undefined && P.format === 'GIF') settings.loopCount = P.loopCount;
445
+ if (P.scale !== undefined) settings.constraint = { type: 'SCALE', value: P.scale };
446
+ else if (P.width !== undefined) settings.constraint = { type: 'WIDTH', value: P.width };
447
+ else if (P.height !== undefined) settings.constraint = { type: 'HEIGHT', value: P.height };
448
+ let bytes;
449
+ try { bytes = await node.exportAsync(settings); }
450
+ catch (e) { throw new Error('Video export failed: ' + (e && e.message ? e.message : e) + '. Export works on a frame that has animated content (Motion); use its top-level frame.'); }
451
+ return { nodeId: node.id, name: node.name, format: P.format, bytes: bytes.length, base64: figma.base64Encode(bytes) };
452
+ `;
453
+ const MIME = { MP4: "video/mp4", GIF: "image/gif", WEBM: "video/webm" };
454
+ /** Validate fps/scale for a video format; returns an error message or null. */
455
+ export function validateVideoSettings(args) {
456
+ const allowed = args.format === "GIF" ? GIF_FPS : MP4_WEBM_FPS;
457
+ if (args.fps !== undefined && !allowed.includes(args.fps)) {
458
+ return `fps ${args.fps} is not supported for ${args.format}. Use one of: ${allowed.join(", ")}`;
459
+ }
460
+ if (args.scale !== undefined && !VIDEO_SCALES.includes(args.scale)) {
461
+ return `scale ${args.scale} is not supported. Use one of: ${VIDEO_SCALES.join(", ")}`;
462
+ }
463
+ return null;
464
+ }
465
+ function ok(data) {
466
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
467
+ }
468
+ function fail(tool, error) {
469
+ logger.error({ error }, `${tool} failed`);
470
+ return {
471
+ content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }) }],
472
+ isError: true,
473
+ };
474
+ }
475
+ async function run(getDesktopConnector, script, timeout, fileKey) {
476
+ const connector = await getDesktopConnector();
477
+ const result = await connector.executeCodeViaUI(script, timeout, fileKey);
478
+ if (!result?.success)
479
+ throw new Error(result?.error || "Motion script failed");
480
+ return result.result;
481
+ }
482
+ export function registerMotionTools(server, getDesktopConnector, options = {}) {
483
+ server.tool("figma_get_animations", "Read a node's Motion animations: applied animation styles (Figma presets and custom styles, with their entries), manual keyframe tracks, timelines and the playhead. Easing comes back with a CSS equivalent (cubic-bezier) where one exists, and token bindings come back as EASING/TIMING variable references. Set includeDescendants to also list animated layers inside it. Requires Desktop Bridge and Figma Desktop with the Motion API (beta).", {
484
+ nodeId: z.string().optional().describe("Node to read. Defaults to the current selection"),
485
+ includeDescendants: z.boolean().optional().default(false).describe("Also return animated layers inside the node"),
486
+ limit: z.number().int().min(1).max(500).optional().default(100).describe("Max descendants to return"),
487
+ fileKey: fileKeySchema,
488
+ }, async ({ nodeId, includeDescendants, limit, fileKey }) => {
489
+ try {
490
+ return ok(await run(getDesktopConnector, motionScript({ nodeId, includeDescendants, limit }, GET_ANIMATIONS_BODY), 30000, fileKey));
491
+ }
492
+ catch (error) {
493
+ return fail("figma_get_animations", error);
494
+ }
495
+ });
496
+ server.tool("figma_apply_animation", `Animate a node with Figma Motion. Pass exactly one of:
497
+ - preset: a Figma animation style (Position, Scale, Rotation, Size, Opacity, Path, TextStagger), with optional duration, timelineOffset, easing and preset props. List presets and their props with figma_manage_animation_styles action 'list_presets'.
498
+ - customStyleId: a local custom animation style (see figma_manage_animation_styles).
499
+ - keyframeTracks: manual keyframes per property, paint color or effect field. Each track replaces any existing track for that target.
500
+ Easing can be a literal curve or an EASING variable (easing.variableId); a preset's duration can be bound to a TIMING variable (durationVariableId). Main components can't be animated through the Plugin API: use an instance or frame. Motion plays in Figma's preview and video export, not in static screenshots.`, {
501
+ nodeId: z.string().describe("Node to animate (an instance or frame, not a main component)"),
502
+ preset: z.string().optional().describe("Figma animation style id, e.g. 'Opacity', 'Scale', 'Position'"),
503
+ customStyleId: z.string().optional().describe("Local custom animation style id"),
504
+ keyframeTracks: z.array(keyframeTrackSchema).optional().describe("Manual keyframe tracks"),
505
+ duration: z.number().min(0).optional().describe("Preset duration in seconds"),
506
+ durationVariableId: z.string().optional().describe("Bind the preset's duration to a TIMING variable"),
507
+ timelineOffset: z.number().min(0).optional().describe("Start offset on the timeline in seconds"),
508
+ easing: easingSchema.optional(),
509
+ props: propsSchema,
510
+ fileKey: fileKeySchema,
511
+ }, async (args) => {
512
+ try {
513
+ const modes = [args.preset, args.customStyleId, args.keyframeTracks?.length ? "k" : undefined].filter(Boolean);
514
+ if (modes.length !== 1)
515
+ throw new Error("Pass exactly one of preset, customStyleId or keyframeTracks");
516
+ const { fileKey, ...payload } = args;
517
+ return ok(await run(getDesktopConnector, motionScript(payload, APPLY_ANIMATION_BODY), 30000, fileKey));
518
+ }
519
+ catch (error) {
520
+ return fail("figma_apply_animation", error);
521
+ }
522
+ });
523
+ server.tool("figma_remove_animation", "Remove animations from a node: one applied animation style (appliedId, from figma_get_animations), one manual keyframe track, or everything (all: true). Returns what's left.", {
524
+ nodeId: z.string().describe("Animated node"),
525
+ appliedId: z.string().optional().describe("Applied animation style id (figma_get_animations → animationStyles[].appliedId)"),
526
+ keyframeTrack: keyframeTargetSchema.optional(),
527
+ all: z.boolean().optional().default(false).describe("Remove every animation style and keyframe track"),
528
+ fileKey: fileKeySchema,
529
+ }, async ({ fileKey, ...payload }) => {
530
+ try {
531
+ if (!payload.all && !payload.appliedId && !payload.keyframeTrack) {
532
+ throw new Error("Pass appliedId, keyframeTrack, or all: true");
533
+ }
534
+ return ok(await run(getDesktopConnector, motionScript(payload, REMOVE_ANIMATION_BODY), 30000, fileKey));
535
+ }
536
+ catch (error) {
537
+ return fail("figma_remove_animation", error);
538
+ }
539
+ });
540
+ const entrySchema = z.object({
541
+ preset: z.string().describe("Figma animation style id, e.g. 'Opacity'"),
542
+ duration: z.number().min(0).optional(),
543
+ durationVariableId: z.string().optional().describe("Bind duration to a TIMING variable"),
544
+ timelineOffset: z.number().min(0).optional(),
545
+ easing: easingSchema.optional(),
546
+ props: propsSchema,
547
+ });
548
+ server.tool("figma_manage_animation_styles", `Manage reusable Motion animation styles, the motion layer of a design system.
549
+ - list_presets: Figma's built-in animation styles with each one's props and defaults.
550
+ - list: local custom animation styles and their entries.
551
+ - create: a custom style (name, description) from entries, each a preset with duration, offset, easing and props. Durations and easings can bind to TIMING/EASING variables.
552
+ - update: rename, change the description, or append entries.
553
+ - remove_entry: drop one entry (entryId from list).
554
+ - delete: remove the style.
555
+ Apply a custom style with figma_apply_animation customStyleId.`, {
556
+ action: z.enum(["list_presets", "list", "create", "update", "remove_entry", "delete"]),
557
+ styleId: z.string().optional().describe("Custom animation style id (update, remove_entry, delete)"),
558
+ name: z.string().optional().describe("Style name; use '/' for folders, e.g. 'Motion/Enter'"),
559
+ description: z.string().optional(),
560
+ entries: z.array(entrySchema).optional().describe("Entries to add (create, update)"),
561
+ entryId: z.string().optional().describe("Entry id to remove (remove_entry)"),
562
+ fileKey: fileKeySchema,
563
+ }, async ({ fileKey, ...payload }) => {
564
+ try {
565
+ return ok(await run(getDesktopConnector, motionScript(payload, MANAGE_STYLES_BODY), 30000, fileKey));
566
+ }
567
+ catch (error) {
568
+ return fail("figma_manage_animation_styles", error);
569
+ }
570
+ });
571
+ server.tool("figma_export_animation", `Export an animated frame as MP4, GIF or WebM. The node should be the frame that holds the Motion timeline (its top-level frame).${options.allowFileWrite
572
+ ? " The file is written to outputPath (default: the OS temp folder) and the path is returned. An existing file is never replaced unless overwrite is true."
573
+ : ` Cloud Mode returns the video as base64 (up to ${MAX_INLINE_EXPORT_BYTES / 1024 / 1024} MB); use a lower fps, quality or scale for long animations.`}`, {
574
+ nodeId: z.string().optional().describe("Frame to export. Defaults to the current selection"),
575
+ format: z.enum(["MP4", "GIF", "WEBM"]),
576
+ fps: z.number().int().optional().describe("MP4/WebM: 12, 24, 30 or 60. GIF: 8, 12, 15, 24 or 30"),
577
+ quality: z.enum(["LOW", "MEDIUM", "HIGH"]).optional().describe("MP4/WebM quality"),
578
+ loopCount: z.number().int().min(0).optional().describe("GIF loops (0 = forever)"),
579
+ scale: z.number().optional().describe("0.5, 0.75, 1, 1.5, 2, 3 or 4. Use scale, width or height, not several"),
580
+ width: z.number().positive().optional().describe("Output width in px"),
581
+ height: z.number().positive().optional().describe("Output height in px"),
582
+ ...(options.allowFileWrite
583
+ ? {
584
+ outputPath: z.string().optional().describe("Where to write the file. Default: OS temp folder"),
585
+ overwrite: z.boolean().optional().default(false).describe("Replace a file that already exists at outputPath"),
586
+ }
587
+ : {}),
588
+ fileKey: fileKeySchema,
589
+ }, async (args) => {
590
+ try {
591
+ const settingsError = validateVideoSettings(args);
592
+ if (settingsError)
593
+ throw new Error(settingsError);
594
+ const { fileKey, outputPath, overwrite, ...payload } = args;
595
+ const explicitTarget = options.allowFileWrite && outputPath ? (isAbsolute(outputPath) ? outputPath : resolve(outputPath)) : undefined;
596
+ // Checked before the export so a long render isn't wasted.
597
+ if (explicitTarget && !overwrite && existsSync(explicitTarget)) {
598
+ throw new Error(`Nothing was written. "${explicitTarget}" already exists. Pass overwrite: true to replace it, or choose another outputPath.`);
599
+ }
600
+ const result = await run(getDesktopConnector, motionScript(payload, EXPORT_ANIMATION_BODY), 180000, fileKey);
601
+ const ext = String(args.format).toLowerCase();
602
+ const meta = { nodeId: result.nodeId, name: result.name, format: result.format, bytes: result.bytes, mimeType: MIME[args.format] };
603
+ if (options.allowFileWrite) {
604
+ const safeName = String(result.name || "animation").replace(/[^a-z0-9-_]+/gi, "-").slice(0, 60);
605
+ const target = explicitTarget
606
+ ?? join(tmpdir(), `figma-${safeName}-${Date.now()}.${ext}`);
607
+ mkdirSync(dirname(target), { recursive: true });
608
+ writeFileSync(target, Buffer.from(result.base64, "base64"));
609
+ return ok({ ...meta, path: target });
610
+ }
611
+ if (result.bytes > MAX_INLINE_EXPORT_BYTES) {
612
+ throw new Error(`The ${args.format} is ${(result.bytes / 1024 / 1024).toFixed(1)} MB, above the ${MAX_INLINE_EXPORT_BYTES / 1024 / 1024} MB that can be returned inline. Lower fps, quality or scale.`);
613
+ }
614
+ return ok({ ...meta, base64: result.base64 });
615
+ }
616
+ catch (error) {
617
+ return fail("figma_export_animation", error);
618
+ }
619
+ });
620
+ }
621
+ //# sourceMappingURL=motion-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion-tools.js","sourceRoot":"","sources":["../../src/core/motion-tools.ts"],"names":[],"mappings":"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"}