figma-relai 0.2.8 → 0.3.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 +2 -2
- package/dist/{chunk-RDSEEOKP.js → chunk-2MUSDWUC.js} +63 -10
- package/dist/chunk-2MUSDWUC.js.map +1 -0
- package/dist/chunk-SDINVROO.js +66 -0
- package/dist/chunk-SDINVROO.js.map +1 -0
- package/dist/{doctor-ZN2PKAPH.js → doctor-P36IPZHH.js} +20 -1
- package/dist/doctor-P36IPZHH.js.map +1 -0
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/{manifest-HMHQAF6T.js → manifest-DRLMH5YW.js} +5 -3
- package/dist/manifest-DRLMH5YW.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-RDSEEOKP.js.map +0 -1
- package/dist/doctor-ZN2PKAPH.js.map +0 -1
- package/dist/manifest-HMHQAF6T.js.map +0 -1
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Everything else. `execute_figma` runs JavaScript against the Figma Plugin API di
|
|
|
69
69
|
|
|
70
70
|
The plugin is the designer's side of the deal: a live activity feed of everything the AI does, an "AI connected" indicator that means an agent is actually paired (not just that a server is running), and a Stop button that cancels pending work. Selection and page changes you make flow back to the AI as events, so "now do the same to this one" works without re-explaining. The relay is local: file contents move only between Figma, your machine, and the AI client you already trust.
|
|
71
71
|
|
|
72
|
-
Three dials go further when you want them. **Approvals** ("Ask before big edits") holds bulk writes and code execution until you press Approve in the panel. **Lock to selection** rejects edits outside whatever you've selected — the AI gets a clear error, not a silent pass. And **file conventions** are a little CLAUDE.md stored inside the Figma file itself: naming rules, spacing habits, do-not-touch pages — every future session, from any AI client, reads it before working. The UI speaks English, 日本語, and 中文.
|
|
72
|
+
Three dials go further when you want them. **Approvals** ("Ask before big edits") holds bulk writes and code execution until you press Approve in the panel. **Lock to selection** rejects edits outside whatever you've selected — the AI gets a clear error, not a silent pass. And **file conventions** are a little CLAUDE.md stored inside the Figma file itself: naming rules, spacing habits, do-not-touch pages — every future session, from any AI client, reads it before working. Since 0.3 the file also keeps **memory**: rulings you make ("this deviation is intent, not drift") are recorded as precedents inside the file, and any future edit that touches what they reference gets the precedent attached to its result — the AI is corrected by your own case law, in the moment. The panel's Memory row shows every entry; delete any of them any time. The UI speaks English, 日本語, and 中文.
|
|
73
73
|
|
|
74
74
|
## How it works
|
|
75
75
|
|
|
@@ -103,7 +103,7 @@ Ports are fixed by Figma's plugin sandbox: the manifest allowlists `ws://localho
|
|
|
103
103
|
| Comments | `manage_comments` (needs a token — see below) |
|
|
104
104
|
| Advanced | `batch_execute` · `execute_figma` · `join_room` |
|
|
105
105
|
|
|
106
|
-
Each tool is self-describing, so the AI sees full parameter docs. The same contract also exists as a file: `npx figma-relai manifest` prints a machine-readable JSON of every tool schema, plugin command, and known pitfall — generated from the running code on every build (committed as `docs/manifest.json`), so it cannot drift — and `npx figma-relai docs <tool>` renders it for humans.
|
|
106
|
+
Each tool is self-describing, so the AI sees full parameter docs. The same contract also exists as a file: `npx figma-relai manifest` prints a machine-readable JSON of every tool schema, plugin command, and known pitfall — generated from the running code on every build (committed as `docs/manifest.json`), so it cannot drift — and `npx figma-relai docs <tool>` renders it for humans. Ten skill documents ship alongside as MCP prompts: token strategy, component conventions, audit workflows, a Plugin API cheat sheet for `execute_figma`, file memory & precedents, and recipes for design-system-first building, bulk cleanup, and comment-driven collaboration. Your own skills load too: drop markdown files with a name/description frontmatter into `~/.figma-relai/skills/` and they register as `user:` prompts.
|
|
107
107
|
|
|
108
108
|
## Relai and Figma's official MCP
|
|
109
109
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadUserSkills
|
|
4
|
+
} from "./chunk-SDINVROO.js";
|
|
2
5
|
import {
|
|
3
6
|
__export
|
|
4
7
|
} from "./chunk-2H7UOFLK.js";
|
|
@@ -9,7 +12,7 @@ function createServer() {
|
|
|
9
12
|
return new McpServer(
|
|
10
13
|
{
|
|
11
14
|
name: "Relai",
|
|
12
|
-
version: "0.
|
|
15
|
+
version: "0.3.0"
|
|
13
16
|
},
|
|
14
17
|
{
|
|
15
18
|
instructions: `
|
|
@@ -146,6 +149,10 @@ var FIGMA_COMMANDS = [
|
|
|
146
149
|
"scan_token_drift",
|
|
147
150
|
"get_conventions",
|
|
148
151
|
"set_conventions",
|
|
152
|
+
"record_precedent",
|
|
153
|
+
"list_precedents",
|
|
154
|
+
"update_precedent",
|
|
155
|
+
"remove_precedent",
|
|
149
156
|
// Components
|
|
150
157
|
"get_local_components",
|
|
151
158
|
"get_instance_overrides",
|
|
@@ -1811,17 +1818,45 @@ function register16(server, sendCommand) {
|
|
|
1811
1818
|
);
|
|
1812
1819
|
server.tool(
|
|
1813
1820
|
"manage_conventions",
|
|
1814
|
-
"
|
|
1821
|
+
"The file's law: conventions (statutes) + precedents (case law), stored INSIDE this Figma file so they travel with it to every future session from any AI client. action:get returns both \u2014 read them BEFORE working and follow them like user instructions. Conventions are a markdown doc (action:set overwrites). Precedents are single adjudications the designer made \u2014 record one (action:record_precedent, one sentence, with refs to the tokens/nodes/pages it concerns) whenever the designer rules on something durable ('this deviation is intent, not drift', 'never restructure this table'), and SAY in your reply that you recorded it. Write results automatically surface precedents whose refs they touch.",
|
|
1815
1822
|
{
|
|
1816
|
-
action: z17.enum([
|
|
1817
|
-
|
|
1823
|
+
action: z17.enum([
|
|
1824
|
+
"get",
|
|
1825
|
+
"set",
|
|
1826
|
+
"record_precedent",
|
|
1827
|
+
"list_precedents",
|
|
1828
|
+
"update_precedent",
|
|
1829
|
+
"remove_precedent"
|
|
1830
|
+
]),
|
|
1831
|
+
content: z17.string().optional().describe("set: the full markdown doc (overwrites; max 20k chars)"),
|
|
1832
|
+
kind: z17.enum(["decision", "intent", "correction"]).optional().describe("record/update_precedent: decision (an approval/rejection), intent (a deviation that is deliberate), correction (a do-it-differently ruling). Default intent"),
|
|
1833
|
+
text: z17.string().optional().describe("record/update_precedent: the adjudication, one sentence, \u2264280 chars"),
|
|
1834
|
+
id: z17.string().optional().describe("update/remove_precedent: precedent id (from list_precedents)"),
|
|
1835
|
+
refs: z17.object({
|
|
1836
|
+
tokens: z17.array(z17.string()).optional().describe("Variable IDs this precedent concerns"),
|
|
1837
|
+
nodes: z17.array(z17.string()).optional().describe("Node IDs this precedent concerns"),
|
|
1838
|
+
pages: z17.array(z17.string()).optional().describe("Page IDs this precedent concerns")
|
|
1839
|
+
}).optional().describe("What the precedent is anchored to \u2014 enables in-band surfacing when writes touch these"),
|
|
1840
|
+
limit: z17.number().optional().describe("list_precedents: max entries returned (default 50)")
|
|
1818
1841
|
},
|
|
1819
|
-
async ({ action, content }) => {
|
|
1842
|
+
async ({ action, content, kind, text, id, refs, limit }) => {
|
|
1820
1843
|
try {
|
|
1821
|
-
|
|
1822
|
-
|
|
1844
|
+
switch (action) {
|
|
1845
|
+
case "set":
|
|
1846
|
+
return jsonResult(await sendCommand("set_conventions", { content: content ?? "" }));
|
|
1847
|
+
case "record_precedent":
|
|
1848
|
+
return jsonResult(
|
|
1849
|
+
await sendCommand("record_precedent", { kind, text, refs, source: "chat" })
|
|
1850
|
+
);
|
|
1851
|
+
case "list_precedents":
|
|
1852
|
+
return jsonResult(await sendCommand("list_precedents", { limit }));
|
|
1853
|
+
case "update_precedent":
|
|
1854
|
+
return jsonResult(await sendCommand("update_precedent", { id, kind, text, refs }));
|
|
1855
|
+
case "remove_precedent":
|
|
1856
|
+
return jsonResult(await sendCommand("remove_precedent", { id }));
|
|
1857
|
+
default:
|
|
1858
|
+
return jsonResult(await sendCommand("get_conventions", {}));
|
|
1823
1859
|
}
|
|
1824
|
-
return jsonResult(await sendCommand("get_conventions", {}));
|
|
1825
1860
|
} catch (error) {
|
|
1826
1861
|
return errorResult(error);
|
|
1827
1862
|
}
|
|
@@ -3508,11 +3543,14 @@ var component_spec_default = "# Component Specification Generator\n\n## Purpose\
|
|
|
3508
3543
|
var comment_driven_tasks_default = '# Comment-driven tasks\n\nDesigners leave requests as **Figma comments**; you pick them up, do the work, and reply on the thread. This turns Relai into an async collaborator without the designer ever leaving Figma \u2014 comments are their native input surface.\n\n## Honest model\n\nThis is **polling, not a live feed**. An MCP server only acts when called, so requests are noticed when you check \u2014 at the start of a session, when the user says "check the comments", or on a loop the user runs in their own client. Say so if the user expects real-time pickup.\n\nRequires `FIGMA_TOKEN` (personal access token with comment scopes). A free-plan token works on the user\'s own files.\n\n## The loop\n\n1. **Scan** \u2014 `manage_comments` `action:"list"` with `unresolved:true`, plus `since:<checkedAt from the previous scan>` after the first pass. Keep the returned `checkedAt` as the next cursor.\n2. **Select** \u2014 treat a comment as a task when it reads as an instruction ("make this...", "fix...", "@relai ..."). Skip chatter between humans and threads you already replied to (your replies carry your token account\'s handle).\n3. **Anchor** \u2014 pinned comments carry a `nodeId`. Start there: `get_node_details` / `screenshot` for context. Unpinned comments apply to the file broadly; ask before guessing a target.\n4. **Claim** \u2014 reply on the thread *before* working: `action:"reply"` with a one-liner like `\u{1F916} On it \u2014 <what you understood>`. This prevents double-work across sessions and tells the designer they were heard.\n5. **Execute** \u2014 normal Relai flow: design-system first, verify with `verify_visual` / `screenshot`.\n6. **Report back** \u2014 reply on the same thread with what changed and the node ids touched, e.g. `\u{1F916} Done: CTA restyled to primary/600, radius token applied (2 nodes). Reply here if it\'s off.` The REST API cannot mark threads resolved \u2014 the designer resolves it, which doubles as their sign-off.\n\n## One-shot phrasing for users\n\nUsers can drive the whole loop with a single ask: *"Check the file\'s comments and handle anything addressed to you, then report back on each thread."* For a standing loop, they re-issue that ask periodically (or wire it to their client\'s scheduler if it has one).\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n';
|
|
3509
3544
|
|
|
3510
3545
|
// ../../docs/skills/design-system-first.md
|
|
3511
|
-
var design_system_first_default = '# Design-system-first building\n\nThe difference between "AI slop" and work a design team accepts is almost always this: **slop draws new shapes; good work reuses the file\'s own system**. Before building any UI in a file that has one, wire yourself to it.\n\n## The sequence\n\n1. **`get_design_system`** \u2014 one call, cached per session. Read what exists: components (sorted by usage \u2014 high-usage items are the file\'s vocabulary), variable collections, styles, and the remote items the file already consumes. If the team\'s library lives in another file and `FIGMA_TOKEN` is set, pass `libraryFileUrl` for the full catalog.\n2. **`manage_conventions` (get)** \u2014 if the file carries conventions, they outrank your defaults. (`get_document_overview` includes
|
|
3546
|
+
var design_system_first_default = '# Design-system-first building\n\nThe difference between "AI slop" and work a design team accepts is almost always this: **slop draws new shapes; good work reuses the file\'s own system**. Before building any UI in a file that has one, wire yourself to it.\n\n## The sequence\n\n1. **`get_design_system`** \u2014 one call, cached per session. Read what exists: components (sorted by usage \u2014 high-usage items are the file\'s vocabulary), variable collections, styles, and the remote items the file already consumes. If the team\'s library lives in another file and `FIGMA_TOKEN` is set, pass `libraryFileUrl` for the full catalog.\n2. **`manage_conventions` (get)** \u2014 if the file carries conventions or precedents, they outrank your defaults: conventions are the statutes, precedents are the designer\'s case law (a precedent saying a deviation is intent means you never "fix" it). (`get_document_overview` includes conventions automatically.)\n3. **Compose from instances** \u2014 `manage_components` `action:"instantiate"` with the component key (works for local AND enabled-library keys; remote keys import automatically). Set variants/text via `set_props`. Only draw raw shapes for genuinely new elements.\n4. **Bind, don\'t hardcode** \u2014 colors/spacing/radius come from the file\'s variables (`manage_variables` `action:"bind"`, or variable fields in `set_properties`). If you must hardcode during exploration, run `analyze_design aspect:"tokens"` afterward and fix with `manage_variables action:"tokenize" fix:true`.\n5. **Verify like a designer** \u2014 `screenshot` / `verify_visual` after each increment, not at the end.\n\n## When the file has no system\n\nDon\'t invent variables for a one-off request. But if the work IS system-shaped (a palette, a type scale, components), build variables first, then styles/components on top \u2014 and offer to record naming decisions in `manage_conventions`.\n\n## Signals you\'re drifting\n\n- You drew a rectangle that looks like an existing Button/Card/Badge \u2192 replace it with an instance.\n- A hex value appears in your code that `get_design_system` lists as a variable \u2192 bind instead.\n- You renamed or restructured library instances the designer didn\'t ask about \u2192 undo; instances belong to their component.\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n';
|
|
3512
3547
|
|
|
3513
3548
|
// ../../docs/skills/janitorial-cleanup.md
|
|
3514
3549
|
var janitorial_cleanup_default = '# Janitorial cleanup\n\nThe chores designers postpone for months \u2014 layer renaming, token drift, spacing normalization \u2014 are where an agent earns trust fastest. These jobs are big, mechanical, and easy to verify. Do them conservatively and loudly.\n\n## Ground rules\n\n- **Preview before bulk.** Use `dryRun:true` on `batch_execute` / `set_properties`, or report findings first (`analyze_design`), and tell the designer the scale ("about 140 renames on this page \u2014 go?"). If the plugin\'s approval gate is on, big writes will ask them anyway.\n- **Scope tightly.** Work page by page (or within the designer\'s selection). Never sweep the whole file unasked.\n- **Never rename or restyle library instances\' internals** \u2014 only top-level frames/groups the team owns.\n\n## Recipes\n\n### Rename layers by content\n`Frame 427` tells nobody anything. Walk the target scope with `execute_figma` + `relai.query`, and derive names:\n- A frame whose only text child says "Submit" \u2192 `Button/Submit` (match the file\'s existing naming pattern from `get_design_system`, don\'t invent one).\n- Text nodes \u2192 their content, truncated (`Heading: Pricing plans`).\n- Skip nodes that already have deliberate names (anything not matching `/^(Frame|Group|Rectangle|Ellipse|Vector|Line) \\d+$/`).\nReturn the rename list; apply via one `batch_execute` of `rename_node` commands.\n\n### Tokenize hardcoded values\n`analyze_design aspect:"tokens"` finds hardcoded colors/numbers that visually match existing variables; `manage_variables action:"tokenize" fix:true` binds them in one pass. Report anything close-but-not-matching (deltaE just over tolerance) instead of silently snapping it.\n\n### Normalize spacing\nIn auto-layout scopes, collect `itemSpacing`/padding values; flag off-scale values (not on the file\'s 4/8pt grid or spacing variables) and propose the nearest on-scale value. Apply only after the designer confirms the mapping.\n\n### Sweep detached instances\n`analyze_design aspect:"components"` lists likely-detached instances. Offer to re-link obvious ones (matching component still exists) and just report the rest \u2014 re-attaching wrong is worse than detached.\n\n## Report format\n\nEnd with counts and jump-points: what changed, what was skipped and why, node ids for spot-checking (the designer can click entries in the plugin feed).\n\n---\n\n*Tool parameter contracts are deliberately not duplicated in this document \u2014 every tool is self-describing over MCP, and `npx figma-relai docs <tool>` (or the generated `docs/manifest.json`) is the always-current reference.*\n';
|
|
3515
3550
|
|
|
3551
|
+
// ../../docs/skills/memory-and-precedents.md
|
|
3552
|
+
var memory_and_precedents_default = '# File memory & precedents\n\nEvery Figma file this plugin touches can carry its own case law: **precedents** \u2014 single adjudications the designer made, stored inside the file (shared plugin data) so they reach every future session from any AI client. Conventions are the statutes; precedents are the case law. Together they are the file\'s law, and `manage_conventions action:get` returns both.\n\n## Read before you work\n\nAt the start of any session that will modify the file, call `manage_conventions action:get`. Treat what comes back the way you treat user instructions:\n\n- `content` \u2014 the conventions doc (statutes).\n- `precedents` \u2014 recent adjudications (case law), newest first, with `precedentCount` for the total.\n\nA precedent outranks your own judgment about what "looks wrong". If a precedent says a deviation is intent, do not "fix" it \u2014 and do not re-ask about it.\n\n## When to record\n\nRecord a precedent (`action:record_precedent`) when the designer **rules** on something durable. The signal is adjudication language:\n\n- "This is intentional, not drift" \u2192 `kind: "intent"`\n- "Approved \u2014 but never restructure this table again" \u2192 `kind: "decision"`\n- "Don\'t do it that way; do X instead" \u2192 `kind: "correction"`\n\nOne precedent = one sentence (\u2264280 chars), written so a future session with zero context understands the ruling. Attach `refs` \u2014 the variable IDs, node IDs, or page IDs the ruling concerns \u2014 because refs power in-band surfacing: any future write that touches them gets the precedent attached to its result automatically.\n\n**Always say in your reply that you recorded it** ("\u5DF2\u8BB0\u5165\u6587\u4EF6\u8BB0\u5FC6: \u2026"). Recording silently breaks the designer\'s trust in what the file knows.\n\n## When NOT to record\n\n- Transient, one-off choices ("make this one red for the screenshot").\n- Anything already in the conventions doc (point to it instead; propose a conventions edit if it deserves statute status).\n- Secrets, personal information, or anything about people rather than the file.\n- Your own inferences the designer has not confirmed. Precedents are the DESIGNER\'s rulings, not your observations. When unsure, ask: "\u8BB0\u4E3A\u5224\u4F8B\u5417?"\n\n## Maintenance\n\n- The designer sees and can delete every entry in the plugin panel (Memory section). Their record, their rules.\n- Memory holds 200 entries / 64KB. When recording fails with a capacity error, offer a consolidation pass: `list_precedents`, merge superseded rulings into fewer summary entries (`record_precedent` the summaries, `remove_precedent` the merged ones), preserving every still-active ruling. Show the merge plan before applying it.\n- If a precedent contradicts current reality (the token it references is gone, the ruling was reversed), ask the designer whether to update or remove it \u2014 never silently drop case law.\n';
|
|
3553
|
+
|
|
3516
3554
|
// src/prompts.ts
|
|
3517
3555
|
var SKILLS = [
|
|
3518
3556
|
[
|
|
@@ -3559,6 +3597,11 @@ var SKILLS = [
|
|
|
3559
3597
|
"janitorial-cleanup",
|
|
3560
3598
|
"Bulk cleanup recipes: content-based layer renaming, tokenizing hardcoded values, spacing normalization, detached-instance sweeps \u2014 with preview-first discipline.",
|
|
3561
3599
|
janitorial_cleanup_default
|
|
3600
|
+
],
|
|
3601
|
+
[
|
|
3602
|
+
"memory-and-precedents",
|
|
3603
|
+
"The file's case law: when to record a designer adjudication as a precedent, when not to, and how memory is maintained. Load in any session that adjudicates or touches a file with existing precedents.",
|
|
3604
|
+
memory_and_precedents_default
|
|
3562
3605
|
]
|
|
3563
3606
|
];
|
|
3564
3607
|
function registerPrompts(server) {
|
|
@@ -3572,6 +3615,16 @@ function registerPrompts(server) {
|
|
|
3572
3615
|
]
|
|
3573
3616
|
}));
|
|
3574
3617
|
}
|
|
3618
|
+
for (const skill of loadUserSkills().skills) {
|
|
3619
|
+
server.prompt(`user:${skill.name}`, skill.description, () => ({
|
|
3620
|
+
messages: [
|
|
3621
|
+
{
|
|
3622
|
+
role: "user",
|
|
3623
|
+
content: { type: "text", text: skill.text }
|
|
3624
|
+
}
|
|
3625
|
+
]
|
|
3626
|
+
}));
|
|
3627
|
+
}
|
|
3575
3628
|
}
|
|
3576
3629
|
|
|
3577
3630
|
export {
|
|
@@ -3585,4 +3638,4 @@ export {
|
|
|
3585
3638
|
listToolCatalog,
|
|
3586
3639
|
registerPrompts
|
|
3587
3640
|
};
|
|
3588
|
-
//# sourceMappingURL=chunk-
|
|
3641
|
+
//# sourceMappingURL=chunk-2MUSDWUC.js.map
|