@atezer/figma-mcp-bridge 1.1.1

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -0
  3. package/dist/browser/base.d.ts +50 -0
  4. package/dist/browser/base.d.ts.map +1 -0
  5. package/dist/browser/base.js +6 -0
  6. package/dist/browser/base.js.map +1 -0
  7. package/dist/browser/local.d.ts +81 -0
  8. package/dist/browser/local.d.ts.map +1 -0
  9. package/dist/browser/local.js +283 -0
  10. package/dist/browser/local.js.map +1 -0
  11. package/dist/cloudflare/browser/base.js +5 -0
  12. package/dist/cloudflare/browser/cloudflare.js +156 -0
  13. package/dist/cloudflare/browser-manager.js +157 -0
  14. package/dist/cloudflare/core/audit-log.js +62 -0
  15. package/dist/cloudflare/core/config.js +163 -0
  16. package/dist/cloudflare/core/console-monitor.js +427 -0
  17. package/dist/cloudflare/core/design-system-manifest.js +260 -0
  18. package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
  19. package/dist/cloudflare/core/enrichment/index.js +7 -0
  20. package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
  21. package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
  22. package/dist/cloudflare/core/figma-api.js +273 -0
  23. package/dist/cloudflare/core/figma-desktop-connector.js +1029 -0
  24. package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
  25. package/dist/cloudflare/core/figma-style-extractor.js +311 -0
  26. package/dist/cloudflare/core/figma-tools.js +2883 -0
  27. package/dist/cloudflare/core/logger.js +53 -0
  28. package/dist/cloudflare/core/plugin-bridge-connector.js +154 -0
  29. package/dist/cloudflare/core/plugin-bridge-server.js +174 -0
  30. package/dist/cloudflare/core/snippet-injector.js +96 -0
  31. package/dist/cloudflare/core/types/enriched.js +5 -0
  32. package/dist/cloudflare/core/types/index.js +4 -0
  33. package/dist/cloudflare/index.js +1061 -0
  34. package/dist/cloudflare/test-browser.js +88 -0
  35. package/dist/core/audit-log.d.ts +26 -0
  36. package/dist/core/audit-log.d.ts.map +1 -0
  37. package/dist/core/audit-log.js +63 -0
  38. package/dist/core/audit-log.js.map +1 -0
  39. package/dist/core/config.d.ts +17 -0
  40. package/dist/core/config.d.ts.map +1 -0
  41. package/dist/core/config.js +164 -0
  42. package/dist/core/config.js.map +1 -0
  43. package/dist/core/console-monitor.d.ts +82 -0
  44. package/dist/core/console-monitor.d.ts.map +1 -0
  45. package/dist/core/console-monitor.js +428 -0
  46. package/dist/core/console-monitor.js.map +1 -0
  47. package/dist/core/design-system-manifest.d.ts +272 -0
  48. package/dist/core/design-system-manifest.d.ts.map +1 -0
  49. package/dist/core/design-system-manifest.js +261 -0
  50. package/dist/core/design-system-manifest.js.map +1 -0
  51. package/dist/core/enrichment/enrichment-service.d.ts +52 -0
  52. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
  53. package/dist/core/enrichment/enrichment-service.js +273 -0
  54. package/dist/core/enrichment/enrichment-service.js.map +1 -0
  55. package/dist/core/enrichment/index.d.ts +8 -0
  56. package/dist/core/enrichment/index.d.ts.map +1 -0
  57. package/dist/core/enrichment/index.js +8 -0
  58. package/dist/core/enrichment/index.js.map +1 -0
  59. package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
  60. package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
  61. package/dist/core/enrichment/relationship-mapper.js +352 -0
  62. package/dist/core/enrichment/relationship-mapper.js.map +1 -0
  63. package/dist/core/enrichment/style-resolver.d.ts +80 -0
  64. package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
  65. package/dist/core/enrichment/style-resolver.js +327 -0
  66. package/dist/core/enrichment/style-resolver.js.map +1 -0
  67. package/dist/core/figma-api.d.ts +137 -0
  68. package/dist/core/figma-api.d.ts.map +1 -0
  69. package/dist/core/figma-api.js +274 -0
  70. package/dist/core/figma-api.js.map +1 -0
  71. package/dist/core/figma-desktop-connector.d.ts +238 -0
  72. package/dist/core/figma-desktop-connector.d.ts.map +1 -0
  73. package/dist/core/figma-desktop-connector.js +1030 -0
  74. package/dist/core/figma-desktop-connector.js.map +1 -0
  75. package/dist/core/figma-reconstruction-spec.d.ts +166 -0
  76. package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
  77. package/dist/core/figma-reconstruction-spec.js +403 -0
  78. package/dist/core/figma-reconstruction-spec.js.map +1 -0
  79. package/dist/core/figma-style-extractor.d.ts +76 -0
  80. package/dist/core/figma-style-extractor.d.ts.map +1 -0
  81. package/dist/core/figma-style-extractor.js +312 -0
  82. package/dist/core/figma-style-extractor.js.map +1 -0
  83. package/dist/core/figma-tools.d.ts +21 -0
  84. package/dist/core/figma-tools.d.ts.map +1 -0
  85. package/dist/core/figma-tools.js +2884 -0
  86. package/dist/core/figma-tools.js.map +1 -0
  87. package/dist/core/logger.d.ts +22 -0
  88. package/dist/core/logger.d.ts.map +1 -0
  89. package/dist/core/logger.js +54 -0
  90. package/dist/core/logger.js.map +1 -0
  91. package/dist/core/plugin-bridge-connector.d.ts +133 -0
  92. package/dist/core/plugin-bridge-connector.d.ts.map +1 -0
  93. package/dist/core/plugin-bridge-connector.js +155 -0
  94. package/dist/core/plugin-bridge-connector.js.map +1 -0
  95. package/dist/core/plugin-bridge-server.d.ts +42 -0
  96. package/dist/core/plugin-bridge-server.d.ts.map +1 -0
  97. package/dist/core/plugin-bridge-server.js +175 -0
  98. package/dist/core/plugin-bridge-server.js.map +1 -0
  99. package/dist/core/snippet-injector.d.ts +24 -0
  100. package/dist/core/snippet-injector.d.ts.map +1 -0
  101. package/dist/core/snippet-injector.js +97 -0
  102. package/dist/core/snippet-injector.js.map +1 -0
  103. package/dist/core/types/enriched.d.ts +213 -0
  104. package/dist/core/types/enriched.d.ts.map +1 -0
  105. package/dist/core/types/enriched.js +6 -0
  106. package/dist/core/types/enriched.js.map +1 -0
  107. package/dist/core/types/index.d.ts +116 -0
  108. package/dist/core/types/index.d.ts.map +1 -0
  109. package/dist/core/types/index.js +5 -0
  110. package/dist/core/types/index.js.map +1 -0
  111. package/dist/local-plugin-only.d.ts +13 -0
  112. package/dist/local-plugin-only.d.ts.map +1 -0
  113. package/dist/local-plugin-only.js +567 -0
  114. package/dist/local-plugin-only.js.map +1 -0
  115. package/dist/local.d.ts +73 -0
  116. package/dist/local.d.ts.map +1 -0
  117. package/dist/local.js +2466 -0
  118. package/dist/local.js.map +1 -0
  119. package/f-mcp-plugin/README.md +280 -0
  120. package/f-mcp-plugin/code.js +2222 -0
  121. package/f-mcp-plugin/manifest.json +14 -0
  122. package/f-mcp-plugin/ui.html +877 -0
  123. package/package.json +82 -0
