free-figma-mcp 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.
Files changed (91) hide show
  1. package/cli.js +298 -0
  2. package/data/motion-presets.json +4316 -0
  3. package/figma-plugin/code.js +1805 -0
  4. package/figma-plugin/manifest.json +14 -0
  5. package/figma-plugin/ui.html +486 -0
  6. package/package.json +57 -0
  7. package/powers/local-figma/POWER.md +112 -0
  8. package/powers/local-figma/mcp.json +13 -0
  9. package/powers/local-figma/steering/code-connect.md +32 -0
  10. package/powers/local-figma/steering/config-2026.md +28 -0
  11. package/powers/local-figma/steering/implement-design.md +37 -0
  12. package/powers/local-figma/steering/setup.md +68 -0
  13. package/powers/local-figma/steering/use-figjam.md +24 -0
  14. package/powers/local-figma/steering/use-figma.md +40 -0
  15. package/server.js +47 -0
  16. package/skills/figma-code-connect/SKILL.md +527 -0
  17. package/skills/figma-code-connect/references/advanced-patterns.md +286 -0
  18. package/skills/figma-code-connect/references/api.md +1014 -0
  19. package/skills/figma-create-design-system-rules/SKILL.md +538 -0
  20. package/skills/figma-generate-design/SKILL.md +410 -0
  21. package/skills/figma-generate-diagram/SKILL.md +112 -0
  22. package/skills/figma-generate-diagram/references/architecture.md +150 -0
  23. package/skills/figma-generate-diagram/references/erd.md +313 -0
  24. package/skills/figma-generate-diagram/references/flowchart.md +392 -0
  25. package/skills/figma-generate-diagram/references/gantt.md +242 -0
  26. package/skills/figma-generate-diagram/references/sequence.md +251 -0
  27. package/skills/figma-generate-diagram/references/state.md +310 -0
  28. package/skills/figma-generate-diagram/references/workflow.md +130 -0
  29. package/skills/figma-generate-library/SKILL.md +315 -0
  30. package/skills/figma-generate-library/references/code-connect-setup.md +260 -0
  31. package/skills/figma-generate-library/references/component-creation.md +972 -0
  32. package/skills/figma-generate-library/references/discovery-phase.md +495 -0
  33. package/skills/figma-generate-library/references/documentation-creation.md +802 -0
  34. package/skills/figma-generate-library/references/error-recovery.md +514 -0
  35. package/skills/figma-generate-library/references/naming-conventions.md +527 -0
  36. package/skills/figma-generate-library/references/token-creation.md +897 -0
  37. package/skills/figma-generate-library/scripts/bindVariablesToComponent.js +110 -0
  38. package/skills/figma-generate-library/scripts/cleanupOrphans.js +127 -0
  39. package/skills/figma-generate-library/scripts/createComponentWithVariants.js +148 -0
  40. package/skills/figma-generate-library/scripts/createDocumentationPage.js +145 -0
  41. package/skills/figma-generate-library/scripts/createSemanticTokens.js +108 -0
  42. package/skills/figma-generate-library/scripts/createVariableCollection.js +49 -0
  43. package/skills/figma-generate-library/scripts/inspectFileStructure.js +121 -0
  44. package/skills/figma-generate-library/scripts/rehydrateState.js +92 -0
  45. package/skills/figma-generate-library/scripts/validateCreation.js +83 -0
  46. package/skills/figma-motion/SKILL.md +52 -0
  47. package/skills/figma-motion-replicate/SKILL.md +113 -0
  48. package/skills/figma-shaders/SKILL.md +38 -0
  49. package/skills/figma-slots/SKILL.md +41 -0
  50. package/skills/figma-text-motion/SKILL.md +53 -0
  51. package/skills/generate-project-plan/SKILL.md +494 -0
  52. package/skills/generate-project-plan/references/blocks/diagram-section.md +249 -0
  53. package/skills/generate-project-plan/references/blocks/intro-callout.md +82 -0
  54. package/skills/generate-project-plan/references/blocks/metadata-strip.md +101 -0
  55. package/skills/generate-project-plan/references/blocks/multi-column-text.md +151 -0
  56. package/skills/generate-project-plan/references/blocks/nested-section.md +105 -0
  57. package/skills/generate-project-plan/references/blocks/section.md +70 -0
  58. package/skills/generate-project-plan/references/blocks/sticky-column.md +83 -0
  59. package/skills/generate-project-plan/references/blocks/table.md +112 -0
  60. package/skills/generate-project-plan/references/blocks/text-primitives.md +114 -0
  61. package/skills/generate-project-plan/references/foundation/codebase-grounding.md +92 -0
  62. package/skills/generate-project-plan/references/foundation/layout.md +112 -0
  63. package/skills/generate-project-plan/references/foundation/palette.md +155 -0
  64. package/skills/generate-project-plan/references/foundation/plugin-api-traps.md +180 -0
  65. package/skills/generate-project-plan/references/section-catalog.md +198 -0
  66. package/skills/local-figma-implement-design/SKILL.md +66 -0
  67. package/skills/local-figma-use/SKILL.md +76 -0
  68. package/skills/local-figma-use/references/common-patterns.md +112 -0
  69. package/skills/local-figma-use/references/component-patterns.md +130 -0
  70. package/skills/local-figma-use/references/gotchas.md +120 -0
  71. package/skills/local-figma-use/references/validation-and-recovery.md +53 -0
  72. package/skills/local-figma-use/references/variable-patterns.md +98 -0
  73. package/skills/local-figma-use-figjam/SKILL.md +31 -0
  74. package/skills/local-figma-use-figjam/references/diagram-patterns.md +55 -0
  75. package/skills/local-figma-use-figjam/references/figjam-patterns.md +68 -0
  76. package/skills/local-figma-use-figjam/references/validation.md +46 -0
  77. package/src/api-catalog.js +100 -0
  78. package/src/bridge.js +374 -0
  79. package/src/capabilities.js +86 -0
  80. package/src/code-connect-store.js +38 -0
  81. package/src/config.js +43 -0
  82. package/src/content.js +27 -0
  83. package/src/generators/motion.js +184 -0
  84. package/src/generators/preset-bake.js +121 -0
  85. package/src/generators/scene-replicate.js +129 -0
  86. package/src/generators/typewriter.js +91 -0
  87. package/src/guidance.js +83 -0
  88. package/src/installer.js +507 -0
  89. package/src/preset-store.js +49 -0
  90. package/src/schema-probe.js +119 -0
  91. package/src/tools.js +646 -0
