@dannote/figma-use 0.2.1 → 0.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/CHANGELOG.md CHANGED
@@ -7,6 +7,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-01-18
11
+
12
+ ### Added
13
+
14
+ - **`defineVars` API for Figma variables** — bind colors to variables by name
15
+ ```tsx
16
+ const colors = defineVars({
17
+ primary: { name: 'Colors/Gray/50', value: '#F8FAFC' },
18
+ accent: { name: 'Colors/Blue/500', value: '#3B82F6' },
19
+ })
20
+ <Frame style={{ backgroundColor: colors.primary }} />
21
+ ```
22
+ - Variable binding for `backgroundColor`, `borderColor`, and text `color`
23
+ - Variables resolved by name at render time (no more magic IDs)
24
+ - `defineVars` support in stdin snippets
25
+ - Explicit fallback values in `defineVars` for proper color display
26
+
27
+ ### Fixed
28
+
29
+ - Auto-layout now works correctly via `trigger-layout` post-render
30
+ - Nested auto-layout frames trigger recursively
31
+ - Variable binding encoding matches Figma's exact wire format
32
+
33
+ ### Changed
34
+
35
+ - Marked React render and variable bindings as **experimental** in docs
36
+
37
+ ## [0.3.1] - 2026-01-18
38
+
39
+ ### Added
40
+
41
+ - **Variable binding via multiplayer protocol** — bind fill colors to Figma variables without plugin API
42
+ - `encodePaintWithVariableBinding()` — encode Paint with color variable binding
43
+ - `encodeNodeChangeWithVariables()` — encode NodeChange with variable-bound paints
44
+ - `parseVariableId()` — parse "VariableID:sessionID:localID" strings
45
+ - New exports: `VariableBinding`, `encodePaintWithVariableBinding`, `encodeNodeChangeWithVariables`, `parseVariableId`
46
+ - `bind-fill-variable` plugin command — bind fill color to variable
47
+ - `bind-stroke-variable` plugin command — bind stroke color to variable
48
+
49
+ ### Fixed
50
+
51
+ - Message field mapping: nodeChanges is field 4, reconnectSequenceNumber is field 25
52
+ - Paint variable binding format now matches Figma's exact wire format
53
+
54
+ ### Technical
55
+
56
+ - Discovered Figma's variable binding wire format via WebSocket traffic analysis
57
+ - Created capture/diff tools for binary protocol analysis (`scripts/capture.ts`, `scripts/diff-hex.ts`)
58
+ - 142 tests passing
59
+
60
+ ## [0.3.0] - 2025-01-17
61
+
62
+ ### Added
63
+
64
+ - **`render` command** — render React/TSX components directly to Figma
65
+ - From file: `figma-use render ./Card.figma.tsx`
66
+ - From stdin: `echo '<Frame style={{...}} />' | figma-use render --stdin`
67
+ - With props: `--props '{"title": "Hello"}'`
68
+ - Into parent: `--parent "1:23"`
69
+ - Dry run: `--dryRun` outputs NodeChanges JSON
70
+ - **Multiplayer WebSocket connection pooling** in proxy
71
+ - First render: ~4s (establishes connection)
72
+ - Subsequent renders: ~0.4s (10x faster!)
73
+ - Connections auto-close after 5min idle
74
+ - **React components** — `Frame`, `Text`, `Rectangle`, `Ellipse`, `Line`, `Star`, `Polygon`, `Vector`, `Component`, `Instance`, `Group`, `Page`, `View`
75
+ - **JSX intrinsic elements** — PascalCase in JSX, lowercase in output
76
+ - **culori integration** — robust color parsing (hex, rgb(), hsl(), named colors)
77
+ - `/render` endpoint in proxy for direct NodeChanges submission
78
+ - `/status` endpoint now shows multiplayer connection pool
79
+
80
+ ### Changed
81
+
82
+ - Proxy now holds persistent WebSocket connections to Figma multiplayer
83
+ - Architecture diagram updated to show dual communication paths
84
+ - 143 tests passing
85
+
86
+ ### Fixed
87
+
88
+ - TypeScript strict mode errors in tests
89
+ - NodeChanges validation before sending (must have guid)
90
+
10
91
  ## [0.2.1] - 2025-01-17
