figma-rust-mcp 3.2.8 → 3.2.11

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/plugin/ui.html CHANGED
@@ -1,211 +1,211 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Figma MCP Loader</title>
7
- <style>
8
- :root {
9
- --bg: #0d0e12;
10
- --surface-base: #12141a;
11
- --surface-card: #171922;
12
- --border-subtle: rgba(255, 255, 255, 0.08);
13
- --border-ghost: rgba(147, 129, 255, 0.35);
14
- --text-primary: #f4f4f7;
15
- --text-secondary: #9aa1b2;
16
- --text-muted: #52596d;
17
- --ghost-lavender: #9381ff;
18
- --ghost-mint: #34d399;
19
- --font-ui: -apple-system, BlinkMacSystemFont, 'Inter', 'SF Pro Text', sans-serif;
20
- --font-mono: 'JetBrains Mono', 'SF Mono', Menlo, monospace;
21
- }
22
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
23
- html, body { width: 100%; height: 100%; overflow: hidden; background: var(--bg); color: var(--text-primary); font-family: var(--font-ui); font-size: 11px; }
24
-
25
- .loader-container {
26
- display: flex;
27
- flex-direction: column;
28
- align-items: center;
29
- justify-content: center;
30
- height: 100%;
31
- padding: 24px;
32
- text-align: center;
33
- position: relative;
34
- }
35
-
36
- .radar-pulse {
37
- position: relative;
38
- width: 54px;
39
- height: 54px;
40
- margin-bottom: 20px;
41
- display: flex;
42
- align-items: center;
43
- justify-content: center;
44
- }
45
- .radar-ring {
46
- position: absolute;
47
- width: 100%;
48
- height: 100%;
49
- border-radius: 50%;
50
- border: 2px solid var(--ghost-lavender);
51
- opacity: 0;
52
- animation: pulse 2s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
53
- }
54
- .radar-ring:nth-child(2) { animation-delay: 0.6s; }
55
- .radar-ring:nth-child(3) { animation-delay: 1.2s; }
56
- .radar-core {
57
- width: 14px;
58
- height: 14px;
59
- border-radius: 50%;
60
- background: var(--ghost-lavender);
61
- box-shadow: 0 0 16px var(--ghost-lavender);
62
- }
63
-
64
- @keyframes pulse {
65
- 0% { transform: scale(0.3); opacity: 0.9; }
66
- 100% { transform: scale(1.6); opacity: 0; }
67
- }
68
-
69
- .title {
70
- font-size: 13px;
71
- font-weight: 600;
72
- letter-spacing: -0.01em;
73
- margin-bottom: 6px;
74
- color: var(--text-primary);
75
- }
76
- .status-text {
77
- font-size: 11px;
78
- color: var(--text-secondary);
79
- margin-bottom: 16px;
80
- line-height: 1.5;
81
- }
82
- .badge {
83
- display: inline-flex;
84
- align-items: center;
85
- gap: 6px;
86
- padding: 4px 10px;
87
- background: var(--surface-card);
88
- border: 1px solid var(--border-subtle);
89
- border-radius: 9999px;
90
- font-family: var(--font-mono);
91
- font-size: 10px;
92
- color: var(--ghost-mint);
93
- margin-bottom: 16px;
94
- }
95
- .hint-box {
96
- background: var(--surface-base);
97
- border: 1px solid var(--border-subtle);
98
- border-radius: 8px;
99
- padding: 10px 12px;
100
- max-width: 280px;
101
- font-size: 10px;
102
- color: var(--text-muted);
103
- line-height: 1.4;
104
- text-align: left;
105
- }
106
- .hint-box code {
107
- font-family: var(--font-mono);
108
- color: var(--ghost-lavender);
109
- display: block;
110
- margin-top: 4px;
111
- background: rgba(0,0,0,0.3);
112
- padding: 3px 6px;
113
- border-radius: 4px;
114
- }
115
- </style>
116
- </head>
117
- <body>
118
- <div class="loader-container">
119
- <div class="radar-pulse">
120
- <div class="radar-ring"></div>
121
- <div class="radar-ring"></div>
122
- <div class="radar-ring"></div>
123
- <div class="radar-core"></div>
124
- </div>
125
- <div class="title">Connecting to figma-mcp</div>
126
- <div id="status" class="status-text">Scanning local ports (38451-38455)...</div>
127
- <div class="badge">
128
- <span>●</span> <span id="port-label">localhost:38451</span>
129
- </div>
130
- <div class="hint-box">
131
- Ensure the local Rust server is running:
132
- <code>npx figma-rust-mcp</code>
133
- </div>
134
- </div>
135
-
136
- <script>
137
- const PORTS = [38451, 38452, 38453, 38454, 38455];
138
- let attempts = 0;
139
-
140
- async function checkPort(port) {
141
- const controller = new AbortController();
142
- const timeoutId = setTimeout(() => controller.abort(), 800);
143
- try {
144
- const res = await fetch(`http://localhost:${port}/plugin/version`, {
145
- signal: controller.signal,
146
- cache: "no-store"
147
- });
148
- clearTimeout(timeoutId);
149
- if (res.ok) {
150
- const data = await res.json();
151
- return { ok: true, data };
152
- }
153
- } catch (_) {
154
- clearTimeout(timeoutId);
155
- }
156
- return { ok: false };
157
- }
158
-
159
- async function bootstrapFromPort(port) {
160
- const statusEl = document.getElementById("status");
161
- if (statusEl) statusEl.textContent = "Loading dynamic runtime...";
162
-
163
- try {
164
- // 1. Fetch main thread code
165
- const codeRes = await fetch(`http://localhost:${port}/plugin/code.js?t=${Date.now()}`);
166
- if (!codeRes.ok) throw new Error("Failed to load plugin code: " + codeRes.status);
167
- const codeText = await codeRes.text();
168
-
169
- // Send to main thread
170
- parent.postMessage({ pluginMessage: { type: "EVAL_MAIN_CODE", code: codeText } }, "*");
171
-
172
- // 2. Fetch full UI HTML
173
- const uiRes = await fetch(`http://localhost:${port}/plugin/ui.html?t=${Date.now()}`);
174
- if (!uiRes.ok) throw new Error("Failed to load plugin UI: " + uiRes.status);
175
- const uiHtml = await uiRes.text();
176
-
177
- // 3. Inject full UI cleanly
178
- document.open();
179
- document.write(uiHtml);
180
- document.close();
181
- } catch (err) {
182
- console.error("[figma-mcp stub]", err);
183
- if (statusEl) statusEl.textContent = "Failed to load: " + err.message + ". Retrying...";
184
- setTimeout(findAndConnect, 1500);
185
- }
186
- }
187
-
188
- async function findAndConnect() {
189
- attempts++;
190
- const statusEl = document.getElementById("status");
191
- const portEl = document.getElementById("port-label");
192
-
193
- for (const port of PORTS) {
194
- if (portEl) portEl.textContent = `localhost:${port}`;
195
- const result = await checkPort(port);
196
- if (result.ok) {
197
- await bootstrapFromPort(port);
198
- return;
199
- }
200
- }
201
-
202
- if (statusEl) {
203
- statusEl.textContent = `Waiting for server (attempt #${attempts})...`;
204
- }
205
- setTimeout(findAndConnect, 1000);
206
- }
207
-
208
- findAndConnect();
209
- </script>
210
- </body>
211
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Figma MCP Loader</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0d0e12;
10
+ --surface-base: #12141a;
11
+ --surface-card: #171922;
12
+ --border-subtle: rgba(255, 255, 255, 0.08);
13
+ --border-ghost: rgba(147, 129, 255, 0.35);
14
+ --text-primary: #f4f4f7;
15
+ --text-secondary: #9aa1b2;
16
+ --text-muted: #52596d;
17
+ --ghost-lavender: #9381ff;
18
+ --ghost-mint: #34d399;
19
+ --font-ui: -apple-system, BlinkMacSystemFont, 'Inter', 'SF Pro Text', sans-serif;
20
+ --font-mono: 'JetBrains Mono', 'SF Mono', Menlo, monospace;
21
+ }
22
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
23
+ html, body { width: 100%; height: 100%; overflow: hidden; background: var(--bg); color: var(--text-primary); font-family: var(--font-ui); font-size: 11px; }
24
+
25
+ .loader-container {
26
+ display: flex;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ justify-content: center;
30
+ height: 100%;
31
+ padding: 24px;
32
+ text-align: center;
33
+ position: relative;
34
+ }
35
+
36
+ .radar-pulse {
37
+ position: relative;
38
+ width: 54px;
39
+ height: 54px;
40
+ margin-bottom: 20px;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ }
45
+ .radar-ring {
46
+ position: absolute;
47
+ width: 100%;
48
+ height: 100%;
49
+ border-radius: 50%;
50
+ border: 2px solid var(--ghost-lavender);
51
+ opacity: 0;
52
+ animation: pulse 2s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
53
+ }
54
+ .radar-ring:nth-child(2) { animation-delay: 0.6s; }
55
+ .radar-ring:nth-child(3) { animation-delay: 1.2s; }
56
+ .radar-core {
57
+ width: 14px;
58
+ height: 14px;
59
+ border-radius: 50%;
60
+ background: var(--ghost-lavender);
61
+ box-shadow: 0 0 16px var(--ghost-lavender);
62
+ }
63
+
64
+ @keyframes pulse {
65
+ 0% { transform: scale(0.3); opacity: 0.9; }
66
+ 100% { transform: scale(1.6); opacity: 0; }
67
+ }
68
+
69
+ .title {
70
+ font-size: 13px;
71
+ font-weight: 600;
72
+ letter-spacing: -0.01em;
73
+ margin-bottom: 6px;
74
+ color: var(--text-primary);
75
+ }
76
+ .status-text {
77
+ font-size: 11px;
78
+ color: var(--text-secondary);
79
+ margin-bottom: 16px;
80
+ line-height: 1.5;
81
+ }
82
+ .badge {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ gap: 6px;
86
+ padding: 4px 10px;
87
+ background: var(--surface-card);
88
+ border: 1px solid var(--border-subtle);
89
+ border-radius: 9999px;
90
+ font-family: var(--font-mono);
91
+ font-size: 10px;
92
+ color: var(--ghost-mint);
93
+ margin-bottom: 16px;
94
+ }
95
+ .hint-box {
96
+ background: var(--surface-base);
97
+ border: 1px solid var(--border-subtle);
98
+ border-radius: 8px;
99
+ padding: 10px 12px;
100
+ max-width: 280px;
101
+ font-size: 10px;
102
+ color: var(--text-muted);
103
+ line-height: 1.4;
104
+ text-align: left;
105
+ }
106
+ .hint-box code {
107
+ font-family: var(--font-mono);
108
+ color: var(--ghost-lavender);
109
+ display: block;
110
+ margin-top: 4px;
111
+ background: rgba(0,0,0,0.3);
112
+ padding: 3px 6px;
113
+ border-radius: 4px;
114
+ }
115
+ </style>
116
+ </head>
117
+ <body>
118
+ <div class="loader-container">
119
+ <div class="radar-pulse">
120
+ <div class="radar-ring"></div>
121
+ <div class="radar-ring"></div>
122
+ <div class="radar-ring"></div>
123
+ <div class="radar-core"></div>
124
+ </div>
125
+ <div class="title">Connecting to figma-mcp</div>
126
+ <div id="status" class="status-text">Scanning local ports (38451-38455)...</div>
127
+ <div class="badge">
128
+ <span>●</span> <span id="port-label">localhost:38451</span>
129
+ </div>
130
+ <div class="hint-box">
131
+ Ensure the local Rust server is running:
132
+ <code>npx figma-rust-mcp</code>
133
+ </div>
134
+ </div>
135
+
136
+ <script>
137
+ const PORTS = [38451, 38452, 38453, 38454, 38455];
138
+ let attempts = 0;
139
+
140
+ async function checkPort(port) {
141
+ const controller = new AbortController();
142
+ const timeoutId = setTimeout(() => controller.abort(), 800);
143
+ try {
144
+ const res = await fetch(`http://localhost:${port}/plugin/version`, {
145
+ signal: controller.signal,
146
+ cache: "no-store"
147
+ });
148
+ clearTimeout(timeoutId);
149
+ if (res.ok) {
150
+ const data = await res.json();
151
+ return { ok: true, data };
152
+ }
153
+ } catch (_) {
154
+ clearTimeout(timeoutId);
155
+ }
156
+ return { ok: false };
157
+ }
158
+
159
+ async function bootstrapFromPort(port) {
160
+ const statusEl = document.getElementById("status");
161
+ if (statusEl) statusEl.textContent = "Loading dynamic runtime...";
162
+
163
+ try {
164
+ // 1. Fetch main thread code
165
+ const codeRes = await fetch(`http://localhost:${port}/plugin/code.js?t=${Date.now()}`);
166
+ if (!codeRes.ok) throw new Error("Failed to load plugin code: " + codeRes.status);
167
+ const codeText = await codeRes.text();
168
+
169
+ // Send to main thread
170
+ parent.postMessage({ pluginMessage: { type: "EVAL_MAIN_CODE", code: codeText } }, "*");
171
+
172
+ // 2. Fetch full UI HTML
173
+ const uiRes = await fetch(`http://localhost:${port}/plugin/ui.html?t=${Date.now()}`);
174
+ if (!uiRes.ok) throw new Error("Failed to load plugin UI: " + uiRes.status);
175
+ const uiHtml = await uiRes.text();
176
+
177
+ // 3. Inject full UI cleanly
178
+ document.open();
179
+ document.write(uiHtml);
180
+ document.close();
181
+ } catch (err) {
182
+ console.error("[figma-mcp stub]", err);
183
+ if (statusEl) statusEl.textContent = "Failed to load: " + err.message + ". Retrying...";
184
+ setTimeout(findAndConnect, 1500);
185
+ }
186
+ }
187
+
188
+ async function findAndConnect() {
189
+ attempts++;
190
+ const statusEl = document.getElementById("status");
191
+ const portEl = document.getElementById("port-label");
192
+
193
+ for (const port of PORTS) {
194
+ if (portEl) portEl.textContent = `localhost:${port}`;
195
+ const result = await checkPort(port);
196
+ if (result.ok) {
197
+ await bootstrapFromPort(port);
198
+ return;
199
+ }
200
+ }
201
+
202
+ if (statusEl) {
203
+ statusEl.textContent = `Waiting for server (attempt #${attempts})...`;
204
+ }
205
+ setTimeout(findAndConnect, 1000);
206
+ }
207
+
208
+ findAndConnect();
209
+ </script>
210
+ </body>
211
+ </html>
@@ -1,51 +1,52 @@
1
- // Build script — concatenate plugin-src/*.js into plugin-runtime/code.js
2
- // No module system used: files are plain JS sharing a single scope when concatenated.
3
- import { readFileSync, writeFileSync } from "fs";
4
-
5
- const { version } = JSON.parse(readFileSync("package.json", "utf8"));
6
- const cargoVersion = readFileSync("Cargo.toml", "utf8").match(/^version\s*=\s*"([^"]+)"/m)?.[1];
7
- if (version !== cargoVersion) throw new Error(`Version mismatch: package.json ${version}, Cargo.toml ${cargoVersion}`);
8
- if (process.env.GITHUB_REF_TYPE === "tag" && process.env.GITHUB_REF_NAME !== `v${version}`) {
9
- throw new Error(`Release tag ${process.env.GITHUB_REF_NAME} does not match build version v${version}`);
10
- }
11
-
12
- const files = [
13
- "plugin-src/utils.js",
14
- "plugin-src/svg-path-helpers.js",
15
- "plugin-src/paint-and-effects.js",
16
- "plugin-src/token-helpers.js",
17
- "plugin-src/read-helpers.js",
18
- "plugin-src/handlers-write.js",
19
- "plugin-src/handlers-library.js",
20
- "plugin-src/handlers-read.js",
21
- "plugin-src/handlers-read-detail.js",
22
- "plugin-src/handlers-tokens.js",
23
- "plugin-src/handlers-write-ops.js",
24
- "plugin-src/main.js",
25
- ];
26
-
27
- const banner = `// figma-mcp — Figma Plugin main thread v${version}
28
- // Handles both WRITE (draw UI) and READ (extract design) operations.
29
- // AUTO-GENERATED by scripts/build-plugin.js — edit plugin-src/*.js instead
30
- `;
31
-
32
- const parts = files.map(f => {
33
- const content = readFileSync(f, "utf8");
34
- return `\n// ── ${f} ──\n${content}`;
35
- });
36
-
37
- const output = (banner + parts.join("\n")).replace(/\{\{PLUGIN_VERSION\}\}/g, version);
38
-
39
- const uiPath = "plugin-runtime/ui.html";
40
- const ui = readFileSync(uiPath, "utf8").replace(/(<span class="brand-ver"[^>]*>)[^<]*(<\/span>)/,
41
- `$1v${version}$2`);
42
- if (!ui.includes(`id="runtime-version">v${version}</span>`)) throw new Error("Missing runtime version badge");
43
- if (process.argv.includes('--check')) {
44
- if (readFileSync("plugin-runtime/code.js", "utf8") !== output || readFileSync(uiPath, "utf8") !== ui) {
45
- throw new Error("Plugin bundle is stale; run npm run build:plugin");
46
- }
47
- } else {
48
- writeFileSync("plugin-runtime/code.js", output);
49
- writeFileSync(uiPath, ui);
50
- }
51
- console.log(`✓ plugin-runtime/code.js written (${output.length} chars, ${output.split("\n").length} lines)`);
1
+ // Build script — concatenate plugin-src/*.js into plugin-runtime/code.js
2
+ // No module system used: files are plain JS sharing a single scope when concatenated.
3
+ import { readFileSync, writeFileSync } from "fs";
4
+
5
+ const { version } = JSON.parse(readFileSync("package.json", "utf8"));
6
+ const cargoVersion = readFileSync("Cargo.toml", "utf8").match(/^version\s*=\s*"([^"]+)"/m)?.[1];
7
+ if (version !== cargoVersion) throw new Error(`Version mismatch: package.json ${version}, Cargo.toml ${cargoVersion}`);
8
+ if (process.env.GITHUB_REF_TYPE === "tag" && process.env.GITHUB_REF_NAME !== `v${version}`) {
9
+ throw new Error(`Release tag ${process.env.GITHUB_REF_NAME} does not match build version v${version}`);
10
+ }
11
+
12
+ const files = [
13
+ "plugin-src/utils.js",
14
+ "plugin-src/svg-path-helpers.js",
15
+ "plugin-src/paint-and-effects.js",
16
+ "plugin-src/token-helpers.js",
17
+ "plugin-src/read-helpers.js",
18
+ "plugin-src/handlers-write.js",
19
+ "plugin-src/handlers-library.js",
20
+ "plugin-src/handlers-read.js",
21
+ "plugin-src/handlers-read-detail.js",
22
+ "plugin-src/handlers-tokens.js",
23
+ "plugin-src/handlers-write-ops.js",
24
+ "plugin-src/task-scope.js",
25
+ "plugin-src/main.js",
26
+ ];
27
+
28
+ const banner = `// figma-mcp — Figma Plugin main thread v${version}
29
+ // Handles both WRITE (draw UI) and READ (extract design) operations.
30
+ // AUTO-GENERATED by scripts/build-plugin.js — edit plugin-src/*.js instead
31
+ `;
32
+
33
+ const parts = files.map(f => {
34
+ const content = readFileSync(f, "utf8");
35
+ return `\n// ── ${f} ──\n${content}`;
36
+ });
37
+
38
+ const output = (banner + parts.join("\n")).replace(/\{\{PLUGIN_VERSION\}\}/g, version);
39
+
40
+ const uiPath = "plugin-runtime/ui.html";
41
+ const ui = readFileSync(uiPath, "utf8").replace(/(<span class="brand-ver"[^>]*>)[^<]*(<\/span>)/,
42
+ `$1v${version}$2`);
43
+ if (!ui.includes(`id="runtime-version">v${version}</span>`)) throw new Error("Missing runtime version badge");
44
+ if (process.argv.includes('--check')) {
45
+ if (readFileSync("plugin-runtime/code.js", "utf8") !== output || readFileSync(uiPath, "utf8") !== ui) {
46
+ throw new Error("Plugin bundle is stale; run npm run build:plugin");
47
+ }
48
+ } else {
49
+ writeFileSync("plugin-runtime/code.js", output);
50
+ writeFileSync(uiPath, ui);
51
+ }
52
+ console.log(`✓ plugin-runtime/code.js written (${output.length} chars, ${output.split("\n").length} lines)`);