figma-local 1.0.0 → 1.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 CHANGED
@@ -1,4 +1,4 @@
1
- # figma-cli
1
+ # figma-local
2
2
 
3
3
  > Control Figma Desktop with Claude Code. Design tokens, shadcn/ui components, AI prompt export, lint, and more — no API key required.
4
4
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## What is this?
14
14
 
15
- **figma-cli** connects directly to Figma Desktop and lets you — or Claude Code — control it with natural language and `fig` commands.
15
+ **figma-local** connects directly to Figma Desktop and lets you — or Claude Code — control it with natural language and `fig` commands.
16
16
 
17
17
  - **Write** — Create frames, components, design tokens, icons, and full UI kits
18
18
  - **Read** — Extract design context in a lean staged format (91–97% fewer tokens than raw data dumps)
@@ -54,13 +54,13 @@ Gives you `fig` and `fig-start` globally on your PATH.
54
54
  ### curl (one-line)
55
55
 
56
56
  ```bash
57
- curl -fsSL https://raw.githubusercontent.com/thepreakerebi/figma-cli/main/install.sh | bash
57
+ curl -fsSL https://raw.githubusercontent.com/thepreakerebi/figma-local/main/install.sh | bash
58
58
  ```
59
59
 
60
60
  ### Homebrew
61
61
 
62
62
  ```bash
63
- brew tap thepreakerebi/figma-cli
63
+ brew tap thepreakerebi/figma-local
64
64
  brew install figma-cli
65
65
  ```
66
66
 
@@ -73,7 +73,7 @@ npx figma-local read
73
73
  ### From source
74
74
 
75
75
  ```bash
76
- git clone https://github.com/thepreakerebi/figma-cli.git
76
+ git clone https://github.com/thepreakerebi/figma-local.git
77
77
  cd figma-cli
78
78
  npm install && npm install -g .
79
79
  ```
@@ -99,7 +99,7 @@ npm install && npm install -g .
99
99
  2. Hamburger menu → **Plugins → Development → Import plugin from manifest...**
100
100
  3. Navigate to the `plugin/` folder in this repo (or `$(npm root -g)/figma-local/plugin/`)
101
101
  4. Select `manifest.json` → click **Open**
102
- 5. Right-click **FigCli** in the plugin list → **Add to toolbar**
102
+ 5. Right-click **Figma Local** in the plugin list → **Add to toolbar**
103
103
 
104
104
  ### 2. Connect
105
105
 
@@ -107,14 +107,14 @@ npm install && npm install -g .
107
107
  fig-start --safe
108
108
  ```
109
109
 
110
- This starts the daemon, waits for you to click FigCli in Figma, then launches Claude Code.
110
+ This starts the daemon, waits for you to click Figma Local in Figma, then launches Claude Code.
111
111
 
112
112
  ---
113
113
 
114
114
  ## Every session after that
115
115
 
116
116
  ```
117
- 1. Open Figma → click FigCli in the toolbar
117
+ 1. Open Figma → click Figma Local in the toolbar
118
118
  2. In terminal: fig-start --safe
119
119
  ```
120
120
 
@@ -127,14 +127,60 @@ Claude Code reads `CLAUDE.md` and knows every command automatically.
127
127
  ### Read & understand your canvas
128
128
 
129
129
  ```bash
130
- fig read # List all frames (fast)
131
- fig read "Login Screen" # Layout + components + used tokens only
132
- fig read "Login Screen" --tokens # Just the design tokens that frame uses
130
+ fig read # List all frames on the current page
131
+ fig read "Login Screen" # Get the layout tree, components, and design tokens for that frame
132
+ fig read "Login Screen" --tokens # Show only the design tokens (colors, spacing) that frame uses
133
+ fig read --selection # Read whatever you have selected right now in Figma
134
+ fig read --link "https://..." # Read a specific node from a Figma selection link
133
135
  fig find "Button" # Find nodes by name
134
136
  fig node tree # Layer hierarchy