package/cli.js ADDED
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Free Figma MCP — interactive setup CLI
4
+ //
5
+ // Wires the MCP server into an IDE/client config and helps install the Figma
6
+ // plugin. Interactive by default; supports headless flags for scripting:
7
+ //
8
+ // node cli.js # interactive menu
9
+ // node cli.js --client cursor # configure one client, print plugin steps
10
+ // node cli.js --plugin [dir] # copy plugin files (default: repo plugin)
11
+ // node cli.js --print # print the mcpServers JSON only
12
+ // node cli.js --npx # use the published package via npx
13
+ // node cli.js --name my-figma # override the server key name
14
+ // =============================================================================
15
+
16
+ import readline from "node:readline";
17
+ import path from "node:path";
18
+ import {
19
+ CLIENTS,
20
+ PACKAGE_NAME,
21
+ SERVER_NAME,
22
+ buildServerEntry,
23
+ clientCapabilities,
24
+ createInstaller,
25
+ listClients,
26
+ mergeConfig,
27
+ pluginInstructions
28
+ } from "./src/installer.js";
29
+ import { pluginDir, serverEntry, skillsDir } from "./src/config.js";
30
+
31
+ function parseArgs(argv) {
32
+ const args = { _: [] };
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const token = argv[i];
35
+ if (token === "--npx") args.npx = true;
36
+ else if (token === "--print") args.print = true;
37
+ else if (token === "--skills") args.skills = true;
38
+ else if (token === "--rules" || token === "--guidance") args.rules = true;
39
+ else if (token === "--global") args.scope = "global";
40
+ else if (token === "--scope") args.scope = argv[++i];
41
+ else if (token === "--client") args.client = argv[++i];
42
+ else if (token === "--name") args.name = argv[++i];
43
+ else if (token === "--plugin") {
44
+ // Optional value: only consume the next token if it isn't another flag.
45
+ const next = argv[i + 1];
46
+ args.plugin = next && !next.startsWith("--") ? argv[++i] : true;
47
+ } else if (token === "--help" || token === "-h") {
48
+ args.help = true;
49
+ } else {
50
+ args._.push(token);
51
+ }
52
+ }
53
+ return args;
54
+ }
55
+
56
+ function printHelp() {
57
+ const clients = listClients().map((c) => ` ${c.key.padEnd(16)} ${c.label}`).join("\n");
58
+ process.stdout.write(`Free Figma MCP setup\n\n` +
59
+ `Usage:\n` +
60
+ ` node cli.js Interactive setup\n` +
61
+ ` node cli.js --client <key> Configure a client (then print plugin steps)\n` +
62
+ ` node cli.js --plugin [dir] Copy the Figma plugin files (default: repo plugin dir)\n` +
63
+ ` node cli.js --skills Install Agent Skills (SKILL.md folders) — skill-capable clients\n` +
64
+ ` node cli.js --rules Install the guidance as the client's native rules\n` +
65
+ ` node cli.js --scope <s> project (default) or global; --global is shorthand\n` +
66
+ ` node cli.js --print Print the mcpServers JSON only\n` +
67
+ ` node cli.js --npx Configure to run the published package via npx\n` +
68
+ ` node cli.js --name <key> Override the MCP server key (default: ${SERVER_NAME})\n\n` +
69
+ `Skills vs rules: skills are model-invoked SKILL.md folders (Claude Code/Desktop);\n` +
70
+ `rules are each client's native instruction system (Cursor/Kiro/Codex/Qoder/etc).\n\n` +
71
+ `Clients:\n${clients}\n`);
72
+ }
73
+
74
+ function printConfigJson({ useNpx, name }) {
75
+ const entry = buildServerEntry({ serverEntry, useNpx, packageName: PACKAGE_NAME });
76
+ const json = mergeConfig({}, "mcpServers", name || SERVER_NAME, entry);
77
+ process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
78
+ }
79
+
80
+ // Buffered line prompter. Using rl.question sequentially with piped (non-TTY)
81
+ // stdin drops lines emitted during await gaps; this queues every line so the
82
+ // flow is reliable for both interactive terminals and scripted input.
83
+ function createPrompter(rl) {
84
+ const queue = [];
85
+ const waiters = [];
86
+ let closed = false;
87
+ rl.on("line", (line) => {
88
+ if (waiters.length) waiters.shift()(line.trim());
89
+ else queue.push(line.trim());
90
+ });
91
+ rl.on("close", () => {
92
+ closed = true;
93
+ while (waiters.length) waiters.shift()("");
94
+ });
95
+ return function ask(question) {
96
+ if (question) process.stdout.write(question);
97
+ if (queue.length) return Promise.resolve(queue.shift());
98
+ if (closed) return Promise.resolve("");
99
+ return new Promise((resolve) => waiters.push(resolve));
100
+ };
101
+ }
102
+
103
+ async function runInteractive() {
104
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
105
+ const ask = createPrompter(rl);
106
+ const installer = createInstaller({ serverEntry, pluginDir, skillsDir });
107
+ try {
108
+ process.stdout.write("\nFree Figma MCP — setup\n======================\n\n");
109
+ process.stdout.write("What do you want to do?\n");
110
+ process.stdout.write(" 1) Configure an IDE / MCP client\n");
111
+ process.stdout.write(" 2) Install the Figma plugin\n");
112
+ process.stdout.write(" 3) Do both\n");
113
+ process.stdout.write(" 4) Print the MCP config JSON\n");
114
+ process.stdout.write(" 5) Exit\n\n");
115
+ const choice = await ask("Choice [1-5]: ");
116
+
117
+ const useNpx = ["1", "3"].includes(choice)
118
+ ? /^y/i.test(await ask("Run the published package via npx instead of this local clone? [y/N]: "))
119
+ : false;
120
+
121
+ if (choice === "1" || choice === "3") {
122
+ await chooseAndConfigure(ask, installer, useNpx);
123
+ }
124
+ if (["1", "2", "3"].includes(choice)) {
125
+ await offerPluginInstall(ask, installer);
126
+ }
127
+ if (choice === "4") {
128
+ printConfigJson({ useNpx: false });
129
+ }
130
+ if (choice === "5" || !["1", "2", "3", "4"].includes(choice)) {
131
+ process.stdout.write("\nNothing to do. Bye.\n");
132
+ }
133
+ } finally {
134
+ rl.close();
135
+ }
136
+ }
137
+
138
+ async function askScope(ask, scopes, what) {
139
+ if (scopes.length === 0) return "project";
140
+ if (scopes.length === 1) {
141
+ process.stdout.write(` (${what}: only ${scopes[0]} scope is supported here)\n`);
142
+ return scopes[0];
143
+ }
144
+ const answer = await ask(` Scope for ${what} — [project] this workspace, or [global] all projects? [project/global]: `);
145
+ return /^g/i.test(answer) ? "global" : "project";
146
+ }
147
+
148
+ async function chooseAndConfigure(ask, installer, useNpx) {
149
+ const clients = listClients();
150
+ process.stdout.write("\nWhich client?\n");
151
+ clients.forEach((c, i) => process.stdout.write(` ${i + 1}) ${c.label}\n`));
152
+ const raw = await ask(`Choice [1-${clients.length}]: `);
153
+ const index = Number(raw) - 1;
154
+ const client = clients[index];
155
+ if (!client) {
156
+ process.stdout.write("Invalid choice; skipping client configuration.\n");
157
+ return;
158
+ }
159
+ const result = installer.configureClient(client.key, { useNpx });
160
+ process.stdout.write(`\nConfigured ${result.label}\n -> ${result.configPath}\n`);
161
+
162
+ const caps = clientCapabilities(client.key);
163
+
164
+ // Skills (Agent Skills = model-invoked SKILL.md folders).
165
+ if (caps.skills.supported) {
166
+ const want = await ask("\nInstall the Figma Agent Skills (SKILL.md folders) for this client? [Y/n]: ");
167
+ if (!/^n/i.test(want)) {
168
+ const scope = await askScope(ask, caps.skills.scopes, "skills");
169
+ const s = installer.installSkills(client.key, { scope });
170
+ process.stdout.write(s.skipped
171
+ ? `Skills: ${s.reason}\n`
172
+ : `Skills installed (${s.scope}): ${s.skills.length} skill folders -> ${s.destDir}\n`);
173
+ }
174
+ }
175
+
176
+ // Rules (the client's native instruction system) — different from skills.
177
+ if (caps.rules.supported) {
178
+ const want = await ask("\nInstall the Figma guidance as this client's native rules? [Y/n]: ");
179
+ if (!/^n/i.test(want)) {
180
+ const scope = await askScope(ask, caps.rules.scopes, "rules");
181
+ const r = installer.installRules(client.key, { scope });
182
+ if (r.skipped) {
183
+ process.stdout.write(`Rules: ${r.reason}\n`);
184
+ } else if (r.kind === "dir") {
185
+ process.stdout.write(`Rules installed (${r.scope}): ${r.rules.length} rule docs -> ${r.destDir}\n`);
186
+ } else {
187
+ process.stdout.write(`Rules ${r.action} (${r.scope}): ${r.filePath}\n`);
188
+ }
189
+ }
190
+ }
191
+
192
+ if (!caps.skills.supported && !caps.rules.supported) {
193
+ process.stdout.write("\nThis client has no skills/rules store — it reads them over the MCP server's prompts/resources automatically.\n");
194
+ }
195
+ }
196
+
197
+ async function offerPluginInstall(ask, installer) {
198
+ const want = await ask("\nCopy the Figma plugin files so you can import them into Figma? [Y/n]: ");
199
+ if (/^n/i.test(want)) {
200
+ process.stdout.write(`\n${pluginInstructions(path.join(pluginDir, "manifest.json"))}\n`);
201
+ return;
202
+ }
203
+ const suggested = installer.suggestedPluginDir();
204
+ const answer = await ask(
205
+ `Where should the plugin files go?\n [Enter] = ${suggested}\n or type a folder path, or '.' for the current directory: `
206
+ );
207
+ let dest = !answer ? suggested : (answer === "." ? process.cwd() : answer);
208
+ try {
209
+ const result = installer.installPlugin(dest);
210
+ process.stdout.write(`\nPlugin files ready in: ${result.destDir}\n\n${pluginInstructions(result.manifestPath)}\n`);
211
+ } catch (err) {
212
+ process.stdout.write(`\nCould not write to ${dest}: ${err.message}\n`);
213
+ const retry = await ask("Enter a different folder, or press Enter for the current directory: ");
214
+ const dest2 = retry || process.cwd();
215
+ const result = installer.installPlugin(dest2);
216
+ process.stdout.write(`\nPlugin files ready in: ${result.destDir}\n\n${pluginInstructions(result.manifestPath)}\n`);
217
+ }
218
+ }
219
+
220
+ async function runHeadless(args) {
221
+ const installer = createInstaller({ serverEntry, pluginDir, skillsDir });
222
+
223
+ if (args.print) {
224
+ printConfigJson({ useNpx: Boolean(args.npx), name: args.name });
225
+ return;
226
+ }
227
+
228
+ let didSomething = false;
229
+
230
+ if (args.client) {
231
+ if (!CLIENTS[args.client]) {
232
+ process.stderr.write(`Unknown client "${args.client}". Known: ${Object.keys(CLIENTS).join(", ")}\n`);
233
+ process.exitCode = 1;
234
+ return;
235
+ }
236
+ const result = installer.configureClient(args.client, { useNpx: Boolean(args.npx), name: args.name });
237
+ process.stdout.write(`Configured ${result.label}\n -> ${result.configPath}\n`);
238
+ didSomething = true;
239
+ }
240
+
241
+ if (args.skills) {
242
+ if (!args.client) {
243
+ process.stderr.write("--skills requires --client <key>.\n");
244
+ process.exitCode = 1;
245
+ return;
246
+ }
247
+ const s = installer.installSkills(args.client, { scope: args.scope || "project" });
248
+ if (s.skipped) {
249
+ process.stdout.write(`Skills: ${s.reason}\n`);
250
+ } else {
251
+ process.stdout.write(`Skills installed (${s.scope}): ${s.skills.length} skill folders -> ${s.destDir}\n`);
252
+ }
253
+ didSomething = true;
254
+ }
255
+
256
+ if (args.rules) {
257
+ if (!args.client) {
258
+ process.stderr.write("--rules requires --client <key>.\n");
259
+ process.exitCode = 1;
260
+ return;
261
+ }
262
+ const r = installer.installRules(args.client, { scope: args.scope || "project" });
263
+ if (r.skipped) {
264
+ process.stdout.write(`Rules: ${r.reason}\n`);
265
+ } else if (r.kind === "dir") {
266
+ process.stdout.write(`Rules installed (${r.scope}): ${r.rules.length} rule docs -> ${r.destDir}\n`);
267
+ } else {
268
+ process.stdout.write(`Rules ${r.action} (${r.scope}): ${r.filePath}\n`);
269
+ }
270
+ didSomething = true;
271
+ }
272
+
273
+ if (args.plugin) {
274
+ const destDir = typeof args.plugin === "string" ? args.plugin : installer.suggestedPluginDir();
275
+ const result = installer.installPlugin(destDir);
276
+ process.stdout.write(`Plugin files ready in: ${result.destDir}\n`);
277
+ process.stdout.write(`\n${pluginInstructions(result.manifestPath)}\n`);
278
+ didSomething = true;
279
+ } else if (args.client) {
280
+ // Configured a client but didn't ask to copy the plugin — show import steps
281
+ // from the source location so the user can import it manually.
282
+ process.stdout.write(`\n${pluginInstructions(path.join(pluginDir, "manifest.json"))}\n`);
283
+ }
284
+
285
+ if (!didSomething) {
286
+ printHelp();
287
+ }
288
+ }
289
+
290
+ const args = parseArgs(process.argv.slice(2));
291
+
292
+ if (args.help) {
293
+ printHelp();
294
+ } else if (args.print || args.client || args.plugin || args.npx || args.name || args.rules || args.skills) {
295
+ await runHeadless(args);
296
+ } else {
297
+ await runInteractive();
298
+ }