figmatk 0.1.1 → 0.2.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/lib/api.mjs
CHANGED
|
@@ -561,7 +561,7 @@ export class Slide {
|
|
|
561
561
|
addRectangle(x, y, width, height, opts = {}) {
|
|
562
562
|
const fd = this._fd;
|
|
563
563
|
const localID = fd.maxLocalID() + 1;
|
|
564
|
-
const fill = opts.fill ??
|
|
564
|
+
const fill = parseColor(fd, opts.fill ?? 'White');
|
|
565
565
|
|
|
566
566
|
const node = {
|
|
567
567
|
guid: { sessionID: 1, localID },
|
|
@@ -904,7 +904,7 @@ export class Slide {
|
|
|
904
904
|
_addShapeWithText(shapeType, x, y, width, height, opts = {}) {
|
|
905
905
|
const fd = this._fd;
|
|
906
906
|
const localID = fd.maxLocalID() + 1;
|
|
907
|
-
const fill = opts.fill ??
|
|
907
|
+
const fill = parseColor(fd, opts.fill ?? 'White');
|
|
908
908
|
const fillPaint = {
|
|
909
909
|
type: 'SOLID',
|
|
910
910
|
color: { r: fill.r, g: fill.g, b: fill.b, a: fill.a ?? 1 },
|
|
@@ -1185,7 +1185,7 @@ export class Slide {
|
|
|
1185
1185
|
fd.message.blobs.push({ bytes: _buildVectorNetworkBlob(allCmds) });
|
|
1186
1186
|
const vnbIdx = fd.message.blobs.length - 1;
|
|
1187
1187
|
|
|
1188
|
-
const fill = opts.fill ??
|
|
1188
|
+
const fill = parseColor(fd, opts.fill ?? 'Black');
|
|
1189
1189
|
const opacity = opts.opacity ?? 1;
|
|
1190
1190
|
|
|
1191
1191
|
const frameId = nextId++;
|
package/mcp-server.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { FigDeck } from './lib/fig-deck.mjs';
|
|
9
|
+
import { Deck } from './lib/api.mjs';
|
|
9
10
|
import { nid, ov, nestedOv, removeNode, parseId, positionChar } from './lib/node-helpers.mjs';
|
|
10
11
|
import { imageOv, hexToHash, hashToHex } from './lib/image-helpers.mjs';
|
|
11
12
|
import { deepClone } from './lib/deep-clone.mjs';
|
|
@@ -246,6 +247,95 @@ server.tool(
|
|
|
246
247
|
}
|
|
247
248
|
);
|
|
248
249
|
|
|
250
|
+
// ── create-deck ─────────────────────────────────────────────────────────
|
|
251
|
+
const THEMES = {
|
|
252
|
+
midnight: { dark: 'Black', light: 'White', accent: { r: 0.792, g: 0.863, b: 0.988 }, textDark: 'White', textLight: 'Black' },
|
|
253
|
+
ocean: { dark: 'Blue', light: 'Pale Blue', accent: { r: 0.129, g: 0.161, b: 0.361 }, textDark: 'White', textLight: 'Black' },
|
|
254
|
+
forest: { dark: 'Green', light: 'Pale Green', accent: { r: 0.592, g: 0.737, b: 0.384 }, textDark: 'White', textLight: 'Black' },
|
|
255
|
+
coral: { dark: 'Persimmon', light: 'Pale Persimmon', accent: { r: 0.184, g: 0.235, b: 0.494 }, textDark: 'White', textLight: 'Black' },
|
|
256
|
+
terracotta: { dark: 'Persimmon', light: 'Pale Persimmon', accent: { r: 0.906, g: 0.910, b: 0.820 }, textDark: 'White', textLight: 'Black' },
|
|
257
|
+
minimal: { dark: 'Black', light: 'White', accent: { r: 0.212, g: 0.271, b: 0.310 }, textDark: 'White', textLight: 'Black' },
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const SlideSchema = z.object({
|
|
261
|
+
type: z.enum(['title', 'bullets', 'two-column', 'stat', 'image-full', 'closing']),
|
|
262
|
+
title: z.string().optional(),
|
|
263
|
+
subtitle: z.string().optional(),
|
|
264
|
+
body: z.string().optional(),
|
|
265
|
+
bullets: z.array(z.string()).optional(),
|
|
266
|
+
stat: z.string().optional(),
|
|
267
|
+
caption: z.string().optional(),
|
|
268
|
+
image: z.string().optional().describe('Absolute path to image file'),
|
|
269
|
+
leftText: z.string().optional(),
|
|
270
|
+
rightText: z.string().optional(),
|
|
271
|
+
background: z.string().optional().describe('Override background (named color)'),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
server.tool(
|
|
275
|
+
'figmatk_create_deck',
|
|
276
|
+
'Create a new Figma Slides .deck file from a structured description. No npm install needed — runs directly in the MCP server.',
|
|
277
|
+
{
|
|
278
|
+
output: z.string().describe('Output path for the .deck file, e.g. /tmp/my-deck.deck'),
|
|
279
|
+
title: z.string().describe('Deck title'),
|
|
280
|
+
theme: z.string().optional().describe('Theme: midnight | ocean | forest | coral | terracotta | minimal (default: midnight)'),
|
|
281
|
+
slides: z.array(SlideSchema).describe('Slides to create'),
|
|
282
|
+
},
|
|
283
|
+
async ({ output, title, theme, slides }) => {
|
|
284
|
+
const t = THEMES[theme ?? 'midnight'] ?? THEMES.midnight;
|
|
285
|
+
const deck = await Deck.create(title);
|
|
286
|
+
|
|
287
|
+
for (const s of slides) {
|
|
288
|
+
const slide = deck.addBlankSlide();
|
|
289
|
+
const isDark = ['title', 'closing', 'stat', 'image-full'].includes(s.type);
|
|
290
|
+
const bg = s.background ?? (isDark ? t.dark : t.light);
|
|
291
|
+
const fg = isDark ? t.textDark : t.textLight;
|
|
292
|
+
slide.setBackground(bg);
|
|
293
|
+
|
|
294
|
+
if (s.type === 'title' || s.type === 'closing') {
|
|
295
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
296
|
+
if (s.title) slide.addText(s.title, { style: 'Title', color: fg, x: 80, y: 360, width: 1760, align: 'CENTER' });
|
|
297
|
+
if (s.subtitle) slide.addText(s.subtitle, { style: 'Body 1', color: fg, x: 80, y: 540, width: 1760, align: 'CENTER' });
|
|
298
|
+
|
|
299
|
+
} else if (s.type === 'bullets') {
|
|
300
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
301
|
+
if (s.title) slide.addText(s.title, { style: 'Header 2', color: fg, x: 80, y: 80, width: 1760, align: 'LEFT' });
|
|
302
|
+
const items = s.bullets ?? (s.body ? s.body.split('\n') : []);
|
|
303
|
+
let y = 240;
|
|
304
|
+
for (const item of items) {
|
|
305
|
+
slide.addRectangle(80, y + 10, 12, 12, { fill: t.accent });
|
|
306
|
+
slide.addText(item, { style: 'Body 1', color: fg, x: 116, y, width: 1724, align: 'LEFT' });
|
|
307
|
+
y += 80;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
} else if (s.type === 'two-column') {
|
|
311
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
312
|
+
if (s.title) slide.addText(s.title, { style: 'Header 2', color: fg, x: 80, y: 80, width: 1760, align: 'LEFT' });
|
|
313
|
+
slide.addRectangle(960, 200, 4, 800, { fill: t.accent });
|
|
314
|
+
if (s.leftText) slide.addText(s.leftText, { style: 'Body 1', color: fg, x: 80, y: 240, width: 840, align: 'LEFT' });
|
|
315
|
+
if (s.rightText) slide.addText(s.rightText, { style: 'Body 1', color: fg, x: 1004, y: 240, width: 836, align: 'LEFT' });
|
|
316
|
+
if (s.image) await slide.addImage(s.image, { x: 1004, y: 200, width: 836, height: 800 });
|
|
317
|
+
|
|
318
|
+
} else if (s.type === 'stat') {
|
|
319
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
320
|
+
if (s.title) slide.addText(s.title, { style: 'Header 2', color: fg, x: 80, y: 80, width: 1760, align: 'LEFT' });
|
|
321
|
+
if (s.stat) slide.addText(s.stat, { style: 'Title', color: fg, x: 80, y: 300, width: 1760, align: 'CENTER' });
|
|
322
|
+
if (s.caption) slide.addText(s.caption, { style: 'Body 1', color: fg, x: 80, y: 720, width: 1760, align: 'CENTER' });
|
|
323
|
+
|
|
324
|
+
} else if (s.type === 'image-full') {
|
|
325
|
+
if (s.image) await slide.addImage(s.image, { x: 0, y: 0, width: 1920, height: 1080 });
|
|
326
|
+
if (s.title || s.body) {
|
|
327
|
+
slide.addRectangle(0, 680, 1920, 400, { fill: { r: 0, g: 0, b: 0 }, opacity: 0.7 });
|
|
328
|
+
if (s.title) slide.addText(s.title, { style: 'Header 1', color: 'White', x: 80, y: 720, width: 1760, align: 'LEFT' });
|
|
329
|
+
if (s.body) slide.addText(s.body, { style: 'Body 1', color: 'White', x: 80, y: 880, width: 1760, align: 'LEFT' });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
await deck.save(output);
|
|
335
|
+
return { content: [{ type: 'text', text: `Created ${output} — ${slides.length} slides. Open in Figma Desktop.` }] };
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
|
|
249
339
|
// ── Start server ────────────────────────────────────────────────────────
|
|
250
340
|
const transport = new StdioServerTransport();
|
|
251
341
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ description: >
|
|
|
6
6
|
clone or remove slides, or produce a .deck file for Figma Slides.
|
|
7
7
|
Powered by FigmaTK under the hood.
|
|
8
8
|
metadata:
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.2.1"
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Figma Slides Creator
|
|
@@ -23,10 +23,10 @@ To let the user view the result: tell them to **open the file in Figma Desktop**
|
|
|
23
23
|
|
|
24
24
|
| Task | Approach |
|
|
25
25
|
|------|----------|
|
|
26
|
-
| Create a new deck from scratch |
|
|
27
|
-
| Edit text or images in an existing deck |
|
|
28
|
-
| Clone, remove, or restructure slides |
|
|
29
|
-
| Inspect structure or read content |
|
|
26
|
+
| Create a new deck from scratch | **`figmatk_create_deck` MCP tool** — no npm install needed |
|
|
27
|
+
| Edit text or images in an existing deck | `figmatk_update_text`, `figmatk_insert_image` |
|
|
28
|
+
| Clone, remove, or restructure slides | `figmatk_clone_slide`, `figmatk_remove_slide` |
|
|
29
|
+
| Inspect structure or read content | `figmatk_inspect`, `figmatk_list_text` |
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
@@ -36,25 +36,60 @@ To let the user view the result: tell them to **open the file in Figma Desktop**
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
## Path A — Create from Scratch (
|
|
39
|
+
## Path A — Create from Scratch (MCP tool — preferred)
|
|
40
|
+
|
|
41
|
+
**Always use this path.** No npm install, no scripts, no workspace setup.
|
|
42
|
+
|
|
43
|
+
Call `figmatk_create_deck` with a structured slide description:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"output": "/tmp/my-deck.deck",
|
|
48
|
+
"title": "My Presentation",
|
|
49
|
+
"theme": "midnight",
|
|
50
|
+
"slides": [
|
|
51
|
+
{ "type": "title", "title": "My Presentation", "subtitle": "A subtitle" },
|
|
52
|
+
{ "type": "bullets", "title": "Key Points", "bullets": ["Point one", "Point two", "Point three"] },
|
|
53
|
+
{ "type": "two-column", "title": "Comparison", "leftText": "Left side content", "rightText": "Right side content" },
|
|
54
|
+
{ "type": "stat", "title": "By the numbers", "stat": "42%", "caption": "of users prefer this" },
|
|
55
|
+
{ "type": "image-full", "image": "/tmp/photo.jpg", "title": "Caption text" },
|
|
56
|
+
{ "type": "closing", "title": "Thank you", "subtitle": "Questions?" }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Slide types
|
|
62
|
+
|
|
63
|
+
| Type | Fields |
|
|
64
|
+
|------|--------|
|
|
65
|
+
| `title` | `title`, `subtitle` |
|
|
66
|
+
| `bullets` | `title`, `bullets` (array) |
|
|
67
|
+
| `two-column` | `title`, `leftText`, `rightText`, `image` (right side) |
|
|
68
|
+
| `stat` | `title`, `stat` (big number), `caption` |
|
|
69
|
+
| `image-full` | `image` (path), `title`, `body` (overlay text) |
|
|
70
|
+
| `closing` | `title`, `subtitle` |
|
|
71
|
+
|
|
72
|
+
### Themes
|
|
73
|
+
|
|
74
|
+
`midnight` · `ocean` · `forest` · `coral` · `terracotta` · `minimal`
|
|
75
|
+
|
|
76
|
+
Each theme handles backgrounds, accent colors, and text colors automatically.
|
|
40
77
|
|
|
41
|
-
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Path A2 — Create from Scratch (Node.js script fallback)
|
|
42
81
|
|
|
43
|
-
|
|
82
|
+
Only use this if `figmatk_create_deck` is unavailable or you need layout control beyond what the MCP tool offers.
|
|
44
83
|
|
|
45
|
-
|
|
84
|
+
### Step 1 — Set up workspace (MANDATORY — never skip)
|
|
46
85
|
|
|
47
86
|
```bash
|
|
48
87
|
[ -d /tmp/figmatk-ws/node_modules ] || (mkdir -p /tmp/figmatk-ws && cd /tmp/figmatk-ws && npm init -y && npm install figmatk)
|
|
49
88
|
```
|
|
50
89
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
### Step 2 — Write the script to `/tmp/figmatk-ws/deck.mjs`
|
|
54
|
-
|
|
55
|
-
**Always write the script to `/tmp/figmatk-ws/deck.mjs`** — not to the current directory, not to any other path.
|
|
90
|
+
### Step 2 — Write script to `/tmp/figmatk-ws/deck.mjs`
|
|
56
91
|
|
|
57
|
-
**Always use
|
|
92
|
+
**Always use bare specifier** `import { Deck } from 'figmatk'` — never a file path.
|
|
58
93
|
|
|
59
94
|
```javascript
|
|
60
95
|
import { Deck } from 'figmatk';
|
|
@@ -64,32 +99,19 @@ function hex(h) {
|
|
|
64
99
|
}
|
|
65
100
|
|
|
66
101
|
const deck = await Deck.create('My Presentation');
|
|
67
|
-
|
|
68
|
-
// Each call to addBlankSlide() returns a new blank slide.
|
|
69
|
-
// The template blank slide is auto-removed on the first call.
|
|
70
102
|
const slide = deck.addBlankSlide();
|
|
71
|
-
slide.setBackground('Black');
|
|
72
|
-
slide.addText('Slide Title', {
|
|
73
|
-
style: 'Title', color: 'White',
|
|
74
|
-
x: 64, y: 80, width: 1792, align: 'LEFT'
|
|
75
|
-
});
|
|
76
|
-
slide.addText('Subtitle', {
|
|
77
|
-
style: 'Body 1', color: 'Grey',
|
|
78
|
-
x: 64, y: 240, width: 1200, align: 'LEFT'
|
|
79
|
-
});
|
|
80
|
-
|
|
103
|
+
slide.setBackground('Black');
|
|
104
|
+
slide.addText('Slide Title', { style: 'Title', color: 'White', x: 64, y: 80, width: 1792, align: 'LEFT' });
|
|
81
105
|
await deck.save('/tmp/my-presentation.deck');
|
|
82
|
-
console.log('Done
|
|
106
|
+
console.log('Done');
|
|
83
107
|
```
|
|
84
108
|
|
|
85
|
-
### Step 3 — Run
|
|
109
|
+
### Step 3 — Run
|
|
86
110
|
|
|
87
111
|
```bash
|
|
88
112
|
node /tmp/figmatk-ws/deck.mjs
|
|
89
113
|
```
|
|
90
114
|
|
|
91
|
-
If this fails, check the error and fix the script — **do not change the workspace setup or the import path**.
|
|
92
|
-
|
|
93
115
|
### ⚠️ Critical gotchas
|
|
94
116
|
|
|
95
117
|
| Issue | Wrong | Right |
|
|
@@ -97,7 +119,7 @@ If this fails, check the error and fix the script — **do not change the worksp
|
|
|
97
119
|
| `setBackground` with hex | `s.setBackground('#1A1A1A')` | `s.setBackground('Black')` |
|
|
98
120
|
| `setBackground` with raw RGB | `s.setBackground({ r:0.1, g:0.1, b:0.1 })` | `s.setBackground('Black')` — raw RGB silently renders white |
|
|
99
121
|
| Shape method signature | `s.addRectangle({ x:0, y:0, width:100 })` | `s.addRectangle(0, 0, 100, 100, opts)` |
|
|
100
|
-
| Shape fill color | `{ fill: '#F4900C' }`
|
|
122
|
+
| Shape fill color | `{ fill: { r:1, g:0, b:0 } }` | `{ fill: '#F4900C' }` or `{ fill: 'Red' }` — hex strings and named colors work directly |
|
|
101
123
|
| `addLine` options | `{ strokeColor: ..., strokeWeight: 2 }` | `{ color: 'Black', weight: 2 }` |
|
|
102
124
|
| `align` value | `align: 'left'` | `align: 'LEFT'` (uppercase) |
|
|
103
125
|
| `addImage` without await | `slide.addImage('/tmp/photo.jpg')` | `await slide.addImage('/tmp/photo.jpg')` — async, images silently missing without await |
|
|
@@ -179,6 +201,7 @@ Use this when the user provides a `.deck` file to modify.
|
|
|
179
201
|
|
|
180
202
|
| Tool | Purpose |
|
|
181
203
|
|------|---------|
|
|
204
|
+
| `figmatk_create_deck` | **Create a new deck from scratch** — no npm install needed |
|
|
182
205
|
| `figmatk_inspect` | Node hierarchy tree — structure, node IDs, slide count |
|
|
183
206
|
| `figmatk_list_text` | All text strings and image hashes per slide |
|
|
184
207
|
| `figmatk_list_overrides` | Editable override keys per symbol (component) |
|