135
137
  fig canvas info # Raw canvas info
136
138
  ```
137
139
 
140
+ ### Inspect design specs
141
+
142
+ ```bash
143
+ fig inspect # Full specs for the selected element (spacing, colors, fonts, effects)
144
+ fig inspect --node "123:456" # Inspect a specific node by ID
145
+ fig inspect --json # Raw JSON output
146
+ ```
147
+
148
+ Returns dimensions, padding, gap, colors (hex + rgba), typography (font family, size, weight, line-height, letter-spacing), border radius, strokes, shadows, opacity, and variable bindings (name + resolved value + per-mode values for Light/Dark) — all in **px and rem**.
149
+
150
+ ### Generate CSS / Tailwind from design
151
+
152
+ ```bash
153
+ fig css # CSS for current selection (rem units)
154
+ fig css --px # CSS in px units
155
+ fig css --tailwind # Tailwind utility classes
156
+ fig css --link "https://..." # CSS from a Figma link
157
+ ```
158
+
159
+ ### Measure spacing
160
+
161
+ ```bash
162
+ fig measure # Select 2 elements in Figma, get the spacing between them
163
+ ```
164
+
165
+ ### Extract style guide from a frame
166
+
167
+ ```bash
168
+ fig styles "Login Screen" # All text styles, colors, spacing values, and radii used
169
+ fig styles --selection # Styles from current selection
170
+ fig styles --json # Raw JSON
171
+ ```
172
+
173
+ ### Document a component (full recursive spec)
174
+
175
+ ```bash
176
+ fig document # Document selected component — all children, all specs
177
+ fig document --json # Structured JSON for coding agents
178
+ fig document --link "https://..." # Document from a Figma link
179
+ fig document --tokens-only # Just the design tokens used
180
+ ```
181
+
182
+ Returns a complete breakdown: summary, all unique design tokens (colors, typography, spacing, radii, shadows), and a recursive tree of every element with full specs. One command gives a coding agent everything it needs to replicate the component.
183
+
138
184
  ### Design tokens
139
185
 
140
186
  ```bash
@@ -247,7 +293,7 @@ fig fj connect "ID1" "ID2"
247
293
  | | Safe Mode | Yolo Mode |
248
294
  |-|-----------|-----------|
249
295
  | How | Plugin bridge | Direct CDP |
250
- | Setup per session | Start FigCli plugin | Nothing (after one-time patch) |
296
+ | Setup per session | Start Figma Local plugin | Nothing (after one-time patch) |
251
297
  | Speed | Standard | ~10× faster |
252
298
  | Extra permissions | None | macOS: Full Disk Access / Windows: Admin |
253
299
  | Command | `fig connect --safe` | `fig connect` |
@@ -271,7 +317,7 @@ The daemon runs only on `127.0.0.1` (never exposed to the network) and is protec
271
317
  | 1 MB request/message body cap | Memory exhaustion from oversized payloads |
272
318
  | Plugin input validation | Code size cap (512 KB), batch size cap (50), strict field types |
273
319
  | Rate limiting | Max 30 evals per 10 s per connection |
274
- | Idle auto-shutdown | Daemon stops after 10 minutes of inactivity |
320
+ | Idle auto-shutdown | Daemon stops after 1 hour of inactivity |
275
321
 
276
322
  The session token is generated fresh on every `fig connect`, stored at `~/.figma-ds-cli/.daemon-token` with `chmod 600` (owner-read only).
277
323
 
@@ -286,6 +332,31 @@ fig-start --safe --here # launch from your project dir; Claude sees both you
286
332
 
287
333
  ---
288
334
 
