figma-relai 0.7.6 → 0.7.8

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,14 +1,15 @@
1
- <img src="assets/relai-logo.svg" alt="Relai" height="36" />
2
-
3
- English | [日本語](README.ja.md) | [中文](README.zh.md)
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/syoooo/figma-relai/main/assets/relai-logo-cream.svg">
3
+ <img src="https://raw.githubusercontent.com/syoooo/figma-relai/main/assets/relai-logo-ink.svg" alt="Relai" height="36">
4
+ </picture>
4
5
 
5
6
  [figma-relai.vercel.app](https://figma-relai.vercel.app)
6
7
 
7
- **Your AI, on the canvas.** Relai connects Claude Code, Cursor, Codex — any MCP client — to Figma, so you can read, edit, audit, and build design systems by talking to the model you already use. Writes go through a Figma plugin rather than the paid REST API, so it works on every Figma plan — and the file itself keeps your rules and rulings as you go.
8
+ **Your AI, on the canvas.** Relai connects Claude Code, Cursor, Codex — any MCP client — to Figma, so you can read, edit, audit, and build design systems by talking to the model you already use. And the file is not a bystander: it carries your conventions, your precedents, your no-go zones. Every client that opens it inherits them, and an edit that breaks them is refused before it runs — the file has a veto. That is what makes it safe to hand agents real work.
8
9
 
9
10
  Relai's position: the AI era should put designers *more* in charge, not less. Taste and judgment stay with you; the labor moves to a model you already trust, with every step visible.
10
11
 
11
- <img src="assets/plugin-ui.png" alt="The Relai plugin: activity feed, connection status, and a Stop button" width="380" />
12
+ <img src="https://raw.githubusercontent.com/syoooo/figma-relai/main/assets/plugin-ui.png" alt="The Relai plugin: activity feed, connection status, and a Stop button" width="380" />
12
13
 
13
14
  ## What a session looks like
14
15
 
@@ -19,8 +20,12 @@ Relai's position: the AI era should put designers *more* in charge, not less. Ta
19
20
  > **You:** Now sweep the whole screen for dark mode.
20
21
  >
21
22
  > **AI:** `set_properties · 24 nodes · 1.2s ✓` → `analyze_design · overall → 92/100`
23
+ >
24
+ > **You:** Same sweep on Brand / Masters.
25
+ >
26
+ > **File:** `✗ blocked — "Brand / Masters" is a no-go zone`
22
27
 
23
- Every command shows up in the plugin as it runs, with timing and success or failure. Click an entry to jump to that layer on the canvas. Press **Stop** if you change your mind — the rest of the batch is cancelled.
28
+ Every command shows up in the plugin as it runs, with timing and success or failure. Click an entry to jump to that layer on the canvas. Press **Stop** if you change your mind — the rest of the batch is cancelled. And some commands never run at all: pages you fenced off reject writes before they touch the canvas, with the reason on the receipt.
24
29
 
25
30
  The numbers scale. Relai is how its author maintains a production design system, and this excerpt is from one of those sessions — July 2026, unedited:
26
31
 
@@ -32,11 +37,43 @@ batch_execute 25,351 bindings rebound
32
37
  re-census 30,251 → 936 · zero visual change
33
38
  ```
34
39
 
40
+ The gate in the middle is the point: one explicit yes from the designer licensed a 25,351-binding rebind — and every fence stayed up while it ran.
41
+
42
+ ## The file carries the law
43
+
44
+ Code already escaped the speed-versus-standards trade, and not by making models careful: the repo pushes back. Lint flags, checks gate, protected branches refuse — which is why a developer can hand an agent real work and go do something else. Relai builds that pushback into the design file itself. Not a leash — a license.
45
+
46
+ Rules you'd normally paste into every prompt live inside the Figma file, so any future session — from any MCP client, by anyone who opens the file — starts already briefed. And the load-bearing part is not prose the model might happen to read: the law runs as checks.
47
+
48
+ **Conventions** are a little CLAUDE.md stored in the file: naming rules, token routing, spacing habits. The AI reads them before working. Unlike a CLAUDE.md, they belong to the file rather than to one person's agent setup — every client inherits them, configured or not. Add one by telling the AI ("new convention: colors route through Theme tokens"); the panel's RULES row shows what's stored.
49
+
50
+ **Memory** holds your precedents. Rule once — the file remembers. Say "this gap is on purpose" and the ruling is recorded in the file; any later edit that touches what it references gets your words attached to its result:
51
+
52
+ ```text
53
+ you "this gap is on purpose — remember it"
54
+ file record_precedent · saved ✓
55
+
56
+ (another session, a different AI, about to "fix" the gap)
57
+ file precedent attached — "…is on purpose" · the edit backs off
58
+ ```
59
+
60
+ The panel's Memory row lists every entry; delete any of them any time. Nothing is recorded silently. Case law, not documentation.
61
+
62
+ **Kits.** One designer usually runs one product, and its files share the same law. Save this file's rules under a name and other files can use it. Figma drops file-carried law on branch merges; the kit puts it back — one click, or automatically if you flip that kit's auto-restore on.
63
+
64
+ **AI no-go zones** fence off whole pages — brand masters, legal, the one that is already pixel-perfect. Writes into a guarded page are rejected before they run, with the reason on the receipt, and only you edit the guard list. Protected branches, for the canvas.
65
+
66
+ **Confirmation** is a dial with four stops — OPEN · RISK · BULK · ALL. At RISK, the default, only destructive operations ask (deleting variables or styles, detach, flatten); everything else keeps moving. Set it in the panel; agents can't move it.
67
+
68
+ Where the law lives, and how far it reaches: it is stored as plugin data inside the file — no extra layers on the canvas, nothing teammates have to tiptoe around — and it survives duplication (branch merges drop it; kits put it back). Enforcement runs in the plugin, on the file's side of the wire, and comes in two strengths: no-go zones and Lock to selection are walls — the write itself is rejected; precedents are your recorded words arriving at the exact moment an edit touches what they reference — the agent backs off because the author shows up. Any client that connects through Relai is bound, including ones you never configured. An agent pointed at some other bridge is not — the law cannot guard a door it does not hold, which is why everything leaves receipts.
69
+
70
+ None of this is setup you grind through — it accumulates. On day one the file is blank law, and the agent behaves like anyone's agent. A month in, it arrives briefed on your conventions and precedents and stops at your fences: the same prompt that produces the average of everyone's design in a fresh file produces yours in yours. Two long-time users are not holding the same tool. They are holding two different instruments.
71
+
35
72
  ## Get started
36
73
 
37
74
  You need [Figma Desktop](https://www.figma.com/downloads/), [Node.js](https://nodejs.org/) 18+, and an MCP client.
38
75
 
39
- **1. Install the plugin.** Download `relai-plugin.zip` from [the latest release](https://github.com/syoooo/figma-relai/releases/latest), unzip it, then open **Plugins → Development → Import plugin from manifest…** in the desktop app and pick `manifest.json`. It connects on its own and remembers its room across restarts.
76
+ **1. Install the plugin.** Download `relai-plugin.zip` from [the latest release](https://github.com/syoooo/figma-relai/releases/latest), unzip it, then open **Plugins → Development → Import plugin from manifest…** in the desktop app and pick `manifest.json`. Open it in the file you want to edit and keep the panel open — writes go through it. It connects on its own and remembers its room across restarts; when a release ships a new plugin, re-import the new zip.
40
77
 
41
78
  **2. Register the server** with your AI client:
42
79
 
@@ -51,7 +88,16 @@ For Cursor, add this to `.cursor/mcp.json`:
51
88
  { "mcpServers": { "Relai": { "command": "npx", "args": ["-y", "figma-relai"] } } }
52
89
  ```
53
90
 
54
- **3. Ask for something.** Pairing is automatic; there is nothing to copy between windows.
91
+ Or hand the setup to your agent — paste this:
92
+
93
+ ```text
94
+ Set up Relai: register the MCP server for this client (the command is
95
+ `npx -y figma-relai`, e.g. `claude mcp add Relai -- npx -y figma-relai`),
96
+ then walk me through importing the Figma plugin from
97
+ https://github.com/syoooo/figma-relai/releases/latest — I'll do the Figma clicks.
98
+ ```
99
+
100
+ **3. Ask for something.** Pairing is automatic; there is nothing to copy between windows. A good first ask: *"How is this screen put together?"*
55
101
 
56
102
  ## What it's good at
57
103
 
@@ -59,35 +105,11 @@ Understanding a design. "How is this screen put together?" gets you structure, c
59
105
 
60
106
  Bulk edits. "Translate every button label to English" or "recolor this for dark mode" become one round-trip across dozens of layers instead of an afternoon of clicking.
61
107
 
62
- Audits. `analyze_design` checks color-token coverage, auto-layout quality, component health, and accessibility (WCAG contrast, touch targets, text sizes) — or all four at once as a weighted 0–100 health score you can put in a review. It also scores agent-readiness (how prepared the file is for AI work, with top gaps), fingerprints the file's voice (its radius/spacing/type signature), and runs a ghost census for references to soft-deleted variables.
108
+ Audits. `analyze_design` checks color-token coverage, auto-layout quality, component health, and accessibility (WCAG contrast, touch targets, text sizes) — or all four at once as a weighted 0–100 health score you can put in a review, with deviations you already ruled intentional staying waived instead of re-flagged. It also scores agent-readiness (how prepared the file is for AI work, with top gaps), fingerprints the file's voice (its radius/spacing/type signature), and runs a ghost census: references still pointing at soft-deleted variables.
63
109
 
64
110
  Design systems. Variable collections with modes, token binding, shared styles, components with proper variants, team-library imports. `get_design_system` inventories what the file — and the libraries it uses — already has (with a token, the library's whole catalog, including components the file has never placed), so the AI builds from your components instead of redrawing near-copies; `analyze_design`'s tokens aspect finds hardcoded values that visually match an existing variable, and one `tokenize` call binds them all. These run as declarative operations with precondition checks, so the same request behaves the same way every time, and a failure tells the AI what to do next ("call set_layout_mode first") instead of dumping a stack trace.
65
111
 
66
- Everything else. `execute_figma` runs JavaScript against the Figma Plugin API directly — the same escape-hatch approach as Figma's official MCP — with a `relai.*` helper library that makes the correct pattern the shortest one, hints attached to known errors, and a lint that flags silent mistakes. If you'd rather the AI never ran code, turn it off with the plugin's "Allow code execution" toggle.
67
-
68
- ## The file carries the law
69
-
70
- Rules you'd normally paste into every prompt live inside the Figma file itself, so any future session — from any MCP client, by anyone who opens the file — starts already briefed.
71
-
72
- **Conventions** are a little CLAUDE.md stored in the file: naming rules, token routing, spacing habits. The AI reads them before working.
73
-
74
- **Memory** holds your precedents. Rule once — "this gap is on purpose" — and the ruling is recorded in the file; any later edit that touches what it references gets your words attached to its result:
75
-
76
- ```text
77
- you "this gap is on purpose — remember it"
78
- file record_precedent · saved ✓
79
-
80
- (another session, a different AI, about to "fix" the gap)
81
- file precedent attached — "…is on purpose" · the edit backs off
82
- ```
83
-
84
- The panel's Memory row lists every entry; delete any of them any time. Nothing is recorded silently.
85
-
86
- **Kits.** One designer usually runs one product, and its files share the same law. Save this file’s rules under a name and other files can use it. Figma drops file-carried law on branch merges; the kit puts it back — one click, or automatically if you flip that kit’s auto-restore on.
87
-
88
- **AI no-go zones** fence off whole pages — brand masters, legal, the one that is already pixel-perfect. Writes into a guarded page are rejected before they run, with the reason on the receipt, and only you edit the guard list.
89
-
90
- **Confirmation** is one level with four stops — OPEN · RISK · BULK · ALL. At RISK, the default, only dangerous operations ask (deleting variables or styles, detach, flatten); everything else keeps moving. Set it in the panel; agents can't move it.
112
+ Everything else. `execute_figma` runs JavaScript against the Figma Plugin API directly — the same escape-hatch approach as Figma's official MCP — with a `relai.*` helper library that makes the correct pattern the shortest one, a registry of pitfalls learned in production that attaches the fix to known errors, and a lint that flags silent mistakes. If you'd rather the AI never ran code, turn it off with the plugin's "Allow code execution" toggle.
91
113
 
92
114
  ## You stay in control
93
115
 
@@ -95,6 +117,8 @@ The plugin is the designer's side of the deal: a live activity feed of everythin
95
117
 
96
118
  **Lock to selection** rejects edits outside whatever you've selected — the AI gets a clear error, not a silent pass. The relay is local: file contents move only between Figma, your machine, and the AI client you already trust. The UI speaks English, 日本語, and 中文.
97
119
 
120
+ When something goes wrong anyway — a bad sweep, a wrong "fix" — the feed is your reconstruction and Figma's version history is your restore path. There is no magic undo, and Relai doesn't pretend to have one: `diff_nodes` checkpoints tell you exactly what changed; version history takes you back. And when the law itself is wrong — a stale precedent guarding a gap you've since changed your mind about — tell the agent to go ahead, then delete the entry from the panel's Memory row. Rulings are one click to remove; the law stays yours to amend.
121
+
98
122
  ## How it works
99
123
 
100
124
  ```
@@ -108,6 +132,8 @@ Figma plugin executes Plugin API calls
108
132
 
109
133
  The relay lives inside the MCP server, so there is no extra process to keep alive. When several MCP clients run at once, the first one hosts the relay and the others connect to it; if the host exits, a survivor takes over. Both sides remember their room, rejoin after restarts or sleep, and find each other without any copy-pasting. The `join_room` tool exists for one rare case only: two Figma files running the plugin at the same time.
110
134
 
135
+ The receipts above are real timings: rule checks run in the plugin before each write, small edits land sub-second, and the 45,509-node audit took 3.9s.
136
+
111
137
  Ports are fixed by Figma's plugin sandbox: the manifest allowlists `ws://localhost:9055–9057`, and other ports cannot work without editing `manifest.json`. That's why there is no port setting in the UI.
112
138
 
113
139
  ## The tools
@@ -131,7 +157,7 @@ Each tool is self-describing, so the AI sees full parameter docs. The same contr
131
157
 
132
158
  ## Relai and Figma's official MCP
133
159
 
134
- Figma's own AI has grown fast — the official MCP server now writes to the canvas, and the Figma Design Agent collaborates right inside the editor. Both are reserved for full seats on paid plans, with usage metered in AI credits and models chosen by Figma. Relai is the open-source counterpart on the other side of that line: every plan including free, whatever model and subscription you already use, everything running on your machine, and the designer holding the controls. If you have the seats, the two coexist happily — run both.
160
+ Figma's own AI has grown fast — the official MCP server now writes to the canvas, and the Figma Design Agent collaborates right inside the editor. Both are reserved for full seats on paid plans, with usage metered in AI credits and models chosen by Figma. Relai is the open-source counterpart on the other side of that line: your model, your subscription, your machine — and the designer holding the controls. Writes go through the Relai Figma plugin rather than the paid REST API, which is why it works on every plan, free included. If you have the seats, the two coexist happily — run both.
135
161
 
136
162
  ## Optional: a Figma token
137
163
 
@@ -182,6 +208,8 @@ bun test
182
208
 
183
209
  Requires [Bun](https://bun.sh/) v1.0+ (the setup script is bash; on Windows use WSL). Load the plugin via **Plugins → Development → Import plugin from manifest…** → `packages/figma-plugin/manifest.json`. A standalone relay (`bun socket`) exists for the unusual case where the relay must run on another machine. More in [CONTRIBUTING.md](CONTRIBUTING.md); manual QA lives in [docs/smoke-checklist.md](docs/smoke-checklist.md).
184
210
 
211
+ Project status: Relai is built and dogfooded daily against a production design system; releases track that work. MIT plus local-first is the longevity hedge — nothing phones home, and nothing stops working if the repo goes quiet.
212
+
185
213
  ## License
186
214
 
187
215
  MIT — see [LICENSE](LICENSE).
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-2H7UOFLK.js";
12
12
 
13
13
  // src/version.ts
14
- var VERSION = "0.7.6";
14
+ var VERSION = "0.7.8";
15
15
 
16
16
  // src/server.ts
17
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -2455,10 +2455,10 @@ var ACTIONS2 = {
2455
2455
  list: ["list_rulesets", []],
2456
2456
  save: ["save_ruleset", ["name", "conventions", "fromFile", "autoRestore", "provenance"]],
2457
2457
  delete: ["delete_ruleset", ["name"]],
2458
- link: ["link_ruleset", ["name", "restore"]],
2458
+ link: ["link_ruleset", ["name", "restore", "force"]],
2459
2459
  unlink: ["unlink_ruleset", []],
2460
2460
  status: ["ruleset_status", []],
2461
- restore: ["restore_from_ruleset", []],
2461
+ restore: ["restore_from_ruleset", ["force"]],
2462
2462
  push: ["push_to_ruleset", []],
2463
2463
  promote: ["promote_precedent", ["id"]],
2464
2464
  export: ["export_ruleset", ["name"]],
@@ -2467,7 +2467,7 @@ var ACTIONS2 = {
2467
2467
  function register17(server, sendCommand) {
2468
2468
  server.tool(
2469
2469
  "manage_rulesets",
2470
- "Kits (rulesets) \u2014 named common-ancestor law the plugin keeps on the designer's machine, spanning every file it opens (one designer usually runs one product; its files share the same law). The panel shows this as the KIT row in the connection cluster \u2014 when speaking to the designer, call it a kit, not a ruleset. A file LINKS to one ruleset; the file's own carried conventions stay the working copy. Figma DISCARDS file-carried law on branch\u2192main merges \u2014 a linked ruleset is the recovery: status shows 'file-empty' after a merge, restore (or the set's autoRestore switch) re-seeds it. Actions: list / status (this file's state: in-sync, file-empty, drifted\u2026) / save (name + conventions, or fromFile:true to capture this file's law) / link (optionally restore:true) / unlink / restore (ruleset\u2192file, also seeds promoted precedents) / push (file\u2192ruleset \u2014 the file's law becomes the ancestor) / promote (id \u2014 copy one precedent into the linked ruleset so every linked file inherits it) / delete / export (markdown package with provenance frontmatter) / import (markdown). Pass only the fields the action needs.",
2470
+ "Kits (rulesets) \u2014 named common-ancestor law the plugin keeps on the designer's machine, spanning every file it opens (one designer usually runs one product; its files share the same law). The panel shows this as the KIT row in the connection cluster \u2014 when speaking to the designer, call it a kit, not a ruleset. A file LINKS to one ruleset; the file's own carried conventions stay the working copy. Figma DISCARDS file-carried law on branch\u2192main merges \u2014 a linked ruleset is the recovery: status shows 'file-empty' after a merge, restore (or the set's autoRestore switch) re-seeds it. Actions: list / status (this file's state: in-sync, file-empty, drifted\u2026) / save (name + conventions, or fromFile:true to capture this file's law) / link (optionally restore:true) / unlink / restore (ruleset\u2192file, also seeds promoted precedents; REFUSED when the file's law is newer than the kit's \u2014 status.newer says which side is, and push is usually the fix \u2014 pass force:true to override) / push (file\u2192ruleset \u2014 the file's law becomes the ancestor) / promote (id \u2014 copy one precedent into the linked ruleset so every linked file inherits it) / delete / export (markdown package with provenance frontmatter) / import (markdown). Pass only the fields the action needs.",
2471
2471
  {
2472
2472
  action: z18.enum(Object.keys(ACTIONS2)),
2473
2473
  name: z18.string().optional().describe("Ruleset name (save/link/delete/export; import fallback)"),
@@ -2476,6 +2476,9 @@ function register17(server, sendCommand) {
2476
2476
  autoRestore: z18.boolean().optional().describe("save: restore without asking when a linked file's law is found wiped"),
2477
2477
  provenance: z18.string().optional().describe("save: where this law comes from (author/repo)"),
2478
2478
  restore: z18.boolean().optional().describe("link: immediately restore the ruleset into the file"),
2479
+ force: z18.boolean().optional().describe(
2480
+ "restore/link: replace the file's law even when it is NEWER than the kit's. Without this, a restore that would walk backwards is refused \u2014 say which side you mean to keep."
2481
+ ),
2479
2482
  id: z18.string().optional().describe("promote: precedent id (list_precedents shows them)"),
2480
2483
  markdown: z18.string().optional().describe("import: a ruleset package exported earlier")
2481
2484
  },
@@ -2919,7 +2922,11 @@ function register19(server, sendCommand) {
2919
2922
  const result = await sendCommand("find_nodes", {
2920
2923
  name: query,
2921
2924
  types,
2922
- parentId
2925
+ parentId,
2926
+ // Sideloaded plugins ≤0.7.7 read the scope as nodeId; npx serves the
2927
+ // latest server automatically, so old-plugin/new-server is the common
2928
+ // pairing. Drop once the 0.7.7 zip is no longer in circulation.
2929
+ nodeId: parentId
2923
2930
  });
2924
2931
  const matches = (Array.isArray(result) ? result : []).map((n) => ({
2925
2932
  id: n.id,
@@ -4482,4 +4489,4 @@ export {
4482
4489
  listToolCatalog,
4483
4490
  registerPrompts
4484
4491
  };
4485
- //# sourceMappingURL=chunk-L3JSJSM5.js.map
4492
+ //# sourceMappingURL=chunk-VKBEC37P.js.map