@dannote/figma-use 0.5.0 → 0.5.2
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 +19 -0
- package/SKILL.md +88 -209
- package/dist/cli/index.js +38 -5
- package/dist/proxy/index.js +21 -32
- package/package.json +7 -1
- package/packages/cli/src/render/component-set.tsx +138 -0
- package/packages/cli/src/render/components.tsx +155 -0
- package/packages/cli/src/render/index.ts +47 -0
- package/packages/cli/src/render/reconciler.ts +845 -0
- package/packages/cli/src/render/vars.ts +185 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.2] - 2026-01-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Multiplayer connection works with Figma's updated protocol (sessionID now from plugin API)
|
|
15
|
+
- Proxy properly handles file switches (closes stale connections)
|
|
16
|
+
- `figma-use status` now shows full connection diagnostics (proxy, plugin, DevTools, file)
|
|
17
|
+
|
|
18
|
+
## [0.5.1] - 2026-01-18
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Package exports for `@dannote/figma-use/render` and `@dannote/figma-use/components`
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- SKILL.md now starts with connection check instructions
|
|
27
|
+
- Simplified SKILL.md for better AI agent comprehension
|
|
28
|
+
|
|
10
29
|
## [0.5.0] - 2026-01-18
|
|
11
30
|
|
|
12
31
|
### Added
|
package/SKILL.md
CHANGED
|
@@ -7,277 +7,156 @@ description: Control Figma via CLI — create shapes, frames, text, components,
|
|
|
7
7
|
|
|
8
8
|
Full control over Figma from the command line.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Before You Start
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
bun install -g @dannote/figma-use
|
|
14
|
-
figma-use plugin # Install plugin (quit Figma first!)
|
|
15
|
-
figma-use proxy # Start proxy server
|
|
16
|
-
# Open Figma → Plugins → Development → Figma Use
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Quick Reference
|
|
20
|
-
|
|
21
|
-
### Create Shapes
|
|
12
|
+
**Always check connection first:**
|
|
22
13
|
|
|
23
14
|
```bash
|
|
24
|
-
figma-use
|
|
25
|
-
figma-use create ellipse --x 0 --y 0 --width 80 --height 80 --fill "#00FF00"
|
|
26
|
-
figma-use create line --x 0 --y 0 --length 100 --stroke "#000"
|
|
27
|
-
figma-use create polygon --x 0 --y 0 --size 60 --sides 6 --fill "#F59E0B"
|
|
28
|
-
figma-use create star --x 0 --y 0 --size 60 --points 5 --fill "#EF4444"
|
|
15
|
+
figma-use status
|
|
29
16
|
```
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
If not connected:
|
|
33
19
|
```bash
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
--layout VERTICAL --gap 16 --padding "24,24,24,24"
|
|
20
|
+
# 1. Start proxy (keep running in background)
|
|
21
|
+
figma-use proxy &
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
--layout HORIZONTAL --gap 8 --padding "12,24,12,24"
|
|
41
|
-
```
|
|
23
|
+
# 2. Start Figma with debug port (for render command)
|
|
24
|
+
figma --remote-debugging-port=9222
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
figma-use create text --x 0 --y 0 --text "Hello" \
|
|
47
|
-
--fontSize 24 --fontFamily "Inter" --fontStyle "Bold" --fill "#000"
|
|
26
|
+
# 3. In Figma: Plugins → Development → Figma Use
|
|
48
27
|
```
|
|
49
28
|
|
|
50
|
-
|
|
51
|
-
|
|
29
|
+
If plugin not installed:
|
|
52
30
|
```bash
|
|
53
|
-
|
|
54
|
-
figma-use
|
|
55
|
-
figma-use node tree --depth 2 # Limit tree depth (also limits node count)
|
|
56
|
-
figma-use node tree -i # Only interactive elements
|
|
57
|
-
figma-use node tree --force # Skip 500 node limit
|
|
58
|
-
figma-use node children <id> # List children
|
|
59
|
-
figma-use node move <id> --x 100 --y 200
|
|
60
|
-
figma-use node resize <id> --width 300 --height 200
|
|
61
|
-
figma-use node rename <id> "New Name"
|
|
62
|
-
figma-use node clone <id>
|
|
63
|
-
figma-use node delete <id>
|
|
31
|
+
# Quit Figma first, then:
|
|
32
|
+
figma-use plugin
|
|
64
33
|
```
|
|
65
34
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
400×300 at (0, 0) | fill: #FFFFFF | layout: col gap=16
|
|
70
|
-
[0] text "Title" (1:24)
|
|
71
|
-
200×32 at (24, 24) | "Hello World" | font: 24px Inter
|
|
72
|
-
```
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## JSX Rendering (Fastest Way)
|
|
73
38
|
|
|
74
|
-
|
|
39
|
+
For complex layouts, use `render --stdin` with pure JSX. No imports needed — elements are built-in:
|
|
75
40
|
|
|
76
41
|
```bash
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
figma-use
|
|
81
|
-
figma-use set opacity <id> 0.8
|
|
82
|
-
figma-use set rotation <id> 45
|
|
83
|
-
figma-use set text <id> "Updated text"
|
|
84
|
-
figma-use set font <id> --family "Inter" --style "Bold" --size 20
|
|
85
|
-
figma-use set layout <id> --mode HORIZONTAL --gap 8 --padding 16
|
|
86
|
-
figma-use set effect <id> --type DROP_SHADOW --radius 10 --offsetY 4 --color "#00000040"
|
|
42
|
+
echo '<Frame style={{padding: 24, gap: 16, flexDirection: "column", backgroundColor: "#FFF", borderRadius: 12}}>
|
|
43
|
+
<Text style={{fontSize: 24, fontWeight: "bold", color: "#000"}}>Card Title</Text>
|
|
44
|
+
<Text style={{fontSize: 14, color: "#666"}}>Description text here</Text>
|
|
45
|
+
</Frame>' | figma-use render --stdin
|
|
87
46
|
```
|
|
88
47
|
|
|
89
|
-
|
|
48
|
+
**Elements:** `Frame`, `Rectangle`, `Ellipse`, `Text`, `Line`, `Star`, `Polygon`, `Vector`, `Group`
|
|
90
49
|
|
|
91
|
-
|
|
92
|
-
figma-use collection list
|
|
93
|
-
figma-use collection create "Colors"
|
|
50
|
+
**Style props:** `width`, `height`, `x`, `y`, `padding`, `paddingTop/Right/Bottom/Left`, `gap`, `flexDirection` (row|column), `justifyContent`, `alignItems`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, `opacity`, `fontSize`, `fontFamily`, `fontWeight`, `color`, `textAlign`
|
|
94
51
|
|
|
95
|
-
|
|
96
|
-
figma-use variable create "Primary" --collection <collectionId> --type COLOR --value "#3B82F6"
|
|
97
|
-
figma-use variable set <varId> --mode <modeId> --value "#1D4ED8"
|
|
98
|
-
figma-use variable bind --node <nodeId> --field fills --variable <varId>
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Styles
|
|
52
|
+
### Buttons Example (3 sizes)
|
|
102
53
|
|
|
103
54
|
```bash
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
55
|
+
echo '<Frame style={{gap: 16, flexDirection: "row", padding: 24}}>
|
|
56
|
+
<Frame name="Small" style={{paddingLeft: 12, paddingRight: 12, paddingTop: 6, paddingBottom: 6, backgroundColor: "#3B82F6", borderRadius: 6, flexDirection: "row", justifyContent: "center", alignItems: "center"}}>
|
|
57
|
+
<Text style={{fontSize: 12, color: "#FFF"}}>Button</Text>
|
|
58
|
+
</Frame>
|
|
59
|
+
<Frame name="Medium" style={{paddingLeft: 16, paddingRight: 16, paddingTop: 8, paddingBottom: 8, backgroundColor: "#3B82F6", borderRadius: 6, flexDirection: "row", justifyContent: "center", alignItems: "center"}}>
|
|
60
|
+
<Text style={{fontSize: 14, color: "#FFF"}}>Button</Text>
|
|
61
|
+
</Frame>
|
|
62
|
+
<Frame name="Large" style={{paddingLeft: 24, paddingRight: 24, paddingTop: 12, paddingBottom: 12, backgroundColor: "#3B82F6", borderRadius: 6, flexDirection: "row", justifyContent: "center", alignItems: "center"}}>
|
|
63
|
+
<Text style={{fontSize: 16, color: "#FFF"}}>Button</Text>
|
|
64
|
+
</Frame>
|
|
65
|
+
</Frame>' | figma-use render --stdin
|
|
107
66
|
```
|
|
108
67
|
|
|
109
|
-
###
|
|
68
|
+
### Advanced: Components & Variants
|
|
110
69
|
|
|
111
|
-
|
|
112
|
-
figma-use export node <id> --format PNG --scale 2 --output design.png
|
|
113
|
-
figma-use export screenshot --output viewport.png
|
|
114
|
-
figma-use export selection --output selection.png
|
|
115
|
-
```
|
|
70
|
+
For `defineComponent`, `defineComponentSet`, `defineVars` — use files with imports:
|
|
116
71
|
|
|
117
|
-
Heavy ops support `--timeout` (seconds):
|
|
118
72
|
```bash
|
|
119
|
-
figma-use
|
|
73
|
+
figma-use render --examples # Full API reference
|
|
74
|
+
figma-use render ./MyComponent.figma.tsx
|
|
120
75
|
```
|
|
121
76
|
|
|
122
|
-
|
|
77
|
+
---
|
|
123
78
|
|
|
124
|
-
|
|
125
|
-
figma-use selection get
|
|
126
|
-
figma-use selection set "1:2,1:3"
|
|
127
|
-
figma-use page list
|
|
128
|
-
figma-use page set "Page Name"
|
|
129
|
-
figma-use viewport zoom-to-fit <ids...>
|
|
130
|
-
```
|
|
79
|
+
## CLI Commands
|
|
131
80
|
|
|
132
|
-
###
|
|
81
|
+
### Create
|
|
133
82
|
|
|
134
83
|
```bash
|
|
135
|
-
figma-use
|
|
136
|
-
figma-use
|
|
137
|
-
figma-use
|
|
138
|
-
figma-use
|
|
139
|
-
figma-use
|
|
84
|
+
figma-use create frame --width 400 --height 300 --fill "#FFF" --radius 12 --layout VERTICAL --gap 16
|
|
85
|
+
figma-use create rect --width 100 --height 50 --fill "#FF0000" --radius 8
|
|
86
|
+
figma-use create ellipse --width 80 --height 80 --fill "#00FF00"
|
|
87
|
+
figma-use create text --text "Hello" --fontSize 24 --fill "#000"
|
|
88
|
+
figma-use create line --length 100 --stroke "#000"
|
|
140
89
|
```
|
|
141
90
|
|
|
142
|
-
###
|
|
91
|
+
### Query
|
|
143
92
|
|
|
144
93
|
```bash
|
|
145
|
-
figma-use
|
|
146
|
-
figma-use
|
|
147
|
-
figma-use
|
|
148
|
-
figma-use
|
|
94
|
+
figma-use node get <id> # Node properties
|
|
95
|
+
figma-use node tree # Page structure
|
|
96
|
+
figma-use node tree --depth 2 # Limit depth
|
|
97
|
+
figma-use node children <id> # Children list
|
|
98
|
+
figma-use find --name "Button" # Find by name
|
|
99
|
+
figma-use find --type FRAME # Find by type
|
|
100
|
+
figma-use selection get # Current selection
|
|
149
101
|
```
|
|
150
102
|
|
|
151
|
-
###
|
|
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):
|
|
103
|
+
### Modify
|
|
156
104
|
|
|
157
105
|
```bash
|
|
158
|
-
|
|
159
|
-
figma-use
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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"
|
|
106
|
+
figma-use set fill <id> "#FF0000"
|
|
107
|
+
figma-use set stroke <id> "#000" --weight 2
|
|
108
|
+
figma-use set radius <id> 12
|
|
109
|
+
figma-use set opacity <id> 0.5
|
|
110
|
+
figma-use set text <id> "New text"
|
|
111
|
+
figma-use set font <id> --family "Inter" --style "Bold" --size 20
|
|
112
|
+
figma-use set layout <id> --mode VERTICAL --gap 12 --padding 16
|
|
113
|
+
figma-use node move <id> --x 100 --y 200
|
|
114
|
+
figma-use node resize <id> --width 300 --height 200
|
|
115
|
+
figma-use node delete <id>
|
|
175
116
|
```
|
|
176
117
|
|
|
177
|
-
|
|
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
|
-
#### Reusable Components
|
|
184
|
-
|
|
185
|
-
**defineComponent** — simple reusable component:
|
|
186
|
-
```tsx
|
|
187
|
-
import { defineComponent, Frame, Text } from '@dannote/figma-use/render'
|
|
188
|
-
|
|
189
|
-
const Button = defineComponent('Button',
|
|
190
|
-
<Frame style={{ padding: 12, backgroundColor: '#3B82F6', borderRadius: 8 }}>
|
|
191
|
-
<Text style={{ color: '#FFF' }}>Click me</Text>
|
|
192
|
-
</Frame>
|
|
193
|
-
)
|
|
118
|
+
### Export
|
|
194
119
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<Button /> {/* Creates Instance */}
|
|
200
|
-
</Frame>
|
|
201
|
-
)
|
|
120
|
+
```bash
|
|
121
|
+
figma-use export node <id> --output design.png
|
|
122
|
+
figma-use export screenshot --output viewport.png
|
|
123
|
+
figma-use export selection --output selection.png
|
|
202
124
|
```
|
|
203
125
|
|
|
204
|
-
|
|
205
|
-
```tsx
|
|
206
|
-
import { defineComponentSet, Frame, Text } from '@dannote/figma-use/render'
|
|
207
|
-
|
|
208
|
-
const Button = defineComponentSet('Button', {
|
|
209
|
-
variant: ['Primary', 'Secondary'] as const,
|
|
210
|
-
size: ['Small', 'Large'] as const,
|
|
211
|
-
}, ({ variant, size }) => (
|
|
212
|
-
<Frame style={{
|
|
213
|
-
padding: size === 'Large' ? 16 : 8,
|
|
214
|
-
backgroundColor: variant === 'Primary' ? '#3B82F6' : '#E5E7EB',
|
|
215
|
-
borderRadius: 8,
|
|
216
|
-
flexDirection: 'row',
|
|
217
|
-
justifyContent: 'center',
|
|
218
|
-
}}>
|
|
219
|
-
<Text style={{
|
|
220
|
-
fontSize: size === 'Large' ? 16 : 14,
|
|
221
|
-
color: variant === 'Primary' ? '#FFF' : '#111',
|
|
222
|
-
}}>
|
|
223
|
-
{variant} Button
|
|
224
|
-
</Text>
|
|
225
|
-
</Frame>
|
|
226
|
-
))
|
|
126
|
+
### Navigate
|
|
227
127
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
<Button variant="Secondary" size="Large" />
|
|
233
|
-
</Frame>
|
|
234
|
-
)
|
|
128
|
+
```bash
|
|
129
|
+
figma-use page list
|
|
130
|
+
figma-use page set "Page Name"
|
|
131
|
+
figma-use viewport zoom-to-fit <ids...>
|
|
235
132
|
```
|
|
236
133
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
#### Variable Bindings (Experimental)
|
|
240
|
-
|
|
241
|
-
Bind Figma variables to colors by name with fallback values:
|
|
242
|
-
|
|
243
|
-
```tsx
|
|
244
|
-
import { defineVars, Frame } from '@dannote/figma-use'
|
|
134
|
+
### Variables & Styles
|
|
245
135
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
export default () => (
|
|
252
|
-
<Frame style={{ backgroundColor: colors.primary, borderColor: colors.border }} />
|
|
253
|
-
)
|
|
136
|
+
```bash
|
|
137
|
+
figma-use variable list
|
|
138
|
+
figma-use variable create "Primary" --collection <id> --type COLOR --value "#3B82F6"
|
|
139
|
+
figma-use style list
|
|
140
|
+
figma-use style create-paint "Brand/Primary" --color "#E11D48"
|
|
254
141
|
```
|
|
255
142
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
### Eval (Arbitrary Code)
|
|
143
|
+
### Escape Hatch
|
|
259
144
|
|
|
260
145
|
```bash
|
|
261
146
|
figma-use eval "return figma.currentPage.name"
|
|
262
|
-
figma-use eval "
|
|
147
|
+
figma-use eval "figma.createRectangle().resize(100, 100)"
|
|
263
148
|
```
|
|
264
149
|
|
|
150
|
+
---
|
|
151
|
+
|
|
265
152
|
## Output
|
|
266
153
|
|
|
267
|
-
Human-readable by default. Add `--json` for machine parsing
|
|
268
|
-
```bash
|
|
269
|
-
figma-use node get <id> --json
|
|
270
|
-
```
|
|
154
|
+
Human-readable by default. Add `--json` for machine parsing.
|
|
271
155
|
|
|
272
156
|
## Colors
|
|
273
157
|
|
|
274
|
-
Hex format: `#RGB`, `#RRGGBB`,
|
|
158
|
+
Hex format: `#RGB`, `#RRGGBB`, `#RRGGBBAA`
|
|
275
159
|
|
|
276
160
|
## Node IDs
|
|
277
161
|
|
|
278
|
-
Format: `
|
|
279
|
-
|
|
280
|
-
Get IDs from:
|
|
281
|
-
- `figma-use selection get`
|
|
282
|
-
- `figma-use node children <parentId>`
|
|
283
|
-
- Figma → right-click → Copy link → ID in URL
|
|
162
|
+
Format: `sessionID:localID` (e.g., `1:2`, `45:123`). Get from `figma-use selection get` or `figma-use node tree`.
|
package/dist/cli/index.js
CHANGED
|
@@ -22839,16 +22839,49 @@ async function getParentGUID() {
|
|
|
22839
22839
|
|
|
22840
22840
|
// packages/cli/src/commands/status.ts
|
|
22841
22841
|
var status_default = defineCommand({
|
|
22842
|
-
meta: { description: "Check
|
|
22842
|
+
meta: { description: "Check connection status (plugin, DevTools, multiplayer)" },
|
|
22843
22843
|
args: { json: { type: "boolean", description: "Output as JSON" } },
|
|
22844
22844
|
async run({ args }) {
|
|
22845
|
+
const result = {
|
|
22846
|
+
proxy: false,
|
|
22847
|
+
plugin: false,
|
|
22848
|
+
devtools: false,
|
|
22849
|
+
fileKey: null,
|
|
22850
|
+
multiplayer: false
|
|
22851
|
+
};
|
|
22845
22852
|
try {
|
|
22846
22853
|
const status = await getStatus();
|
|
22847
|
-
|
|
22848
|
-
|
|
22849
|
-
|
|
22850
|
-
|
|
22854
|
+
result.proxy = true;
|
|
22855
|
+
result.plugin = status.pluginConnected;
|
|
22856
|
+
} catch {}
|
|
22857
|
+
try {
|
|
22858
|
+
result.fileKey = await getFileKey();
|
|
22859
|
+
result.devtools = true;
|
|
22860
|
+
result.multiplayer = true;
|
|
22861
|
+
} catch {}
|
|
22862
|
+
if (args.json) {
|
|
22863
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22864
|
+
return;
|
|
22851
22865
|
}
|
|
22866
|
+
console.log(result.proxy ? "\u2713 Proxy running" : "\u2717 Proxy not running (run: figma-use proxy)");
|
|
22867
|
+
console.log(result.plugin ? "\u2713 Plugin connected" : "\u2717 Plugin not connected (open plugin in Figma)");
|
|
22868
|
+
console.log(result.devtools ? "\u2713 DevTools available" : "\u2717 DevTools not available (run: figma --remote-debugging-port=9222)");
|
|
22869
|
+
if (result.devtools) {
|
|
22870
|
+
if (result.fileKey) {
|
|
22871
|
+
console.log(`\u2713 Figma file ready (${result.fileKey})`);
|
|
22872
|
+
} else {
|
|
22873
|
+
console.log("\u2717 No Figma file open (open a file from figma.com, not local)");
|
|
22874
|
+
}
|
|
22875
|
+
}
|
|
22876
|
+
if (result.proxy && result.plugin && result.multiplayer) {
|
|
22877
|
+
console.log(`
|
|
22878
|
+
\u2713 Ready for render command`);
|
|
22879
|
+
} else if (result.proxy && result.plugin) {
|
|
22880
|
+
console.log(`
|
|
22881
|
+
\u26A0 CLI commands work, but render requires DevTools + Figma file`);
|
|
22882
|
+
}
|
|
22883
|
+
if (!result.proxy)
|
|
22884
|
+
process.exit(1);
|
|
22852
22885
|
}
|
|
22853
22886
|
});
|
|
22854
22887
|
// packages/cli/src/commands/proxy.ts
|
package/dist/proxy/index.js
CHANGED
|
@@ -4637,22 +4637,6 @@ var init_prompt = __esm(() => {
|
|
|
4637
4637
|
});
|
|
4638
4638
|
|
|
4639
4639
|
// packages/cli/src/multiplayer/protocol.ts
|
|
4640
|
-
function parseVarint(data, pos) {
|
|
4641
|
-
let value = 0;
|
|
4642
|
-
let shift = 0;
|
|
4643
|
-
while (pos < data.length) {
|
|
4644
|
-
const byte2 = data[pos];
|
|
4645
|
-
if (byte2 === undefined)
|
|
4646
|
-
break;
|
|
4647
|
-
pos++;
|
|
4648
|
-
value |= (byte2 & KIWI.VARINT_VALUE_MASK) << shift;
|
|
4649
|
-
if (!(byte2 & KIWI.VARINT_CONTINUE_BIT)) {
|
|
4650
|
-
break;
|
|
4651
|
-
}
|
|
4652
|
-
shift += KIWI.VARINT_BITS_PER_BYTE;
|
|
4653
|
-
}
|
|
4654
|
-
return [value, pos];
|
|
4655
|
-
}
|
|
4656
4640
|
function isKiwiMessage(data) {
|
|
4657
4641
|
return data.length >= 2 && data[0] === KIWI.MESSAGE_MARKER;
|
|
4658
4642
|
}
|
|
@@ -4684,7 +4668,7 @@ function buildMultiplayerUrl(fileKey, trackingId) {
|
|
|
4684
4668
|
});
|
|
4685
4669
|
return `wss://www.figma.com/api/multiplayer/${fileKey}?${params}`;
|
|
4686
4670
|
}
|
|
4687
|
-
var MESSAGE_TYPES, ZSTD_MAGIC, KIWI,
|
|
4671
|
+
var MESSAGE_TYPES, ZSTD_MAGIC, KIWI, FIG_WIRE_MAGIC = "fig-wire", PROTOCOL_VERSION = 151;
|
|
4688
4672
|
var init_protocol = __esm(() => {
|
|
4689
4673
|
MESSAGE_TYPES = {
|
|
4690
4674
|
JOIN_START: 0,
|
|
@@ -4711,10 +4695,6 @@ var init_protocol = __esm(() => {
|
|
|
4711
4695
|
VARINT_VALUE_MASK: 127,
|
|
4712
4696
|
VARINT_BITS_PER_BYTE: 7
|
|
4713
4697
|
};
|
|
4714
|
-
SESSION_ID = {
|
|
4715
|
-
MIN: 1e4,
|
|
4716
|
-
MAX: 1e6
|
|
4717
|
-
};
|
|
4718
4698
|
});
|
|
4719
4699
|
|
|
4720
4700
|
// node_modules/kiwi-schema/kiwi-esm.js
|
|
@@ -11868,12 +11848,6 @@ class FigmaMultiplayerClient {
|
|
|
11868
11848
|
if (!isKiwiMessage(decompressed))
|
|
11869
11849
|
return;
|
|
11870
11850
|
const msgType = getKiwiMessageType(decompressed);
|
|
11871
|
-
if (msgType === MESSAGE_TYPES.JOIN_START && decompressed[2] === KIWI.SESSION_ID_FIELD) {
|
|
11872
|
-
const [val] = parseVarint(decompressed, 3);
|
|
11873
|
-
if (val > SESSION_ID.MIN && val < SESSION_ID.MAX) {
|
|
11874
|
-
sessionID = val;
|
|
11875
|
-
}
|
|
11876
|
-
}
|
|
11877
11851
|
if (msgType === MESSAGE_TYPES.JOIN_END) {
|
|
11878
11852
|
joinEndReceived = true;
|
|
11879
11853
|
}
|
|
@@ -11890,10 +11864,10 @@ class FigmaMultiplayerClient {
|
|
|
11890
11864
|
this.options.onMessage(message);
|
|
11891
11865
|
} catch {}
|
|
11892
11866
|
}
|
|
11893
|
-
if (
|
|
11867
|
+
if (joinEndReceived && (this.state === "connecting" || this.state === "connected")) {
|
|
11894
11868
|
clearTimeout(timeout);
|
|
11895
11869
|
this.state = "ready";
|
|
11896
|
-
this.sessionInfo = { sessionID, reconnectSequenceNumber };
|
|
11870
|
+
this.sessionInfo = { sessionID: sessionID || 0, reconnectSequenceNumber };
|
|
11897
11871
|
resolve(this.sessionInfo);
|
|
11898
11872
|
}
|
|
11899
11873
|
} catch {}
|
|
@@ -11967,6 +11941,9 @@ class FigmaMultiplayerClient {
|
|
|
11967
11941
|
isReady() {
|
|
11968
11942
|
return this.state === "ready";
|
|
11969
11943
|
}
|
|
11944
|
+
isConnected() {
|
|
11945
|
+
return this.state === "connected" && this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
11946
|
+
}
|
|
11970
11947
|
close() {
|
|
11971
11948
|
if (this.ws) {
|
|
11972
11949
|
this.ws.close();
|
|
@@ -26821,11 +26798,23 @@ function cleanupIdleConnections() {
|
|
|
26821
26798
|
}
|
|
26822
26799
|
async function getMultiplayerConnection(fileKey) {
|
|
26823
26800
|
await ensureInitialized();
|
|
26801
|
+
for (const [key, conn] of connectionPool) {
|
|
26802
|
+
if (key !== fileKey) {
|
|
26803
|
+
consola.info(`Closing stale connection to ${key} (switched to ${fileKey})`);
|
|
26804
|
+
conn.client.close();
|
|
26805
|
+
connectionPool.delete(key);
|
|
26806
|
+
}
|
|
26807
|
+
}
|
|
26824
26808
|
const existing = connectionPool.get(fileKey);
|
|
26825
26809
|
if (existing) {
|
|
26826
|
-
existing.
|
|
26827
|
-
|
|
26828
|
-
|
|
26810
|
+
if (existing.client.isConnected()) {
|
|
26811
|
+
existing.lastUsed = Date.now();
|
|
26812
|
+
consola.debug(`Reusing multiplayer connection for ${fileKey}`);
|
|
26813
|
+
return { client: existing.client, sessionID: existing.sessionID };
|
|
26814
|
+
} else {
|
|
26815
|
+
consola.warn(`Multiplayer connection to ${fileKey} died, reconnecting...`);
|
|
26816
|
+
connectionPool.delete(fileKey);
|
|
26817
|
+
}
|
|
26829
26818
|
}
|
|
26830
26819
|
consola.info(`Creating multiplayer connection for ${fileKey}`);
|
|
26831
26820
|
const cookies = await getCookiesFromDevTools2();
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dannote/figma-use",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Control Figma from the command line. Full read/write access for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"figma-use": "bin/figma-use.js"
|
|
8
8
|
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/cli/index.js",
|
|
11
|
+
"./render": "./packages/cli/src/render/index.ts",
|
|
12
|
+
"./components": "./packages/cli/src/render/components.tsx"
|
|
13
|
+
},
|
|
9
14
|
"scripts": {
|
|
10
15
|
"build": "bun run build:proxy && bun run build:cli && bun run build:plugin",
|
|
11
16
|
"build:proxy": "bun build packages/proxy/src/index.ts --outdir dist/proxy --target bun",
|
|
@@ -18,6 +23,7 @@
|
|
|
18
23
|
"files": [
|
|
19
24
|
"bin",
|
|
20
25
|
"dist",
|
|
26
|
+
"packages/cli/src/render",
|
|
21
27
|
"packages/plugin/dist",
|
|
22
28
|
"README.md",
|
|
23
29
|
"CHANGELOG.md",
|