335
+ ## Claude Code Plugin (Skills)
336
+
337
+ figma-local ships as a Claude Code plugin with 6 skills that teach coding agents how to use it automatically:
338
+
339
+ | Skill | Triggers on |
340
+ |-------|------------|
341
+ | **figma-local** | "read the design", "what's on the canvas", "Figma to code" |
342
+ | **figma-inspect** | "get specs", "what font/color/spacing", "design specs" |
343
+ | **figma-css** | "generate CSS", "Tailwind classes", "convert to CSS" |
344
+ | **figma-styles** | "style guide", "extract colors/fonts", "spacing scale" |
345
+ | **figma-measure** | "measure spacing", "gap between elements" |
346
+ | **figma-document** | "document this component", "full spec sheet", "deep breakdown" |
347
+
348
+ ### Install the skills
349
+
350
+ Install via [skills.sh](https://skills.sh):
351
+
352
+ ```bash
353
+ npx skills add thepreakerebi/figma-local
354
+ ```
355
+
356
+ Once installed, restart Claude Code. It automatically knows all `fig` commands and uses them when your tasks involve Figma designs.
357
+
358
+ ---
359
+
289
360
  ## For teams
290
361
 
291
362
  ```bash
@@ -299,7 +370,7 @@ npm install -g figma-local
299
370
  Pin a version for consistency:
300
371
 
301
372
  ```bash
302
- npm install -g @jetro/figma-local@1.0.0
373
+ npm install -g figma-local@1.0.0
303
374
  ```
304
375
 
305
376
  ---
@@ -311,7 +382,7 @@ npm uninstall -g figma-local
311
382
  rm -rf ~/.figma-cli ~/.figma-ds-cli
312
383
  ```
313
384
 
314
- Remove the plugin in Figma: Plugins → Development → right-click FigCli → Remove.
385
+ Remove the plugin in Figma: Plugins → Development → right-click Figma Local → Remove.
315
386
 
316
387
  ---
317
388
 
@@ -320,7 +391,7 @@ Remove the plugin in Figma: Plugins → Development → right-click FigCli → R
320
391
  Issues and PRs welcome. For major changes, open an issue first to discuss.
321
392
 
322
393
  ```bash
323
- git clone https://github.com/thepreakerebi/figma-cli.git
394
+ git clone https://github.com/thepreakerebi/figma-local.git
324
395
  cd figma-cli
325
396
  npm install
326
397
  node src/index.js --help
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "figma-local",
3
- "version": "1.0.0",
4
- "description": "Figma CLI — control Figma Desktop with Claude Code. Smart read, write, and AI-prompt export. No API key required.",
3
+ "version": "1.1.0",
4
+ "description": "Control Figma Desktop with Claude Code. Smart read, write, and AI-prompt export. No API key required.",
5
5
  "author": "elvke",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -12,7 +12,9 @@
12
12
  },
13
13
  "files": [
14
14
  "src",
15
- "bin"
15
+ "bin",
16
+ "plugin",
17
+ "skills"
16
18
  ],
17
19
  "keywords": [
18
20
  "figma",
@@ -25,11 +27,11 @@
25
27
  ],
26
28
  "repository": {
27
29
  "type": "git",
28
- "url": "git+https://github.com/thepreakerebi/figma-cli.git"
30
+ "url": "git+https://github.com/thepreakerebi/figma-local.git"
29
31
  },
30
- "homepage": "https://github.com/thepreakerebi/figma-cli",
32
+ "homepage": "https://github.com/thepreakerebi/figma-local",
31
33
  "bugs": {
32
- "url": "https://github.com/thepreakerebi/figma-cli/issues"
34
+ "url": "https://github.com/thepreakerebi/figma-local/issues"
33
35
  },
34
36
  "scripts": {
35
37
  "setup-alias": "bash bin/setup-alias.sh",
package/plugin/code.js ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Figma CLI Bridge Plugin — code.js
3
+ *
4
+ * Safe Mode: Connects to CLI daemon via WebSocket.
5
+ * No debug port, no patching required.
6
+ *
7
+ * Security hardening:
8
+ * - All incoming messages from the UI are validated before execution
9
+ * - Code and batch payloads are size-capped
10
+ * - Batch arrays are length-capped
11
+ * - Notify text is sanitised (no arbitrary HTML/script injection)
12
+ * - Rate limiting: max 30 evals per 10-second window
13
+ */
14
+
15
+ // ── Constants ──────────────────────────────────────────────────
16
+ const MAX_CODE_BYTES = 512 * 1024; // 512 KB max per eval code string
17
+ const MAX_BATCH_COUNT = 50; // Max codes in a single eval-batch
18
+ const MAX_NOTIFY_CHARS = 200; // Max chars shown in Figma notification
19
+
20
+ // ── Rate limiter ───────────────────────────────────────────────
21
+ // Prevents a rogue daemon (or compromised WebSocket) from flooding
22
+ // the plugin with eval calls.
23
+ const RATE_WINDOW_MS = 10000;
24
+ const RATE_MAX = 30;
25
+ let rateCount = 0;
26
+ let rateWindowStart = Date.now();
27
+
28
+ function isRateAllowed() {
29
+ const now = Date.now();
30
+ if (now - rateWindowStart > RATE_WINDOW_MS) {
31
+ rateCount = 0;
32
+ rateWindowStart = now;
33
+ }
34
+ if (rateCount >= RATE_MAX) return false;
35
+ rateCount++;
36
+ return true;
37
+ }
38
+
39
+ // ── Input validators ───────────────────────────────────────────
40
+ function isValidId(id) {
41
+ return typeof id === 'number' && Number.isFinite(id) && id >= 0;
42
+ }
43
+
44
+ function isValidCode(code) {
45
+ return typeof code === 'string' && code.length > 0 && code.length <= MAX_CODE_BYTES;
46
+ }
47
+
48
+ function sanitiseNotify(text) {
49
+ if (typeof text !== 'string') return 'Unknown error';
50
+ // Strip anything that looks like HTML tags, keep plain text only
51
+ return text.replace(/<[^>]*>/g, '').slice(0, MAX_NOTIFY_CHARS);
52
+ }
53
+
54
+ // Show minimal UI (needed for WebSocket connection)
55
+ figma.showUI(__html__, {
56
+ width: 200,
57
+ height: 92
58
+ });
59
+
60
+ // Execute code with auto-return and timeout protection
61
+ async function executeCode(code, timeoutMs = 25000) {
62
+ let trimmed = code.trim();
63
+
64
+ // Don't add return if code already starts with return
65
+ if (!trimmed.startsWith('return ')) {
66
+ const isSimpleExpr = !trimmed.includes(';');
67
+ const isIIFE = trimmed.startsWith('(function') || trimmed.startsWith('(async function');
68
+ const isArrowIIFE = trimmed.startsWith('(() =>') || trimmed.startsWith('(async () =>');
69
+
70
+ if (isSimpleExpr || isIIFE || isArrowIIFE) {
71
+ trimmed = `return ${trimmed}`;
72
+ } else {
73
+ const lastSemicolon = trimmed.lastIndexOf(';');
74
+ if (lastSemicolon !== -1) {
75
+ const beforeLast = trimmed.substring(0, lastSemicolon + 1);
76
+ const lastStmt = trimmed.substring(lastSemicolon + 1).trim();
77
+ if (lastStmt && !lastStmt.startsWith('return ')) {
78
+ trimmed = beforeLast + ' return ' + lastStmt;
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
85
+ const fn = new AsyncFunction('figma', `return (async () => { ${trimmed} })()`);
86
+
87
+ // Execute with timeout protection
88
+ const execPromise = fn(figma);
89
+ const timeoutPromise = new Promise((_, reject) =>
90
+ setTimeout(() => reject(new Error(`Execution timeout (${timeoutMs/1000}s)`)), timeoutMs)
91
+ );
92
+
93
+ return Promise.race([execPromise, timeoutPromise]);
94
+ }
95
+
96
+ // Handle messages from UI (WebSocket bridge)
97
+ figma.ui.onmessage = async (msg) => {
98
+ if (!msg || typeof msg.type !== 'string') return;
99
+
100
+ // ── Single eval ─────────────────────────────────────────────
101
+ if (msg.type === 'eval') {
102
+ // Validate id and code before doing anything
103
+ if (!isValidId(msg.id)) return;
104
+ if (!isValidCode(msg.code)) {
105
+ figma.ui.postMessage({ type: 'result', id: msg.id, error: 'Invalid or oversized code payload' });
106
+ return;
107
+ }
108
+ if (!isRateAllowed()) {
109
+ figma.ui.postMessage({ type: 'result', id: msg.id, error: 'Rate limit exceeded — slow down' });
110
+ return;
111
+ }
112
+ try {
113
+ const result = await executeCode(msg.code);
114
+ figma.ui.postMessage({ type: 'result', id: msg.id, result });
115
+ } catch (error) {
116
+ figma.ui.postMessage({ type: 'result', id: msg.id, error: error.message });
117
+ }
118
+ return;
119
+ }
120
+
121
+ // ── Batch eval ──────────────────────────────────────────────
122
+ if (msg.type === 'eval-batch') {
123
+ if (!isValidId(msg.id)) return;
124
+ if (!Array.isArray(msg.codes)) {
125
+ figma.ui.postMessage({ type: 'batch-result', id: msg.id, results: [{ success: false, error: 'codes must be an array' }] });
126
+ return;
127
+ }
128
+ // Cap array length
129
+ if (msg.codes.length > MAX_BATCH_COUNT) {
130
+ figma.ui.postMessage({ type: 'batch-result', id: msg.id, results: [{ success: false, error: `Batch too large (max ${MAX_BATCH_COUNT})` }] });
131
+ return;
132
+ }
133
+ // Validate each code string
134
+ for (const c of msg.codes) {
135
+ if (!isValidCode(c)) {
136
+ figma.ui.postMessage({ type: 'batch-result', id: msg.id, results: [{ success: false, error: 'Invalid or oversized code in batch' }] });
137
+ return;
138
+ }
139
+ }
140
+ if (!isRateAllowed()) {
141
+ figma.ui.postMessage({ type: 'batch-result', id: msg.id, results: [{ success: false, error: 'Rate limit exceeded' }] });
142
+ return;
143
+ }
144
+ const results = [];
145
+ for (const code of msg.codes) {
146
+ try {
147
+ const result = await executeCode(code);
148
+ results.push({ success: true, result });
149
+ } catch (error) {
150
+ results.push({ success: false, error: error.message });
151
+ }
152
+ }
153
+ figma.ui.postMessage({ type: 'batch-result', id: msg.id, results });
154
+ return;
155
+ }
156
+
157
+ // ── Connection lifecycle (no eval, safe to handle directly) ──
158
+ if (msg.type === 'connected') {
159
+ figma.notify('✓ Figma Local connected', { timeout: 2000 });
160
+ return;
161
+ }
162
+ if (msg.type === 'disconnected') {
163
+ figma.notify('Figma Local disconnected', { timeout: 2000 });
164
+ return;
165
+ }
166
+ if (msg.type === 'error') {
167
+ // Sanitise before displaying in Figma UI
168
+ figma.notify('Figma Local: ' + sanitiseNotify(msg.message), { error: true });
169
+ return;
170
+ }
171
+ };
172
+
173
+ // Keep plugin alive
174
+ figma.on('close', () => {
175
+ // Plugin closed
176
+ });
177
+
178
+ console.log('Figma DS CLI plugin started');
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "Figma Local",
3
+ "id": "figma-cli-bridge",
4
+ "api": "1.0.0",
5
+ "main": "code.js",
6
+ "ui": "ui.html",
7
+ "capabilities": [],
8
+ "enableProposedApi": false,
9
+ "editorType": ["figma", "figjam"],
10
+ "networkAccess": {
11
+ "allowedDomains": ["*"],
12
+ "reasoning": "Connects to local CLI daemon for command execution"
13
+ }
14
+ }