11
92
 
12
93
  ### Added
@@ -137,7 +218,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
137
218
  - Export commands: PNG/SVG/PDF export, screenshot
138
219
  - Inline styling: `--fill`, `--stroke`, `--radius` etc. on create commands
139
220
 
140
- [unreleased]: https://github.com/dannote/figma-use/compare/v0.2.1...HEAD
221
+ [unreleased]: https://github.com/dannote/figma-use/compare/v0.3.0...HEAD
222
+ [0.3.0]: https://github.com/dannote/figma-use/compare/v0.2.1...v0.3.0
141
223
  [0.2.1]: https://github.com/dannote/figma-use/compare/v0.2.0...v0.2.1
142
224
  [0.2.0]: https://github.com/dannote/figma-use/compare/v0.1.5...v0.2.0
143
225
  [0.1.5]: https://github.com/dannote/figma-use/compare/v0.1.4...v0.1.5
package/README.md CHANGED
@@ -27,16 +27,28 @@ figma-use gives AI agents **full read/write control** over Figma.
27
27
  ## How it works
28
28
 
29
29
  ```
30
- ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
31
- │ │ │ │ │ │
32
- │ AI Agent / │────▶│ figma-use │────▶│ Figma │
33
- │ CLI │ HTTP│ proxy │ WS │ Plugin │
34
- │◀────│ :38451 │◀────│
35
- │ │ │ │ │ │
36
- └─────────────────┘ └─────────────────┘ └─────────────────┘
30
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
31
+ │ │ │ │ │ │
32
+ │ AI Agent / │─────▶│ figma-use │─────▶│ Figma │
33
+ │ CLI │ HTTP │ proxy │ WS │ Plugin │
34
+ │◀─────│ :38451 │◀─────│
35
+ │ │ │ │ │ │
36
+ └─────────────────┘ └────────┬────────┘ └─────────────────┘
37
+
38
+ │ WebSocket (persistent)
39
+
40
+ ┌─────────────────┐
41
+ │ Figma │
42
+ │ Multiplayer │
43
+ │ Server │
44
+ └─────────────────┘
37
45
  ```
38
46
 
39
- The CLI sends commands to a local proxy server, which forwards them via WebSocket to a Figma plugin. The plugin executes commands using the Figma API and returns results.
47
+ Two communication paths:
48
+ - **Plugin API** — most commands go through the Figma plugin for full API access
49
+ - **Multiplayer WebSocket** — the `render` command writes directly to Figma's multiplayer server for ~100x faster node creation
50
+
51
+ The proxy maintains persistent connections for fast repeated operations.
40
52
 
41
53
  ## Installation
42
54
 
@@ -226,10 +238,10 @@ figma-use viewport zoom-to-fit <ids...>
226
238
  ```bash
227
239
  figma-use find --name "Button"
228
240
  figma-use find --name "Icon" --type FRAME
229
- figma-use find --type INSTANCE # Find all instances on current page
241
+ figma-use find --type INSTANCE --limit 50 # Limit results (default: 100)
230
242
  figma-use get pages
231
243
  figma-use get components --name "Button" # Filter by name
232
- figma-use get components --limit 50 # Limit results (default: 100)
244
+ figma-use get components --limit 50 # Limit results (default: 50)
233
245
  figma-use get styles
234
246
  ```
235
247
 
@@ -246,6 +258,82 @@ figma-use group ungroup <id>
246
258
  figma-use group flatten "1:2,1:3"
