figmatk 0.3.1 → 0.3.7
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +39 -21
- package/cli.mjs +2 -0
- package/commands/render.mjs +56 -0
- package/lib/rasterizer/deck-rasterizer.mjs +228 -0
- package/lib/rasterizer/download-font.mjs +57 -0
- package/lib/rasterizer/font-resolver.mjs +602 -0
- package/lib/rasterizer/fonts/DarkerGrotesque-400.ttf +0 -0
- package/lib/rasterizer/fonts/DarkerGrotesque-500.ttf +0 -0
- package/lib/rasterizer/fonts/DarkerGrotesque-600.ttf +0 -0
- package/lib/rasterizer/fonts/Inter-Regular.ttf +0 -0
- package/lib/rasterizer/fonts/Inter-Variable.ttf +0 -0
- package/lib/rasterizer/fonts/Inter.var.woff2 +0 -0
- package/lib/rasterizer/fonts/darker-grotesque-patched-400-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/darker-grotesque-patched-500-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/darker-grotesque-patched-600-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/darker-grotesque-patched-700-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/inter-v3-400-italic.woff2 +0 -0
- package/lib/rasterizer/fonts/inter-v3-400-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/inter-v3-500-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/inter-v3-600-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/inter-v3-700-italic.woff2 +0 -0
- package/lib/rasterizer/fonts/inter-v3-700-normal.woff2 +0 -0
- package/lib/rasterizer/fonts/inter-v3-meta.json +6 -0
- package/lib/rasterizer/render-report-lib.mjs +127 -0
- package/lib/rasterizer/render-report.mjs +25 -0
- package/lib/rasterizer/svg-builder.mjs +571 -0
- package/lib/rasterizer/test-render.mjs +63 -0
- package/manifest.json +21 -0
- package/mcp-server.mjs +65 -4
- package/package.json +17 -2
- package/skills/figma-slides-creator/SKILL.md +82 -209
- package/skills/figma-template-builder/SKILL.md +11 -1
package/mcp-server.mjs
CHANGED
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
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
|
+
import packageJson from './package.json' with { type: 'json' };
|
|
8
9
|
import { FigDeck } from './lib/fig-deck.mjs';
|
|
9
10
|
import { Deck } from './lib/api.mjs';
|
|
11
|
+
import { slideToSvg } from './lib/rasterizer/svg-builder.mjs';
|
|
12
|
+
import { svgToPng } from './lib/rasterizer/deck-rasterizer.mjs';
|
|
13
|
+
import { resolveFonts } from './lib/rasterizer/font-resolver.mjs';
|
|
10
14
|
import {
|
|
11
15
|
annotateTemplateLayout,
|
|
12
16
|
createDraftTemplate,
|
|
@@ -20,7 +24,7 @@ import { deepClone } from './lib/deep-clone.mjs';
|
|
|
20
24
|
|
|
21
25
|
const server = new McpServer({
|
|
22
26
|
name: 'figmatk',
|
|
23
|
-
version:
|
|
27
|
+
version: packageJson.version,
|
|
24
28
|
});
|
|
25
29
|
|
|
26
30
|
// ── inspect ─────────────────────────────────────────────────────────────
|
|
@@ -435,7 +439,7 @@ server.tool(
|
|
|
435
439
|
|
|
436
440
|
server.tool(
|
|
437
441
|
'figmatk_list_template_layouts',
|
|
438
|
-
'Inspect a template or draft template .deck file and return
|
|
442
|
+
'Inspect a template or draft template .deck file and return a layout library catalog with explicit text/image slot metadata. Use this to inventory candidate layouts, classify what each layout is for, and choose a subset before calling figmatk_create_from_template. Do not edit, remove, or reorder the source template as a way to build the output deck.',
|
|
439
443
|
{
|
|
440
444
|
template: z.string().describe('Path to the .deck template file'),
|
|
441
445
|
},
|
|
@@ -459,7 +463,7 @@ server.tool(
|
|
|
459
463
|
// ── figmatk_create_from_template ─────────────────────────────────────────
|
|
460
464
|
server.tool(
|
|
461
465
|
'figmatk_create_from_template',
|
|
462
|
-
'Create a new Figma Slides deck by
|
|
466
|
+
'Create a new Figma Slides deck by selecting any ordered subset of layouts from a draft, published, or publish-like template .deck file and populating explicit text/image slots. The slides array defines the output order. This clones the chosen layouts into a new deck; do not remove, reorder, or mutate the source template to build the result. Works with flat-frame template slides as well as module-backed layouts, and preserves colors, fonts, internal assets, and special nodes.',
|
|
463
467
|
{
|
|
464
468
|
template: z.string().describe('Path to the source .deck template file'),
|
|
465
469
|
output: z.string().describe('Output path for the new .deck file (use /tmp/)'),
|
|
@@ -467,7 +471,7 @@ server.tool(
|
|
|
467
471
|
slideId: z.string().describe('Slide ID from figmatk_list_template_layouts (e.g. "1:74")'),
|
|
468
472
|
text: z.record(z.string()).optional().describe('Map of text slot/name/nodeId -> value (e.g. { "title": "My Company" })'),
|
|
469
473
|
images: z.record(z.string()).optional().describe('Map of image slot/name/nodeId -> absolute image path (e.g. { "hero_image": "/tmp/photo.jpg" })'),
|
|
470
|
-
})).describe('Ordered
|
|
474
|
+
})).describe('Ordered subset of template layouts to include in the output deck. The array order becomes the output deck order, regardless of the template\'s original order.'),
|
|
471
475
|
},
|
|
472
476
|
async ({ template, output, slides }) => {
|
|
473
477
|
const bytes = await createFromTemplate(template, output, slides);
|
|
@@ -475,6 +479,63 @@ server.tool(
|
|
|
475
479
|
}
|
|
476
480
|
);
|
|
477
481
|
|
|
482
|
+
// ── render-slide ───────────────────────────────────────────────────────
|
|
483
|
+
server.tool(
|
|
484
|
+
'figmatk_render_slide',
|
|
485
|
+
'Render a slide from a .deck file to an image. Without output path, returns inline WebP for visual QA. With output path, saves full PNG. Default is 1920×1080; use width (pixels) or scale (e.g. "50%") to resize.',
|
|
486
|
+
{
|
|
487
|
+
path: z.string().describe('Path to .deck file'),
|
|
488
|
+
slide: z.number().int().min(1).describe('Slide number (1-based)'),
|
|
489
|
+
output: z.string().optional().describe('Optional output path to save the PNG file (full resolution)'),
|
|
490
|
+
width: z.number().int().optional().describe('Output width in pixels (height scales proportionally)'),
|
|
491
|
+
scale: z.string().optional().describe('Scale as percentage, e.g. "50%" or "25%"'),
|
|
492
|
+
},
|
|
493
|
+
async ({ path, slide, output, width, scale }) => {
|
|
494
|
+
const deck = await FigDeck.fromDeckFile(path);
|
|
495
|
+
await resolveFonts(deck, { quiet: true });
|
|
496
|
+
const slides = deck.getActiveSlides();
|
|
497
|
+
|
|
498
|
+
if (slide > slides.length) {
|
|
499
|
+
return { content: [{ type: 'text', text: `Slide ${slide} does not exist — deck has ${slides.length} slides` }] };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const svg = slideToSvg(deck, slides[slide - 1]);
|
|
503
|
+
|
|
504
|
+
// Build render options
|
|
505
|
+
const renderOpts = {};
|
|
506
|
+
if (width) {
|
|
507
|
+
renderOpts.width = width;
|
|
508
|
+
} else if (scale) {
|
|
509
|
+
const pct = parseFloat(scale.replace('%', ''));
|
|
510
|
+
if (!isNaN(pct) && pct > 0) renderOpts.scale = pct / 100;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const png = await svgToPng(svg, renderOpts);
|
|
514
|
+
const buf = Buffer.from(png);
|
|
515
|
+
|
|
516
|
+
if (output) {
|
|
517
|
+
const { writeFileSync } = await import('fs');
|
|
518
|
+
writeFileSync(output, buf);
|
|
519
|
+
return { content: [{ type: 'text', text: `Rendered slide ${slide} → ${output} (${buf.length} bytes)` }] };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// For inline display: convert to WebP (much smaller) via sharp
|
|
523
|
+
// Default to width=800 if no size specified to stay under MCP 1MB limit
|
|
524
|
+
const sharp = (await import('sharp')).default;
|
|
525
|
+
let img = sharp(buf);
|
|
526
|
+
if (!width && !scale) img = img.resize(800);
|
|
527
|
+
const webp = await img.webp({ quality: 80 }).toBuffer();
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
content: [{
|
|
531
|
+
type: 'image',
|
|
532
|
+
data: webp.toString('base64'),
|
|
533
|
+
mimeType: 'image/webp',
|
|
534
|
+
}],
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
|
|
478
539
|
// ── Start server ────────────────────────────────────────────────────────
|
|
479
540
|
const transport = new StdioServerTransport();
|
|
480
541
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figmatk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Figma Toolkit — Swiss-army knife CLI for Figma .deck and .fig files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"cli.mjs",
|
|
19
19
|
"mcp-server.mjs",
|
|
20
|
+
"manifest.json",
|
|
20
21
|
"lib/",
|
|
21
22
|
"commands/",
|
|
22
23
|
"skills/",
|
|
@@ -26,7 +27,12 @@
|
|
|
26
27
|
"README.md"
|
|
27
28
|
],
|
|
28
29
|
"scripts": {
|
|
29
|
-
"start": "node cli.mjs"
|
|
30
|
+
"start": "node cli.mjs",
|
|
31
|
+
"pack": "node scripts/pack.mjs",
|
|
32
|
+
"release": "node scripts/release.mjs",
|
|
33
|
+
"validate:extension": "mcpb validate manifest.json",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest"
|
|
30
36
|
},
|
|
31
37
|
"engines": {
|
|
32
38
|
"node": ">=18"
|
|
@@ -42,13 +48,22 @@
|
|
|
42
48
|
"author": "rcoenen",
|
|
43
49
|
"dependencies": {
|
|
44
50
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
51
|
+
"@resvg/resvg-wasm": "^2.6.2",
|
|
45
52
|
"archiver": "^7.0.1",
|
|
46
53
|
"fzstd": "^0.1.1",
|
|
47
54
|
"kiwi-schema": "^0.5.0",
|
|
48
55
|
"pako": "^2.1.0",
|
|
49
56
|
"sharp": "^0.34.5",
|
|
57
|
+
"ssim.js": "^3.5.0",
|
|
50
58
|
"zstd-codec": "^0.1.5"
|
|
51
59
|
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@anthropic-ai/mcpb": "^2.1.2",
|
|
62
|
+
"@fontsource/darker-grotesque": "^5.2.8",
|
|
63
|
+
"@fontsource/inter": "^5.2.8",
|
|
64
|
+
"@fontsource/irish-grover": "^5.2.7",
|
|
65
|
+
"vitest": "^4.1.0"
|
|
66
|
+
},
|
|
52
67
|
"keywords": [
|
|
53
68
|
"figma",
|
|
54
69
|
"deck",
|
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: figma-slides-creator
|
|
3
3
|
description: >
|
|
4
|
-
Create,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
order. Do not use this skill to author reusable templates themselves.
|
|
4
|
+
Create, edit, and inspect Figma Slides .deck files. Use when the user asks to
|
|
5
|
+
create a presentation, build a slide deck, edit slides, update text or images,
|
|
6
|
+
clone or remove slides, or produce a .deck file for Figma Slides.
|
|
8
7
|
Powered by FigmaTK under the hood.
|
|
9
8
|
metadata:
|
|
10
|
-
version: "0.3.
|
|
9
|
+
version: "0.3.7"
|
|
11
10
|
---
|
|
12
11
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
Use this skill for the default workflow: take an existing template and build a new presentation from it. For authoring reusable templates themselves, use `skills/figma-template-builder/SKILL.md`.
|
|
16
|
-
|
|
17
|
-
## Skill Boundary
|
|
18
|
-
|
|
19
|
-
Use this skill when the outcome is a finished deck for immediate use.
|
|
20
|
-
|
|
21
|
-
Switch to `skills/figma-template-builder/SKILL.md` when the user wants to:
|
|
22
|
-
|
|
23
|
-
- build a reusable template
|
|
24
|
-
- define layouts or placeholders
|
|
25
|
-
- rename slots for future sessions
|
|
26
|
-
- derive a new template system from references or examples
|
|
12
|
+
# FigmaTK Skill
|
|
27
13
|
|
|
28
14
|
## ⚠️ Never open .deck files directly
|
|
29
15
|
|
|
@@ -37,144 +23,39 @@ To let the user view the result: tell them to **open the file in Figma Desktop**
|
|
|
37
23
|
|
|
38
24
|
| Task | Approach |
|
|
39
25
|
|------|----------|
|
|
40
|
-
| Create from scratch |
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
| Clone, remove, or restructure slides | `figmatk_clone_slide`, `figmatk_remove_slide` |
|
|
45
|
-
| Inspect structure or read content | `figmatk_inspect`, `figmatk_list_text` |
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## File locations — always use /tmp
|
|
50
|
-
|
|
51
|
-
**All files go in `/tmp/`** — scripts, output decks, images, everything. Never write to the Desktop, Documents, Downloads, or any user directory. Never create intermediate notes or reference markdown files. Just build and save the deck.
|
|
52
|
-
|
|
53
|
-
## Default Workflow
|
|
54
|
-
|
|
55
|
-
1. Inspect the template or deck.
|
|
56
|
-
2. Pick the minimum set of layouts or edits needed.
|
|
57
|
-
3. Populate text slots first, then image slots.
|
|
58
|
-
4. Save to a new `/tmp/` output path.
|
|
59
|
-
5. Sanity-check the result with `figmatk_list_text` or by opening it in Figma Desktop.
|
|
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`) |
|
|
60
30
|
|
|
61
31
|
---
|
|
62
32
|
|
|
63
|
-
## Path
|
|
64
|
-
|
|
65
|
-
Use this path when the user provides a `.deck` template file. The output deck inherits all fonts, colors, spacing, and visual design from the template verbatim.
|
|
66
|
-
|
|
67
|
-
### Step 1 — Inspect the template
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
figmatk_list_template_layouts("/path/to/template.deck")
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Returns a catalog of all available slide layouts. Each entry includes:
|
|
74
|
-
- `slideId` — the ID to reference this layout
|
|
75
|
-
- Layout state — `draft` or `published`
|
|
76
|
-
- Text slots — explicit `slot:text:*` fields when present, otherwise fallback text candidates
|
|
77
|
-
- Image slots — explicit `slot:image:*` fields when present, otherwise fallback image candidates
|
|
78
|
-
- Node IDs — usable for direct targeting when the template has not been fully annotated yet
|
|
79
|
-
|
|
80
|
-
**Read the catalog carefully before picking layouts:**
|
|
81
|
-
- Prefer layouts with explicit slot metadata when available
|
|
82
|
-
- Match each slide's purpose to your content; the existing copy is often the best hint
|
|
83
|
-
- Use slot names first, then node IDs, then raw node names when populating content
|
|
84
|
-
- If a layout exposes no explicit image slots, treat heuristic image candidates as weaker signals and avoid overwriting decorative sample imagery unless the user clearly wants that
|
|
85
|
-
|
|
86
|
-
### Step 2 — Create the deck
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
figmatk_create_from_template({
|
|
90
|
-
template: "/path/to/template.deck",
|
|
91
|
-
output: "/tmp/my-deck.deck",
|
|
92
|
-
slides: [
|
|
93
|
-
{ slideId: "1:74", text: { "title": "My Company" } },
|
|
94
|
-
{ slideId: "1:112", text: { "header": "The problem.", "body": "Description here." }, images: { "hero_image": "/tmp/problem-photo.jpg" } },
|
|
95
|
-
{ slideId: "1:643", text: { "title": "Thank you!" } }
|
|
96
|
-
]
|
|
97
|
-
})
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Only pass slots or node IDs that exist in the layout's catalog. Extra keys are silently ignored.
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## Path A — Create from Scratch (MCP tool — no template)
|
|
105
|
-
|
|
106
|
-
**Always use this path.** No npm install, no scripts, no workspace setup.
|
|
107
|
-
|
|
108
|
-
Call `figmatk_create_deck` with a structured slide description:
|
|
109
|
-
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"output": "/tmp/my-deck.deck",
|
|
113
|
-
"title": "My Presentation",
|
|
114
|
-
"theme": "midnight",
|
|
115
|
-
"slides": [
|
|
116
|
-
{ "type": "title", "title": "My Presentation", "subtitle": "A subtitle" },
|
|
117
|
-
{ "type": "bullets", "title": "Key Points", "bullets": ["Point one", "Point two", "Point three"] },
|
|
118
|
-
{ "type": "two-column", "title": "Comparison", "leftText": "Left side content", "rightText": "Right side content" },
|
|
119
|
-
{ "type": "stat", "title": "By the numbers", "stat": "42%", "caption": "of users prefer this" },
|
|
120
|
-
{ "type": "image-full", "image": "/tmp/photo.jpg", "title": "Caption text" },
|
|
121
|
-
{ "type": "closing", "title": "Thank you", "subtitle": "Questions?" }
|
|
122
|
-
]
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Slide types
|
|
127
|
-
|
|
128
|
-
| Type | Fields |
|
|
129
|
-
|------|--------|
|
|
130
|
-
| `title` | `title`, `subtitle` |
|
|
131
|
-
| `bullets` | `title`, `bullets` (array) |
|
|
132
|
-
| `two-column` | `title`, `leftText`, `rightText`, `image` (right side) |
|
|
133
|
-
| `stat` | `title`, `stat` (big number), `caption` |
|
|
134
|
-
| `image-full` | `image` (path), `title`, `body` (overlay text) |
|
|
135
|
-
| `closing` | `title`, `subtitle` |
|
|
136
|
-
|
|
137
|
-
### Themes
|
|
33
|
+
## Path A — Create from Scratch (High-Level API)
|
|
138
34
|
|
|
139
|
-
|
|
35
|
+
Use this when the user wants a new presentation. Write a Node.js script and execute it.
|
|
140
36
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
## Path A2 — Create from Scratch (Node.js script fallback)
|
|
146
|
-
|
|
147
|
-
Only use this if `figmatk_create_deck` is unavailable or you need layout control beyond what the MCP tool offers.
|
|
148
|
-
|
|
149
|
-
### Step 1 — Set up workspace (MANDATORY — never skip)
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
[ -d /tmp/figmatk-ws/node_modules ] || (mkdir -p /tmp/figmatk-ws && cd /tmp/figmatk-ws && npm init -y && npm install figmatk)
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### Step 2 — Write script to `/tmp/figmatk-ws/deck.mjs`
|
|
156
|
-
|
|
157
|
-
**Always use bare specifier** `import { Deck } from 'figmatk'` — never a file path.
|
|
37
|
+
> **Import path:** `figmatk` is an npm package. Import from the installed package:
|
|
38
|
+
> ```javascript
|
|
39
|
+
> import { Deck } from 'figmatk';
|
|
40
|
+
> ```
|
|
158
41
|
|
|
159
42
|
```javascript
|
|
160
43
|
import { Deck } from 'figmatk';
|
|
161
44
|
|
|
162
|
-
function hex(h) {
|
|
163
|
-
return { r: parseInt(h.slice(1,3),16)/255, g: parseInt(h.slice(3,5),16)/255, b: parseInt(h.slice(5,7),16)/255 };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
45
|
const deck = await Deck.create('My Presentation');
|
|
167
|
-
const slide = deck.addBlankSlide();
|
|
168
|
-
slide.setBackground('Black');
|
|
169
|
-
slide.addText('Slide Title', { style: 'Title', color: 'White', x: 64, y: 80, width: 1792, align: 'LEFT' });
|
|
170
|
-
await deck.save('/tmp/my-presentation.deck');
|
|
171
|
-
console.log('Done');
|
|
172
|
-
```
|
|
173
46
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
47
|
+
const slide = deck.addBlankSlide(); // template blank slide auto-removed
|
|
48
|
+
slide.setBackground('Black'); // named color — see list below
|
|
49
|
+
slide.addText('Slide Title', {
|
|
50
|
+
style: 'Title', color: 'White',
|
|
51
|
+
x: 64, y: 80, width: 1792, align: 'LEFT'
|
|
52
|
+
});
|
|
53
|
+
slide.addText('Subtitle', {
|
|
54
|
+
style: 'Body 1', color: 'Grey',
|
|
55
|
+
x: 64, y: 240, width: 1200, align: 'LEFT'
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await deck.save('/path/to/output.deck');
|
|
178
59
|
```
|
|
179
60
|
|
|
180
61
|
### ⚠️ Critical gotchas
|
|
@@ -184,11 +65,9 @@ node /tmp/figmatk-ws/deck.mjs
|
|
|
184
65
|
| `setBackground` with hex | `s.setBackground('#1A1A1A')` | `s.setBackground('Black')` |
|
|
185
66
|
| `setBackground` with raw RGB | `s.setBackground({ r:0.1, g:0.1, b:0.1 })` | `s.setBackground('Black')` — raw RGB silently renders white |
|
|
186
67
|
| Shape method signature | `s.addRectangle({ x:0, y:0, width:100 })` | `s.addRectangle(0, 0, 100, 100, opts)` |
|
|
187
|
-
| Shape fill color | `{ fill:
|
|
68
|
+
| Shape fill color | `{ fill: '#F4900C' }` | `{ fill: hex('#F4900C') }` — use the hex() helper |
|
|
188
69
|
| `addLine` options | `{ strokeColor: ..., strokeWeight: 2 }` | `{ color: 'Black', weight: 2 }` |
|
|
189
70
|
| `align` value | `align: 'left'` | `align: 'LEFT'` (uppercase) |
|
|
190
|
-
| `addImage` without await | `slide.addImage('/tmp/photo.jpg')` | `await slide.addImage('/tmp/photo.jpg')` — async, images silently missing without await |
|
|
191
|
-
| `addImage` old signature | `await slide.addImage(x, y, w, h, path)` | `await slide.addImage(path, { x, y, width, height })` — path is first arg now |
|
|
192
71
|
|
|
193
72
|
### Hex color helper (for shape fills)
|
|
194
73
|
|
|
@@ -212,17 +91,13 @@ function hex(h) {
|
|
|
212
91
|
| `Body 3` | 24pt | Regular | Captions, labels |
|
|
213
92
|
| `Note` | 20pt | Regular | Footnotes, sources |
|
|
214
93
|
|
|
215
|
-
###
|
|
94
|
+
### Named colors for `setBackground()`
|
|
216
95
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
**Named colors** (case-sensitive, from the Light Slides theme):
|
|
96
|
+
> **Case-sensitive.** `'Black'` works, `'black'` does not.
|
|
220
97
|
|
|
221
98
|
`'Black'`, `'White'`, `'Grey'`, `'Blue'`, `'Red'`, `'Yellow'`, `'Green'`, `'Orange'`, `'Pink'`, `'Purple'`, `'Teal'`, `'Violet'`, `'Persimmon'`, `'Pale Pink'`, `'Pale Blue'`, `'Pale Green'`, `'Pale Teal'`, `'Pale Purple'`, `'Pale Persimmon'`, `'Pale Violet'`, `'Pale Red'`, `'Pale Yellow'`
|
|
222
99
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
**Designer aliases** (0.0.12+): `slide.setBackground('navy')`, `slide.setBackground('coral')`, `slide.setBackground('terracotta')` etc.
|
|
100
|
+
Use `'Black'` for dark backgrounds, `'White'` for light. For custom slide backgrounds, use the closest named color — **not hex**.
|
|
226
101
|
|
|
227
102
|
### Slide dimensions
|
|
228
103
|
|
|
@@ -240,8 +115,8 @@ slide.addDiamond(x, y, width, height, opts)
|
|
|
240
115
|
slide.addTriangle(x, y, width, height, opts)
|
|
241
116
|
slide.addStar(x, y, width, height, opts)
|
|
242
117
|
slide.addLine(x1, y1, x2, y2, opts) // opts: color, weight
|
|
243
|
-
|
|
244
|
-
slide.addTable(
|
|
118
|
+
slide.addImage(path, opts) // opts: x, y, width, height
|
|
119
|
+
slide.addTable(data, opts) // 2D string array; opts: x, y, width, colWidths, rowHeight
|
|
245
120
|
slide.addSVG(x, y, width, svgPathOrBuf, opts)
|
|
246
121
|
```
|
|
247
122
|
|
|
@@ -266,7 +141,6 @@ Use this when the user provides a `.deck` file to modify.
|
|
|
266
141
|
|
|
267
142
|
| Tool | Purpose |
|
|
268
143
|
|------|---------|
|
|
269
|
-
| `figmatk_create_deck` | **Create a new deck from scratch** — no npm install needed |
|
|
270
144
|
| `figmatk_inspect` | Node hierarchy tree — structure, node IDs, slide count |
|
|
271
145
|
| `figmatk_list_text` | All text strings and image hashes per slide |
|
|
272
146
|
| `figmatk_list_overrides` | Editable override keys per symbol (component) |
|
|
@@ -275,45 +149,55 @@ Use this when the user provides a `.deck` file to modify.
|
|
|
275
149
|
| `figmatk_clone_slide` | Deep-clone a slide with new text and images |
|
|
276
150
|
| `figmatk_remove_slide` | Mark slides as REMOVED (never deleted) |
|
|
277
151
|
| `figmatk_roundtrip` | Decode + re-encode for pipeline validation |
|
|
278
|
-
|
|
279
|
-
## Final Checks
|
|
280
|
-
|
|
281
|
-
Before finishing, prefer at least one of these:
|
|
282
|
-
|
|
283
|
-
- `figmatk_list_text` on the output deck
|
|
284
|
-
- `figmatk_roundtrip` if the deck went through multiple edits
|
|
285
|
-
- a manual open check in Figma Desktop when the user is validating upload/render behavior
|
|
152
|
+
| `figmatk_render_slide` | Render a slide to image (inline WebP or saved PNG) |
|
|
286
153
|
|
|
287
154
|
---
|
|
288
155
|
|
|
289
|
-
##
|
|
156
|
+
## Path C — Visual QA (Render + Inspect)
|
|
290
157
|
|
|
291
|
-
|
|
158
|
+
After creating or modifying a deck, **always render and visually inspect** the output. This catches issues that text inspection misses: overflowing text, broken layouts, wrong colors, misaligned elements.
|
|
292
159
|
|
|
293
|
-
###
|
|
294
|
-
|
|
295
|
-
A proper deck has a clear spine. Follow this slide order:
|
|
160
|
+
### Workflow
|
|
296
161
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
162
|
+
1. Render each slide at preview size (returns inline WebP image):
|
|
163
|
+
```
|
|
164
|
+
figmatk_render_slide(path: "/tmp/my-deck.deck", slide: 1)
|
|
165
|
+
```
|
|
166
|
+
2. Inspect the returned image for:
|
|
167
|
+
- Text overflowing its bounding box or clipped
|
|
168
|
+
- Layout misalignment or overlapping elements
|
|
169
|
+
- Wrong colors or missing backgrounds
|
|
170
|
+
- Missing images or broken fills
|
|
171
|
+
3. If issues are found, fix them and re-render
|
|
172
|
+
4. For full-resolution export:
|
|
173
|
+
```
|
|
174
|
+
figmatk_render_slide(path: "/tmp/my-deck.deck", slide: 1, output: "/tmp/slide-1.png")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Render options
|
|
178
|
+
|
|
179
|
+
| Option | Example | Effect |
|
|
180
|
+
|--------|---------|--------|
|
|
181
|
+
| (none) | `slide: 1` | Inline WebP at 800px wide (for QA) |
|
|
182
|
+
| `width` | `width: 400` | Resize to 400px wide (proportional) |
|
|
183
|
+
| `scale` | `scale: "50%"` | Half size (960×540) |
|
|
184
|
+
| `output` | `output: "/tmp/s.png"` | Save full PNG to disk |
|
|
185
|
+
|
|
186
|
+
### CLI alternative
|
|
304
187
|
|
|
305
|
-
|
|
188
|
+
```bash
|
|
189
|
+
figmatk render my-deck.deck -o /tmp/renders/ # all slides
|
|
190
|
+
figmatk render my-deck.deck -o /tmp/renders/ --slide 3 # single slide
|
|
191
|
+
figmatk render my-deck.deck -o /tmp/renders/ --width 400 # thumbnail size
|
|
192
|
+
```
|
|
306
193
|
|
|
307
|
-
|
|
194
|
+
**Important:** Always run visual QA on every deck you create or modify. Do not skip this step.
|
|
308
195
|
|
|
309
|
-
|
|
196
|
+
---
|
|
310
197
|
|
|
311
|
-
|
|
312
|
-
- **Left colour panel**: tall rectangle on the left third, text floats right
|
|
313
|
-
- **Corner badge**: small filled circle or square in bottom-right with slide number or logo
|
|
314
|
-
- **Bottom rule**: thin full-width line at y=1040
|
|
198
|
+
## Design Philosophy
|
|
315
199
|
|
|
316
|
-
|
|
200
|
+
Every deck must look **intentionally designed**, not AI-generated.
|
|
317
201
|
|
|
318
202
|
### Colour
|
|
319
203
|
|
|
@@ -332,29 +216,18 @@ Without a motif, slides look unrelated. With one, the deck feels designed.
|
|
|
332
216
|
| Ocean | `'Blue'` | `hex('#21295C')` | `'White'` |
|
|
333
217
|
| Minimal | `'White'` | `hex('#36454F')` | `'Black'` |
|
|
334
218
|
|
|
335
|
-
### Layout
|
|
336
|
-
|
|
337
|
-
Each content slide should use a **different layout type**. Never repeat the same structure back-to-back.
|
|
219
|
+
### Layout
|
|
338
220
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
| 2×2 or 2×3 grid | Features, icons, categories |
|
|
343
|
-
| Large stat callout | One big number + explanation |
|
|
344
|
-
| Half-background image | Photo-rich slides |
|
|
345
|
-
| Timeline / steps | Process, history, roadmap |
|
|
346
|
-
| Icon + text rows | Lists that need visual weight |
|
|
347
|
-
| Full-bleed image | Impact moment, section break |
|
|
221
|
+
- Every slide needs at least **one visual element** — shape, image, SVG, or table.
|
|
222
|
+
- **Vary layouts** — never repeat the same structure slide after slide.
|
|
223
|
+
- Carry one visual motif through every slide (coloured accent bar, icon circles, etc.).
|
|
348
224
|
|
|
349
|
-
|
|
225
|
+
**Layout options:** two-column, icon+text rows, 2×2/2×3 grid, large stat callout, half-background image, timeline/steps.
|
|
350
226
|
|
|
351
227
|
### Typography
|
|
352
228
|
|
|
353
|
-
- Left-align body text. Centre only titles
|
|
229
|
+
- Left-align body text. Centre only titles.
|
|
354
230
|
- Minimum 64px margin from slide edges. 24–48px between content blocks.
|
|
355
|
-
- Use `Header 2` or `Header 3` for slide titles on content slides (not `Title` — that's for the title slide only).
|
|
356
|
-
- **Body text: max 2 sentences per text block.** Text boxes have fixed heights — overflow gets clipped. If you have more to say, use a bullet list or split across slides.
|
|
357
|
-
- **Bullets: max 6 items, max 8 words per bullet.** Longer bullets wrap and push content off-slide.
|
|
358
231
|
|
|
359
232
|
### Never do
|
|
360
233
|
|
|
@@ -362,17 +235,17 @@ Every slide needs at least **one visual element** — shape, image, SVG, or tabl
|
|
|
362
235
|
- Centre body text
|
|
363
236
|
- Use accent lines under slide titles (hallmark of AI-generated slides)
|
|
364
237
|
- Text-only slides
|
|
365
|
-
- Low-contrast text against background
|
|
366
|
-
- Skip the closing slide — it makes the deck feel unfinished
|
|
367
|
-
- Put long paragraphs in body/caption fields — text overflows the container
|
|
238
|
+
- Low-contrast text against background
|
|
368
239
|
|
|
369
240
|
---
|
|
370
241
|
|
|
371
242
|
## QA
|
|
372
243
|
|
|
373
244
|
1. Self-check: no placeholder text (`lorem ipsum`, `[title here]`) remains
|
|
374
|
-
2.
|
|
375
|
-
3.
|
|
245
|
+
2. **Render every slide** using `figmatk_render_slide` and visually inspect for overflows, clipping, alignment, and color issues
|
|
246
|
+
3. Fix any issues found and re-render to confirm
|
|
247
|
+
4. Tell the user to open the `.deck` in Figma Desktop for final review
|
|
248
|
+
5. Offer to fix anything they report
|
|
376
249
|
|
|
377
250
|
---
|
|
378
251
|
|
|
@@ -6,13 +6,23 @@ description: >
|
|
|
6
6
|
template from an existing deck, define reusable layouts, mark editable
|
|
7
7
|
text/image slots, or prepare a draft template for later instantiation.
|
|
8
8
|
metadata:
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.3.7"
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Figma Template Builder
|
|
13
13
|
|
|
14
14
|
Use this skill when the goal is to build or refine the template itself. For the common workflow of taking an existing template and producing a new presentation, use `skills/figma-slides-creator/SKILL.md`.
|
|
15
15
|
|
|
16
|
+
## MCP First
|
|
17
|
+
|
|
18
|
+
In Claude Cowork, keep template authoring inside the MCP plugin.
|
|
19
|
+
|
|
20
|
+
- Do not inspect the installed `figmatk` package to discover template features.
|
|
21
|
+
- Do not write direct Node.js scripts for draft creation, annotation, wrapping, or instantiation when MCP tools exist for those steps.
|
|
22
|
+
- Prefer `figmatk_create_template_draft`, `figmatk_annotate_template_layout`, `figmatk_publish_template_draft`, and `figmatk_list_template_layouts`.
|
|
23
|
+
|
|
24
|
+
Only fall back to direct library code if the MCP server is unavailable or the required capability is missing from the MCP surface.
|
|
25
|
+
|
|
16
26
|
## Skill Boundary
|
|
17
27
|
|
|
18
28
|
Use this skill when the deliverable is a reusable template, not a one-off deck.
|