@atezer/figma-mcp-bridge 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -119
- package/dist/cloudflare/core/figma-desktop-connector.js +28 -16
- package/dist/cloudflare/core/figma-tools.js +290 -254
- package/dist/cloudflare/core/plugin-bridge-connector.js +38 -4
- package/dist/cloudflare/core/plugin-bridge-server.js +106 -29
- package/dist/core/figma-desktop-connector.d.ts +6 -2
- package/dist/core/figma-desktop-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.js +28 -16
- package/dist/core/figma-desktop-connector.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +290 -254
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/plugin-bridge-connector.d.ts +18 -2
- package/dist/core/plugin-bridge-connector.d.ts.map +1 -1
- package/dist/core/plugin-bridge-connector.js +38 -4
- package/dist/core/plugin-bridge-connector.js.map +1 -1
- package/dist/core/plugin-bridge-server.d.ts +7 -2
- package/dist/core/plugin-bridge-server.d.ts.map +1 -1
- package/dist/core/plugin-bridge-server.js +106 -29
- package/dist/core/plugin-bridge-server.js.map +1 -1
- package/dist/local-plugin-only.d.ts.map +1 -1
- package/dist/local-plugin-only.js +289 -95
- package/dist/local-plugin-only.js.map +1 -1
- package/dist/local.js +323 -183
- package/dist/local.js.map +1 -1
- package/f-mcp-plugin/code.js +341 -53
- package/f-mcp-plugin/manifest.json +22 -3
- package/f-mcp-plugin/ui.html +226 -43
- package/package.json +3 -2
package/f-mcp-plugin/code.js
CHANGED
|
@@ -20,13 +20,30 @@ console.error = function() { _pushLog('error', arguments); _origError.apply(cons
|
|
|
20
20
|
console.log('🌉 [F-MCP ATezer Bridge] Plugin loaded and ready');
|
|
21
21
|
|
|
22
22
|
// Show minimal UI - compact status indicator
|
|
23
|
-
figma.showUI(__html__, { width:
|
|
23
|
+
figma.showUI(__html__, { width: 200, height: 56, visible: true, themeColors: true });
|
|
24
24
|
|
|
25
25
|
// Immediately fetch and send variables data to UI
|
|
26
26
|
(async () => {
|
|
27
27
|
try {
|
|
28
28
|
console.log('🌉 [F-MCP ATezer Bridge] Fetching variables...');
|
|
29
29
|
|
|
30
|
+
// FigJam does not support figma.variables API
|
|
31
|
+
if (!figma.variables || typeof figma.variables.getLocalVariablesAsync !== 'function') {
|
|
32
|
+
console.log('🌉 [F-MCP ATezer Bridge] Variables API not available (FigJam or limited editor), skipping variable load');
|
|
33
|
+
figma.ui.postMessage({
|
|
34
|
+
type: 'VARIABLES_DATA',
|
|
35
|
+
data: {
|
|
36
|
+
success: true,
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
fileKey: figma.fileKey || null,
|
|
39
|
+
variables: [],
|
|
40
|
+
variableCollections: [],
|
|
41
|
+
_note: 'Variables API not available in this editor type'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
// Get all local variables and collections
|
|
31
48
|
const variables = await figma.variables.getLocalVariablesAsync();
|
|
32
49
|
const collections = await figma.variables.getLocalVariableCollectionsAsync();
|
|
@@ -149,6 +166,205 @@ function hexToFigmaRGB(hex) {
|
|
|
149
166
|
// Listen for requests from UI (e.g., component data requests, write operations)
|
|
150
167
|
figma.ui.onmessage = async (msg) => {
|
|
151
168
|
|
|
169
|
+
function rgbaToHex(color) {
|
|
170
|
+
if (!color || typeof color !== 'object') return null;
|
|
171
|
+
var r = Math.round((Number(color.r) !== undefined ? Number(color.r) : 0) * 255);
|
|
172
|
+
var g = Math.round((Number(color.g) !== undefined ? Number(color.g) : 0) * 255);
|
|
173
|
+
var b = Math.round((Number(color.b) !== undefined ? Number(color.b) : 0) * 255);
|
|
174
|
+
return '#' + [r, g, b].map(function(x) { return x.toString(16).padStart(2, '0'); }).join('');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function nameToSuiComponent(name, description) {
|
|
178
|
+
var raw = (name || '') + (description ? ' ' + description : '');
|
|
179
|
+
raw = raw.replace(/[0-9_\-\s]+/g, ' ').trim().split(/\s+/);
|
|
180
|
+
if (raw.length === 0 || (raw.length === 1 && !raw[0])) return null;
|
|
181
|
+
return raw.map(function(w) { return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase(); }).join('');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildLayoutSummary(layout, outputHint) {
|
|
185
|
+
if (!layout || !layout.layoutMode) return null;
|
|
186
|
+
var parts = [];
|
|
187
|
+
var dir = layout.layoutMode === 'HORIZONTAL' ? 'row' : layout.layoutMode === 'VERTICAL' ? 'col' : 'grid';
|
|
188
|
+
if (outputHint === 'tailwind') {
|
|
189
|
+
parts.push('flex' + (dir === 'row' ? '' : '-col'));
|
|
190
|
+
if (layout.itemSpacing != null) parts.push('gap-' + Math.round(layout.itemSpacing));
|
|
191
|
+
var p = layout.paddingLeft || layout.paddingRight || layout.paddingTop || layout.paddingBottom;
|
|
192
|
+
if (p != null) parts.push('p-' + Math.round(p));
|
|
193
|
+
} else if (outputHint === 'react') {
|
|
194
|
+
parts.push('flex ' + dir);
|
|
195
|
+
if (layout.itemSpacing != null) parts.push('gap ' + layout.itemSpacing);
|
|
196
|
+
if (layout.paddingLeft != null) parts.push('paddingLeft ' + layout.paddingLeft);
|
|
197
|
+
if (layout.paddingTop != null) parts.push('paddingTop ' + layout.paddingTop);
|
|
198
|
+
} else {
|
|
199
|
+
parts.push(dir === 'grid' ? 'grid' : 'flex ' + dir);
|
|
200
|
+
if (layout.itemSpacing != null) parts.push('gap ' + layout.itemSpacing);
|
|
201
|
+
if (layout.paddingLeft != null || layout.paddingTop != null) parts.push('padding ' + (layout.paddingLeft || 0) + '/' + (layout.paddingTop || 0));
|
|
202
|
+
}
|
|
203
|
+
return parts.join(', ');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Resolve variable alias(es) to names (SUI token reference — 2.3)
|
|
207
|
+
async function resolveVariableNames(aliases) {
|
|
208
|
+
if (!aliases) return [];
|
|
209
|
+
var list = Array.isArray(aliases) ? aliases : (aliases.id ? [aliases] : []);
|
|
210
|
+
var names = [];
|
|
211
|
+
for (var i = 0; i < list.length; i++) {
|
|
212
|
+
var alias = list[i];
|
|
213
|
+
if (alias && alias.id) {
|
|
214
|
+
try {
|
|
215
|
+
var v = await figma.variables.getVariableByIdAsync(alias.id);
|
|
216
|
+
if (v && v.name) names.push(v.name);
|
|
217
|
+
} catch (e) { /* skip */ }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return names;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Build node payload for GET_DOCUMENT_STRUCTURE / GET_NODE_CONTEXT (layout, constraints, visual, typography, code-ready, SUI)
|
|
224
|
+
async function buildNodePayload(node, currentDepth, maxDepth, opts) {
|
|
225
|
+
if (currentDepth > maxDepth) return null;
|
|
226
|
+
var verbosity = opts.verbosity || 'summary';
|
|
227
|
+
var includeLayout = opts.includeLayout === true || verbosity === 'full';
|
|
228
|
+
var includeVisual = opts.includeVisual === true || verbosity === 'full';
|
|
229
|
+
var includeTypography = opts.includeTypography === true || verbosity === 'full';
|
|
230
|
+
var includeCodeReady = opts.includeCodeReady !== false && (includeLayout || includeVisual);
|
|
231
|
+
var outputHint = opts.outputHint || null;
|
|
232
|
+
|
|
233
|
+
var out = { id: node.id, name: node.name, type: node.type };
|
|
234
|
+
|
|
235
|
+
if (verbosity !== 'inventory' && verbosity !== 'summary') {
|
|
236
|
+
if (node.absoluteBoundingBox) out.absoluteBoundingBox = node.absoluteBoundingBox;
|
|
237
|
+
if (node.width !== undefined) out.width = node.width;
|
|
238
|
+
if (node.height !== undefined) out.height = node.height;
|
|
239
|
+
if (node.type === 'TEXT' && node.characters !== undefined) out.characters = node.characters;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
var incompleteReasons = [];
|
|
243
|
+
if (node.description !== undefined && node.description !== '') out.description = node.description;
|
|
244
|
+
var suiName = nameToSuiComponent(node.name, node.description);
|
|
245
|
+
if (suiName) { out.roleHint = suiName; out.suiComponent = suiName; }
|
|
246
|
+
|
|
247
|
+
if (node.type === 'INSTANCE' && 'componentProperties' in node && node.componentProperties) {
|
|
248
|
+
var props = node.componentProperties;
|
|
249
|
+
var propNames = Object.keys(props);
|
|
250
|
+
if (propNames.length > 0) {
|
|
251
|
+
out.suggestedProps = {};
|
|
252
|
+
var summaryParts = [];
|
|
253
|
+
for (var i = 0; i < propNames.length; i++) {
|
|
254
|
+
var pn = propNames[i];
|
|
255
|
+
var v = props[pn];
|
|
256
|
+
out.suggestedProps[pn] = v;
|
|
257
|
+
summaryParts.push(pn + '=' + (typeof v === 'string' ? v : String(v)));
|
|
258
|
+
}
|
|
259
|
+
out.variantSummary = summaryParts.join(', ');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (includeLayout) {
|
|
264
|
+
if ('constraints' in node && node.constraints) {
|
|
265
|
+
out.constraints = { horizontal: node.constraints.horizontal, vertical: node.constraints.vertical };
|
|
266
|
+
}
|
|
267
|
+
if ('layoutMode' in node && node.layoutMode && node.layoutMode !== 'NONE') {
|
|
268
|
+
var layout = { layoutMode: node.layoutMode };
|
|
269
|
+
if (node.paddingLeft !== undefined) layout.paddingLeft = node.paddingLeft;
|
|
270
|
+
if (node.paddingRight !== undefined) layout.paddingRight = node.paddingRight;
|
|
271
|
+
if (node.paddingTop !== undefined) layout.paddingTop = node.paddingTop;
|
|
272
|
+
if (node.paddingBottom !== undefined) layout.paddingBottom = node.paddingBottom;
|
|
273
|
+
if (node.itemSpacing !== undefined) layout.itemSpacing = node.itemSpacing;
|
|
274
|
+
if (node.primaryAxisAlignItems !== undefined) layout.primaryAxisAlignItems = node.primaryAxisAlignItems;
|
|
275
|
+
if (node.counterAxisAlignItems !== undefined) layout.counterAxisAlignItems = node.counterAxisAlignItems;
|
|
276
|
+
if (node.primaryAxisSizingMode !== undefined) layout.primaryAxisSizingMode = node.primaryAxisSizingMode;
|
|
277
|
+
if (node.counterAxisSizingMode !== undefined) layout.counterAxisSizingMode = node.counterAxisSizingMode;
|
|
278
|
+
if (node.layoutWrap !== undefined) layout.layoutWrap = node.layoutWrap;
|
|
279
|
+
if (node.counterAxisSpacing != null) layout.counterAxisSpacing = node.counterAxisSpacing;
|
|
280
|
+
if (node.layoutMode === 'GRID') {
|
|
281
|
+
if (node.gridRowCount !== undefined) layout.gridRowCount = node.gridRowCount;
|
|
282
|
+
if (node.gridColumnCount !== undefined) layout.gridColumnCount = node.gridColumnCount;
|
|
283
|
+
if (node.gridRowGap !== undefined) layout.gridRowGap = node.gridRowGap;
|
|
284
|
+
if (node.gridColumnGap !== undefined) layout.gridColumnGap = node.gridColumnGap;
|
|
285
|
+
}
|
|
286
|
+
out.layout = layout;
|
|
287
|
+
if (includeCodeReady) out.layoutSummary = buildLayoutSummary(layout, outputHint);
|
|
288
|
+
}
|
|
289
|
+
if ('layoutAlign' in node) out.layoutAlign = node.layoutAlign;
|
|
290
|
+
if ('layoutGrow' in node) out.layoutGrow = node.layoutGrow;
|
|
291
|
+
if ('layoutPositioning' in node) out.layoutPositioning = node.layoutPositioning;
|
|
292
|
+
if ('layoutSizingHorizontal' in node) out.layoutSizingHorizontal = node.layoutSizingHorizontal;
|
|
293
|
+
if ('layoutSizingVertical' in node) out.layoutSizingVertical = node.layoutSizingVertical;
|
|
294
|
+
if (node.minWidth != null) out.minWidth = node.minWidth;
|
|
295
|
+
if (node.maxWidth != null) out.maxWidth = node.maxWidth;
|
|
296
|
+
if (node.minHeight != null) out.minHeight = node.minHeight;
|
|
297
|
+
if (node.maxHeight != null) out.maxHeight = node.maxHeight;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
var isMixed = typeof figma !== 'undefined' && figma.mixed !== undefined ? function(v) { return v === figma.mixed; } : function() { return false; };
|
|
301
|
+
if (includeVisual) {
|
|
302
|
+
var hasImageFill = false;
|
|
303
|
+
if ('fills' in node && node.fills !== undefined && !isMixed(node.fills)) {
|
|
304
|
+
try {
|
|
305
|
+
var fillsCopy = JSON.parse(JSON.stringify(node.fills));
|
|
306
|
+
out.fills = fillsCopy;
|
|
307
|
+
if (Array.isArray(fillsCopy) && fillsCopy.length > 0) {
|
|
308
|
+
var first = fillsCopy[0];
|
|
309
|
+
if (first && first.type === 'SOLID' && first.color) {
|
|
310
|
+
var hex = rgbaToHex(first.color);
|
|
311
|
+
if (hex) { out.colorHex = hex; out.primaryColorHex = hex; }
|
|
312
|
+
}
|
|
313
|
+
for (var f = 0; f < fillsCopy.length; f++) {
|
|
314
|
+
if (fillsCopy[f].type === 'IMAGE' || fillsCopy[f].imageRef) hasImageFill = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch (e) { out.fills = []; }
|
|
318
|
+
} else if ('fills' in node && node.fills !== undefined) {
|
|
319
|
+
out.fills = 'mixed';
|
|
320
|
+
incompleteReasons.push('mixed fills');
|
|
321
|
+
}
|
|
322
|
+
if (hasImageFill) { out.hasImageFill = true; incompleteReasons.push('image fill'); }
|
|
323
|
+
if ('strokes' in node && node.strokes !== undefined && !isMixed(node.strokes)) {
|
|
324
|
+
try { out.strokes = JSON.parse(JSON.stringify(node.strokes)); } catch (e) { out.strokes = []; }
|
|
325
|
+
} else if ('strokes' in node && node.strokes !== undefined) {
|
|
326
|
+
out.strokes = 'mixed';
|
|
327
|
+
incompleteReasons.push('mixed stroke');
|
|
328
|
+
}
|
|
329
|
+
if ('effects' in node && node.effects && node.effects.length > 0) {
|
|
330
|
+
try { out.effects = JSON.parse(JSON.stringify(node.effects)); } catch (e) { out.effects = []; }
|
|
331
|
+
}
|
|
332
|
+
if ('opacity' in node && node.opacity !== undefined) out.opacity = node.opacity;
|
|
333
|
+
if ('cornerRadius' in node && node.cornerRadius !== undefined && !isMixed(node.cornerRadius)) out.cornerRadius = node.cornerRadius;
|
|
334
|
+
if ('strokeWeight' in node && node.strokeWeight !== undefined && !isMixed(node.strokeWeight)) out.strokeWeight = node.strokeWeight;
|
|
335
|
+
if ('strokeAlign' in node) out.strokeAlign = node.strokeAlign;
|
|
336
|
+
/* Variable adı çözümlemesi sadece verbosity full'da (büyük dosyada binlerce getVariableByIdAsync timeout'a yol açar) */
|
|
337
|
+
if (verbosity === 'full' && 'boundVariables' in node && node.boundVariables) {
|
|
338
|
+
var bv = node.boundVariables;
|
|
339
|
+
if (bv.fills && (Array.isArray(bv.fills) ? bv.fills.length : (bv.fills && bv.fills.id))) {
|
|
340
|
+
var fillNames = await resolveVariableNames(bv.fills);
|
|
341
|
+
if (fillNames.length) out.fillVariableNames = fillNames;
|
|
342
|
+
}
|
|
343
|
+
if (bv.strokes && (Array.isArray(bv.strokes) ? bv.strokes.length : (bv.strokes && bv.strokes.id))) {
|
|
344
|
+
var strokeNames = await resolveVariableNames(bv.strokes);
|
|
345
|
+
if (strokeNames.length) out.strokeVariableNames = strokeNames;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (includeTypography && node.type === 'TEXT') {
|
|
351
|
+
if (node.fontName !== undefined && !isMixed(node.fontName)) out.fontName = node.fontName;
|
|
352
|
+
else if (node.type === 'TEXT') incompleteReasons.push('font not loaded');
|
|
353
|
+
if (node.fontSize !== undefined && !isMixed(node.fontSize)) out.fontSize = node.fontSize;
|
|
354
|
+
if (node.lineHeight !== undefined && !isMixed(node.lineHeight)) out.lineHeight = node.lineHeight;
|
|
355
|
+
if (node.textStyleId !== undefined && !isMixed(node.textStyleId) && node.textStyleId) out.textStyleId = node.textStyleId;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (incompleteReasons.length > 0) out.incompleteReasons = incompleteReasons;
|
|
359
|
+
|
|
360
|
+
if (node.children && node.children.length > 0 && currentDepth < maxDepth) {
|
|
361
|
+
var childPayloads = await Promise.all(node.children.map(function(c) { return buildNodePayload(c, currentDepth + 1, maxDepth, opts); }));
|
|
362
|
+
out.children = childPayloads.filter(Boolean);
|
|
363
|
+
if (verbosity === 'summary' || verbosity === 'inventory') out.childCount = node.children.length;
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
|
|
152
368
|
// ============================================================================
|
|
153
369
|
// EXECUTE_CODE - Arbitrary code execution (Power Tool)
|
|
154
370
|
// ============================================================================
|
|
@@ -621,6 +837,16 @@ figma.ui.onmessage = async (msg) => {
|
|
|
621
837
|
try {
|
|
622
838
|
console.log('🌉 [F-MCP ATezer Bridge] Refreshing variables data...');
|
|
623
839
|
|
|
840
|
+
if (!figma.variables || typeof figma.variables.getLocalVariablesAsync !== 'function') {
|
|
841
|
+
figma.ui.postMessage({
|
|
842
|
+
type: 'REFRESH_VARIABLES_RESULT',
|
|
843
|
+
requestId: msg.requestId,
|
|
844
|
+
success: true,
|
|
845
|
+
data: { success: true, timestamp: Date.now(), fileKey: figma.fileKey || null, variables: [], variableCollections: [] }
|
|
846
|
+
});
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
624
850
|
var variables = await figma.variables.getLocalVariablesAsync();
|
|
625
851
|
var collections = await figma.variables.getLocalVariableCollectionsAsync();
|
|
626
852
|
|
|
@@ -702,12 +928,12 @@ figma.ui.onmessage = async (msg) => {
|
|
|
702
928
|
componentPropertyDefinitions: (node.type === 'COMPONENT_SET' || (node.type === 'COMPONENT' && !isVariant))
|
|
703
929
|
? node.componentPropertyDefinitions
|
|
704
930
|
: undefined,
|
|
705
|
-
// Get children info (
|
|
706
|
-
children: node.children ? node.children.map(child =>
|
|
707
|
-
id: child.id,
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
})
|
|
931
|
+
// Get children info (include text content for TEXT nodes)
|
|
932
|
+
children: node.children ? node.children.map(child => {
|
|
933
|
+
var c = { id: child.id, name: child.name, type: child.type };
|
|
934
|
+
if (child.type === 'TEXT' && child.characters !== undefined) c.characters = child.characters;
|
|
935
|
+
return c;
|
|
936
|
+
}) : undefined
|
|
711
937
|
}
|
|
712
938
|
};
|
|
713
939
|
|
|
@@ -735,11 +961,15 @@ figma.ui.onmessage = async (msg) => {
|
|
|
735
961
|
// ============================================================================
|
|
736
962
|
else if (msg.type === 'GET_LOCAL_COMPONENTS') {
|
|
737
963
|
try {
|
|
738
|
-
|
|
964
|
+
// Default true: avoid full-doc scan (timeout on large files). Only scan all pages when explicitly false.
|
|
965
|
+
var currentPageOnly = msg.currentPageOnly !== false;
|
|
966
|
+
var limit = msg.limit != null ? Math.max(0, parseInt(msg.limit, 10) || 0) : 0;
|
|
967
|
+
console.log('🌉 [F-MCP ATezer Bridge] Fetching local components (currentPageOnly:', currentPageOnly, ', limit:', limit || 'none', ')...');
|
|
739
968
|
|
|
740
969
|
// Find all component sets and standalone components in the file
|
|
741
970
|
var components = [];
|
|
742
971
|
var componentSets = [];
|
|
972
|
+
var hitLimit = false;
|
|
743
973
|
|
|
744
974
|
// Helper to extract component data
|
|
745
975
|
function extractComponentData(node, isPartOfSet) {
|
|
@@ -845,38 +1075,46 @@ figma.ui.onmessage = async (msg) => {
|
|
|
845
1075
|
};
|
|
846
1076
|
}
|
|
847
1077
|
|
|
848
|
-
//
|
|
849
|
-
function
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (!node
|
|
857
|
-
|
|
1078
|
+
// Use findAllWithCriteria (async-friendly, no sync tree walk) — works in dynamic-page
|
|
1079
|
+
async function processNodeList(nodes) {
|
|
1080
|
+
for (var i = 0; i < nodes.length && !hitLimit; i++) {
|
|
1081
|
+
if (limit > 0 && components.length + componentSets.length >= limit) {
|
|
1082
|
+
hitLimit = true;
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
var node = nodes[i];
|
|
1086
|
+
if (!node) continue;
|
|
1087
|
+
if (node.loadAsync) await node.loadAsync();
|
|
1088
|
+
if (node.type === 'COMPONENT_SET') {
|
|
1089
|
+
componentSets.push(extractComponentSetData(node));
|
|
1090
|
+
} else if (node.type === 'COMPONENT') {
|
|
1091
|
+
if (!node.parent || node.parent.type !== 'COMPONENT_SET') {
|
|
1092
|
+
components.push(extractComponentData(node, false));
|
|
1093
|
+
}
|
|
858
1094
|
}
|
|
859
1095
|
}
|
|
1096
|
+
}
|
|
860
1097
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
});
|
|
1098
|
+
if (currentPageOnly) {
|
|
1099
|
+
var page = figma.currentPage;
|
|
1100
|
+
if (page) {
|
|
1101
|
+
await (page.loadAsync && page.loadAsync());
|
|
1102
|
+
var nodes = page.findAllWithCriteria ? page.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
|
|
1103
|
+
await processNodeList(nodes);
|
|
1104
|
+
}
|
|
1105
|
+
} else {
|
|
1106
|
+
console.log('🌉 [F-MCP ATezer Bridge] Loading all pages...');
|
|
1107
|
+
await figma.loadAllPagesAsync();
|
|
1108
|
+
console.log('🌉 [F-MCP ATezer Bridge] All pages loaded, searching for components...');
|
|
1109
|
+
var pages = figma.root.children;
|
|
1110
|
+
for (var p = 0; p < pages.length && !hitLimit; p++) {
|
|
1111
|
+
var pg = pages[p];
|
|
1112
|
+
if (pg && pg.loadAsync) await pg.loadAsync();
|
|
1113
|
+
var pageNodes = pg && pg.findAllWithCriteria ? pg.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
|
|
1114
|
+
await processNodeList(pageNodes);
|
|
866
1115
|
}
|
|
867
1116
|
}
|
|
868
1117
|
|
|
869
|
-
// Load all pages first (required before accessing children)
|
|
870
|
-
console.log('🌉 [F-MCP ATezer Bridge] Loading all pages...');
|
|
871
|
-
await figma.loadAllPagesAsync();
|
|
872
|
-
console.log('🌉 [F-MCP ATezer Bridge] All pages loaded, searching for components...');
|
|
873
|
-
|
|
874
|
-
// Search through all pages
|
|
875
|
-
var pages = figma.root.children;
|
|
876
|
-
pages.forEach(function(page) {
|
|
877
|
-
findComponents(page);
|
|
878
|
-
});
|
|
879
|
-
|
|
880
1118
|
console.log('🌉 [F-MCP ATezer Bridge] Found ' + components.length + ' components and ' + componentSets.length + ' component sets');
|
|
881
1119
|
|
|
882
1120
|
figma.ui.postMessage({
|
|
@@ -888,7 +1126,8 @@ figma.ui.onmessage = async (msg) => {
|
|
|
888
1126
|
componentSets: componentSets,
|
|
889
1127
|
totalComponents: components.length,
|
|
890
1128
|
totalComponentSets: componentSets.length,
|
|
891
|
-
|
|
1129
|
+
currentPageOnly: currentPageOnly,
|
|
1130
|
+
truncatedByLimit: hitLimit && limit > 0,
|
|
892
1131
|
fileName: figma.root.name,
|
|
893
1132
|
fileKey: figma.fileKey || null,
|
|
894
1133
|
timestamp: Date.now()
|
|
@@ -1969,30 +2208,21 @@ figma.ui.onmessage = async (msg) => {
|
|
|
1969
2208
|
|
|
1970
2209
|
var depth = Math.min(Math.max(msg.depth || 1, 0), 3);
|
|
1971
2210
|
var verbosity = msg.verbosity || 'summary';
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
if (node.width !== undefined) out.width = node.width;
|
|
1981
|
-
if (node.height !== undefined) out.height = node.height;
|
|
1982
|
-
}
|
|
1983
|
-
if (node.children && node.children.length > 0 && currentDepth < depth) {
|
|
1984
|
-
out.children = node.children.map(function(c) { return walkNode(c, currentDepth + 1); }).filter(Boolean);
|
|
1985
|
-
if (verbosity === 'summary' || verbosity === 'inventory') out.childCount = node.children.length;
|
|
1986
|
-
}
|
|
1987
|
-
return out;
|
|
1988
|
-
}
|
|
2211
|
+
var opts = {
|
|
2212
|
+
verbosity: verbosity,
|
|
2213
|
+
includeLayout: msg.includeLayout === true,
|
|
2214
|
+
includeVisual: msg.includeVisual === true,
|
|
2215
|
+
includeTypography: msg.includeTypography === true,
|
|
2216
|
+
includeCodeReady: msg.includeCodeReady !== false,
|
|
2217
|
+
outputHint: msg.outputHint || null
|
|
2218
|
+
};
|
|
1989
2219
|
|
|
1990
2220
|
var document = {
|
|
1991
2221
|
name: figma.root.name,
|
|
1992
2222
|
id: figma.root.id,
|
|
1993
2223
|
type: 'DOCUMENT',
|
|
1994
2224
|
fileKey: figma.fileKey || null,
|
|
1995
|
-
children: figma.root.children ? figma.root.children.map(function(p) { return
|
|
2225
|
+
children: figma.root.children ? (await Promise.all(figma.root.children.map(function(p) { return buildNodePayload(p, 1, depth, opts); }))).filter(Boolean) : []
|
|
1996
2226
|
};
|
|
1997
2227
|
|
|
1998
2228
|
figma.ui.postMessage({
|
|
@@ -2012,6 +2242,64 @@ figma.ui.onmessage = async (msg) => {
|
|
|
2012
2242
|
}
|
|
2013
2243
|
}
|
|
2014
2244
|
|
|
2245
|
+
// ============================================================================
|
|
2246
|
+
// GET_NODE_CONTEXT - Subtree for one node with text content (token-efficient design context)
|
|
2247
|
+
// ============================================================================
|
|
2248
|
+
else if (msg.type === 'GET_NODE_CONTEXT') {
|
|
2249
|
+
try {
|
|
2250
|
+
var nodeId = msg.nodeId;
|
|
2251
|
+
if (!nodeId) {
|
|
2252
|
+
figma.ui.postMessage({
|
|
2253
|
+
type: 'GET_NODE_CONTEXT_RESULT',
|
|
2254
|
+
requestId: msg.requestId,
|
|
2255
|
+
success: false,
|
|
2256
|
+
error: 'nodeId is required'
|
|
2257
|
+
});
|
|
2258
|
+
} else {
|
|
2259
|
+
var targetNode = await figma.getNodeByIdAsync(nodeId);
|
|
2260
|
+
if (!targetNode) {
|
|
2261
|
+
figma.ui.postMessage({
|
|
2262
|
+
type: 'GET_NODE_CONTEXT_RESULT',
|
|
2263
|
+
requestId: msg.requestId,
|
|
2264
|
+
success: false,
|
|
2265
|
+
error: 'Node not found: ' + nodeId
|
|
2266
|
+
});
|
|
2267
|
+
} else {
|
|
2268
|
+
var depthNode = Math.min(Math.max(msg.depth || 2, 0), 3);
|
|
2269
|
+
var verbosityNode = msg.verbosity || 'standard';
|
|
2270
|
+
var optsNode = {
|
|
2271
|
+
verbosity: verbosityNode,
|
|
2272
|
+
includeLayout: msg.includeLayout === true,
|
|
2273
|
+
includeVisual: msg.includeVisual === true,
|
|
2274
|
+
includeTypography: msg.includeTypography === true,
|
|
2275
|
+
includeCodeReady: msg.includeCodeReady !== false,
|
|
2276
|
+
outputHint: msg.outputHint || null
|
|
2277
|
+
};
|
|
2278
|
+
|
|
2279
|
+
var nodeTree = await buildNodePayload(targetNode, 0, depthNode, optsNode);
|
|
2280
|
+
figma.ui.postMessage({
|
|
2281
|
+
type: 'GET_NODE_CONTEXT_RESULT',
|
|
2282
|
+
requestId: msg.requestId,
|
|
2283
|
+
success: true,
|
|
2284
|
+
data: {
|
|
2285
|
+
node: nodeTree,
|
|
2286
|
+
fileKey: figma.fileKey || null,
|
|
2287
|
+
fileName: figma.root.name
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
var errMsg = error && error.message ? error.message : String(error);
|
|
2294
|
+
figma.ui.postMessage({
|
|
2295
|
+
type: 'GET_NODE_CONTEXT_RESULT',
|
|
2296
|
+
requestId: msg.requestId,
|
|
2297
|
+
success: false,
|
|
2298
|
+
error: errMsg
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2015
2303
|
// ============================================================================
|
|
2016
2304
|
// GET_CONSOLE_LOGS - Plugin console buffer (no CDP)
|
|
2017
2305
|
else if (msg.type === 'GET_CONSOLE_LOGS') {
|
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "F-MCP ATezer Bridge",
|
|
3
|
-
"id": "
|
|
3
|
+
"id": "1608016072388581583",
|
|
4
4
|
"api": "1.0.0",
|
|
5
5
|
"main": "code.js",
|
|
6
6
|
"ui": "ui.html",
|
|
7
|
-
"editorType": ["figma", "dev"],
|
|
7
|
+
"editorType": ["figma", "figjam", "dev"],
|
|
8
8
|
"capabilities": ["inspect"],
|
|
9
9
|
"documentAccess": "dynamic-page",
|
|
10
10
|
"networkAccess": {
|
|
11
|
-
"allowedDomains": [
|
|
11
|
+
"allowedDomains": [
|
|
12
|
+
"http://localhost",
|
|
13
|
+
"http://localhost:5454", "ws://localhost:5454",
|
|
14
|
+
"http://localhost:5455", "ws://localhost:5455",
|
|
15
|
+
"http://localhost:5456", "ws://localhost:5456",
|
|
16
|
+
"http://localhost:5457", "ws://localhost:5457",
|
|
17
|
+
"http://localhost:5458", "ws://localhost:5458",
|
|
18
|
+
"http://localhost:5459", "ws://localhost:5459",
|
|
19
|
+
"http://localhost:5460", "ws://localhost:5460",
|
|
20
|
+
"http://localhost:5461", "ws://localhost:5461",
|
|
21
|
+
"http://localhost:5462", "ws://localhost:5462",
|
|
22
|
+
"http://localhost:5463", "ws://localhost:5463",
|
|
23
|
+
"http://localhost:5464", "ws://localhost:5464",
|
|
24
|
+
"http://localhost:5465", "ws://localhost:5465",
|
|
25
|
+
"http://localhost:5466", "ws://localhost:5466",
|
|
26
|
+
"http://localhost:5467", "ws://localhost:5467",
|
|
27
|
+
"http://localhost:5468", "ws://localhost:5468",
|
|
28
|
+
"http://localhost:5469", "ws://localhost:5469",
|
|
29
|
+
"http://localhost:5470", "ws://localhost:5470"
|
|
30
|
+
],
|
|
12
31
|
"reasoning": "Connect to local MCP server (no Figma debug port needed)"
|
|
13
32
|
}
|
|
14
33
|
}
|