247
259
  ```
248
260
 
261
+ ### Render React Components (Experimental)
262
+
263
+ > ⚠️ **Experimental**: The React render feature uses Figma's internal multiplayer protocol, which is undocumented and may change without notice. Use for prototyping and automation, not production workflows.
264
+
265
+ Render TSX/JSX components directly to Figma via WebSocket (bypasses plugin API for ~100x speed):
266
+
267
+ ```bash
268
+ # From file
269
+ figma-use render ./Card.figma.tsx
270
+
271
+ # With props
272
+ figma-use render ./Card.figma.tsx --props '{"title": "Hello", "items": ["A", "B"]}'
273
+
274
+ # JSX snippet from stdin
275
+ echo '<Frame style={{width: 200, height: 100, backgroundColor: "#FF0000"}} />' | figma-use render --stdin
276
+
277
+ # Nested elements
278
+ echo '<Frame style={{padding: 20, gap: 10}}>
279
+ <Text style={{fontSize: 24}}>Title</Text>
280
+ <Rectangle style={{width: 100, height: 50, backgroundColor: "#3B82F6"}} />
281
+ </Frame>' | figma-use render --stdin
282
+
283
+ # Full component from stdin (with imports/exports)
284
+ cat component.tsx | figma-use render --stdin
285
+
286
+ # Into specific parent
287
+ figma-use render ./Card.figma.tsx --parent "1:23"
288
+
289
+ # Dry run (output NodeChanges JSON without sending)
290
+ figma-use render ./Card.figma.tsx --dryRun
291
+ ```
292
+
293
+ **Important:** The `render` command requires:
294
+ 1. Figma running with remote debugging: `figma --remote-debugging-port=9222`
295
+ 2. Proxy server running: `figma-use proxy`
296
+
297
+ The proxy maintains persistent WebSocket connections for fast repeated renders:
298
+ - First render: ~4s (establishes connection)
299
+ - Subsequent renders: ~0.4s (reuses connection)
300
+
301
+ Example component (`Card.figma.tsx`):
302
+
303
+ ```tsx
304
+ import * as React from 'react'
305
+ import { Frame, Text, Rectangle } from '@dannote/figma-use/components'
306
+
307
+ interface CardProps {
308
+ title: string
309
+ items: string[]
310
+ }
311
+
312
+ export default function Card({ title, items }: CardProps) {
313
+ return (
314
+ <Frame name="Card" style={{
315
+ width: 300,
316
+ flexDirection: 'column',
317
+ padding: 24,
318
+ gap: 16,
319
+ backgroundColor: '#FFFFFF',
320
+ borderRadius: 12,
321
+ }}>
322
+ <Text name="Title" style={{ fontSize: 24, fontWeight: 'bold', color: '#000' }}>
323
+ {title}
324
+ </Text>
325
+ <Frame name="Items" style={{ flexDirection: 'column', gap: 8 }}>
326
+ {items.map((item, i) => (
327
+ <Text key={i} style={{ fontSize: 16, color: '#666' }}>{item}</Text>
328
+ ))}
329
+ </Frame>
330
+ </Frame>
331
+ )
332
+ }
333
+ ```
334
+
335
+ Available elements: `Frame`, `Rectangle`, `Ellipse`, `Text`, `Line`, `Star`, `Polygon`, `Vector`, `Component`, `Instance`, `Group`, `Page`, `View`
336
+
249
337
  ### Advanced
250
338
 
251
339
  ```bash
@@ -357,3 +445,33 @@ Workflow:
357
445
  ## License
358
446
 
359
447
  MIT