@@ -0,0 +1,877 @@
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
+ .bridge-row {
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: 6px;
86
+ width: 100%;
87
+ }
88
+ .bridge-port {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 6px;
92
+ font-size: 10px;
93
+ }
94
+ .bridge-port label {
95
+ color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.7));
96
+ }
97
+ .bridge-port input {
98
+ width: 56px;
99
+ padding: 2px 4px;
100
+ background: var(--figma-color-bg-tertiary, #1e1e1e);
101
+ border: 1px solid var(--figma-color-border, #4a4a4a);
102
+ border-radius: 3px;
103
+ color: inherit;
104
+ font-size: 11px;
105
+ }
106
+
107
+ /* Light theme support */
108
+ @media (prefers-color-scheme: light) {
109
+ body {
110
+ background: #f5f5f5;
111
+ color: #333;
112
+ }
113
+ .bridge-status {
114
+ background: #fff;
115
+ border-color: #e5e5e5;
116
+ }
117
+ .status-text .label {
118
+ color: #666;
119
+ }
120
+ .status-text .state {
121
+ color: #333;
122
+ }
123
+ }
124
+ </style>
125
+ </head>
126
+ <body>
127
+ <div class="bridge-row">
128
+ <div class="bridge-status" id="status-container" title="When 'no server': run MCP server (e.g. npm run dev:local) or set correct port">
129
+ <div class="status-indicator loading" id="status-dot"></div>
130
+ <div class="status-text">
131
+ <span class="label">MCP</span>
132
+ <span class="state" id="status-state">connecting</span>
133
+ </div>
134
+ </div>
135
+ <div class="bridge-port" title="Port (multi-instance: her kullanici farkli port)">
136
+ <label for="mcp-port">Port</label>
137
+ <input type="number" id="mcp-port" min="5454" max="5470" value="5454" />
138
+ </div>
139
+ </div>
140
+
141
+ <script>
142
+ // ============================================================================
143
+ // GLOBAL STATE - Data storage for Puppeteer/MCP access
144
+ // ============================================================================
145
+ window.__figmaVariablesData = null;
146
+ window.__figmaVariablesReady = false;
147
+ window.__figmaComponentData = null;
148
+ window.__figmaComponentRequests = new Map();
149
+ window.__figmaPendingRequests = new Map();
150
+
151
+ let requestIdCounter = 0;
152
+
153
+ // UI update helper
154
+ function updateStatus(state, isActive, isError) {
155
+ const dot = document.getElementById('status-dot');
156
+ const stateText = document.getElementById('status-state');
157
+
158
+ dot.className = 'status-indicator ' + (isError ? 'error' : (isActive ? 'active' : 'loading'));
159
+ stateText.textContent = state;
160
+ }
161
+
162
+ // ============================================================================
163
+ // COMMAND INFRASTRUCTURE - Generic plugin command sender
164
+ // ============================================================================
165
+ window.sendPluginCommand = (type, params, timeoutMs) => {
166
+ timeoutMs = timeoutMs || 15000;
167
+ return new Promise((resolve, reject) => {
168
+ const requestId = type.toLowerCase() + '_' + (++requestIdCounter) + '_' + Date.now();
169
+
170
+ const timeoutId = setTimeout(() => {
171
+ if (window.__figmaPendingRequests.has(requestId)) {
172
+ window.__figmaPendingRequests.delete(requestId);
173
+ reject(new Error(type + ' request timed out after ' + timeoutMs + 'ms'));
174
+ }
175
+ }, timeoutMs);
176
+
177
+ window.__figmaPendingRequests.set(requestId, { resolve: resolve, reject: reject, type: type, timeoutId: timeoutId });
178
+
179
+ var message = { type: type, requestId: requestId };
180
+ for (var key in params) {
181
+ if (params.hasOwnProperty(key)) {
182
+ message[key] = params[key];
183
+ }
184
+ }
185
+
186
+ parent.postMessage({ pluginMessage: message }, '*');
187
+ console.log('[F-MCP ATezer Bridge] Sent:', type);
188
+ });
189
+ };
190
+
191
+ // ============================================================================
192
+ // VARIABLE OPERATIONS
193
+ // ============================================================================
194
+
195
+ window.executeCode = (code, timeout) => {
196
+ return window.sendPluginCommand('EXECUTE_CODE', { code: code, timeout: timeout || 5000 }, (timeout || 5000) + 2000)
197
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
198
+ };
199
+
200
+ window.updateVariable = (variableId, modeId, value) => {
201
+ return window.sendPluginCommand('UPDATE_VARIABLE', { variableId: variableId, modeId: modeId, value: value })
202
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
203
+ };
204
+
205
+ window.createVariable = (name, collectionId, resolvedType, options) => {
206
+ var params = { name: name, collectionId: collectionId, resolvedType: resolvedType };
207
+ if (options) {
208
+ if (options.valuesByMode) params.valuesByMode = options.valuesByMode;
209
+ if (options.description) params.description = options.description;
210
+ if (options.scopes) params.scopes = options.scopes;
211
+ }
212
+ return window.sendPluginCommand('CREATE_VARIABLE', params)
213
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
214
+ };
215
+
216
+ window.createVariableCollection = (name, options) => {
217
+ var params = { name: name };
218
+ if (options) {
219
+ if (options.initialModeName) params.initialModeName = options.initialModeName;
220
+ if (options.additionalModes) params.additionalModes = options.additionalModes;
221
+ }
222
+ return window.sendPluginCommand('CREATE_VARIABLE_COLLECTION', params)
223
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
224
+ };
225
+
226
+ window.deleteVariable = (variableId) => {
227
+ return window.sendPluginCommand('DELETE_VARIABLE', { variableId: variableId })
228
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
229
+ };
230
+
231
+ window.deleteVariableCollection = (collectionId) => {
232
+ return window.sendPluginCommand('DELETE_VARIABLE_COLLECTION', { collectionId: collectionId })
233
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
234
+ };
235
+
236
+ window.renameVariable = (variableId, newName) => {
237
+ return window.sendPluginCommand('RENAME_VARIABLE', { variableId: variableId, newName: newName })
238
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
239
+ };
240
+
241
+ window.addMode = (collectionId, modeName) => {
242
+ return window.sendPluginCommand('ADD_MODE', { collectionId: collectionId, modeName: modeName })
243
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
244
+ };
245
+
246
+ window.renameMode = (collectionId, modeId, newName) => {
247
+ return window.sendPluginCommand('RENAME_MODE', { collectionId: collectionId, modeId: modeId, newName: newName })
248
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
249
+ };
250
+
251
+ window.refreshVariables = () => {
252
+ return window.sendPluginCommand('REFRESH_VARIABLES', {})
253
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
254
+ };
255
+
256
+ // ============================================================================
257
+ // COMPONENT OPERATIONS
258
+ // ============================================================================
259
+
260
+ window.getLocalComponents = () => {
261
+ return window.sendPluginCommand('GET_LOCAL_COMPONENTS', {}, 300000)
262
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
263
+ };
264
+
265
+ window.instantiateComponent = (componentKey, options) => {
266
+ var params = { componentKey: componentKey };
267
+ if (options) {
268
+ if (options.nodeId) params.nodeId = options.nodeId;
269
+ if (options.position) params.position = options.position;
270
+ if (options.size) params.size = options.size;
271
+ if (options.overrides) params.overrides = options.overrides;
272
+ if (options.variant) params.variant = options.variant;
273
+ if (options.parentId) params.parentId = options.parentId;
274
+ }
275
+ return window.sendPluginCommand('INSTANTIATE_COMPONENT', params)
276
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
277
+ };
278
+
279
+ window.requestComponentData = (nodeId) => {
280
+ return new Promise((resolve, reject) => {
281
+ const requestId = 'component_' + (++requestIdCounter) + '_' + Date.now();
282
+ window.__figmaComponentRequests.set(requestId, { resolve: resolve, reject: reject });
283
+ parent.postMessage({ pluginMessage: { type: 'GET_COMPONENT', requestId: requestId, nodeId: nodeId } }, '*');
284
+ setTimeout(() => {
285
+ if (window.__figmaComponentRequests.has(requestId)) {
286
+ window.__figmaComponentRequests.delete(requestId);
287
+ reject(new Error('Component request timed out'));
288
+ }
289
+ }, 10000);
290
+ });
291
+ };
292
+
293
+ // ============================================================================
294
+ // NEW: COMPONENT PROPERTY MANAGEMENT
295
+ // ============================================================================
296
+
297
+ // Set component/node description
298
+ window.setNodeDescription = (nodeId, description, descriptionMarkdown) => {
299
+ return window.sendPluginCommand('SET_NODE_DESCRIPTION', {
300
+ nodeId: nodeId,
301
+ description: description,
302
+ descriptionMarkdown: descriptionMarkdown
303
+ }).catch(function(err) { return { success: false, error: err.message || String(err) }; });
304
+ };
305
+
306
+ // Add a component property (BOOLEAN, TEXT, INSTANCE_SWAP, VARIANT)
307
+ // Note: We use 'propertyType' instead of 'type' to avoid collision with message type field
308
+ window.addComponentProperty = (nodeId, propertyName, type, defaultValue, options) => {
309
+ var params = { nodeId: nodeId, propertyName: propertyName, propertyType: type, defaultValue: defaultValue };
310
+ if (options) {
311
+ if (options.preferredValues) params.preferredValues = options.preferredValues;
312
+ }
313
+ return window.sendPluginCommand('ADD_COMPONENT_PROPERTY', params)
314
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
315
+ };
316
+
317
+ // Edit an existing component property
318
+ window.editComponentProperty = (nodeId, propertyName, newValue) => {
319
+ return window.sendPluginCommand('EDIT_COMPONENT_PROPERTY', {
320
+ nodeId: nodeId,
321
+ propertyName: propertyName,
322
+ newValue: newValue
323
+ }).catch(function(err) { return { success: false, error: err.message || String(err) }; });
324
+ };
325
+
326
+ // Delete a component property
327
+ window.deleteComponentProperty = (nodeId, propertyName) => {
328
+ return window.sendPluginCommand('DELETE_COMPONENT_PROPERTY', {
329
+ nodeId: nodeId,
330
+ propertyName: propertyName
331
+ }).catch(function(err) { return { success: false, error: err.message || String(err) }; });
332
+ };
333
+
334
+ // ============================================================================
335
+ // NEW: NODE MANIPULATION
336
+ // ============================================================================
337
+
338
+ // Resize any node
339
+ window.resizeNode = (nodeId, width, height, withConstraints) => {
340
+ return window.sendPluginCommand('RESIZE_NODE', {
341
+ nodeId: nodeId,
342
+ width: width,
343
+ height: height,
344
+ withConstraints: withConstraints !== false
345
+ }).catch(function(err) { return { success: false, error: err.message || String(err) }; });
346
+ };
347
+
348
+ // Move/position a node
349
+ window.moveNode = (nodeId, x, y) => {
350
+ return window.sendPluginCommand('MOVE_NODE', { nodeId: nodeId, x: x, y: y })
351
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
352
+ };
353
+
354
+ // Set node fills (colors)
355
+ window.setNodeFills = (nodeId, fills) => {
356
+ return window.sendPluginCommand('SET_NODE_FILLS', { nodeId: nodeId, fills: fills })
357
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
358
+ };
359
+
360
+ // Set node strokes
361
+ window.setNodeStrokes = (nodeId, strokes, strokeWeight) => {
362
+ var params = { nodeId: nodeId, strokes: strokes };
363
+ if (strokeWeight !== undefined) params.strokeWeight = strokeWeight;
364
+ return window.sendPluginCommand('SET_NODE_STROKES', params)
365
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
366
+ };
367
+
368
+ // Set node opacity
369
+ window.setNodeOpacity = (nodeId, opacity) => {
370
+ return window.sendPluginCommand('SET_NODE_OPACITY', { nodeId: nodeId, opacity: opacity })
371
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
372
+ };
373
+
374
+ // Set node corner radius
375
+ window.setNodeCornerRadius = (nodeId, radius) => {
376
+ return window.sendPluginCommand('SET_NODE_CORNER_RADIUS', { nodeId: nodeId, radius: radius })
377
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
378
+ };
379
+
380
+ // Clone a node
381
+ window.cloneNode = (nodeId) => {
382
+ return window.sendPluginCommand('CLONE_NODE', { nodeId: nodeId })
383
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
384
+ };
385
+
386
+ // Delete a node
387
+ window.deleteNode = (nodeId) => {
388
+ return window.sendPluginCommand('DELETE_NODE', { nodeId: nodeId })
389
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
390
+ };
391
+
392
+ // Rename a node
393
+ window.renameNode = (nodeId, newName) => {
394
+ return window.sendPluginCommand('RENAME_NODE', { nodeId: nodeId, newName: newName })
395
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
396
+ };
397
+
398
+ // Set text content (for text nodes)
399
+ window.setTextContent = (nodeId, text, options) => {
400
+ var params = { nodeId: nodeId, text: text };
401
+ if (options) {
402
+ if (options.fontSize) params.fontSize = options.fontSize;
403
+ if (options.fontWeight) params.fontWeight = options.fontWeight;
404
+ if (options.fontFamily) params.fontFamily = options.fontFamily;
405
+ }
406
+ return window.sendPluginCommand('SET_TEXT_CONTENT', params)
407
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
408
+ };
409
+
410
+ // Create a new node as child
411
+ window.createChildNode = (parentId, nodeType, properties) => {
412
+ return window.sendPluginCommand('CREATE_CHILD_NODE', {
413
+ parentId: parentId,
414
+ nodeType: nodeType,
415
+ properties: properties || {}
416
+ }).catch(function(err) { return { success: false, error: err.message || String(err) }; });
417
+ };
418
+
419
+ // ============================================================================
420
+ // NEW: SCREENSHOT & INSTANCE PROPERTIES (Fix for visual validation loop)
421
+ // ============================================================================
422
+
423
+ // Capture screenshot using plugin's exportAsync (reads current plugin state, not cloud)
424
+ // This solves the race condition where REST API screenshots show stale state
425
+ window.captureScreenshot = (nodeId, options) => {
426
+ var params = { nodeId: nodeId };
427
+ if (options) {
428
+ if (options.format) params.format = options.format; // PNG, JPG, SVG
429
+ if (options.scale) params.scale = options.scale; // 1, 2, 4, etc.
430
+ }
431
+ return window.sendPluginCommand('CAPTURE_SCREENSHOT', params, 30000)
432
+ .catch(function(err) { return { success: false, error: err.message || String(err) }; });
433
+ };
434
+
435
+ // Set component instance properties (TEXT, BOOLEAN, INSTANCE_SWAP, VARIANT)
436
+ // This is the correct way to update component instances vs direct text node editing
437
+ window.setInstanceProperties = (nodeId, properties) => {
438
+ return window.sendPluginCommand('SET_INSTANCE_PROPERTIES', {
439
+ nodeId: nodeId,
440
+ properties: properties
441
+ }).catch(function(err) { return { success: false, error: err.message || String(err) }; });
442
+ };
443
+
444
+ // ============================================================================
445
+ // MESSAGE HANDLER - Process responses from plugin worker
446
+ // ============================================================================
447
+ window.onmessage = (event) => {
448
+ const msg = event.data.pluginMessage;
449
+ if (!msg) return;
450
+
451
+ // Generic result handler
452
+ const handleResult = (resultType, dataKey) => {
453
+ const request = window.__figmaPendingRequests.get(msg.requestId);
454
+ if (request) {
455
+ if (request.timeoutId) clearTimeout(request.timeoutId);
456
+ if (msg.success) {
457
+ var result = { success: true };
458
+ if (dataKey && msg[dataKey] !== undefined) result[dataKey] = msg[dataKey];
459
+ if (msg.data !== undefined) result.data = msg.data;
460
+ request.resolve(result);
461
+ } else {
462
+ request.resolve({ success: false, error: msg.error || 'Unknown error' });
463
+ }
464
+ window.__figmaPendingRequests.delete(msg.requestId);
465
+ }
466
+ };
467
+
468
+ // Handle message types
469
+ switch (msg.type) {
470
+ case 'VARIABLES_DATA':
471
+ window.__figmaVariablesData = msg.data;
472
+ window.__figmaVariablesReady = true;
473
+ updateStatus('ready', true, false);
474
+ console.log('[F-MCP ATezer Bridge] Active - ' + (msg.data.variables?.length || 0) + ' vars');
475
+ break;
476
+
477
+ case 'COMPONENT_DATA':
478
+ window.__figmaComponentData = msg.data;
479
+ var req = window.__figmaComponentRequests.get(msg.requestId);
480
+ if (req) { req.resolve(msg.data); window.__figmaComponentRequests.delete(msg.requestId); }
481
+ break;
482
+
483
+ case 'COMPONENT_ERROR':
484
+ var req2 = window.__figmaComponentRequests.get(msg.requestId);
485
+ if (req2) { req2.reject(new Error(msg.error)); window.__figmaComponentRequests.delete(msg.requestId); }
486
+ break;
487
+
488
+ case 'ERROR':
489
+ window.__figmaVariablesReady = false;
490
+ updateStatus('error', false, true);
491
+ console.error('[F-MCP ATezer Bridge] Error:', msg.error);
492
+ break;
493
+
494
+ // Variable operations
495
+ case 'EXECUTE_CODE_RESULT':
496
+ handleResult('EXECUTE_CODE', 'result');
497
+ break;
498
+ case 'UPDATE_VARIABLE_RESULT':
499
+ handleResult('UPDATE_VARIABLE', 'variable');
500
+ break;
501
+ case 'CREATE_VARIABLE_RESULT':
502
+ handleResult('CREATE_VARIABLE', 'variable');
503
+ break;
504
+ case 'CREATE_VARIABLE_COLLECTION_RESULT':
505
+ handleResult('CREATE_VARIABLE_COLLECTION', 'collection');
506
+ break;
507
+ case 'DELETE_VARIABLE_RESULT':
508
+ handleResult('DELETE_VARIABLE', 'deleted');
509
+ break;
510
+ case 'DELETE_VARIABLE_COLLECTION_RESULT':
511
+ handleResult('DELETE_VARIABLE_COLLECTION', 'deleted');
512
+ break;
513
+ case 'REFRESH_VARIABLES_RESULT':
514
+ handleResult('REFRESH_VARIABLES', null);
515
+ break;
516
+ case 'RENAME_VARIABLE_RESULT':
517
+ handleResult('RENAME_VARIABLE', 'variable');
518
+ break;
519
+ case 'ADD_MODE_RESULT':
520
+ handleResult('ADD_MODE', 'collection');
521
+ break;
522
+ case 'RENAME_MODE_RESULT':
523
+ handleResult('RENAME_MODE', 'collection');
524
+ break;
525
+ case 'GET_LOCAL_COMPONENTS_RESULT':
526
+ handleResult('GET_LOCAL_COMPONENTS', null);
527
+ break;
528
+ case 'INSTANTIATE_COMPONENT_RESULT':
529
+ handleResult('INSTANTIATE_COMPONENT', 'instance');
530
+ break;
531
+
532
+ // NEW: Component property operations
533
+ case 'SET_NODE_DESCRIPTION_RESULT':
534
+ handleResult('SET_NODE_DESCRIPTION', 'node');
535
+ break;
536
+ case 'ADD_COMPONENT_PROPERTY_RESULT':
537
+ handleResult('ADD_COMPONENT_PROPERTY', 'propertyName');
538
+ break;
539
+ case 'EDIT_COMPONENT_PROPERTY_RESULT':
540
+ handleResult('EDIT_COMPONENT_PROPERTY', 'propertyName');
541
+ break;
542
+ case 'DELETE_COMPONENT_PROPERTY_RESULT':
543
+ handleResult('DELETE_COMPONENT_PROPERTY', null);
544
+ break;
545
+
546
+ // NEW: Node manipulation operations
547
+ case 'RESIZE_NODE_RESULT':
548
+ handleResult('RESIZE_NODE', 'node');
549
+ break;
550
+ case 'MOVE_NODE_RESULT':
551
+ handleResult('MOVE_NODE', 'node');
552
+ break;
553
+ case 'SET_NODE_FILLS_RESULT':
554
+ handleResult('SET_NODE_FILLS', 'node');
555
+ break;
556
+ case 'SET_NODE_STROKES_RESULT':
557
+ handleResult('SET_NODE_STROKES', 'node');
558
+ break;
559
+ case 'SET_NODE_OPACITY_RESULT':
560
+ handleResult('SET_NODE_OPACITY', 'node');
561
+ break;
562
+ case 'SET_NODE_CORNER_RADIUS_RESULT':
563
+ handleResult('SET_NODE_CORNER_RADIUS', 'node');
564
+ break;
565
+ case 'CLONE_NODE_RESULT':
566
+ handleResult('CLONE_NODE', 'node');
567
+ break;
568
+ case 'DELETE_NODE_RESULT':
569
+ handleResult('DELETE_NODE', 'deleted');
570
+ break;
571
+ case 'RENAME_NODE_RESULT':
572
+ handleResult('RENAME_NODE', 'node');
573
+ break;
574
+ case 'SET_TEXT_CONTENT_RESULT':
575
+ handleResult('SET_TEXT_CONTENT', 'node');
576
+ break;
577
+ case 'CREATE_CHILD_NODE_RESULT':
578
+ handleResult('CREATE_CHILD_NODE', 'node');
579
+ break;
580
+
581
+ // NEW: Screenshot and instance properties (visual validation loop fix)
582
+ case 'CAPTURE_SCREENSHOT_RESULT':
583
+ handleResult('CAPTURE_SCREENSHOT', 'image');
584
+ break;
585
+ case 'SET_INSTANCE_PROPERTIES_RESULT':
586
+ handleResult('SET_INSTANCE_PROPERTIES', 'instance');
587
+ break;
588
+ case 'GET_DOCUMENT_STRUCTURE_RESULT':
589
+ handleResult('GET_DOCUMENT_STRUCTURE', null);
590
+ break;
591
+ case 'GET_LOCAL_STYLES_RESULT':
592
+ handleResult('GET_LOCAL_STYLES', null);
593
+ break;
594
+ case 'CONSOLE_LOGS_RESULT':
595
+ handleResult('GET_CONSOLE_LOGS', 'data');
596
+ break;
597
+ case 'CLEAR_CONSOLE_RESULT':
598
+ handleResult('CLEAR_CONSOLE', null);
599
+ break;
600
+ case 'BATCH_CREATE_VARIABLES_RESULT':
601
+ handleResult('BATCH_CREATE_VARIABLES', 'data');
602
+ break;
603
+ case 'BATCH_UPDATE_VARIABLES_RESULT':
604
+ handleResult('BATCH_UPDATE_VARIABLES', 'data');
605
+ break;
606
+ case 'SETUP_DESIGN_TOKENS_RESULT':
607
+ handleResult('SETUP_DESIGN_TOKENS', 'data');
608
+ break;
609
+ case 'ARRANGE_COMPONENT_SET_RESULT':
610
+ handleResult('ARRANGE_COMPONENT_SET', 'data');
611
+ break;
612
+ }
613
+ };
614
+
615
+ window.getDocumentStructure = (depth, verbosity) => {
616
+ return window.sendPluginCommand('GET_DOCUMENT_STRUCTURE', { depth: depth || 1, verbosity: verbosity || 'summary' }, 30000).then(function(r) {
617
+ if (r && r.success === false) return r;
618
+ return r && r.data !== undefined ? r.data : r;
619
+ });
620
+ };
621
+ window.getLocalStyles = (verbosity) => {
622
+ return window.sendPluginCommand('GET_LOCAL_STYLES', { verbosity: verbosity || 'summary' }, 15000).then(function(r) { return r.data; });
623
+ };
624
+ window.getConsoleLogs = (limit) => {
625
+ return window.sendPluginCommand('GET_CONSOLE_LOGS', { limit: limit || 50 }, 5000).then(function(r) {
626
+ if (r && r.success === false) return r;
627
+ return r && r.data ? r.data : { logs: [], total: 0 };
628
+ });
629
+ };
630
+ window.clearConsole = () => {
631
+ return window.sendPluginCommand('CLEAR_CONSOLE', {}, 3000).then(function(r) { return r || { success: true }; });
632
+ };
633
+ window.batchCreateVariables = (items) => {
634
+ return window.sendPluginCommand('BATCH_CREATE_VARIABLES', { items: items || [] }, 60000).then(function(r) { return r && r.data ? r.data : r; });
635
+ };
636
+ window.batchUpdateVariables = (items) => {
637
+ return window.sendPluginCommand('BATCH_UPDATE_VARIABLES', { items: items || [] }, 60000).then(function(r) { return r && r.data ? r.data : r; });
638
+ };
639
+ window.setupDesignTokens = (payload) => {
640
+ return window.sendPluginCommand('SETUP_DESIGN_TOKENS', {
641
+ collectionName: payload.collectionName,
642
+ modes: payload.modes,
643
+ tokens: payload.tokens
644
+ }, 30000).then(function(r) { return r; });
645
+ };
646
+ window.arrangeComponentSet = (nodeIds) => {
647
+ return window.sendPluginCommand('ARRANGE_COMPONENT_SET', { nodeIds: nodeIds || [] }, 15000).then(function(r) { return r && r.data ? r.data : r; });
648
+ };
649
+
650
+ // ============================================================================
651
+ // MCP PLUGIN BRIDGE - WebSocket client (no Figma debug port needed)
652
+ // Port configurable for multi-instance: her kullanici kendi portunu secer (5454-5470)
653
+ // ============================================================================
654
+ var MCP_BRIDGE_STORAGE_KEY = 'f-mcp-bridge-port';
655
+ var mcpBridgeWs = null;
656
+ var mcpBridgeReconnectTimer = null;
657
+
658
+ function getMcpBridgePort() {
659
+ var el = document.getElementById('mcp-port');
660
+ if (!el) return 5454;
661
+ var n = parseInt(el.value, 10);
662
+ if (isNaN(n) || n < 5454 || n > 5470) return 5454;
663
+ return n;
664
+ }
665
+
666
+ function initMcpPortInput() {
667
+ try {
668
+ var stored = localStorage.getItem(MCP_BRIDGE_STORAGE_KEY);
669
+ if (stored) {
670
+ var n = parseInt(stored, 10);
671
+ if (n >= 5454 && n <= 5470) {
672
+ var el = document.getElementById('mcp-port');
673
+ if (el) { el.value = n; }
674
+ }
675
+ }
676
+ } catch (e) {}
677
+ var el = document.getElementById('mcp-port');
678
+ if (el) {
679
+ el.addEventListener('change', function() {
680
+ var port = getMcpBridgePort();
681
+ try { localStorage.setItem(MCP_BRIDGE_STORAGE_KEY, String(port)); } catch (e) {}
682
+ if (mcpBridgeWs) {
683
+ mcpBridgeWs.close();
684
+ mcpBridgeWs = null;
685
+ }
686
+ connectMcpBridge();
687
+ });
688
+ }
689
+ }
690
+
691
+ function connectMcpBridge() {
692
+ if (mcpBridgeWs && mcpBridgeWs.readyState === 1) return;
693
+ var port = getMcpBridgePort();
694
+ var url = 'ws://localhost:' + port;
695
+ try {
696
+ mcpBridgeWs = new WebSocket(url);
697
+ mcpBridgeWs.onopen = function() {
698
+ console.log('[F-MCP ATezer Bridge] Connected to MCP server on port ' + port);
699
+ updateStatus('ready', true, false);
700
+ mcpBridgeWs.send(JSON.stringify({ type: 'ready' }));
701
+ };
702
+ mcpBridgeWs.onmessage = function(event) {
703
+ try {
704
+ var msg = JSON.parse(event.data);
705
+ if (!msg.id || !msg.method) return;
706
+ var id = msg.id;
707
+ var method = msg.method;
708
+ var params = msg.params || {};
709
+ var run = function() {
710
+ if (method === 'getVariablesFromPluginUI') {
711
+ if (!window.__figmaVariablesReady || !window.__figmaVariablesData)
712
+ return { success: false, error: 'Variables not loaded yet' };
713
+ return { success: true, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
714
+ }
715
+ if (method === 'getComponentFromPluginUI') {
716
+ return window.requestComponentData(params.nodeId).then(function(data) {
717
+ return { success: true, component: data };
718
+ });
719
+ }
720
+ if (method === 'getComponentByNodeId') {
721
+ return window.requestComponentData(params.nodeId).then(function(data) {
722
+ return { success: true, component: data };
723
+ });
724
+ }
725
+ if (method === 'getVariables') {
726
+ if (!window.__figmaVariablesReady || !window.__figmaVariablesData)
727
+ return { success: false, error: 'Variables not loaded yet' };
728
+ return { success: true, timestamp: Date.now(), fileMetadata: {}, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
729
+ }
730
+ if (method === 'executeCodeViaUI') {
731
+ return window.executeCode(params.code, params.timeout);
732
+ }
733
+ if (method === 'updateVariable') {
734
+ return window.updateVariable(params.variableId, params.modeId, params.value);
735
+ }
736
+ if (method === 'createVariable') {
737
+ return window.createVariable(params.name, params.collectionId, params.resolvedType, params.options);
738
+ }
739
+ if (method === 'createVariableCollection') {
740
+ return window.createVariableCollection(params.name, params.options);
741
+ }
742
+ if (method === 'deleteVariable') {
743
+ return window.deleteVariable(params.variableId);
744
+ }
745
+ if (method === 'deleteVariableCollection') {
746
+ return window.deleteVariableCollection(params.collectionId);
747
+ }
748
+ if (method === 'renameVariable') {
749
+ return window.renameVariable(params.variableId, params.newName);
750
+ }
751
+ if (method === 'addMode') {
752
+ return window.addMode(params.collectionId, params.modeName);
753
+ }
754
+ if (method === 'renameMode') {
755
+ return window.renameMode(params.collectionId, params.modeId, params.newName);
756
+ }
757
+ if (method === 'refreshVariables') {
758
+ return window.refreshVariables();
759
+ }
760
+ if (method === 'getLocalComponents') {
761
+ return window.getLocalComponents();
762
+ }
763
+ if (method === 'instantiateComponent') {
764
+ return window.instantiateComponent(params.componentKey, params.options);
765
+ }
766
+ if (method === 'setNodeDescription') {
767
+ return window.setNodeDescription(params.nodeId, params.description, params.descriptionMarkdown);
768
+ }
769
+ if (method === 'addComponentProperty') {
770
+ return window.addComponentProperty(params.nodeId, params.propertyName, params.type, params.defaultValue, params.options);
771
+ }
772
+ if (method === 'editComponentProperty') {
773
+ return window.editComponentProperty(params.nodeId, params.propertyName, params.newValue);
774
+ }
775
+ if (method === 'deleteComponentProperty') {
776
+ return window.deleteComponentProperty(params.nodeId, params.propertyName);
777
+ }
778
+ if (method === 'resizeNode') {
779
+ return window.resizeNode(params.nodeId, params.width, params.height, params.withConstraints);
780
+ }
781
+ if (method === 'moveNode') {
782
+ return window.moveNode(params.nodeId, params.x, params.y);
783
+ }
784
+ if (method === 'setNodeFills') {
785
+ return window.setNodeFills(params.nodeId, params.fills);
786
+ }
787
+ if (method === 'setNodeStrokes') {
788
+ return window.setNodeStrokes(params.nodeId, params.strokes, params.strokeWeight);
789
+ }
790
+ if (method === 'setNodeOpacity') {
791
+ return window.setNodeOpacity(params.nodeId, params.opacity);
792
+ }
793
+ if (method === 'setNodeCornerRadius') {
794
+ return window.setNodeCornerRadius(params.nodeId, params.radius);
795
+ }
796
+ if (method === 'cloneNode') {
797
+ return window.cloneNode(params.nodeId);
798
+ }
799
+ if (method === 'deleteNode') {
800
+ return window.deleteNode(params.nodeId);
801
+ }
802
+ if (method === 'renameNode') {
803
+ return window.renameNode(params.nodeId, params.newName);
804
+ }
805
+ if (method === 'setTextContent') {
806
+ return window.setTextContent(params.nodeId, params.text, params.options);
807
+ }
808
+ if (method === 'createChildNode') {
809
+ return window.createChildNode(params.parentId, params.nodeType, params.properties);
810
+ }
811
+ if (method === 'captureScreenshot') {
812
+ return window.captureScreenshot(params.nodeId, params.options);
813
+ }
814
+ if (method === 'setInstanceProperties') {
815
+ return window.setInstanceProperties(params.nodeId, params.properties);
816
+ }
817
+ if (method === 'getDocumentStructure') {
818
+ return window.getDocumentStructure(params.depth, params.verbosity);
819
+ }
820
+ if (method === 'getLocalStyles') {
821
+ return window.getLocalStyles(params.verbosity);
822
+ }
823
+ if (method === 'getConsoleLogs') {
824
+ return window.getConsoleLogs(params.limit);
825
+ }
826
+ if (method === 'clearConsole') {
827
+ return window.clearConsole();
828
+ }
829
+ if (method === 'batchCreateVariables') {
830
+ return window.batchCreateVariables(params.items);
831
+ }
832
+ if (method === 'batchUpdateVariables') {
833
+ return window.batchUpdateVariables(params.items);
834
+ }
835
+ if (method === 'setupDesignTokens') {
836
+ return window.setupDesignTokens(params);
837
+ }
838
+ if (method === 'arrangeComponentSet') {
839
+ return window.arrangeComponentSet(params.nodeIds);
840
+ }
841
+ return { success: false, error: 'Unknown method: ' + method };
842
+ };
843
+ Promise.resolve(run()).then(function(result) {
844
+ mcpBridgeWs.send(JSON.stringify({ id: id, result: result }));
845
+ }).catch(function(err) {
846
+ mcpBridgeWs.send(JSON.stringify({ id: id, error: err.message || String(err) }));
847
+ });
848
+ } catch (e) {
849
+ if (mcpBridgeWs && mcpBridgeWs.readyState === 1 && msg && msg.id)
850
+ mcpBridgeWs.send(JSON.stringify({ id: msg.id, error: e.message || String(e) }));
851
+ }
852
+ };
853
+ mcpBridgeWs.onclose = function() {
854
+ mcpBridgeWs = null;
855
+ updateStatus('no server', false, true);
856
+ if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
857
+ mcpBridgeReconnectTimer = setTimeout(connectMcpBridge, 2000);
858
+ };
859
+ mcpBridgeWs.onerror = function() {
860
+ mcpBridgeWs = null;
861
+ updateStatus('no server', false, true);
862
+ if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
863
+ mcpBridgeReconnectTimer = setTimeout(connectMcpBridge, 2000);
864
+ };
865
+ } catch (e) {
866
+ updateStatus('no server', false, true);
867
+ if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
868
+ mcpBridgeReconnectTimer = setTimeout(connectMcpBridge, 2000);
869
+ }
870
+ }
871
+ if (typeof WebSocket !== 'undefined') {
872
+ initMcpPortInput();
873
+ connectMcpBridge();
874
+ }
875
+ </script>
876
+ </body>
877
+ </html>