figmatk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "name": "figmatk",
15
15
  "description": "Swiss Army Knife for Figma Files (.deck)",
16
- "version": "0.1.0",
16
+ "version": "0.2.0",
17
17
  "author": {
18
18
  "name": "FigmaTK Contributors"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figmatk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Create and edit Figma Slides .deck files programmatically — no Figma API required",
5
5
  "author": {
6
6
  "name": "FigmaTK Contributors"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figmatk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Figma Toolkit — Swiss-army knife CLI for Figma .deck and .fig files",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.1.0"
9
+ version: "0.2.0"
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 | Use the high-level JS API (`lib/api.mjs`) |
27
- | Edit text or images in an existing deck | Use MCP tools (`figmatk_update_text`, `figmatk_insert_image`) |
28
- | Clone, remove, or restructure slides | Use MCP tools (`figmatk_clone_slide`, `figmatk_remove_slide`) |
29
- | Inspect structure or read content | Use MCP tools (`figmatk_inspect`, `figmatk_list_text`) |
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 (High-Level API)
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
+ ```
40
60
 
41
- Use this when the user wants a new presentation. Follow these steps **in order, every time, no exceptions**.
61
+ ### Slide types
42
62
 
43
- ### Step 1 Set up workspace (MANDATORY FIRST STEP — never skip)
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` |
44
71
 
45
- The environment has no `node_modules`. **Before writing any script**, run this exact command:
72
+ ### Themes
46
73
 
47
- ```bash
48
- mkdir -p /tmp/figmatk-ws && cd /tmp/figmatk-ws && npm init -y && npm install figmatk
49
- ```
74
+ `midnight` · `ocean` · `forest` · `coral` · `terracotta` · `minimal`
75
+
76
+ Each theme handles backgrounds, accent colors, and text colors automatically.
77
+
78
+ ---
50
79
 
51
- Do not proceed to Step 2 until this command succeeds.
80
+ ## Path A2 Create from Scratch (Node.js script fallback)
52
81
 
53
- ### Step 2 Write the script to `/tmp/figmatk-ws/deck.mjs`
82
+ Only use this if `figmatk_create_deck` is unavailable or you need layout control beyond what the MCP tool offers.
54
83
 
55
- **Always write the script to `/tmp/figmatk-ws/deck.mjs`** not to the current directory, not to any other path.
84
+ ### Step 1Set up workspace (MANDATORY never skip)
56
85
 
57
- **Always use the bare specifier** `import { Deck } from 'figmatk'` — never a file path import.
86
+ ```bash
87
+ [ -d /tmp/figmatk-ws/node_modules ] || (mkdir -p /tmp/figmatk-ws && cd /tmp/figmatk-ws && npm init -y && npm install figmatk)
88
+ ```
89
+
90
+ ### Step 2 — Write script to `/tmp/figmatk-ws/deck.mjs`
91
+
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'); // named color — see list below
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 — open /tmp/my-presentation.deck in Figma Desktop');
106
+ console.log('Done');
83
107
  ```
84
108
 
85
- ### Step 3 — Run the script
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 |
@@ -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) |