figma-local 1.2.0 → 1.4.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.
- package/README.md +73 -16
- package/package.json +1 -1
- package/skills/figma-compare/SKILL.md +124 -0
- package/skills/figma-screenshot/SKILL.md +73 -0
- package/src/index.js +159 -5
package/README.md
CHANGED
|
@@ -99,32 +99,66 @@ npm install && npm install -g .
|
|
|
99
99
|
|
|
100
100
|
## Setup (one time only)
|
|
101
101
|
|
|
102
|
-
### 1
|
|
102
|
+
### Step 1 — Install the CLI
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
2. Hamburger menu → **Plugins → Development → Import plugin from manifest...**
|
|
106
|
-
3. Navigate to the `plugin/` folder in this repo (or `$(npm root -g)/figma-local/plugin/`)
|
|
107
|
-
4. Select `manifest.json` → click **Open**
|
|
108
|
-
5. Right-click **Figma Local** in the plugin list → **Add to toolbar**
|
|
104
|
+
Pick one of the install methods above (npm recommended):
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
```bash
|
|
107
|
+
npm install -g figma-local
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Verify it worked:
|
|
111
111
|
|
|
112
112
|
```bash
|
|
113
|
-
fig
|
|
113
|
+
fig --help
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Step 2 — Import the Figma plugin into Figma Desktop
|
|
117
|
+
|
|
118
|
+
The plugin lets figma-local talk to Figma. You only need to do this once per Figma account.
|
|
119
|
+
|
|
120
|
+
1. Open **Figma Desktop** (not the browser — the desktop app)
|
|
121
|
+
2. Open any design file
|
|
122
|
+
3. Click the **hamburger menu** (☰) in the top-left corner
|
|
123
|
+
4. Go to **Plugins → Development → Import plugin from manifest...**
|
|
124
|
+
5. In the file picker, navigate to the plugin folder:
|
|
125
|
+
- If you installed via **npm**: run `echo "$(npm root -g)/figma-local/plugin"` in your terminal to get the path, then navigate there
|
|
126
|
+
- If you cloned the **repo**: go to `figma-local/plugin/` in the cloned folder
|
|
127
|
+
6. Select `manifest.json` → click **Open**
|
|
128
|
+
7. You should see **"Figma Local"** appear in your plugin list
|
|
129
|
+
8. *(Optional but recommended)* Right-click **Figma Local** in the plugin list → **Add to toolbar** for one-click access
|
|
130
|
+
|
|
131
|
+
### Step 3 — Connect and verify
|
|
132
|
+
|
|
133
|
+
1. In Figma, start the plugin: **Plugins → Development → Figma Local** (or click it in your toolbar)
|
|
134
|
+
2. You should see a small widget that says **"Figma Local"** with a connecting status
|
|
135
|
+
3. In your terminal, run:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
fig connect --safe
|
|
114
139
|
```
|
|
115
140
|
|
|
116
|
-
|
|
141
|
+
4. The plugin widget should show a **green dot** and say **"Connected"**
|
|
142
|
+
5. Try reading your canvas:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
fig read
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
If you see a list of frames, you're all set!
|
|
117
149
|
|
|
118
150
|
---
|
|
119
151
|
|
|
120
152
|
## Every session after that
|
|
121
153
|
|
|
122
|
-
```
|
|
123
|
-
1. Open Figma
|
|
124
|
-
2.
|
|
154
|
+
```bash
|
|
155
|
+
# 1. Open Figma Desktop and your design file
|
|
156
|
+
# 2. Click "Figma Local" in the toolbar (or Plugins → Development → Figma Local)
|
|
157
|
+
# 3. In your terminal:
|
|
158
|
+
fig-start --safe
|
|
125
159
|
```
|
|
126
160
|
|
|
127
|
-
Claude Code reads `CLAUDE.md` and knows every command automatically.
|
|
161
|
+
This connects to Figma and launches Claude Code. Claude reads `CLAUDE.md` and knows every `fig` command automatically.
|
|
128
162
|
|
|
129
163
|
---
|
|
130
164
|
|
|
@@ -255,13 +289,34 @@ fig prompt "Login" \
|
|
|
255
289
|
|
|
256
290
|
Generates ~45 tokens of structured text instead of attaching a Figma frame (300–500+ hidden tokens). **91–97% smaller input, more consistent AI output.**
|
|
257
291
|
|
|
292
|
+
### Screenshots
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
fig screenshot # Screenshot current selection
|
|
296
|
+
fig screenshot --node "123:456" # Screenshot a specific node
|
|
297
|
+
fig screenshot --link "https://..." # Screenshot from a Figma link
|
|
298
|
+
fig screenshot -o design.png -s 2 # Save to file at 2x scale
|
|
299
|
+
fig screenshot -f svg -o icon.svg # Export as SVG
|
|
300
|
+
```
|
|
301
|
+
|
|
258
302
|
### Verify & compare
|
|
259
303
|
|
|
260
304
|
```bash
|
|
261
|
-
fig verify
|
|
262
|
-
fig verify --
|
|
305
|
+
fig verify # Screenshot of selection for AI review
|
|
306
|
+
fig verify --link "https://..." # Verify from a Figma link
|
|
307
|
+
fig verify --node "123:456" # Verify a specific node
|
|
308
|
+
fig verify --compare "https://..." # Diff prototype vs Figma design → correction prompts
|
|
309
|
+
|
|
310
|
+
# Visual comparison between any two sources
|
|
311
|
+
fig compare --a selection --b "123:456" # Compare selection vs a node
|
|
312
|
+
fig compare --a design.png --b "123:456" # Compare a screenshot file vs a Figma node
|
|
313
|
+
fig compare --a design.png --b coded.png # Compare two screenshot files
|
|
314
|
+
fig compare --a-link "https://..." --b-link "https://..." # Compare two Figma links
|
|
315
|
+
fig compare --a "123:456" --b "789:012" # Compare two nodes by ID
|
|
263
316
|
```
|
|
264
317
|
|
|
318
|
+
Sources for `--a` and `--b` can be: `selection`, a node ID (`123:456`), or a file path (`screenshot.png`). Use `--a-link` / `--b-link` for Figma selection URLs.
|
|
319
|
+
|
|
265
320
|
### Export
|
|
266
321
|
|
|
267
322
|
```bash
|
|
@@ -340,7 +395,7 @@ fig-start --safe --here # launch from your project dir; Claude sees both you
|
|
|
340
395
|
|
|
341
396
|
## Claude Code Plugin (Skills)
|
|
342
397
|
|
|
343
|
-
figma-local ships as a Claude Code plugin with
|
|
398
|
+
figma-local ships as a Claude Code plugin with 8 skills that teach coding agents how to use it automatically:
|
|
344
399
|
|
|
345
400
|
| Skill | Triggers on |
|
|
346
401
|
|-------|------------|
|
|
@@ -350,6 +405,8 @@ figma-local ships as a Claude Code plugin with 6 skills that teach coding agents
|
|
|
350
405
|
| **figma-styles** | "style guide", "extract colors/fonts", "spacing scale" |
|
|
351
406
|
| **figma-measure** | "measure spacing", "gap between elements" |
|
|
352
407
|
| **figma-document** | "document this component", "full spec sheet", "deep breakdown" |
|
|
408
|
+
| **figma-screenshot** | "take a screenshot", "export as PNG/SVG", "capture this" |
|
|
409
|
+
| **figma-compare** | "compare", "does this match", "visual diff", "check against design" |
|
|
353
410
|
|
|
354
411
|
### Install the skills
|
|
355
412
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: figma-compare
|
|
3
|
+
description: |
|
|
4
|
+
Use this skill when the user wants to visually compare two things — two Figma elements, a screenshot vs a Figma component, two screenshots, or a design vs coded output. Triggers on: "compare", "diff", "does this match", "check against", "visual comparison", "how close is this", "spot the differences", "compare design to code", "compare these two", "are they the same". Also use when verifying coded UI matches a Figma design. Requires the fig CLI to be connected.
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Bash(fig compare *)
|
|
7
|
+
- Bash(fig compare)
|
|
8
|
+
- Bash(fig verify *)
|
|
9
|
+
- Bash(fig verify)
|
|
10
|
+
- Bash(fig screenshot *)
|
|
11
|
+
- Bash(fig screenshot)
|
|
12
|
+
- Read
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Figma Compare
|
|
16
|
+
|
|
17
|
+
Visually compare any two sources: Figma selections, node IDs, Figma links, or screenshot files. Outputs both images and a structured gap report template for analysis.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
The `fig` CLI must be connected. Check with `fig daemon status`. If not connected: `fig connect --safe`.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Compare current selection vs a node
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
fig compare --a selection --b "123:456"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Compare two screenshot files
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
fig compare --a design.png --b coded-output.png
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Compare a screenshot file vs a Figma node
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
fig compare --a screenshot.png --b "123:456"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Compare two Figma links
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
fig compare --a-link "https://www.figma.com/...?node-id=1-2" --b-link "https://www.figma.com/...?node-id=3-4"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Compare selection vs a Figma link
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
fig compare --a selection --b-link "https://www.figma.com/...?node-id=123-456"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Compare two nodes by ID
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
fig compare --a "123:456" --b "789:012"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Source types
|
|
62
|
+
|
|
63
|
+
The `--a` and `--b` options accept three source types:
|
|
64
|
+
|
|
65
|
+
| Source | Example | Description |
|
|
66
|
+
|--------|---------|-------------|
|
|
67
|
+
| `selection` | `--a selection` | Whatever is currently selected in Figma |
|
|
68
|
+
| Node ID | `--a "123:456"` | A specific Figma node by its ID |
|
|
69
|
+
| File path | `--a design.png` | An existing screenshot/image file on disk |
|
|
70
|
+
|
|
71
|
+
For Figma selection URLs, use `--a-link` / `--b-link` instead.
|
|
72
|
+
|
|
73
|
+
## Options
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
fig compare --a selection --b "123:456" -s 2 # 2x scale for exports
|
|
77
|
+
fig compare --a selection --b "123:456" --max 3000 # Max dimension 3000px
|
|
78
|
+
fig compare --a selection --b "123:456" --save-dir . # Save images to current dir
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Output
|
|
82
|
+
|
|
83
|
+
The command exports both sources as PNG images (saved to `/tmp/` by default) and outputs:
|
|
84
|
+
|
|
85
|
+
1. **File paths** for both images — use `Read` tool to view them
|
|
86
|
+
2. **Structured JSON** with a gap report template:
|
|
87
|
+
- `matches` — elements that are the same
|
|
88
|
+
- `differences` — table of element, property, value in A, value in B, severity
|
|
89
|
+
- `summary` — overall assessment
|
|
90
|
+
|
|
91
|
+
## Workflow: Design vs Code Comparison
|
|
92
|
+
|
|
93
|
+
1. Take a screenshot of the coded output (browser screenshot or `fig screenshot`)
|
|
94
|
+
2. Get the Figma node ID or link for the original design
|
|
95
|
+
3. Run compare:
|
|
96
|
+
```bash
|
|
97
|
+
fig compare --a coded-output.png --b-link "https://www.figma.com/...?node-id=123-456"
|
|
98
|
+
```
|
|
99
|
+
4. Read both output images to visually analyze differences
|
|
100
|
+
5. Use `fig inspect` on the Figma node to get exact specs for fixing differences
|
|
101
|
+
|
|
102
|
+
## Workflow: Before/After Comparison
|
|
103
|
+
|
|
104
|
+
1. Screenshot the current state: `fig screenshot --node "123:456" -o before.png`
|
|
105
|
+
2. Make changes in Figma
|
|
106
|
+
3. Compare: `fig compare --a before.png --b "123:456"`
|
|
107
|
+
|
|
108
|
+
## Verify command (simpler alternative)
|
|
109
|
+
|
|
110
|
+
For quick verification of a single element without a full comparison:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
fig verify # Screenshot selection for AI review
|
|
114
|
+
fig verify --node "123:456" # Verify specific node
|
|
115
|
+
fig verify --link "https://..." # Verify from Figma link
|
|
116
|
+
fig verify --compare "https://..." # Compare against a prototype URL
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Tips
|
|
120
|
+
|
|
121
|
+
- Use `--save-dir .` to save comparison images in your project directory instead of `/tmp/`
|
|
122
|
+
- For AI-powered analysis, read both output images with the `Read` tool after running compare
|
|
123
|
+
- Combine with `fig inspect --deep` on the Figma source to get exact specs for fixing any differences
|
|
124
|
+
- When comparing design to code, screenshot the browser at the same viewport width as the Figma frame for best results
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: figma-screenshot
|
|
3
|
+
description: |
|
|
4
|
+
Use this skill when the user wants to take a screenshot or export an image from Figma — current selection, a specific node, or from a Figma link. Triggers on: "screenshot", "take a screenshot", "export as PNG", "export as SVG", "capture this", "save as image", "export this frame", "get an image of". Requires the fig CLI to be connected.
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Bash(fig screenshot *)
|
|
7
|
+
- Bash(fig screenshot)
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Figma Screenshot
|
|
11
|
+
|
|
12
|
+
Export any Figma element as an image (PNG, JPG, SVG, or PDF).
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
The `fig` CLI must be connected. Check with `fig daemon status`. If not connected: `fig connect --safe`.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Screenshot current selection
|
|
21
|
+
|
|
22
|
+
The user must select an element in Figma first, then:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
fig screenshot
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Saves to `screenshot.png` in the current directory by default.
|
|
29
|
+
|
|
30
|
+
### Screenshot from a Figma link
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
fig screenshot --link "https://www.figma.com/design/FILEID/Name?node-id=123-456"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Screenshot a specific node by ID
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
fig screenshot --node "123:456"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Options
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
fig screenshot -o design.png # Custom output file name
|
|
46
|
+
fig screenshot -s 2 # 2x scale (default is 2)
|
|
47
|
+
fig screenshot -s 4 # 4x scale for high-res
|
|
48
|
+
fig screenshot -f svg # SVG format
|
|
49
|
+
fig screenshot -f jpg # JPG format
|
|
50
|
+
fig screenshot -f pdf # PDF format
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Combine options
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
fig screenshot --link "https://..." -o hero-section.png -s 3
|
|
57
|
+
fig screenshot --node "123:456" -f svg -o icon.svg
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Export a specific node by ID (alternative)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
fig export node "123:456" # PNG at 2x
|
|
64
|
+
fig export node "123:456" -f svg -o out.svg # SVG
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Tips
|
|
68
|
+
|
|
69
|
+
- Default scale is 2x which gives crisp images on retina displays
|
|
70
|
+
- Use `-s 1` for pixel-accurate exports (1:1 with Figma dimensions)
|
|
71
|
+
- SVG format is best for icons and vector graphics
|
|
72
|
+
- For AI verification of created components, use `fig verify` instead (optimized for smaller file size)
|
|
73
|
+
- Screenshots can be used with `fig compare` to visually diff against other screenshots or Figma nodes
|
package/src/index.js
CHANGED
|
@@ -5205,18 +5205,30 @@ const exp = program
|
|
|
5205
5205
|
|
|
5206
5206
|
exp
|
|
5207
5207
|
.command('screenshot')
|
|
5208
|
-
.description('Take a screenshot of selected node or
|
|
5208
|
+
.description('Take a screenshot of selected node, specific node, or from a Figma link')
|
|
5209
5209
|
.option('-o, --output <file>', 'Output file', 'screenshot.png')
|
|
5210
5210
|
.option('-s, --scale <number>', 'Export scale (1-4)', '2')
|
|
5211
5211
|
.option('-f, --format <format>', 'Format: png, jpg, svg, pdf', 'png')
|
|
5212
|
+
.option('--node <id>', 'Screenshot a specific node by ID')
|
|
5213
|
+
.option('--link <url>', 'Screenshot a node from a Figma selection link')
|
|
5212
5214
|
.action((options) => {
|
|
5213
5215
|
checkConnection();
|
|
5214
5216
|
const format = options.format.toUpperCase();
|
|
5215
5217
|
const scale = parseFloat(options.scale);
|
|
5218
|
+
let nodeResolver;
|
|
5219
|
+
if (options.link) {
|
|
5220
|
+
const nodeId = parseNodeIdFromLink(options.link);
|
|
5221
|
+
if (!nodeId) { console.error(chalk.red('✗'), 'Could not parse node ID from link'); process.exit(1); }
|
|
5222
|
+
nodeResolver = `node = await figma.getNodeByIdAsync('${nodeId}');`;
|
|
5223
|
+
} else if (options.node) {
|
|
5224
|
+
nodeResolver = `node = await figma.getNodeByIdAsync('${options.node}');`;
|
|
5225
|
+
} else {
|
|
5226
|
+
nodeResolver = `const sel = figma.currentPage.selection; node = sel.length > 0 ? sel[0] : figma.currentPage;`;
|
|
5227
|
+
}
|
|
5216
5228
|
const code = `(async () => {
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
if (!node) return { error: 'No
|
|
5229
|
+
let node;
|
|
5230
|
+
${nodeResolver}
|
|
5231
|
+
if (!node) return { error: 'No node found' };
|
|
5220
5232
|
if (!('exportAsync' in node)) return { error: 'Node cannot be exported' };
|
|
5221
5233
|
const bytes = await node.exportAsync({ format: '${format}', constraint: { type: 'SCALE', value: ${scale} } });
|
|
5222
5234
|
return {
|
|
@@ -5333,14 +5345,25 @@ program
|
|
|
5333
5345
|
.option('--save [path]', 'Save as PNG file (default: /tmp/figma-verify-{id}.png)')
|
|
5334
5346
|
.option('--compare <url>', 'Compare against a prototype/preview URL and generate correction prompts')
|
|
5335
5347
|
.option('--compare-save <path>', 'Save prototype screenshot to this path when using --compare')
|
|
5348
|
+
.option('--link <url>', 'Verify a node from a Figma selection link')
|
|
5349
|
+
.option('--node <id>', 'Verify a specific node by ID')
|
|
5336
5350
|
.action(async (nodeId, options) => {
|
|
5337
5351
|
checkConnection();
|
|
5338
5352
|
const scale = parseFloat(options.scale);
|
|
5339
5353
|
const maxDim = parseInt(options.max);
|
|
5340
5354
|
|
|
5355
|
+
// Resolve node: --link > --node > positional nodeId > selection
|
|
5356
|
+
let resolvedNodeId = nodeId;
|
|
5357
|
+
if (options.link) {
|
|
5358
|
+
resolvedNodeId = parseNodeIdFromLink(options.link);
|
|
5359
|
+
if (!resolvedNodeId) { console.error(chalk.red('✗'), 'Could not parse node ID from link'); process.exit(1); }
|
|
5360
|
+
} else if (options.node) {
|
|
5361
|
+
resolvedNodeId = options.node;
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5341
5364
|
const code = `(async () => {
|
|
5342
5365
|
let node;
|
|
5343
|
-
${
|
|
5366
|
+
${resolvedNodeId ? `node = await figma.getNodeByIdAsync('${resolvedNodeId}');` : `
|
|
5344
5367
|
const sel = figma.currentPage.selection;
|
|
5345
5368
|
node = sel.length > 0 ? sel[0] : null;
|
|
5346
5369
|
`}
|
|
@@ -5441,6 +5464,137 @@ program
|
|
|
5441
5464
|
}
|
|
5442
5465
|
});
|
|
5443
5466
|
|
|
5467
|
+
// ============ COMPARE (Visual Comparison) ============
|
|
5468
|
+
|
|
5469
|
+
function exportNodeScreenshot(nodeResolver, scale, maxDim) {
|
|
5470
|
+
const code = `(async () => {
|
|
5471
|
+
let node;
|
|
5472
|
+
${nodeResolver}
|
|
5473
|
+
if (!node) return { error: 'No node found' };
|
|
5474
|
+
if (!('exportAsync' in node)) return { error: 'Node cannot be exported' };
|
|
5475
|
+
const nodeWidth = node.width || 100;
|
|
5476
|
+
const nodeHeight = node.height || 100;
|
|
5477
|
+
let finalScale = ${scale};
|
|
5478
|
+
const maxNodeDim = Math.max(nodeWidth, nodeHeight);
|
|
5479
|
+
if (maxNodeDim * finalScale > ${maxDim}) finalScale = ${maxDim} / maxNodeDim;
|
|
5480
|
+
if (maxNodeDim * finalScale > 7500) finalScale = 7500 / maxNodeDim;
|
|
5481
|
+
const bytes = await node.exportAsync({ format: 'PNG', constraint: { type: 'SCALE', value: finalScale } });
|
|
5482
|
+
return {
|
|
5483
|
+
name: node.name,
|
|
5484
|
+
id: node.id,
|
|
5485
|
+
type: node.type,
|
|
5486
|
+
width: Math.round(nodeWidth * finalScale),
|
|
5487
|
+
height: Math.round(nodeHeight * finalScale),
|
|
5488
|
+
originalWidth: Math.round(nodeWidth),
|
|
5489
|
+
originalHeight: Math.round(nodeHeight),
|
|
5490
|
+
bytes: Array.from(bytes)
|
|
5491
|
+
};
|
|
5492
|
+
})()`;
|
|
5493
|
+
return figmaEvalSync(code);
|
|
5494
|
+
}
|
|
5495
|
+
|
|
5496
|
+
program
|
|
5497
|
+
.command('compare')
|
|
5498
|
+
.description('Compare two things visually: screenshots, Figma nodes, or a mix. Outputs both images for AI analysis.')
|
|
5499
|
+
.option('--a <source>', 'First source: file path, node ID, or "selection"', 'selection')
|
|
5500
|
+
.option('--b <source>', 'Second source: file path, node ID, or Figma link')
|
|
5501
|
+
.option('--a-link <url>', 'First source from a Figma selection link')
|
|
5502
|
+
.option('--b-link <url>', 'Second source from a Figma selection link')
|
|
5503
|
+
.option('-s, --scale <number>', 'Export scale for Figma nodes', '1')
|
|
5504
|
+
.option('--max <pixels>', 'Max dimension for exports', '2000')
|
|
5505
|
+
.option('--save-dir <dir>', 'Directory to save comparison images', '/tmp')
|
|
5506
|
+
.addHelpText('after', `
|
|
5507
|
+
Examples:
|
|
5508
|
+
fig compare --a selection --b "123:456" Compare selection vs a node
|
|
5509
|
+
fig compare --a design.png --b "123:456" Compare a screenshot file vs a Figma node
|
|
5510
|
+
fig compare --a design.png --b coded.png Compare two screenshot files
|
|
5511
|
+
fig compare --a-link "https://..." --b-link "https://..." Compare two Figma links
|
|
5512
|
+
fig compare --a selection --b-link "https://..." Compare selection vs a Figma link
|
|
5513
|
+
fig compare --a "123:456" --b "789:012" Compare two nodes by ID
|
|
5514
|
+
`)
|
|
5515
|
+
.action((options) => {
|
|
5516
|
+
checkConnection();
|
|
5517
|
+
const scale = parseFloat(options.scale);
|
|
5518
|
+
const maxDim = parseInt(options.max);
|
|
5519
|
+
const saveDir = options.saveDir;
|
|
5520
|
+
const timestamp = Date.now();
|
|
5521
|
+
|
|
5522
|
+
function resolveSource(source, linkOpt, label) {
|
|
5523
|
+
// If it's a --link option
|
|
5524
|
+
if (linkOpt) {
|
|
5525
|
+
const nodeId = parseNodeIdFromLink(linkOpt);
|
|
5526
|
+
if (!nodeId) { console.error(chalk.red('✗'), `Could not parse node ID from ${label} link`); process.exit(1); }
|
|
5527
|
+
const result = exportNodeScreenshot(`node = await figma.getNodeByIdAsync('${nodeId}');`, scale, maxDim);
|
|
5528
|
+
if (result.error) { console.error(chalk.red('✗'), `${label}: ${result.error}`); process.exit(1); }
|
|
5529
|
+
const filePath = `${saveDir}/figma-compare-${label}-${timestamp}.png`;
|
|
5530
|
+
writeFileSync(filePath, Buffer.from(result.bytes));
|
|
5531
|
+
return { type: 'figma-node', name: result.name, id: result.id, nodeType: result.type, width: result.width, height: result.height, originalWidth: result.originalWidth, originalHeight: result.originalHeight, path: filePath };
|
|
5532
|
+
}
|
|
5533
|
+
|
|
5534
|
+
// File path (existing screenshot)
|
|
5535
|
+
if (source && existsSync(source)) {
|
|
5536
|
+
return { type: 'file', name: source.split('/').pop(), path: source };
|
|
5537
|
+
}
|
|
5538
|
+
|
|
5539
|
+
// "selection"
|
|
5540
|
+
if (source === 'selection') {
|
|
5541
|
+
const result = exportNodeScreenshot(`const sel = figma.currentPage.selection; node = sel.length > 0 ? sel[0] : null;`, scale, maxDim);
|
|
5542
|
+
if (result.error) { console.error(chalk.red('✗'), `${label}: ${result.error}`); process.exit(1); }
|
|
5543
|
+
const filePath = `${saveDir}/figma-compare-${label}-${timestamp}.png`;
|
|
5544
|
+
writeFileSync(filePath, Buffer.from(result.bytes));
|
|
5545
|
+
return { type: 'figma-node', name: result.name, id: result.id, nodeType: result.type, width: result.width, height: result.height, originalWidth: result.originalWidth, originalHeight: result.originalHeight, path: filePath };
|
|
5546
|
+
}
|
|
5547
|
+
|
|
5548
|
+
// Node ID (contains ":")
|
|
5549
|
+
if (source && source.includes(':')) {
|
|
5550
|
+
const result = exportNodeScreenshot(`node = await figma.getNodeByIdAsync('${source}');`, scale, maxDim);
|
|
5551
|
+
if (result.error) { console.error(chalk.red('✗'), `${label}: ${result.error}`); process.exit(1); }
|
|
5552
|
+
const filePath = `${saveDir}/figma-compare-${label}-${timestamp}.png`;
|
|
5553
|
+
writeFileSync(filePath, Buffer.from(result.bytes));
|
|
5554
|
+
return { type: 'figma-node', name: result.name, id: result.id, nodeType: result.type, width: result.width, height: result.height, originalWidth: result.originalWidth, originalHeight: result.originalHeight, path: filePath };
|
|
5555
|
+
}
|
|
5556
|
+
|
|
5557
|
+
console.error(chalk.red('✗'), `${label}: "${source}" is not a valid file path, node ID, or "selection"`);
|
|
5558
|
+
process.exit(1);
|
|
5559
|
+
}
|
|
5560
|
+
|
|
5561
|
+
const sourceA = resolveSource(options.a, options.aLink, 'a');
|
|
5562
|
+
const sourceB = resolveSource(options.b, options.bLink, 'b');
|
|
5563
|
+
|
|
5564
|
+
console.log(chalk.bold('\n## Visual Comparison\n'));
|
|
5565
|
+
console.log(chalk.cyan('Source A:'), sourceA.name, sourceA.type === 'figma-node' ? `(${sourceA.nodeType} ${sourceA.id}, ${sourceA.originalWidth}x${sourceA.originalHeight})` : '(file)');
|
|
5566
|
+
console.log(chalk.cyan('Source B:'), sourceB.name, sourceB.type === 'figma-node' ? `(${sourceB.nodeType} ${sourceB.id}, ${sourceB.originalWidth}x${sourceB.originalHeight})` : '(file)');
|
|
5567
|
+
console.log('');
|
|
5568
|
+
console.log(chalk.green('Image A:'), sourceA.path);
|
|
5569
|
+
console.log(chalk.green('Image B:'), sourceB.path);
|
|
5570
|
+
console.log('');
|
|
5571
|
+
|
|
5572
|
+
// Output structured data for AI agents
|
|
5573
|
+
console.log(JSON.stringify({
|
|
5574
|
+
mode: 'visual-comparison',
|
|
5575
|
+
sourceA: { ...sourceA, bytes: undefined },
|
|
5576
|
+
sourceB: { ...sourceB, bytes: undefined },
|
|
5577
|
+
instructions: [
|
|
5578
|
+
`1. Open and examine Image A: ${sourceA.path}`,
|
|
5579
|
+
`2. Open and examine Image B: ${sourceB.path}`,
|
|
5580
|
+
'3. Compare them visually for differences in:',
|
|
5581
|
+
' - Layout and spacing (padding, margins, gaps)',
|
|
5582
|
+
' - Colors (backgrounds, text, borders)',
|
|
5583
|
+
' - Typography (font size, weight, line-height)',
|
|
5584
|
+
' - Border radius and shadows',
|
|
5585
|
+
' - Missing or extra elements',
|
|
5586
|
+
' - Alignment and positioning',
|
|
5587
|
+
'4. Output a structured gap report with specific differences',
|
|
5588
|
+
'5. For each difference, provide the exact values from both sources',
|
|
5589
|
+
],
|
|
5590
|
+
gapReportTemplate: {
|
|
5591
|
+
matches: '(list elements that match between A and B)',
|
|
5592
|
+
differences: '(table: element | property | value_in_A | value_in_B | severity)',
|
|
5593
|
+
summary: '(brief overall assessment: how closely do they match)',
|
|
5594
|
+
}
|
|
5595
|
+
}, null, 2));
|
|
5596
|
+
});
|
|
5597
|
+
|
|
5444
5598
|
// ============ EVAL ============
|
|
5445
5599
|
|
|
5446
5600
|
program
|