@mp3wizard/figma-console-mcp 1.14.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/LICENSE +21 -0
- package/README.md +816 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.js +278 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts +29 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.js +358 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.js +342 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.js +231 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/engine.d.ts +27 -0
- package/dist/apps/design-system-dashboard/scoring/engine.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/engine.js +93 -0
- package/dist/apps/design-system-dashboard/scoring/engine.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.js +309 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.js +350 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/types.d.ts +89 -0
- package/dist/apps/design-system-dashboard/scoring/types.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/types.js +41 -0
- package/dist/apps/design-system-dashboard/scoring/types.js.map +1 -0
- package/dist/apps/design-system-dashboard/server.d.ts +24 -0
- package/dist/apps/design-system-dashboard/server.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/server.js +160 -0
- package/dist/apps/design-system-dashboard/server.js.map +1 -0
- package/dist/apps/token-browser/server.d.ts +26 -0
- package/dist/apps/token-browser/server.d.ts.map +1 -0
- package/dist/apps/token-browser/server.js +137 -0
- package/dist/apps/token-browser/server.js.map +1 -0
- package/dist/browser/base.d.ts +58 -0
- package/dist/browser/base.d.ts.map +1 -0
- package/dist/browser/base.js +6 -0
- package/dist/browser/base.js.map +1 -0
- package/dist/browser/local.d.ts +87 -0
- package/dist/browser/local.d.ts.map +1 -0
- package/dist/browser/local.js +318 -0
- package/dist/browser/local.js.map +1 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +277 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +357 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +341 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +230 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +92 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +308 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +349 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +40 -0
- package/dist/cloudflare/apps/design-system-dashboard/server.js +159 -0
- package/dist/cloudflare/apps/token-browser/server.js +136 -0
- package/dist/cloudflare/browser/base.js +5 -0
- package/dist/cloudflare/browser/cloudflare.js +156 -0
- package/dist/cloudflare/browser-manager.js +157 -0
- package/dist/cloudflare/core/cloud-websocket-connector.js +267 -0
- package/dist/cloudflare/core/cloud-websocket-relay.js +199 -0
- package/dist/cloudflare/core/comment-tools.js +292 -0
- package/dist/cloudflare/core/config.js +161 -0
- package/dist/cloudflare/core/console-monitor.js +427 -0
- package/dist/cloudflare/core/design-code-tools.js +2504 -0
- package/dist/cloudflare/core/design-system-manifest.js +260 -0
- package/dist/cloudflare/core/design-system-tools.js +863 -0
- package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
- package/dist/cloudflare/core/enrichment/index.js +7 -0
- package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
- package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
- package/dist/cloudflare/core/figma-api.js +409 -0
- package/dist/cloudflare/core/figma-connector.js +7 -0
- package/dist/cloudflare/core/figma-desktop-connector.js +1184 -0
- package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
- package/dist/cloudflare/core/figma-style-extractor.js +311 -0
- package/dist/cloudflare/core/figma-tools.js +2947 -0
- package/dist/cloudflare/core/logger.js +53 -0
- package/dist/cloudflare/core/port-discovery.js +282 -0
- package/dist/cloudflare/core/snippet-injector.js +96 -0
- package/dist/cloudflare/core/types/design-code.js +4 -0
- package/dist/cloudflare/core/types/enriched.js +5 -0
- package/dist/cloudflare/core/types/index.js +4 -0
- package/dist/cloudflare/core/websocket-connector.js +256 -0
- package/dist/cloudflare/core/websocket-server.js +646 -0
- package/dist/cloudflare/core/write-tools.js +2091 -0
- package/dist/cloudflare/index.js +2899 -0
- package/dist/cloudflare/test-browser.js +88 -0
- package/dist/core/comment-tools.d.ts +11 -0
- package/dist/core/comment-tools.d.ts.map +1 -0
- package/dist/core/comment-tools.js +293 -0
- package/dist/core/comment-tools.js.map +1 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +162 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/console-monitor.d.ts +82 -0
- package/dist/core/console-monitor.d.ts.map +1 -0
- package/dist/core/console-monitor.js +428 -0
- package/dist/core/console-monitor.js.map +1 -0
- package/dist/core/design-code-tools.d.ts +127 -0
- package/dist/core/design-code-tools.d.ts.map +1 -0
- package/dist/core/design-code-tools.js +2505 -0
- package/dist/core/design-code-tools.js.map +1 -0
- package/dist/core/design-system-manifest.d.ts +272 -0
- package/dist/core/design-system-manifest.d.ts.map +1 -0
- package/dist/core/design-system-manifest.js +261 -0
- package/dist/core/design-system-manifest.js.map +1 -0
- package/dist/core/design-system-tools.d.ts +17 -0
- package/dist/core/design-system-tools.d.ts.map +1 -0
- package/dist/core/design-system-tools.js +864 -0
- package/dist/core/design-system-tools.js.map +1 -0
- package/dist/core/enrichment/enrichment-service.d.ts +52 -0
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
- package/dist/core/enrichment/enrichment-service.js +273 -0
- package/dist/core/enrichment/enrichment-service.js.map +1 -0
- package/dist/core/enrichment/index.d.ts +8 -0
- package/dist/core/enrichment/index.d.ts.map +1 -0
- package/dist/core/enrichment/index.js +8 -0
- package/dist/core/enrichment/index.js.map +1 -0
- package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
- package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
- package/dist/core/enrichment/relationship-mapper.js +352 -0
- package/dist/core/enrichment/relationship-mapper.js.map +1 -0
- package/dist/core/enrichment/style-resolver.d.ts +80 -0
- package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
- package/dist/core/enrichment/style-resolver.js +327 -0
- package/dist/core/enrichment/style-resolver.js.map +1 -0
- package/dist/core/figma-api.d.ts +201 -0
- package/dist/core/figma-api.d.ts.map +1 -0
- package/dist/core/figma-api.js +410 -0
- package/dist/core/figma-api.js.map +1 -0
- package/dist/core/figma-connector.d.ts +48 -0
- package/dist/core/figma-connector.d.ts.map +1 -0
- package/dist/core/figma-connector.js +8 -0
- package/dist/core/figma-connector.js.map +1 -0
- package/dist/core/figma-desktop-connector.d.ts +265 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -0
- package/dist/core/figma-desktop-connector.js +1184 -0
- package/dist/core/figma-desktop-connector.js.map +1 -0
- package/dist/core/figma-reconstruction-spec.d.ts +166 -0
- package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
- package/dist/core/figma-reconstruction-spec.js +403 -0
- package/dist/core/figma-reconstruction-spec.js.map +1 -0
- package/dist/core/figma-style-extractor.d.ts +76 -0
- package/dist/core/figma-style-extractor.d.ts.map +1 -0
- package/dist/core/figma-style-extractor.js +312 -0
- package/dist/core/figma-style-extractor.js.map +1 -0
- package/dist/core/figma-tools.d.ts +23 -0
- package/dist/core/figma-tools.d.ts.map +1 -0
- package/dist/core/figma-tools.js +2948 -0
- package/dist/core/figma-tools.js.map +1 -0
- package/dist/core/logger.d.ts +22 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +54 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/port-discovery.d.ts +110 -0
- package/dist/core/port-discovery.d.ts.map +1 -0
- package/dist/core/port-discovery.js +283 -0
- package/dist/core/port-discovery.js.map +1 -0
- package/dist/core/snippet-injector.d.ts +24 -0
- package/dist/core/snippet-injector.d.ts.map +1 -0
- package/dist/core/snippet-injector.js +97 -0
- package/dist/core/snippet-injector.js.map +1 -0
- package/dist/core/types/design-code.d.ts +262 -0
- package/dist/core/types/design-code.d.ts.map +1 -0
- package/dist/core/types/design-code.js +5 -0
- package/dist/core/types/design-code.js.map +1 -0
- package/dist/core/types/enriched.d.ts +213 -0
- package/dist/core/types/enriched.d.ts.map +1 -0
- package/dist/core/types/enriched.js +6 -0
- package/dist/core/types/enriched.js.map +1 -0
- package/dist/core/types/index.d.ts +112 -0
- package/dist/core/types/index.d.ts.map +1 -0
- package/dist/core/types/index.js +5 -0
- package/dist/core/types/index.js.map +1 -0
- package/dist/core/websocket-connector.d.ts +55 -0
- package/dist/core/websocket-connector.d.ts.map +1 -0
- package/dist/core/websocket-connector.js +257 -0
- package/dist/core/websocket-connector.js.map +1 -0
- package/dist/core/websocket-server.d.ts +191 -0
- package/dist/core/websocket-server.d.ts.map +1 -0
- package/dist/core/websocket-server.js +647 -0
- package/dist/core/websocket-server.js.map +1 -0
- package/dist/core/write-tools.d.ts +7 -0
- package/dist/core/write-tools.d.ts.map +1 -0
- package/dist/core/write-tools.js +2092 -0
- package/dist/core/write-tools.js.map +1 -0
- package/dist/local.d.ts +84 -0
- package/dist/local.d.ts.map +1 -0
- package/dist/local.js +5039 -0
- package/dist/local.js.map +1 -0
- package/figma-desktop-bridge/README.md +313 -0
- package/figma-desktop-bridge/code.js +2818 -0
- package/figma-desktop-bridge/manifest.json +67 -0
- package/figma-desktop-bridge/ui.html +1236 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1236 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<style>
|
|
6
|
+
* {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
14
|
+
font-size: 11px;
|
|
15
|
+
background: var(--figma-color-bg, #2c2c2c);
|
|
16
|
+
color: var(--figma-color-text, rgba(255, 255, 255, 0.9));
|
|
17
|
+
height: 100%;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
padding: 6px 8px;
|
|
22
|
+
user-select: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.bridge-status {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 6px;
|
|
29
|
+
padding: 4px 8px;
|
|
30
|
+
background: var(--figma-color-bg-secondary, #383838);
|
|
31
|
+
border: 1px solid var(--figma-color-border, #4a4a4a);
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.status-indicator {
|
|
37
|
+
width: 8px;
|
|
38
|
+
height: 8px;
|
|
39
|
+
border-radius: 50%;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.status-indicator.loading {
|
|
44
|
+
background: #f5a623;
|
|
45
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.status-indicator.active {
|
|
49
|
+
background: #18a957;
|
|
50
|
+
box-shadow: 0 0 6px rgba(24, 169, 87, 0.6);
|
|
51
|
+
animation: glow 2s ease-in-out infinite;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.status-indicator.error {
|
|
55
|
+
background: #f24822;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@keyframes pulse {
|
|
59
|
+
0%, 100% { opacity: 0.4; }
|
|
60
|
+
50% { opacity: 1; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@keyframes glow {
|
|
64
|
+
0%, 100% { box-shadow: 0 0 4px rgba(24, 169, 87, 0.4); }
|
|
65
|
+
50% { box-shadow: 0 0 8px rgba(24, 169, 87, 0.8); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.status-text {
|
|
69
|
+
font-weight: 500;
|
|
70
|
+
letter-spacing: 0.2px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.status-text .label {
|
|
74
|
+
color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.7));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.status-text .state {
|
|
78
|
+
color: var(--figma-color-text, rgba(255, 255, 255, 0.9));
|
|
79
|
+
margin-left: 4px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Cloud Mode toggle */
|
|
83
|
+
.cloud-section {
|
|
84
|
+
display: none;
|
|
85
|
+
width: 100%;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cloud-section.visible {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
gap: 6px;
|
|
92
|
+
margin-top: 6px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.cloud-toggle {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 3px;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
font-size: 10px;
|
|
101
|
+
color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.5));
|
|
102
|
+
user-select: none;
|
|
103
|
+
margin-top: 2px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.cloud-toggle:hover {
|
|
107
|
+
color: var(--figma-color-text, rgba(255, 255, 255, 0.9));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.cloud-toggle .chevron {
|
|
111
|
+
display: inline-block;
|
|
112
|
+
font-size: 8px;
|
|
113
|
+
transition: transform 0.15s ease;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.cloud-toggle.expanded .chevron {
|
|
117
|
+
transform: rotate(90deg);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.cloud-input-row input {
|
|
121
|
+
width: 100%;
|
|
122
|
+
background: var(--figma-color-bg, #2c2c2c);
|
|
123
|
+
border: 1px solid var(--figma-color-border, #4a4a4a);
|
|
124
|
+
border-radius: 3px;
|
|
125
|
+
color: var(--figma-color-text, rgba(255, 255, 255, 0.9));
|
|
126
|
+
font-family: monospace;
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
padding: 4px 6px;
|
|
129
|
+
text-transform: uppercase;
|
|
130
|
+
letter-spacing: 3px;
|
|
131
|
+
text-align: center;
|
|
132
|
+
box-sizing: border-box;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.cloud-input-row input::placeholder {
|
|
136
|
+
text-transform: none;
|
|
137
|
+
letter-spacing: normal;
|
|
138
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
139
|
+
font-size: 10px;
|
|
140
|
+
color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.3));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.cloud-input-row button {
|
|
144
|
+
width: 100%;
|
|
145
|
+
background: var(--figma-color-bg-brand, #0d99ff);
|
|
146
|
+
color: #fff;
|
|
147
|
+
border: none;
|
|
148
|
+
border-radius: 3px;
|
|
149
|
+
font-size: 10px;
|
|
150
|
+
font-weight: 500;
|
|
151
|
+
padding: 5px 8px;
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.cloud-input-row button:disabled {
|
|
156
|
+
opacity: 0.5;
|
|
157
|
+
cursor: default;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.cloud-status {
|
|
161
|
+
font-size: 9px;
|
|
162
|
+
color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.5));
|
|
163
|
+
text-align: center;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.cloud-status.connected {
|
|
167
|
+
color: #18a957;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.cloud-status.error {
|
|
171
|
+
color: #f24822;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Light theme support */
|
|
175
|
+
@media (prefers-color-scheme: light) {
|
|
176
|
+
body {
|
|
177
|
+
background: #f5f5f5;
|
|
178
|
+
color: #333;
|
|
179
|
+
}
|
|
180
|
+
.bridge-status {
|
|
181
|
+
background: #fff;
|
|
182
|
+
border-color: #e5e5e5;
|
|
183
|
+
}
|
|
184
|
+
.status-text .label {
|
|
185
|
+
color: #666;
|
|
186
|
+
}
|
|
187
|
+
.status-text .state {
|
|
188
|
+
color: #333;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
</style>
|
|
192
|
+
</head>
|
|
193
|
+
<body>
|
|
194
|
+
<div style="display: flex; flex-direction: column; align-items: center; width: 100%;">
|
|
195
|
+
<div class="bridge-status" id="status-container">
|
|
196
|
+
<div class="status-indicator loading" id="status-dot"></div>
|
|
197
|
+
<div class="status-text">
|
|
198
|
+
<span class="label">MCP</span>
|
|
199
|
+
<span class="state" id="status-state">connecting</span>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div class="cloud-toggle" id="cloud-toggle" onclick="toggleCloudSection()">
|
|
204
|
+
<span class="chevron">▶</span> Cloud Mode
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="cloud-section" id="cloud-section">
|
|
208
|
+
<div class="cloud-input-row">
|
|
209
|
+
<input type="text" id="cloud-code" maxlength="6" placeholder="Pairing code" autocomplete="off" />
|
|
210
|
+
</div>
|
|
211
|
+
<div class="cloud-input-row">
|
|
212
|
+
<button id="cloud-btn" onclick="cloudConnect()">Connect</button>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="cloud-status" id="cloud-status"></div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<script>
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// GLOBAL STATE - Data storage for Puppeteer/MCP access
|
|
221
|
+
// ============================================================================
|
|
222
|
+
window.__figmaVariablesData = null;
|
|
223
|
+
window.__figmaVariablesReady = false;
|
|
224
|
+
window.__figmaComponentData = null;
|
|
225
|
+
window.__figmaComponentRequests = new Map();
|
|
226
|
+
window.__figmaPendingRequests = new Map();
|
|
227
|
+
|
|
228
|
+
let requestIdCounter = 0;
|
|
229
|
+
|
|
230
|
+
// UI update helper
|
|
231
|
+
function updateStatus(state, isActive, isError) {
|
|
232
|
+
const dot = document.getElementById('status-dot');
|
|
233
|
+
const stateText = document.getElementById('status-state');
|
|
234
|
+
|
|
235
|
+
dot.className = 'status-indicator ' + (isError ? 'error' : (isActive ? 'active' : 'loading'));
|
|
236
|
+
stateText.textContent = state;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// COMMAND INFRASTRUCTURE - Generic plugin command sender
|
|
241
|
+
// ============================================================================
|
|
242
|
+
window.sendPluginCommand = (type, params, timeoutMs) => {
|
|
243
|
+
timeoutMs = timeoutMs || 15000;
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
const requestId = type.toLowerCase() + '_' + (++requestIdCounter) + '_' + Date.now();
|
|
246
|
+
|
|
247
|
+
const timeoutId = setTimeout(() => {
|
|
248
|
+
if (window.__figmaPendingRequests.has(requestId)) {
|
|
249
|
+
window.__figmaPendingRequests.delete(requestId);
|
|
250
|
+
reject(new Error(type + ' request timed out after ' + timeoutMs + 'ms'));
|
|
251
|
+
}
|
|
252
|
+
}, timeoutMs);
|
|
253
|
+
|
|
254
|
+
window.__figmaPendingRequests.set(requestId, { resolve: resolve, reject: reject, type: type, timeoutId: timeoutId });
|
|
255
|
+
|
|
256
|
+
var message = { type: type, requestId: requestId };
|
|
257
|
+
for (var key in params) {
|
|
258
|
+
if (params.hasOwnProperty(key)) {
|
|
259
|
+
message[key] = params[key];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
parent.postMessage({ pluginMessage: message }, '*');
|
|
264
|
+
console.log('[MCP Bridge] Sent:', type);
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// ============================================================================
|
|
269
|
+
// VARIABLE OPERATIONS
|
|
270
|
+
// ============================================================================
|
|
271
|
+
|
|
272
|
+
window.executeCode = (code, timeout) => {
|
|
273
|
+
return window.sendPluginCommand('EXECUTE_CODE', { code: code, timeout: timeout || 5000 }, (timeout || 5000) + 2000)
|
|
274
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
window.updateVariable = (variableId, modeId, value) => {
|
|
278
|
+
return window.sendPluginCommand('UPDATE_VARIABLE', { variableId: variableId, modeId: modeId, value: value })
|
|
279
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
window.createVariable = (name, collectionId, resolvedType, options) => {
|
|
283
|
+
var params = { name: name, collectionId: collectionId, resolvedType: resolvedType };
|
|
284
|
+
if (options) {
|
|
285
|
+
if (options.valuesByMode) params.valuesByMode = options.valuesByMode;
|
|
286
|
+
if (options.description) params.description = options.description;
|
|
287
|
+
if (options.scopes) params.scopes = options.scopes;
|
|
288
|
+
}
|
|
289
|
+
return window.sendPluginCommand('CREATE_VARIABLE', params)
|
|
290
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
window.createVariableCollection = (name, options) => {
|
|
294
|
+
var params = { name: name };
|
|
295
|
+
if (options) {
|
|
296
|
+
if (options.initialModeName) params.initialModeName = options.initialModeName;
|
|
297
|
+
if (options.additionalModes) params.additionalModes = options.additionalModes;
|
|
298
|
+
}
|
|
299
|
+
return window.sendPluginCommand('CREATE_VARIABLE_COLLECTION', params)
|
|
300
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
window.deleteVariable = (variableId) => {
|
|
304
|
+
return window.sendPluginCommand('DELETE_VARIABLE', { variableId: variableId })
|
|
305
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
window.deleteVariableCollection = (collectionId) => {
|
|
309
|
+
return window.sendPluginCommand('DELETE_VARIABLE_COLLECTION', { collectionId: collectionId })
|
|
310
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
window.renameVariable = (variableId, newName) => {
|
|
314
|
+
return window.sendPluginCommand('RENAME_VARIABLE', { variableId: variableId, newName: newName })
|
|
315
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
window.setVariableDescription = (variableId, description) => {
|
|
319
|
+
return window.sendPluginCommand('SET_VARIABLE_DESCRIPTION', { variableId: variableId, description: description })
|
|
320
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
window.addMode = (collectionId, modeName) => {
|
|
324
|
+
return window.sendPluginCommand('ADD_MODE', { collectionId: collectionId, modeName: modeName })
|
|
325
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
window.renameMode = (collectionId, modeId, newName) => {
|
|
329
|
+
return window.sendPluginCommand('RENAME_MODE', { collectionId: collectionId, modeId: modeId, newName: newName })
|
|
330
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
window.refreshVariables = () => {
|
|
334
|
+
return window.sendPluginCommand('REFRESH_VARIABLES', {}, 300000)
|
|
335
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// COMPONENT OPERATIONS
|
|
340
|
+
// ============================================================================
|
|
341
|
+
|
|
342
|
+
window.getLocalComponents = () => {
|
|
343
|
+
return window.sendPluginCommand('GET_LOCAL_COMPONENTS', {}, 300000)
|
|
344
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
window.instantiateComponent = (componentKey, options) => {
|
|
348
|
+
var params = { componentKey: componentKey };
|
|
349
|
+
if (options) {
|
|
350
|
+
if (options.nodeId) params.nodeId = options.nodeId;
|
|
351
|
+
if (options.position) params.position = options.position;
|
|
352
|
+
if (options.size) params.size = options.size;
|
|
353
|
+
if (options.overrides) params.overrides = options.overrides;
|
|
354
|
+
if (options.variant) params.variant = options.variant;
|
|
355
|
+
if (options.parentId) params.parentId = options.parentId;
|
|
356
|
+
}
|
|
357
|
+
return window.sendPluginCommand('INSTANTIATE_COMPONENT', params)
|
|
358
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
window.requestComponentData = (nodeId) => {
|
|
362
|
+
return new Promise((resolve, reject) => {
|
|
363
|
+
const requestId = 'component_' + (++requestIdCounter) + '_' + Date.now();
|
|
364
|
+
window.__figmaComponentRequests.set(requestId, { resolve: resolve, reject: reject });
|
|
365
|
+
parent.postMessage({ pluginMessage: { type: 'GET_COMPONENT', requestId: requestId, nodeId: nodeId } }, '*');
|
|
366
|
+
setTimeout(() => {
|
|
367
|
+
if (window.__figmaComponentRequests.has(requestId)) {
|
|
368
|
+
window.__figmaComponentRequests.delete(requestId);
|
|
369
|
+
reject(new Error('Component request timed out'));
|
|
370
|
+
}
|
|
371
|
+
}, 10000);
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// ============================================================================
|
|
376
|
+
// NEW: COMPONENT PROPERTY MANAGEMENT
|
|
377
|
+
// ============================================================================
|
|
378
|
+
|
|
379
|
+
// Set component/node description
|
|
380
|
+
window.setNodeDescription = (nodeId, description, descriptionMarkdown) => {
|
|
381
|
+
return window.sendPluginCommand('SET_NODE_DESCRIPTION', {
|
|
382
|
+
nodeId: nodeId,
|
|
383
|
+
description: description,
|
|
384
|
+
descriptionMarkdown: descriptionMarkdown
|
|
385
|
+
}).catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Add a component property (BOOLEAN, TEXT, INSTANCE_SWAP, VARIANT)
|
|
389
|
+
// Note: We use 'propertyType' instead of 'type' to avoid collision with message type field
|
|
390
|
+
window.addComponentProperty = (nodeId, propertyName, type, defaultValue, options) => {
|
|
391
|
+
var params = { nodeId: nodeId, propertyName: propertyName, propertyType: type, defaultValue: defaultValue };
|
|
392
|
+
if (options) {
|
|
393
|
+
if (options.preferredValues) params.preferredValues = options.preferredValues;
|
|
394
|
+
}
|
|
395
|
+
return window.sendPluginCommand('ADD_COMPONENT_PROPERTY', params)
|
|
396
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Edit an existing component property
|
|
400
|
+
window.editComponentProperty = (nodeId, propertyName, newValue) => {
|
|
401
|
+
return window.sendPluginCommand('EDIT_COMPONENT_PROPERTY', {
|
|
402
|
+
nodeId: nodeId,
|
|
403
|
+
propertyName: propertyName,
|
|
404
|
+
newValue: newValue
|
|
405
|
+
}).catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Delete a component property
|
|
409
|
+
window.deleteComponentProperty = (nodeId, propertyName) => {
|
|
410
|
+
return window.sendPluginCommand('DELETE_COMPONENT_PROPERTY', {
|
|
411
|
+
nodeId: nodeId,
|
|
412
|
+
propertyName: propertyName
|
|
413
|
+
}).catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// ============================================================================
|
|
417
|
+
// NEW: NODE MANIPULATION
|
|
418
|
+
// ============================================================================
|
|
419
|
+
|
|
420
|
+
// Resize any node
|
|
421
|
+
window.resizeNode = (nodeId, width, height, withConstraints) => {
|
|
422
|
+
return window.sendPluginCommand('RESIZE_NODE', {
|
|
423
|
+
nodeId: nodeId,
|
|
424
|
+
width: width,
|
|
425
|
+
height: height,
|
|
426
|
+
withConstraints: withConstraints !== false
|
|
427
|
+
}).catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// Move/position a node
|
|
431
|
+
window.moveNode = (nodeId, x, y) => {
|
|
432
|
+
return window.sendPluginCommand('MOVE_NODE', { nodeId: nodeId, x: x, y: y })
|
|
433
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// Set node fills (colors)
|
|
437
|
+
window.setNodeFills = (nodeId, fills) => {
|
|
438
|
+
return window.sendPluginCommand('SET_NODE_FILLS', { nodeId: nodeId, fills: fills })
|
|
439
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Set node strokes
|
|
443
|
+
window.setNodeStrokes = (nodeId, strokes, strokeWeight) => {
|
|
444
|
+
var params = { nodeId: nodeId, strokes: strokes };
|
|
445
|
+
if (strokeWeight !== undefined) params.strokeWeight = strokeWeight;
|
|
446
|
+
return window.sendPluginCommand('SET_NODE_STROKES', params)
|
|
447
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// Set node opacity
|
|
451
|
+
window.setNodeOpacity = (nodeId, opacity) => {
|
|
452
|
+
return window.sendPluginCommand('SET_NODE_OPACITY', { nodeId: nodeId, opacity: opacity })
|
|
453
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Set node corner radius
|
|
457
|
+
window.setNodeCornerRadius = (nodeId, radius) => {
|
|
458
|
+
return window.sendPluginCommand('SET_NODE_CORNER_RADIUS', { nodeId: nodeId, radius: radius })
|
|
459
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// Clone a node
|
|
463
|
+
window.cloneNode = (nodeId) => {
|
|
464
|
+
return window.sendPluginCommand('CLONE_NODE', { nodeId: nodeId })
|
|
465
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Delete a node
|
|
469
|
+
window.deleteNode = (nodeId) => {
|
|
470
|
+
return window.sendPluginCommand('DELETE_NODE', { nodeId: nodeId })
|
|
471
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Rename a node
|
|
475
|
+
window.renameNode = (nodeId, newName) => {
|
|
476
|
+
return window.sendPluginCommand('RENAME_NODE', { nodeId: nodeId, newName: newName })
|
|
477
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// Set text content (for text nodes)
|
|
481
|
+
window.setTextContent = (nodeId, text, options) => {
|
|
482
|
+
var params = { nodeId: nodeId, text: text };
|
|
483
|
+
if (options) {
|
|
484
|
+
if (options.fontSize) params.fontSize = options.fontSize;
|
|
485
|
+
if (options.fontWeight) params.fontWeight = options.fontWeight;
|
|
486
|
+
if (options.fontFamily) params.fontFamily = options.fontFamily;
|
|
487
|
+
}
|
|
488
|
+
return window.sendPluginCommand('SET_TEXT_CONTENT', params)
|
|
489
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// Create a new node as child
|
|
493
|
+
window.createChildNode = (parentId, nodeType, properties) => {
|
|
494
|
+
return window.sendPluginCommand('CREATE_CHILD_NODE', {
|
|
495
|
+
parentId: parentId,
|
|
496
|
+
nodeType: nodeType,
|
|
497
|
+
properties: properties || {}
|
|
498
|
+
}).catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// ============================================================================
|
|
502
|
+
// NEW: SCREENSHOT & INSTANCE PROPERTIES (Fix for visual validation loop)
|
|
503
|
+
// ============================================================================
|
|
504
|
+
|
|
505
|
+
// Capture screenshot using plugin's exportAsync (reads current plugin state, not cloud)
|
|
506
|
+
// This solves the race condition where REST API screenshots show stale state
|
|
507
|
+
window.captureScreenshot = (nodeId, options) => {
|
|
508
|
+
var params = { nodeId: nodeId };
|
|
509
|
+
if (options) {
|
|
510
|
+
if (options.format) params.format = options.format; // PNG, JPG, SVG
|
|
511
|
+
if (options.scale) params.scale = options.scale; // 1, 2, 4, etc.
|
|
512
|
+
}
|
|
513
|
+
return window.sendPluginCommand('CAPTURE_SCREENSHOT', params, 30000)
|
|
514
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// Set image fill on nodes — decodes base64 in browser context (atob available here)
|
|
518
|
+
// then sends raw bytes to plugin where figma.createImage() is called
|
|
519
|
+
window.setImageFill = (nodeIds, imageData, scaleMode) => {
|
|
520
|
+
// Decode base64 to Uint8Array in browser context where atob() is available
|
|
521
|
+
var binaryStr = atob(imageData);
|
|
522
|
+
var bytes = new Uint8Array(binaryStr.length);
|
|
523
|
+
for (var i = 0; i < binaryStr.length; i++) {
|
|
524
|
+
bytes[i] = binaryStr.charCodeAt(i);
|
|
525
|
+
}
|
|
526
|
+
// Send as plain Array (postMessage can't always transfer typed arrays cleanly)
|
|
527
|
+
return window.sendPluginCommand('SET_IMAGE_FILL', {
|
|
528
|
+
nodeIds: Array.isArray(nodeIds) ? nodeIds : [nodeIds],
|
|
529
|
+
imageBytes: Array.from(bytes),
|
|
530
|
+
scaleMode: scaleMode || 'FILL'
|
|
531
|
+
}, 60000)
|
|
532
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// Set component instance properties (TEXT, BOOLEAN, INSTANCE_SWAP, VARIANT)
|
|
536
|
+
// This is the correct way to update component instances vs direct text node editing
|
|
537
|
+
window.setInstanceProperties = (nodeId, properties) => {
|
|
538
|
+
return window.sendPluginCommand('SET_INSTANCE_PROPERTIES', {
|
|
539
|
+
nodeId: nodeId,
|
|
540
|
+
properties: properties
|
|
541
|
+
}).catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Lint design for accessibility and quality issues
|
|
545
|
+
window.lintDesign = (nodeId, rules, maxDepth, maxFindings) => {
|
|
546
|
+
var params = {};
|
|
547
|
+
if (nodeId) params.nodeId = nodeId;
|
|
548
|
+
if (rules) params.rules = rules;
|
|
549
|
+
if (maxDepth !== undefined) params.maxDepth = maxDepth;
|
|
550
|
+
if (maxFindings !== undefined) params.maxFindings = maxFindings;
|
|
551
|
+
return window.sendPluginCommand('LINT_DESIGN', params, 120000)
|
|
552
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// ============================================================================
|
|
556
|
+
// WEBSOCKET BRIDGE CLIENT - Fallback transport when CDP is unavailable
|
|
557
|
+
// ============================================================================
|
|
558
|
+
(function() {
|
|
559
|
+
// Port range for multi-instance support (matches server's port-discovery.ts)
|
|
560
|
+
var WS_PORT_RANGE_START = 9223;
|
|
561
|
+
var WS_PORT_RANGE_END = 9232;
|
|
562
|
+
|
|
563
|
+
// Multi-connection state: plugin connects to ALL active MCP servers
|
|
564
|
+
// so that every Claude tab/CLI instance gets Figma access.
|
|
565
|
+
var activeConnections = []; // Array of { port, ws }
|
|
566
|
+
var wsReconnectDelay = 500;
|
|
567
|
+
var wsMaxReconnectDelay = 5000;
|
|
568
|
+
var wsReconnectAttempts = 0;
|
|
569
|
+
var wsMaxReconnectAttempts = 50;
|
|
570
|
+
var isScanning = false;
|
|
571
|
+
|
|
572
|
+
// Backward-compat: ws and wsConnected reflect "at least one connection"
|
|
573
|
+
var ws = null;
|
|
574
|
+
var wsPort = null;
|
|
575
|
+
var wsConnected = false;
|
|
576
|
+
|
|
577
|
+
// Method-to-function mapping
|
|
578
|
+
var methodMap = {
|
|
579
|
+
'EXECUTE_CODE': function(params) { return window.executeCode(params.code, params.timeout); },
|
|
580
|
+
'UPDATE_VARIABLE': function(params) { return window.updateVariable(params.variableId, params.modeId, params.value); },
|
|
581
|
+
'CREATE_VARIABLE': function(params) { return window.createVariable(params.name, params.collectionId, params.resolvedType, params); },
|
|
582
|
+
'DELETE_VARIABLE': function(params) { return window.deleteVariable(params.variableId); },
|
|
583
|
+
'DELETE_VARIABLE_COLLECTION': function(params) { return window.deleteVariableCollection(params.collectionId); },
|
|
584
|
+
'RENAME_VARIABLE': function(params) { return window.renameVariable(params.variableId, params.newName); },
|
|
585
|
+
'SET_VARIABLE_DESCRIPTION': function(params) { return window.setVariableDescription(params.variableId, params.description); },
|
|
586
|
+
'ADD_MODE': function(params) { return window.addMode(params.collectionId, params.modeName); },
|
|
587
|
+
'RENAME_MODE': function(params) { return window.renameMode(params.collectionId, params.modeId, params.newName); },
|
|
588
|
+
'REFRESH_VARIABLES': function() { return window.refreshVariables(); },
|
|
589
|
+
'CREATE_VARIABLE_COLLECTION': function(params) { return window.createVariableCollection(params.name, params); },
|
|
590
|
+
'GET_LOCAL_COMPONENTS': function() { return window.getLocalComponents(); },
|
|
591
|
+
'INSTANTIATE_COMPONENT': function(params) { return window.instantiateComponent(params.componentKey, params); },
|
|
592
|
+
'GET_COMPONENT': function(params) { return window.requestComponentData(params.nodeId); },
|
|
593
|
+
'SET_NODE_DESCRIPTION': function(params) { return window.setNodeDescription(params.nodeId, params.description, params.descriptionMarkdown); },
|
|
594
|
+
'ADD_COMPONENT_PROPERTY': function(params) { return window.addComponentProperty(params.nodeId, params.propertyName, params.propertyType, params.defaultValue, params); },
|
|
595
|
+
'EDIT_COMPONENT_PROPERTY': function(params) { return window.editComponentProperty(params.nodeId, params.propertyName, params.newValue); },
|
|
596
|
+
'DELETE_COMPONENT_PROPERTY': function(params) { return window.deleteComponentProperty(params.nodeId, params.propertyName); },
|
|
597
|
+
'RESIZE_NODE': function(params) { return window.resizeNode(params.nodeId, params.width, params.height, params.withConstraints); },
|
|
598
|
+
'MOVE_NODE': function(params) { return window.moveNode(params.nodeId, params.x, params.y); },
|
|
599
|
+
'SET_NODE_FILLS': function(params) { return window.setNodeFills(params.nodeId, params.fills); },
|
|
600
|
+
'SET_NODE_STROKES': function(params) { return window.setNodeStrokes(params.nodeId, params.strokes, params.strokeWeight); },
|
|
601
|
+
'SET_NODE_OPACITY': function(params) { return window.setNodeOpacity(params.nodeId, params.opacity); },
|
|
602
|
+
'SET_NODE_CORNER_RADIUS': function(params) { return window.setNodeCornerRadius(params.nodeId, params.radius); },
|
|
603
|
+
'CLONE_NODE': function(params) { return window.cloneNode(params.nodeId); },
|
|
604
|
+
'DELETE_NODE': function(params) { return window.deleteNode(params.nodeId); },
|
|
605
|
+
'RENAME_NODE': function(params) { return window.renameNode(params.nodeId, params.newName); },
|
|
606
|
+
'SET_TEXT_CONTENT': function(params) { return window.setTextContent(params.nodeId, params.text, params); },
|
|
607
|
+
'CREATE_CHILD_NODE': function(params) { return window.createChildNode(params.parentId, params.nodeType, params.properties); },
|
|
608
|
+
'CAPTURE_SCREENSHOT': function(params) { return window.captureScreenshot(params.nodeId, params); },
|
|
609
|
+
'SET_IMAGE_FILL': function(params) { return window.setImageFill(params.nodeIds || params.nodeId, params.imageData, params.scaleMode); },
|
|
610
|
+
'SET_INSTANCE_PROPERTIES': function(params) { return window.setInstanceProperties(params.nodeId, params.properties); },
|
|
611
|
+
'LINT_DESIGN': function(params) { return window.lintDesign(params.nodeId, params.rules, params.maxDepth, params.maxFindings); },
|
|
612
|
+
'GET_VARIABLES_DATA': function() {
|
|
613
|
+
// Return the cached variables data directly
|
|
614
|
+
if (window.__figmaVariablesReady && window.__figmaVariablesData) {
|
|
615
|
+
return Promise.resolve(window.__figmaVariablesData);
|
|
616
|
+
}
|
|
617
|
+
return Promise.reject(new Error('Variables data not ready. Make sure the Desktop Bridge plugin has loaded.'));
|
|
618
|
+
},
|
|
619
|
+
'GET_FILE_INFO': function() {
|
|
620
|
+
return window.sendPluginCommand('GET_FILE_INFO', {});
|
|
621
|
+
},
|
|
622
|
+
'CLEAR_CONSOLE': function() {
|
|
623
|
+
// Console buffer is maintained server-side; this is a no-op ack
|
|
624
|
+
return Promise.resolve({ cleared: true });
|
|
625
|
+
},
|
|
626
|
+
'RELOAD_UI': function() {
|
|
627
|
+
return window.sendPluginCommand('RELOAD_UI', {});
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Check if we already have a connection to a specific port.
|
|
633
|
+
*/
|
|
634
|
+
function isPortConnected(port) {
|
|
635
|
+
for (var i = 0; i < activeConnections.length; i++) {
|
|
636
|
+
if (activeConnections[i].port === port && activeConnections[i].ws.readyState === 1) {
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Remove a connection from the active list and update compat state.
|
|
645
|
+
*/
|
|
646
|
+
function removeConnection(port) {
|
|
647
|
+
activeConnections = activeConnections.filter(function(c) { return c.port !== port; });
|
|
648
|
+
updateCompatState();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Update backward-compat variables (ws, wsPort, wsConnected).
|
|
653
|
+
*/
|
|
654
|
+
function updateCompatState() {
|
|
655
|
+
var live = activeConnections.filter(function(c) { return c.ws.readyState === 1; });
|
|
656
|
+
wsConnected = live.length > 0;
|
|
657
|
+
if (live.length > 0) {
|
|
658
|
+
ws = live[0].ws;
|
|
659
|
+
wsPort = live[0].port;
|
|
660
|
+
} else {
|
|
661
|
+
ws = null;
|
|
662
|
+
wsPort = null;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Initialize a new connection to a server: send FILE_INFO, variables, attach handlers.
|
|
668
|
+
*/
|
|
669
|
+
function initializeConnection(connWs, port) {
|
|
670
|
+
// Forward cached variables if available
|
|
671
|
+
if (window.__figmaVariablesReady && window.__figmaVariablesData) {
|
|
672
|
+
connWs.send(JSON.stringify({
|
|
673
|
+
type: 'VARIABLES_DATA',
|
|
674
|
+
data: window.__figmaVariablesData
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Proactively report file identity to this server
|
|
679
|
+
window.sendPluginCommand('GET_FILE_INFO', {})
|
|
680
|
+
.then(function(info) {
|
|
681
|
+
if (connWs.readyState === 1 && info && info.success !== false) {
|
|
682
|
+
connWs.send(JSON.stringify({ type: 'FILE_INFO', data: info.fileInfo || info }));
|
|
683
|
+
}
|
|
684
|
+
})
|
|
685
|
+
.catch(function() { /* non-critical */ });
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Scan the port range and connect to ALL active MCP servers.
|
|
690
|
+
* Each server (e.g., Chat tab on 9223, Code tab on 9224) gets its own
|
|
691
|
+
* independent WebSocket connection so every Claude instance has Figma access.
|
|
692
|
+
* Falls back to retry with backoff if no servers found at all.
|
|
693
|
+
*/
|
|
694
|
+
// Maximum scan attempts on initial load (prevents infinite error loops).
|
|
695
|
+
// After connecting, disconnect-triggered retries have their own limit.
|
|
696
|
+
var initialScanAttempts = 0;
|
|
697
|
+
var MAX_INITIAL_SCANS = 3;
|
|
698
|
+
|
|
699
|
+
function wsScanAndConnect() {
|
|
700
|
+
if (isScanning) return;
|
|
701
|
+
isScanning = true;
|
|
702
|
+
|
|
703
|
+
var portsToTry = [];
|
|
704
|
+
for (var p = WS_PORT_RANGE_START; p <= WS_PORT_RANGE_END; p++) {
|
|
705
|
+
if (!isPortConnected(p)) portsToTry.push(p);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (portsToTry.length === 0) { isScanning = false; return; }
|
|
709
|
+
|
|
710
|
+
console.log('[MCP Bridge] Scanning ports ' + WS_PORT_RANGE_START + '-' + WS_PORT_RANGE_END + ' for MCP servers...');
|
|
711
|
+
|
|
712
|
+
var foundAny = false;
|
|
713
|
+
var pending = portsToTry.length;
|
|
714
|
+
|
|
715
|
+
portsToTry.forEach(function(port) {
|
|
716
|
+
try {
|
|
717
|
+
var testWs = new WebSocket('ws://localhost:' + port);
|
|
718
|
+
|
|
719
|
+
var timeout = setTimeout(function() {
|
|
720
|
+
if (testWs.readyState !== 1) testWs.close();
|
|
721
|
+
}, 3000);
|
|
722
|
+
|
|
723
|
+
testWs.onopen = function() {
|
|
724
|
+
clearTimeout(timeout);
|
|
725
|
+
foundAny = true;
|
|
726
|
+
activeConnections.push({ port: port, ws: testWs });
|
|
727
|
+
updateCompatState();
|
|
728
|
+
console.log('[MCP Bridge] WebSocket connected to port ' + port + ' (' + activeConnections.length + ' server(s) total)');
|
|
729
|
+
attachWsHandlers(testWs, port);
|
|
730
|
+
initializeConnection(testWs, port);
|
|
731
|
+
pending--;
|
|
732
|
+
if (pending <= 0) {
|
|
733
|
+
isScanning = false;
|
|
734
|
+
wsReconnectDelay = 500;
|
|
735
|
+
wsReconnectAttempts = 0;
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
testWs.onerror = function() {
|
|
740
|
+
clearTimeout(timeout);
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
testWs.onclose = function() {
|
|
744
|
+
clearTimeout(timeout);
|
|
745
|
+
pending--;
|
|
746
|
+
if (pending <= 0) {
|
|
747
|
+
isScanning = false;
|
|
748
|
+
// Retry with backoff if no servers found, up to MAX_INITIAL_SCANS
|
|
749
|
+
if (!foundAny && activeConnections.length === 0) {
|
|
750
|
+
initialScanAttempts++;
|
|
751
|
+
if (initialScanAttempts < MAX_INITIAL_SCANS) {
|
|
752
|
+
var delay = 3000 * initialScanAttempts; // 3s, 6s
|
|
753
|
+
console.log('[MCP Bridge] No servers found, retry ' + initialScanAttempts + '/' + MAX_INITIAL_SCANS + ' in ' + (delay/1000) + 's');
|
|
754
|
+
setTimeout(wsScanAndConnect, delay);
|
|
755
|
+
} else {
|
|
756
|
+
console.log('[MCP Bridge] No MCP servers found after ' + MAX_INITIAL_SCANS + ' scans. Restart plugin to retry.');
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
} catch (e) {
|
|
762
|
+
pending--;
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Reconnect a specific port that disconnected.
|
|
769
|
+
* Tries the same port first (server may have just restarted),
|
|
770
|
+
* then does a full rescan to pick up any new servers.
|
|
771
|
+
*/
|
|
772
|
+
function wsReconnectPort(port) {
|
|
773
|
+
try {
|
|
774
|
+
var testWs = new WebSocket('ws://localhost:' + port);
|
|
775
|
+
var timeout = setTimeout(function() {
|
|
776
|
+
if (testWs.readyState !== 1) testWs.close();
|
|
777
|
+
}, 2000);
|
|
778
|
+
|
|
779
|
+
testWs.onopen = function() {
|
|
780
|
+
clearTimeout(timeout);
|
|
781
|
+
activeConnections.push({ port: port, ws: testWs });
|
|
782
|
+
updateCompatState();
|
|
783
|
+
console.log('[MCP Bridge] Reconnected to port ' + port + ' (' + activeConnections.length + ' server(s) total)');
|
|
784
|
+
attachWsHandlers(testWs, port);
|
|
785
|
+
initializeConnection(testWs, port);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
testWs.onerror = function() {
|
|
789
|
+
clearTimeout(timeout);
|
|
790
|
+
};
|
|
791
|
+
} catch (e) {
|
|
792
|
+
// Port gone — no further scanning to avoid console spam
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Attach message/close/error handlers to an established WebSocket connection.
|
|
798
|
+
*/
|
|
799
|
+
function attachWsHandlers(activeWs, port) {
|
|
800
|
+
activeWs.onmessage = function(event) {
|
|
801
|
+
try {
|
|
802
|
+
var message = JSON.parse(event.data);
|
|
803
|
+
|
|
804
|
+
// Handle server identity messages
|
|
805
|
+
if (message.type === 'SERVER_HELLO' && message.data) {
|
|
806
|
+
console.log('[MCP Bridge] Connected to server on port ' + message.data.port + ' (PID: ' + message.data.pid + ', v' + message.data.serverVersion + ')');
|
|
807
|
+
var conn = activeConnections.find(function(c) { return c.ws === activeWs; });
|
|
808
|
+
if (conn) conn.serverInfo = message.data;
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (!message.id || !message.method) {
|
|
813
|
+
console.log('[MCP Bridge] WS:' + port + ': Ignoring malformed message');
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
var handler = methodMap[message.method];
|
|
818
|
+
if (!handler) {
|
|
819
|
+
activeWs.send(JSON.stringify({ id: message.id, error: 'Unknown method: ' + message.method }));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Call the handler (returns a Promise) and send back the result
|
|
824
|
+
Promise.resolve(handler(message.params || {}))
|
|
825
|
+
.then(function(result) {
|
|
826
|
+
if (activeWs.readyState === 1) {
|
|
827
|
+
activeWs.send(JSON.stringify({ id: message.id, result: result }));
|
|
828
|
+
}
|
|
829
|
+
})
|
|
830
|
+
.catch(function(err) {
|
|
831
|
+
if (activeWs.readyState === 1) {
|
|
832
|
+
activeWs.send(JSON.stringify({ id: message.id, error: err.message || String(err) }));
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
} catch (e) {
|
|
836
|
+
console.error('[MCP Bridge] WS:' + port + ': Failed to process message:', e);
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
activeWs.onclose = function(event) {
|
|
841
|
+
removeConnection(port);
|
|
842
|
+
console.log('[MCP Bridge] WebSocket disconnected from port ' + port + ' (' + activeConnections.length + ' server(s) remaining)');
|
|
843
|
+
|
|
844
|
+
// If replaced by same file reconnecting (e.g., plugin reloaded), stop
|
|
845
|
+
var wasReplaced = (event.code === 1000 && (
|
|
846
|
+
event.reason === 'Replaced by new connection' ||
|
|
847
|
+
event.reason === 'Replaced by same file reconnection'
|
|
848
|
+
));
|
|
849
|
+
if (wasReplaced) {
|
|
850
|
+
console.log('[MCP Bridge] WebSocket:' + port + ': replaced by newer connection, stopping reconnect');
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Retry the specific port with limited attempts (no full rescan)
|
|
855
|
+
wsReconnectAttempts++;
|
|
856
|
+
if (wsReconnectAttempts <= 5) {
|
|
857
|
+
var delay = Math.min(1000 * wsReconnectAttempts, 5000);
|
|
858
|
+
setTimeout(function() { wsReconnectPort(port); }, delay);
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
activeWs.onerror = function() {
|
|
863
|
+
// onclose will fire after this, triggering reconnect
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Broadcast a message to ALL active WebSocket connections.
|
|
869
|
+
* Events like variable changes, selections, document changes need to
|
|
870
|
+
* reach every MCP server instance so they all have current state.
|
|
871
|
+
*/
|
|
872
|
+
function broadcastToAll(message) {
|
|
873
|
+
var json = JSON.stringify(message);
|
|
874
|
+
activeConnections.forEach(function(conn) {
|
|
875
|
+
if (conn.ws.readyState === 1) {
|
|
876
|
+
try { conn.ws.send(json); } catch(e) { /* ignore send errors */ }
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Forward VARIABLES_DATA to all connected MCP servers
|
|
882
|
+
window.__wsForwardVariables = function(data) {
|
|
883
|
+
if (wsConnected) {
|
|
884
|
+
broadcastToAll({ type: 'VARIABLES_DATA', data: data });
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
// Forward DOCUMENT_CHANGE events to all servers for cache invalidation
|
|
889
|
+
window.__wsForwardDocumentChange = function(data) {
|
|
890
|
+
if (wsConnected) {
|
|
891
|
+
broadcastToAll({ type: 'DOCUMENT_CHANGE', data: data });
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// Forward CONSOLE_CAPTURE events to all servers for console monitoring
|
|
896
|
+
window.__wsForwardConsoleCapture = function(data) {
|
|
897
|
+
if (wsConnected) {
|
|
898
|
+
broadcastToAll({ type: 'CONSOLE_CAPTURE', data: data });
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// Forward SELECTION_CHANGE events to all servers for selection tracking
|
|
903
|
+
window.__wsForwardSelectionChange = function(data) {
|
|
904
|
+
if (wsConnected) {
|
|
905
|
+
broadcastToAll({ type: 'SELECTION_CHANGE', data: data });
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// Forward PAGE_CHANGE events to all servers for page tracking
|
|
910
|
+
window.__wsForwardPageChange = function(data) {
|
|
911
|
+
if (wsConnected) {
|
|
912
|
+
broadcastToAll({ type: 'PAGE_CHANGE', data: data });
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
// Expose functions for Cloud Mode to add connections to the same pool
|
|
917
|
+
window.__wsAddCloudConnection = function(cloudWs, label, onDisconnect) {
|
|
918
|
+
activeConnections.push({ port: label, ws: cloudWs });
|
|
919
|
+
updateCompatState();
|
|
920
|
+
attachWsHandlers(cloudWs, label);
|
|
921
|
+
// Chain cloud disconnect callback after attachWsHandlers' onclose
|
|
922
|
+
if (onDisconnect) {
|
|
923
|
+
var origOnClose = cloudWs.onclose;
|
|
924
|
+
cloudWs.onclose = function(event) {
|
|
925
|
+
if (origOnClose) origOnClose.call(cloudWs, event);
|
|
926
|
+
onDisconnect();
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
initializeConnection(cloudWs, label);
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
window.__wsGetActiveConnections = function() { return activeConnections; };
|
|
933
|
+
|
|
934
|
+
// Single scan on load — no periodic rescanning to avoid console spam.
|
|
935
|
+
// If no server found, retries up to MAX_INITIAL_SCANS times then stops.
|
|
936
|
+
// On disconnect, retries the specific port up to 5 times then stops.
|
|
937
|
+
wsScanAndConnect();
|
|
938
|
+
})();
|
|
939
|
+
|
|
940
|
+
// ============================================================================
|
|
941
|
+
// CLOUD MODE — Connect to remote relay via pairing code
|
|
942
|
+
// ============================================================================
|
|
943
|
+
var cloudWs = null;
|
|
944
|
+
var CLOUD_RELAY_HOST = 'wss://figma-console-mcp.southleft.com';
|
|
945
|
+
|
|
946
|
+
function toggleCloudSection() {
|
|
947
|
+
var section = document.getElementById('cloud-section');
|
|
948
|
+
var toggle = document.getElementById('cloud-toggle');
|
|
949
|
+
var isExpanding = !section.classList.contains('visible');
|
|
950
|
+
section.classList.toggle('visible');
|
|
951
|
+
toggle.classList.toggle('expanded');
|
|
952
|
+
|
|
953
|
+
// Resize plugin window to fit content
|
|
954
|
+
var height = isExpanding ? 130 : 50;
|
|
955
|
+
parent.postMessage({ pluginMessage: { type: 'RESIZE_UI', width: 140, height: height } }, '*');
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function resetCloudUI() {
|
|
959
|
+
var statusEl = document.getElementById('cloud-status');
|
|
960
|
+
var btn = document.getElementById('cloud-btn');
|
|
961
|
+
if (statusEl) {
|
|
962
|
+
statusEl.textContent = 'Disconnected';
|
|
963
|
+
statusEl.className = 'cloud-status';
|
|
964
|
+
}
|
|
965
|
+
if (btn) {
|
|
966
|
+
btn.disabled = false;
|
|
967
|
+
btn.textContent = 'Connect';
|
|
968
|
+
btn.onclick = cloudConnect;
|
|
969
|
+
}
|
|
970
|
+
cloudWs = null;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function cloudConnect() {
|
|
974
|
+
var codeInput = document.getElementById('cloud-code');
|
|
975
|
+
var btn = document.getElementById('cloud-btn');
|
|
976
|
+
var statusEl = document.getElementById('cloud-status');
|
|
977
|
+
var code = (codeInput.value || '').trim().toUpperCase();
|
|
978
|
+
|
|
979
|
+
if (!code || code.length < 4) {
|
|
980
|
+
statusEl.textContent = 'Enter pairing code';
|
|
981
|
+
statusEl.className = 'cloud-status error';
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
btn.disabled = true;
|
|
986
|
+
statusEl.textContent = 'Connecting...';
|
|
987
|
+
statusEl.className = 'cloud-status';
|
|
988
|
+
|
|
989
|
+
// Close existing cloud connection if any
|
|
990
|
+
if (cloudWs && cloudWs.readyState <= 1) {
|
|
991
|
+
cloudWs.close();
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
try {
|
|
995
|
+
cloudWs = new WebSocket(CLOUD_RELAY_HOST + '/ws/pair?code=' + code);
|
|
996
|
+
|
|
997
|
+
cloudWs.onopen = function() {
|
|
998
|
+
statusEl.textContent = 'Connected to cloud relay';
|
|
999
|
+
statusEl.className = 'cloud-status connected';
|
|
1000
|
+
btn.disabled = false;
|
|
1001
|
+
btn.textContent = 'Disconnect';
|
|
1002
|
+
btn.onclick = cloudDisconnect;
|
|
1003
|
+
|
|
1004
|
+
// Add to the shared connection pool (uses same handlers as localhost).
|
|
1005
|
+
// Pass resetCloudUI as disconnect callback — attachWsHandlers overwrites
|
|
1006
|
+
// onclose, so this callback ensures cloud UI resets on server-initiated close.
|
|
1007
|
+
if (window.__wsAddCloudConnection) {
|
|
1008
|
+
window.__wsAddCloudConnection(cloudWs, 'cloud', resetCloudUI);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Persist cloud config via code.js clientStorage
|
|
1012
|
+
parent.postMessage({ pluginMessage: {
|
|
1013
|
+
type: 'STORE_CLOUD_CONFIG',
|
|
1014
|
+
code: code
|
|
1015
|
+
}}, '*');
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
cloudWs.onerror = function() {
|
|
1019
|
+
statusEl.textContent = 'Connection failed — check code';
|
|
1020
|
+
statusEl.className = 'cloud-status error';
|
|
1021
|
+
btn.disabled = false;
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// Note: onclose here handles pre-connection close (e.g., bad code).
|
|
1025
|
+
// After onopen, attachWsHandlers overwrites this — resetCloudUI callback
|
|
1026
|
+
// handles post-connection close instead.
|
|
1027
|
+
cloudWs.onclose = function(event) {
|
|
1028
|
+
resetCloudUI();
|
|
1029
|
+
};
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
statusEl.textContent = 'Failed: ' + e.message;
|
|
1032
|
+
statusEl.className = 'cloud-status error';
|
|
1033
|
+
btn.disabled = false;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function cloudDisconnect() {
|
|
1038
|
+
if (cloudWs) {
|
|
1039
|
+
cloudWs.close();
|
|
1040
|
+
}
|
|
1041
|
+
// Reset UI immediately — don't rely on onclose (may be overwritten)
|
|
1042
|
+
resetCloudUI();
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// ============================================================================
|
|
1046
|
+
// MESSAGE HANDLER - Process responses from plugin worker
|
|
1047
|
+
// ============================================================================
|
|
1048
|
+
window.onmessage = (event) => {
|
|
1049
|
+
const msg = event.data.pluginMessage;
|
|
1050
|
+
if (!msg) return;
|
|
1051
|
+
|
|
1052
|
+
// Generic result handler
|
|
1053
|
+
const handleResult = (resultType, dataKey) => {
|
|
1054
|
+
const request = window.__figmaPendingRequests.get(msg.requestId);
|
|
1055
|
+
if (request) {
|
|
1056
|
+
if (request.timeoutId) clearTimeout(request.timeoutId);
|
|
1057
|
+
if (msg.success) {
|
|
1058
|
+
var result = { success: true };
|
|
1059
|
+
if (dataKey && msg[dataKey] !== undefined) result[dataKey] = msg[dataKey];
|
|
1060
|
+
if (msg.data !== undefined) result.data = msg.data;
|
|
1061
|
+
if (msg.oldName !== undefined) result.oldName = msg.oldName;
|
|
1062
|
+
if (msg.instance !== undefined) result.instance = msg.instance;
|
|
1063
|
+
request.resolve(result);
|
|
1064
|
+
} else {
|
|
1065
|
+
request.resolve({ success: false, error: msg.error || 'Unknown error' });
|
|
1066
|
+
}
|
|
1067
|
+
window.__figmaPendingRequests.delete(msg.requestId);
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// Handle message types
|
|
1072
|
+
switch (msg.type) {
|
|
1073
|
+
case 'VARIABLES_DATA':
|
|
1074
|
+
window.__figmaVariablesData = msg.data;
|
|
1075
|
+
window.__figmaVariablesReady = true;
|
|
1076
|
+
updateStatus('ready', true, false);
|
|
1077
|
+
console.log('[MCP Bridge] Active - ' + (msg.data.variables?.length || 0) + ' vars');
|
|
1078
|
+
// Forward to WebSocket client if connected
|
|
1079
|
+
if (window.__wsForwardVariables) window.__wsForwardVariables(msg.data);
|
|
1080
|
+
break;
|
|
1081
|
+
|
|
1082
|
+
case 'COMPONENT_DATA':
|
|
1083
|
+
window.__figmaComponentData = msg.data;
|
|
1084
|
+
var req = window.__figmaComponentRequests.get(msg.requestId);
|
|
1085
|
+
if (req) { req.resolve(msg.data); window.__figmaComponentRequests.delete(msg.requestId); }
|
|
1086
|
+
break;
|
|
1087
|
+
|
|
1088
|
+
case 'COMPONENT_ERROR':
|
|
1089
|
+
var req2 = window.__figmaComponentRequests.get(msg.requestId);
|
|
1090
|
+
if (req2) { req2.reject(new Error(msg.error)); window.__figmaComponentRequests.delete(msg.requestId); }
|
|
1091
|
+
break;
|
|
1092
|
+
|
|
1093
|
+
case 'ERROR':
|
|
1094
|
+
window.__figmaVariablesReady = false;
|
|
1095
|
+
updateStatus('error', false, true);
|
|
1096
|
+
console.error('[MCP Bridge] Error:', msg.error);
|
|
1097
|
+
break;
|
|
1098
|
+
|
|
1099
|
+
// Variable operations
|
|
1100
|
+
case 'EXECUTE_CODE_RESULT':
|
|
1101
|
+
handleResult('EXECUTE_CODE', 'result');
|
|
1102
|
+
break;
|
|
1103
|
+
case 'UPDATE_VARIABLE_RESULT':
|
|
1104
|
+
handleResult('UPDATE_VARIABLE', 'variable');
|
|
1105
|
+
break;
|
|
1106
|
+
case 'CREATE_VARIABLE_RESULT':
|
|
1107
|
+
handleResult('CREATE_VARIABLE', 'variable');
|
|
1108
|
+
break;
|
|
1109
|
+
case 'CREATE_VARIABLE_COLLECTION_RESULT':
|
|
1110
|
+
handleResult('CREATE_VARIABLE_COLLECTION', 'collection');
|
|
1111
|
+
break;
|
|
1112
|
+
case 'DELETE_VARIABLE_RESULT':
|
|
1113
|
+
handleResult('DELETE_VARIABLE', 'deleted');
|
|
1114
|
+
break;
|
|
1115
|
+
case 'DELETE_VARIABLE_COLLECTION_RESULT':
|
|
1116
|
+
handleResult('DELETE_VARIABLE_COLLECTION', 'deleted');
|
|
1117
|
+
break;
|
|
1118
|
+
case 'REFRESH_VARIABLES_RESULT':
|
|
1119
|
+
handleResult('REFRESH_VARIABLES', null);
|
|
1120
|
+
break;
|
|
1121
|
+
case 'RENAME_VARIABLE_RESULT':
|
|
1122
|
+
handleResult('RENAME_VARIABLE', 'variable');
|
|
1123
|
+
break;
|
|
1124
|
+
case 'SET_VARIABLE_DESCRIPTION_RESULT':
|
|
1125
|
+
handleResult('SET_VARIABLE_DESCRIPTION', 'variable');
|
|
1126
|
+
break;
|
|
1127
|
+
case 'ADD_MODE_RESULT':
|
|
1128
|
+
handleResult('ADD_MODE', 'collection');
|
|
1129
|
+
break;
|
|
1130
|
+
case 'RENAME_MODE_RESULT':
|
|
1131
|
+
handleResult('RENAME_MODE', 'collection');
|
|
1132
|
+
break;
|
|
1133
|
+
case 'GET_LOCAL_COMPONENTS_RESULT':
|
|
1134
|
+
handleResult('GET_LOCAL_COMPONENTS', null);
|
|
1135
|
+
break;
|
|
1136
|
+
case 'INSTANTIATE_COMPONENT_RESULT':
|
|
1137
|
+
handleResult('INSTANTIATE_COMPONENT', 'instance');
|
|
1138
|
+
break;
|
|
1139
|
+
|
|
1140
|
+
// NEW: Component property operations
|
|
1141
|
+
case 'SET_NODE_DESCRIPTION_RESULT':
|
|
1142
|
+
handleResult('SET_NODE_DESCRIPTION', 'node');
|
|
1143
|
+
break;
|
|
1144
|
+
case 'ADD_COMPONENT_PROPERTY_RESULT':
|
|
1145
|
+
handleResult('ADD_COMPONENT_PROPERTY', 'propertyName');
|
|
1146
|
+
break;
|
|
1147
|
+
case 'EDIT_COMPONENT_PROPERTY_RESULT':
|
|
1148
|
+
handleResult('EDIT_COMPONENT_PROPERTY', 'propertyName');
|
|
1149
|
+
break;
|
|
1150
|
+
case 'DELETE_COMPONENT_PROPERTY_RESULT':
|
|
1151
|
+
handleResult('DELETE_COMPONENT_PROPERTY', null);
|
|
1152
|
+
break;
|
|
1153
|
+
|
|
1154
|
+
// NEW: Node manipulation operations
|
|
1155
|
+
case 'RESIZE_NODE_RESULT':
|
|
1156
|
+
handleResult('RESIZE_NODE', 'node');
|
|
1157
|
+
break;
|
|
1158
|
+
case 'MOVE_NODE_RESULT':
|
|
1159
|
+
handleResult('MOVE_NODE', 'node');
|
|
1160
|
+
break;
|
|
1161
|
+
case 'SET_NODE_FILLS_RESULT':
|
|
1162
|
+
handleResult('SET_NODE_FILLS', 'node');
|
|
1163
|
+
break;
|
|
1164
|
+
case 'SET_NODE_STROKES_RESULT':
|
|
1165
|
+
handleResult('SET_NODE_STROKES', 'node');
|
|
1166
|
+
break;
|
|
1167
|
+
case 'SET_NODE_OPACITY_RESULT':
|
|
1168
|
+
handleResult('SET_NODE_OPACITY', 'node');
|
|
1169
|
+
break;
|
|
1170
|
+
case 'SET_NODE_CORNER_RADIUS_RESULT':
|
|
1171
|
+
handleResult('SET_NODE_CORNER_RADIUS', 'node');
|
|
1172
|
+
break;
|
|
1173
|
+
case 'CLONE_NODE_RESULT':
|
|
1174
|
+
handleResult('CLONE_NODE', 'node');
|
|
1175
|
+
break;
|
|
1176
|
+
case 'DELETE_NODE_RESULT':
|
|
1177
|
+
handleResult('DELETE_NODE', 'deleted');
|
|
1178
|
+
break;
|
|
1179
|
+
case 'RENAME_NODE_RESULT':
|
|
1180
|
+
handleResult('RENAME_NODE', 'node');
|
|
1181
|
+
break;
|
|
1182
|
+
case 'SET_TEXT_CONTENT_RESULT':
|
|
1183
|
+
handleResult('SET_TEXT_CONTENT', 'node');
|
|
1184
|
+
break;
|
|
1185
|
+
case 'CREATE_CHILD_NODE_RESULT':
|
|
1186
|
+
handleResult('CREATE_CHILD_NODE', 'node');
|
|
1187
|
+
break;
|
|
1188
|
+
|
|
1189
|
+
// NEW: Screenshot and instance properties (visual validation loop fix)
|
|
1190
|
+
case 'CAPTURE_SCREENSHOT_RESULT':
|
|
1191
|
+
handleResult('CAPTURE_SCREENSHOT', 'image');
|
|
1192
|
+
break;
|
|
1193
|
+
case 'SET_IMAGE_FILL_RESULT':
|
|
1194
|
+
handleResult('SET_IMAGE_FILL', 'imageHash');
|
|
1195
|
+
break;
|
|
1196
|
+
case 'SET_INSTANCE_PROPERTIES_RESULT':
|
|
1197
|
+
handleResult('SET_INSTANCE_PROPERTIES', 'instance');
|
|
1198
|
+
break;
|
|
1199
|
+
case 'LINT_DESIGN_RESULT':
|
|
1200
|
+
handleResult('LINT_DESIGN', 'data');
|
|
1201
|
+
break;
|
|
1202
|
+
|
|
1203
|
+
// File info
|
|
1204
|
+
case 'GET_FILE_INFO_RESULT':
|
|
1205
|
+
handleResult('GET_FILE_INFO', 'fileInfo');
|
|
1206
|
+
break;
|
|
1207
|
+
|
|
1208
|
+
// Plugin UI reload
|
|
1209
|
+
case 'RELOAD_UI_RESULT':
|
|
1210
|
+
handleResult('RELOAD_UI', null);
|
|
1211
|
+
break;
|
|
1212
|
+
|
|
1213
|
+
// Document change events (for cache invalidation via WebSocket)
|
|
1214
|
+
case 'DOCUMENT_CHANGE':
|
|
1215
|
+
if (window.__wsForwardDocumentChange) window.__wsForwardDocumentChange(msg.data);
|
|
1216
|
+
break;
|
|
1217
|
+
|
|
1218
|
+
// Console capture events (for console monitoring via WebSocket)
|
|
1219
|
+
case 'CONSOLE_CAPTURE':
|
|
1220
|
+
if (window.__wsForwardConsoleCapture) window.__wsForwardConsoleCapture(msg);
|
|
1221
|
+
break;
|
|
1222
|
+
|
|
1223
|
+
// Selection change events (for selection tracking via WebSocket)
|
|
1224
|
+
case 'SELECTION_CHANGE':
|
|
1225
|
+
if (window.__wsForwardSelectionChange) window.__wsForwardSelectionChange(msg.data);
|
|
1226
|
+
break;
|
|
1227
|
+
|
|
1228
|
+
// Page change events (for page tracking via WebSocket)
|
|
1229
|
+
case 'PAGE_CHANGE':
|
|
1230
|
+
if (window.__wsForwardPageChange) window.__wsForwardPageChange(msg.data);
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
</script>
|
|
1235
|
+
</body>
|
|
1236
|
+
</html>
|