448
+
449
+ ### Variable Bindings (Experimental)
450
+
451
+ > ⚠️ **Experimental**: Variable binding uses reverse-engineered protocol. Supports `backgroundColor`, `borderColor`, and text `color`.
452
+
453
+ Bind Figma variables to colors using human-readable names:
454
+
455
+ ```tsx
456
+ // tokens.figma.ts
457
+ import { defineVars } from '@dannote/figma-use'
458
+
459
+ export const colors = defineVars({
460
+ primary: { name: 'Colors/Gray/50', value: '#F8FAFC' },
461
+ accent: { name: 'Colors/Blue/500', value: '#3B82F6' },
462
+ text: { name: 'Colors/Gray/900', value: '#0F172A' },
463
+ })
464
+
465
+ // Card.figma.tsx
466
+ import { colors } from './tokens.figma'
467
+
468
+ export function Card({ title }: { title: string }) {
469
+ return (
470
+ <Frame style={{ backgroundColor: colors.primary }}>
471
+ <Text style={{ color: colors.text }}>{title}</Text>
472
+ </Frame>
473
+ )
474
+ }
475
+ ```
476
+
477
+ The `value` field provides a fallback color for display. Variables are bound at the protocol level — no plugin API calls needed.
package/SKILL.md CHANGED
@@ -148,6 +148,57 @@ figma-use group create "1:2,1:3"
148
148
  figma-use group ungroup <id>
149
149
  ```
150
150
 
151
+ ### Render React Components (Experimental)
152
+
153
+ > ⚠️ Uses Figma's internal multiplayer protocol — may break without notice.
154
+
155
+ Render TSX/JSX directly to Figma (~100x faster than plugin API):
156
+
157
+ ```bash
158
+ # From file
159
+ figma-use render ./Card.figma.tsx
160
+
161
+ # JSX snippet from stdin
162
+ echo '<Frame style={{width: 200, height: 100, backgroundColor: "#FF0000"}} />' | figma-use render --stdin
163
+
164
+ # Nested elements
165
+ echo '<Frame style={{padding: 20, gap: 10, flexDirection: "column"}}>
166
+ <Text style={{fontSize: 24, color: "#000"}}>Title</Text>
167
+ <Rectangle style={{width: 100, height: 50, backgroundColor: "#3B82F6"}} />
168
+ </Frame>' | figma-use render --stdin
169
+
170
+ # With props
171
+ figma-use render ./Card.figma.tsx --props '{"title": "Hello"}'
172
+
173
+ # Into specific parent
174
+ figma-use render ./Card.figma.tsx --parent "1:23"
175
+ ```
176
+
177
+ **Requires:**
178
+ 1. Figma with `figma --remote-debugging-port=9222`
179
+ 2. Proxy running: `figma-use proxy`
180
+
181
+ Available elements: `Frame`, `Rectangle`, `Ellipse`, `Text`, `Line`, `Star`, `Polygon`, `Vector`, `Component`, `Instance`, `Group`
182
+
183
+ #### Variable Bindings (Experimental)
184
+
185
+ Bind Figma variables to colors by name with fallback values:
186
+
187
+ ```tsx
188
+ import { defineVars, Frame } from '@dannote/figma-use'
189
+
190
+ const colors = defineVars({
191
+ primary: { name: 'Colors/Gray/50', value: '#F8FAFC' },
192
+ border: { name: 'Colors/Gray/500', value: '#64748B' },
193
+ })
194
+
195
+ export default () => (
196
+ <Frame style={{ backgroundColor: colors.primary, borderColor: colors.border }} />
197
+ )
198
+ ```
199
+
200
+ Supports: `backgroundColor`, `borderColor`, text `color`.
201
+
151
202
  ### Eval (Arbitrary Code)
152
203
 
153
204
  ```bash
@@ -0,0 +1,3 @@
1
+ import * as React from 'react'
2
+ const Frame: React.FC<any> = ({ children, ...props }) => React.createElement('frame', props, children)
3
+ export default () => (<Frame style={{width: 200, height: 100, backgroundColor: "#FF0000"}} />)
@@ -0,0 +1,3 @@
1
+ import * as React from 'react'
2
+ const Frame: React.FC<any> = ({ children, ...props }) => React.createElement('frame', props, children)
3
+ export default () => (<Frame style={{width: 200, height: 100, backgroundColor: "#FF0000"}} />)