design-lazyyy-cli 0.1.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 +291 -0
- package/package.json +30 -0
- package/src/figjam-client.js +310 -0
- package/src/index.js +2528 -0
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# design-lazyyy-cli
|
|
4
|
+
|
|
5
|
+
**Control Figma Desktop from your terminal. No API key needed.**
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[]()
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Create design tokens, render UI with JSX, manage variables, export CSS/Tailwind
|
|
14
|
+
all through a single CLI connected to Figma Desktop via Chrome DevTools Protocol.
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<br>
|
|
19
|
+
|
|
20
|
+
## Why design-lazyyy-cli?
|
|
21
|
+
|
|
22
|
+
| Pain Point | Solution |
|
|
23
|
+
|:---|:---|
|
|
24
|
+
| Figma REST API requires tokens & has rate limits | Direct desktop connection, zero config |
|
|
25
|
+
| Manually creating 200+ color variables | `tokens tailwind` generates 242 variables instantly |
|
|
26
|
+
| Switching between Figma and code for tokens | `export css` / `export tailwind` in one command |
|
|
27
|
+
| Building complex layouts click-by-click | JSX-like `render` command creates nested auto-layouts |
|
|
28
|
+
| Repetitive design system setup | `tokens ds` bootstraps a complete system in seconds |
|
|
29
|
+
|
|
30
|
+
<br>
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Prerequisites
|
|
35
|
+
|
|
36
|
+
- **Node.js** >= 18
|
|
37
|
+
- **Figma Desktop** (free account works)
|
|
38
|
+
|
|
39
|
+
### Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Install globally via npm
|
|
43
|
+
npm install -g design-lazyyy-cli
|
|
44
|
+
|
|
45
|
+
# One-time setup: patch Figma & install figma-use
|
|
46
|
+
design-lazyyy-cli init
|
|
47
|
+
|
|
48
|
+
# Connect to Figma Desktop
|
|
49
|
+
design-lazyyy-cli connect
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> **Tip:** Run `design-lazyyy-cli status` to verify the connection is active.
|
|
53
|
+
|
|
54
|
+
<br>
|
|
55
|
+
|
|
56
|
+
## Usage with Claude Code
|
|
57
|
+
|
|
58
|
+
design-lazyyy-cli ships with a `CLAUDE.md` that teaches Claude every command. No `/init` needed.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd design-lazyyy-cli
|
|
62
|
+
claude
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then just talk naturally:
|
|
66
|
+
|
|
67
|
+
> "Create a complete design system with Tailwind colors"
|
|
68
|
+
> "Render a card component with title and description"
|
|
69
|
+
> "Export all variables as CSS custom properties"
|
|
70
|
+
|
|
71
|
+
Claude handles the rest.
|
|
72
|
+
|
|
73
|
+
<br>
|
|
74
|
+
|
|
75
|
+
## Core Commands
|
|
76
|
+
|
|
77
|
+
### Connect & Status
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
node src/index.js connect # Connect to Figma Desktop
|
|
81
|
+
node src/index.js status # Check connection status
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Design Tokens
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Full design system (colors, spacing, typography, radii)
|
|
88
|
+
node src/index.js tokens ds
|
|
89
|
+
|
|
90
|
+
# Tailwind CSS palette (22 families x 11 shades = 242 variables)
|
|
91
|
+
node src/index.js tokens tailwind
|
|
92
|
+
|
|
93
|
+
# Spacing scale (4px base system)
|
|
94
|
+
node src/index.js tokens spacing
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Create Elements
|
|
98
|
+
|
|
99
|
+
All `create` commands auto-position to avoid overlaps.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
node src/index.js create rect "Card" -w 320 -h 200 --fill "#fff" --radius 12
|
|
103
|
+
node src/index.js create circle "Avatar" -w 48 --fill "#3b82f6"
|
|
104
|
+
node src/index.js create text "Hello" -s 24 -c "#000" -w bold
|
|
105
|
+
node src/index.js create autolayout "Stack" -d col -g 16 -p 24
|
|
106
|
+
node src/index.js create icon lucide:star -s 24 -c "#f59e0b"
|
|
107
|
+
node src/index.js create component "Button"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Render with JSX
|
|
111
|
+
|
|
112
|
+
Build complex nested layouts in a single command:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
node src/index.js render '<Frame name="Card" w={320} bg="#fff" rounded={16} flex="col" gap={8} p={24}>
|
|
116
|
+
<Text size={20} weight="bold" color="#111">Card Title</Text>
|
|
117
|
+
<Text size={14} color="#666" w="fill">Description goes here</Text>
|
|
118
|
+
</Frame>'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
<details>
|
|
122
|
+
<summary><strong>JSX Props Reference</strong></summary>
|
|
123
|
+
|
|
124
|
+
| Prop | Description | Example |
|
|
125
|
+
|:-----|:-----------|:--------|
|
|
126
|
+
| `w` | Width (px or `"fill"`) | `w={320}` |
|
|
127
|
+
| `h` | Height | `h={200}` |
|
|
128
|
+
| `bg` | Background color | `bg="#fff"` |
|
|
129
|
+
| `rounded` | Corner radius | `rounded={16}` |
|
|
130
|
+
| `flex` | Auto-layout direction | `flex="col"` or `flex="row"` |
|
|
131
|
+
| `gap` | Spacing between children | `gap={8}` |
|
|
132
|
+
| `p` | Padding (all sides) | `p={24}` |
|
|
133
|
+
| `x`, `y` | Canvas position | `x={100} y={0}` |
|
|
134
|
+
| `size` | Font size (Text only) | `size={20}` |
|
|
135
|
+
| `weight` | Font weight (Text only) | `weight="bold"` |
|
|
136
|
+
| `color` | Text color (Text only) | `color="#111"` |
|
|
137
|
+
|
|
138
|
+
</details>
|
|
139
|
+
|
|
140
|
+
### Modify Elements
|
|
141
|
+
|
|
142
|
+
Works on current selection or a specific node with `-n "1:234"`.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
node src/index.js set fill "#3b82f6"
|
|
146
|
+
node src/index.js set stroke "#e4e4e7" -w 1
|
|
147
|
+
node src/index.js set radius 12
|
|
148
|
+
node src/index.js set size 320 200
|
|
149
|
+
node src/index.js set pos 100 100
|
|
150
|
+
node src/index.js set opacity 0.5
|
|
151
|
+
node src/index.js set autolayout row -g 8 -p 16
|
|
152
|
+
node src/index.js set name "Header"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Variable Binding
|
|
156
|
+
|
|
157
|
+
Bind design tokens to elements for theme-responsive designs:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
node src/index.js bind fill "primary/500"
|
|
161
|
+
node src/index.js bind stroke "border/default"
|
|
162
|
+
node src/index.js bind radius "radius/md"
|
|
163
|
+
node src/index.js bind gap "spacing/md"
|
|
164
|
+
node src/index.js bind padding "spacing/lg"
|
|
165
|
+
node src/index.js bind list # Show all bindable variables
|
|
166
|
+
node src/index.js bind list -t COLOR # Filter by type
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Sizing & Layout
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
node src/index.js sizing hug # Hug contents
|
|
173
|
+
node src/index.js sizing fill # Fill container
|
|
174
|
+
node src/index.js sizing fixed 320 200 # Fixed dimensions
|
|
175
|
+
node src/index.js padding 16 24 # Vertical, horizontal
|
|
176
|
+
node src/index.js gap 16
|
|
177
|
+
node src/index.js align center
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Find, Select & Inspect
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
node src/index.js find "Button" # Search by name
|
|
184
|
+
node src/index.js find "Card" -t FRAME # Filter by type
|
|
185
|
+
node src/index.js select "1:234" # Select by node ID
|
|
186
|
+
node src/index.js get # Inspect selection
|
|
187
|
+
node src/index.js canvas info # Overview of canvas
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Duplicate, Delete & Arrange
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
node src/index.js duplicate # Duplicate selection
|
|
194
|
+
node src/index.js delete "1:234" # Delete node
|
|
195
|
+
node src/index.js arrange -g 100 -c 3 # Arrange in 3-column grid
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Export
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
node src/index.js export css # CSS custom properties
|
|
202
|
+
node src/index.js export tailwind # Tailwind config
|
|
203
|
+
node src/index.js export screenshot -o shot.png # Screenshot
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Variables & Collections
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
node src/index.js var list # List all variables
|
|
210
|
+
node src/index.js var create "primary/500" -c "CollectionId" -t COLOR -v "#3b82f6"
|
|
211
|
+
node src/index.js col list # List collections
|
|
212
|
+
node src/index.js col create "Semantic Colors" # Create collection
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### FigJam
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
node src/index.js fj list # List pages
|
|
219
|
+
node src/index.js fj sticky "Note" -x 100 -y 100
|
|
220
|
+
node src/index.js fj shape "Box" -x 200 -y 100 -w 200 -h 100
|
|
221
|
+
node src/index.js fj connect "NODE_1" "NODE_2"
|
|
222
|
+
node src/index.js fj nodes # List elements
|
|
223
|
+
node src/index.js fj eval "figma.currentPage.children.length"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Advanced: Run Figma Plugin API
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
node src/index.js eval "figma.currentPage.name"
|
|
230
|
+
node src/index.js raw query "//COMPONENT"
|
|
231
|
+
node src/index.js raw query "//*[contains(@name, 'Button')]"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
<br>
|
|
235
|
+
|
|
236
|
+
## How It Works
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
┌──────────────────┐ ┌──────────────────┐
|
|
240
|
+
│ │ Chrome DevTools │ │
|
|
241
|
+
│ design-lazyyy │ ◄────Protocol──────► │ Figma Desktop │
|
|
242
|
+
│ CLI │ (localhost:9222) │ │
|
|
243
|
+
└──────────────────┘ └──────────────────┘
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The CLI connects to Figma Desktop through the Chrome DevTools Protocol on port `9222`. This means:
|
|
247
|
+
|
|
248
|
+
- **No API key** required
|
|
249
|
+
- **No rate limits**
|
|
250
|
+
- **Full Plugin API** access (same as writing a Figma plugin)
|
|
251
|
+
- Works with **free Figma accounts**
|
|
252
|
+
|
|
253
|
+
<br>
|
|
254
|
+
|
|
255
|
+
## Recommended Workflow
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
init ➜ connect ➜ tokens ds ➜ tokens tailwind ➜ create / render ➜ bind variables ➜ export
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
1. **Setup once** with `init` (patches Figma, installs dependencies)
|
|
262
|
+
2. **Connect** to Figma Desktop
|
|
263
|
+
3. **Bootstrap tokens** with `tokens ds` for a complete design system
|
|
264
|
+
4. **Add Tailwind colors** with `tokens tailwind` (242 color variables)
|
|
265
|
+
5. **Build UI** using `create` commands or `render` JSX
|
|
266
|
+
6. **Bind variables** to elements for theme support
|
|
267
|
+
7. **Export** as CSS or Tailwind config
|
|
268
|
+
|
|
269
|
+
<br>
|
|
270
|
+
|
|
271
|
+
## Tips
|
|
272
|
+
|
|
273
|
+
- Use **`render`** instead of `eval` for anything with text the render command handles font loading automatically
|
|
274
|
+
- **Node IDs** look like `1:234` get them from `find`, `canvas info`, or `raw query`
|
|
275
|
+
- Every **`create` command auto-positions** so elements never overlap
|
|
276
|
+
- Use **`rescale()` not `resize()`** in eval scripts to preserve layer integrity
|
|
277
|
+
- Target specific nodes with **`-n "1:234"`** on any `set` or `bind` command
|
|
278
|
+
|
|
279
|
+
<br>
|
|
280
|
+
|
|
281
|
+
## Credits
|
|
282
|
+
|
|
283
|
+
Built on top of [figma-use](https://github.com/dannote/figma-use) by dannote.
|
|
284
|
+
|
|
285
|
+
## Author
|
|
286
|
+
|
|
287
|
+
**plugin87**
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "design-lazyyy-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for managing Figma design systems. Create variables, components, and more. No API key required.",
|
|
5
|
+
"author": "plugin87",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"design-lazyyy-cli": "src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"figma",
|
|
16
|
+
"design-system",
|
|
17
|
+
"design-tokens",
|
|
18
|
+
"cli",
|
|
19
|
+
"variables"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"commander": "^12.0.0",
|
|
27
|
+
"ora": "^8.0.0",
|
|
28
|
+
"ws": "^8.16.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FigJam CDP Client
|
|
3
|
+
*
|
|
4
|
+
* Connects directly to FigJam via Chrome DevTools Protocol,
|
|
5
|
+
* bypassing figma-use which has compatibility issues with FigJam.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import WebSocket from 'ws';
|
|
9
|
+
|
|
10
|
+
export class FigJamClient {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ws = null;
|
|
13
|
+
this.contexts = [];
|
|
14
|
+
this.figmaContextId = null;
|
|
15
|
+
this.msgId = 0;
|
|
16
|
+
this.callbacks = new Map();
|
|
17
|
+
this.pageTitle = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* List all available FigJam pages
|
|
22
|
+
*/
|
|
23
|
+
static async listPages() {
|
|
24
|
+
const response = await fetch('http://localhost:9222/json');
|
|
25
|
+
const pages = await response.json();
|
|
26
|
+
return pages
|
|
27
|
+
.filter(p => p.title.includes('FigJam'))
|
|
28
|
+
.map(p => ({ title: p.title, id: p.id, url: p.url }));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Connect to a FigJam page by title (partial match)
|
|
33
|
+
*/
|
|
34
|
+
async connect(pageTitle) {
|
|
35
|
+
const response = await fetch('http://localhost:9222/json');
|
|
36
|
+
const pages = await response.json();
|
|
37
|
+
const page = pages.find(p => p.title.includes(pageTitle) && p.title.includes('FigJam'));
|
|
38
|
+
|
|
39
|
+
if (!page) {
|
|
40
|
+
const figjamPages = pages.filter(p => p.title.includes('FigJam'));
|
|
41
|
+
if (figjamPages.length > 0) {
|
|
42
|
+
throw new Error(`Page "${pageTitle}" not found. Available FigJam pages: ${figjamPages.map(p => p.title).join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
throw new Error('No FigJam pages open. Please open a FigJam file in Figma Desktop.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.pageTitle = page.title;
|
|
48
|
+
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
this.ws = new WebSocket(page.webSocketDebuggerUrl);
|
|
51
|
+
|
|
52
|
+
this.ws.on('open', async () => {
|
|
53
|
+
await this.send('Runtime.enable');
|
|
54
|
+
|
|
55
|
+
// Wait for contexts to be discovered
|
|
56
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
57
|
+
|
|
58
|
+
// Find figma context
|
|
59
|
+
for (const ctx of this.contexts) {
|
|
60
|
+
try {
|
|
61
|
+
const result = await this.send('Runtime.evaluate', {
|
|
62
|
+
expression: 'typeof figma !== "undefined"',
|
|
63
|
+
contextId: ctx.id,
|
|
64
|
+
returnByValue: true
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (result.result?.result?.value === true) {
|
|
68
|
+
this.figmaContextId = ctx.id;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!this.figmaContextId) {
|
|
75
|
+
reject(new Error('Could not find figma context. Try refreshing the FigJam page.'));
|
|
76
|
+
} else {
|
|
77
|
+
resolve(this);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.ws.on('message', (data) => {
|
|
82
|
+
const msg = JSON.parse(data);
|
|
83
|
+
|
|
84
|
+
if (msg.method === 'Runtime.executionContextCreated') {
|
|
85
|
+
this.contexts.push(msg.params.context);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (msg.id && this.callbacks.has(msg.id)) {
|
|
89
|
+
this.callbacks.get(msg.id)(msg);
|
|
90
|
+
this.callbacks.delete(msg.id);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.ws.on('error', reject);
|
|
95
|
+
|
|
96
|
+
setTimeout(() => reject(new Error('Connection timeout')), 10000);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
send(method, params = {}) {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const id = ++this.msgId;
|
|
103
|
+
this.callbacks.set(id, resolve);
|
|
104
|
+
this.ws.send(JSON.stringify({ id, method, params }));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Evaluate JavaScript in the FigJam context
|
|
110
|
+
*/
|
|
111
|
+
async eval(expression) {
|
|
112
|
+
if (!this.figmaContextId) {
|
|
113
|
+
throw new Error('Not connected to FigJam');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = await this.send('Runtime.evaluate', {
|
|
117
|
+
expression,
|
|
118
|
+
contextId: this.figmaContextId,
|
|
119
|
+
returnByValue: true,
|
|
120
|
+
awaitPromise: true
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (result.result?.exceptionDetails) {
|
|
124
|
+
const error = result.result.exceptionDetails;
|
|
125
|
+
throw new Error(error.exception?.description || error.text || 'Evaluation error');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result.result?.result?.value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get current page info
|
|
133
|
+
*/
|
|
134
|
+
async getPageInfo() {
|
|
135
|
+
return await this.eval(`
|
|
136
|
+
(function() {
|
|
137
|
+
return {
|
|
138
|
+
name: figma.currentPage.name,
|
|
139
|
+
id: figma.currentPage.id,
|
|
140
|
+
childCount: figma.currentPage.children.length,
|
|
141
|
+
editorType: figma.editorType
|
|
142
|
+
};
|
|
143
|
+
})()
|
|
144
|
+
`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* List all nodes on the current page
|
|
149
|
+
*/
|
|
150
|
+
async listNodes(limit = 50) {
|
|
151
|
+
return await this.eval(`
|
|
152
|
+
figma.currentPage.children.slice(0, ${limit}).map(function(n) {
|
|
153
|
+
return {
|
|
154
|
+
id: n.id,
|
|
155
|
+
type: n.type,
|
|
156
|
+
name: n.name || '',
|
|
157
|
+
x: Math.round(n.x),
|
|
158
|
+
y: Math.round(n.y)
|
|
159
|
+
};
|
|
160
|
+
})
|
|
161
|
+
`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a sticky note
|
|
166
|
+
*/
|
|
167
|
+
async createSticky(text, x = 0, y = 0, color) {
|
|
168
|
+
return await this.eval(`
|
|
169
|
+
(async function() {
|
|
170
|
+
var sticky = figma.createSticky();
|
|
171
|
+
sticky.x = ${x};
|
|
172
|
+
sticky.y = ${y};
|
|
173
|
+
${color ? `sticky.fills = [{type: 'SOLID', color: ${JSON.stringify(hexToRgb(color))}}];` : ''}
|
|
174
|
+
// Load font before setting text
|
|
175
|
+
await figma.loadFontAsync({ family: "Inter", style: "Medium" });
|
|
176
|
+
sticky.text.characters = ${JSON.stringify(text)};
|
|
177
|
+
return { id: sticky.id, x: sticky.x, y: sticky.y };
|
|
178
|
+
})()
|
|
179
|
+
`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create a shape with text
|
|
184
|
+
*/
|
|
185
|
+
async createShape(text, x = 0, y = 0, width = 200, height = 100, shapeType = 'ROUNDED_RECTANGLE') {
|
|
186
|
+
return await this.eval(`
|
|
187
|
+
(async function() {
|
|
188
|
+
var shape = figma.createShapeWithText();
|
|
189
|
+
shape.shapeType = ${JSON.stringify(shapeType)};
|
|
190
|
+
shape.x = ${x};
|
|
191
|
+
shape.y = ${y};
|
|
192
|
+
shape.resize(${width}, ${height});
|
|
193
|
+
if (shape.text) {
|
|
194
|
+
await figma.loadFontAsync({ family: "Inter", style: "Medium" });
|
|
195
|
+
shape.text.characters = ${JSON.stringify(text)};
|
|
196
|
+
}
|
|
197
|
+
return { id: shape.id, x: shape.x, y: shape.y };
|
|
198
|
+
})()
|
|
199
|
+
`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create a connector between two nodes
|
|
204
|
+
*/
|
|
205
|
+
async createConnector(startNodeId, endNodeId) {
|
|
206
|
+
return await this.eval(`
|
|
207
|
+
(function() {
|
|
208
|
+
var startNode = figma.getNodeById(${JSON.stringify(startNodeId)});
|
|
209
|
+
var endNode = figma.getNodeById(${JSON.stringify(endNodeId)});
|
|
210
|
+
if (!startNode || !endNode) return { error: 'Node not found' };
|
|
211
|
+
|
|
212
|
+
var connector = figma.createConnector();
|
|
213
|
+
connector.connectorStart = { endpointNodeId: startNode.id, magnet: 'AUTO' };
|
|
214
|
+
connector.connectorEnd = { endpointNodeId: endNode.id, magnet: 'AUTO' };
|
|
215
|
+
return { id: connector.id };
|
|
216
|
+
})()
|
|
217
|
+
`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create a text node
|
|
222
|
+
*/
|
|
223
|
+
async createText(text, x = 0, y = 0, fontSize = 16) {
|
|
224
|
+
return await this.eval(`
|
|
225
|
+
(async function() {
|
|
226
|
+
var textNode = figma.createText();
|
|
227
|
+
textNode.x = ${x};
|
|
228
|
+
textNode.y = ${y};
|
|
229
|
+
await figma.loadFontAsync({ family: "Inter", style: "Medium" });
|
|
230
|
+
textNode.characters = ${JSON.stringify(text)};
|
|
231
|
+
textNode.fontSize = ${fontSize};
|
|
232
|
+
return { id: textNode.id, x: textNode.x, y: textNode.y };
|
|
233
|
+
})()
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Delete a node by ID
|
|
239
|
+
*/
|
|
240
|
+
async deleteNode(nodeId) {
|
|
241
|
+
return await this.eval(`
|
|
242
|
+
(function() {
|
|
243
|
+
var node = figma.getNodeById(${JSON.stringify(nodeId)});
|
|
244
|
+
if (node) {
|
|
245
|
+
node.remove();
|
|
246
|
+
return { deleted: true };
|
|
247
|
+
}
|
|
248
|
+
return { deleted: false, error: 'Node not found' };
|
|
249
|
+
})()
|
|
250
|
+
`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Move a node
|
|
255
|
+
*/
|
|
256
|
+
async moveNode(nodeId, x, y) {
|
|
257
|
+
return await this.eval(`
|
|
258
|
+
(function() {
|
|
259
|
+
var node = figma.getNodeById(${JSON.stringify(nodeId)});
|
|
260
|
+
if (node) {
|
|
261
|
+
node.x = ${x};
|
|
262
|
+
node.y = ${y};
|
|
263
|
+
return { id: node.id, x: node.x, y: node.y };
|
|
264
|
+
}
|
|
265
|
+
return { error: 'Node not found' };
|
|
266
|
+
})()
|
|
267
|
+
`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Update text content of a node
|
|
272
|
+
*/
|
|
273
|
+
async updateText(nodeId, text) {
|
|
274
|
+
return await this.eval(`
|
|
275
|
+
(async function() {
|
|
276
|
+
var node = figma.getNodeById(${JSON.stringify(nodeId)});
|
|
277
|
+
if (!node) return { error: 'Node not found' };
|
|
278
|
+
|
|
279
|
+
await figma.loadFontAsync({ family: "Inter", style: "Medium" });
|
|
280
|
+
|
|
281
|
+
if (node.type === 'STICKY' || node.type === 'SHAPE_WITH_TEXT') {
|
|
282
|
+
node.text.characters = ${JSON.stringify(text)};
|
|
283
|
+
} else if (node.type === 'TEXT') {
|
|
284
|
+
node.characters = ${JSON.stringify(text)};
|
|
285
|
+
} else {
|
|
286
|
+
return { error: 'Node does not support text' };
|
|
287
|
+
}
|
|
288
|
+
return { id: node.id, updated: true };
|
|
289
|
+
})()
|
|
290
|
+
`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
close() {
|
|
294
|
+
if (this.ws) {
|
|
295
|
+
this.ws.close();
|
|
296
|
+
this.ws = null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function hexToRgb(hex) {
|
|
302
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
303
|
+
return result ? {
|
|
304
|
+
r: parseInt(result[1], 16) / 255,
|
|
305
|
+
g: parseInt(result[2], 16) / 255,
|
|
306
|
+
b: parseInt(result[3], 16) / 255
|
|
307
|
+
} : { r: 1, g: 0.9, b: 0.5 }; // default yellow
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export default FigJamClient;
|