@atezer/figma-mcp-bridge 1.5.0 → 1.5.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.
@@ -1,3 +1,2767 @@
1
- "use strict";var __consoleLogBuffer=[],__consoleLogLimit=200,_origLog=console.log,_origWarn=console.warn,_origError=console.error;function _pushLog(level,args){var arr=Array.prototype.slice.call(args);__consoleLogBuffer.push({level,time:Date.now(),args:arr}),__consoleLogBuffer.length>__consoleLogLimit&&__consoleLogBuffer.shift()}console.log=function(){_pushLog("log",arguments),_origLog.apply(console,arguments)},console.warn=function(){_pushLog("warn",arguments),_origWarn.apply(console,arguments)},console.error=function(){_pushLog("error",arguments),_origError.apply(console,arguments)},console.log("\u{1F309} [F-MCP ATezer Bridge] Plugin loaded and ready"),figma.showUI(__html__,{width:280,height:96,visible:!0,themeColors:!0}),figma.ui.postMessage({type:"FILE_IDENTITY",fileKey:figma.fileKey||null,fileName:figma.root.name||null}),(async()=>{try{var saved=await figma.clientStorage.getAsync("figmaRestToken");saved&&typeof saved=="object"&&saved.token&&(figma.ui.postMessage({type:"RESTORE_TOKEN",token:saved.token,savedAt:saved.savedAt||0,expiresAt:saved.expiresAt||0}),console.log("\u{1F309} [F-MCP] Restored saved REST API token from clientStorage"))}catch(e){console.warn("\u{1F309} [F-MCP] Could not restore token:",e&&e.message?e.message:String(e))}})(),(async()=>{try{if(console.log("\u{1F309} [F-MCP ATezer Bridge] Fetching variables..."),!figma.variables||typeof figma.variables.getLocalVariablesAsync!="function"){console.log("\u{1F309} [F-MCP ATezer Bridge] Variables API not available (FigJam or limited editor), skipping variable load"),figma.ui.postMessage({type:"VARIABLES_DATA",data:{success:!0,timestamp:Date.now(),fileKey:figma.fileKey||null,variables:[],variableCollections:[],_note:"Variables API not available in this editor type"}});return}const variables2=await figma.variables.getLocalVariablesAsync(),collections2=await figma.variables.getLocalVariableCollectionsAsync();console.log(`\u{1F309} [F-MCP ATezer Bridge] Found ${variables2.length} variables in ${collections2.length} collections`);const variablesData2={success:!0,timestamp:Date.now(),fileKey:figma.fileKey||null,variables:variables2.map(v2=>({id:v2.id,name:v2.name,key:v2.key,resolvedType:v2.resolvedType,valuesByMode:v2.valuesByMode,variableCollectionId:v2.variableCollectionId,scopes:v2.scopes,description:v2.description,hiddenFromPublishing:v2.hiddenFromPublishing})),variableCollections:collections2.map(c=>({id:c.id,name:c.name,key:c.key,modes:c.modes,defaultModeId:c.defaultModeId,variableIds:c.variableIds}))};figma.ui.postMessage({type:"VARIABLES_DATA",data:variablesData2}),console.log("\u{1F309} [F-MCP ATezer Bridge] Variables data sent to UI successfully"),console.log("\u{1F309} [F-MCP ATezer Bridge] UI iframe now has variables data accessible via window.__figmaVariablesData")}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Error fetching variables:",error),figma.ui.postMessage({type:"ERROR",error:error.message||String(error)})}})();function serializeVariable(v2){return{id:v2.id,name:v2.name,key:v2.key,resolvedType:v2.resolvedType,valuesByMode:v2.valuesByMode,variableCollectionId:v2.variableCollectionId,scopes:v2.scopes,description:v2.description,hiddenFromPublishing:v2.hiddenFromPublishing}}function serializeCollection(c){return{id:c.id,name:c.name,key:c.key,modes:c.modes,defaultModeId:c.defaultModeId,variableIds:c.variableIds}}function hexToFigmaRGB(hex){if(hex=hex.replace(/^#/,""),!/^[0-9A-Fa-f]+$/.test(hex))throw new Error('Invalid hex color: "'+hex+'" contains non-hex characters. Use only 0-9 and A-F.');var r,g,b,a=1;if(hex.length===3)r=parseInt(hex[0]+hex[0],16)/255,g=parseInt(hex[1]+hex[1],16)/255,b=parseInt(hex[2]+hex[2],16)/255;else if(hex.length===4)r=parseInt(hex[0]+hex[0],16)/255,g=parseInt(hex[1]+hex[1],16)/255,b=parseInt(hex[2]+hex[2],16)/255,a=parseInt(hex[3]+hex[3],16)/255;else if(hex.length===6)r=parseInt(hex.substring(0,2),16)/255,g=parseInt(hex.substring(2,4),16)/255,b=parseInt(hex.substring(4,6),16)/255;else if(hex.length===8)r=parseInt(hex.substring(0,2),16)/255,g=parseInt(hex.substring(2,4),16)/255,b=parseInt(hex.substring(4,6),16)/255,a=parseInt(hex.substring(6,8),16)/255;else throw new Error('Invalid hex color format: "'+hex+'". Expected 3, 4, 6, or 8 hex characters (e.g., #RGB, #RGBA, #RRGGBB, #RRGGBBAA).');return{r,g,b,a}}figma.ui.onmessage=async msg=>{if(msg.type==="SAVE_TOKEN"){try{await figma.clientStorage.setAsync("figmaRestToken",{token:msg.token,savedAt:Date.now(),expiresAt:msg.expiresAt||0}),figma.ui.postMessage({type:"TOKEN_SAVED",success:!0}),console.log("\u{1F309} [F-MCP] Token saved to clientStorage")}catch(e){figma.ui.postMessage({type:"TOKEN_SAVED",success:!1,error:String(e)})}return}if(msg.type==="DELETE_TOKEN"){try{await figma.clientStorage.deleteAsync("figmaRestToken"),figma.ui.postMessage({type:"TOKEN_DELETED",success:!0}),console.log("\u{1F309} [F-MCP] Token deleted from clientStorage")}catch{figma.ui.postMessage({type:"TOKEN_DELETED",success:!1})}return}if(msg.type==="RESIZE_UI"){try{var requestedWidth=Number(msg.width),requestedHeight=Number(msg.height),width=Math.max(220,Math.min(520,Math.round(isNaN(requestedWidth)?280:requestedWidth))),height=Math.max(72,Math.min(420,Math.round(isNaN(requestedHeight)?96:requestedHeight)));figma.ui.resize(width,height)}catch(error){console.warn("\u{1F309} [F-MCP ATezer Bridge] UI resize failed:",error&&error.message?error.message:String(error))}return}function rgbaToHex(color){if(!color||typeof color!="object")return null;var r=Math.round((Number(color.r)!==void 0?Number(color.r):0)*255),g=Math.round((Number(color.g)!==void 0?Number(color.g):0)*255),b=Math.round((Number(color.b)!==void 0?Number(color.b):0)*255);return"#"+[r,g,b].map(function(x){return x.toString(16).padStart(2,"0")}).join("")}function nameToSuiComponent(name2,description){var raw=(name2||"")+(description?" "+description:"");return raw=raw.replace(/[0-9_\-\s]+/g," ").trim().split(/\s+/),raw.length===0||raw.length===1&&!raw[0]?null:raw.map(function(w){return w.charAt(0).toUpperCase()+w.slice(1).toLowerCase()}).join("")}function buildLayoutSummary(layout,outputHint){if(!layout||!layout.layoutMode)return null;var parts=[],dir=layout.layoutMode==="HORIZONTAL"?"row":layout.layoutMode==="VERTICAL"?"col":"grid";if(outputHint==="tailwind"){parts.push("flex"+(dir==="row"?"":"-col")),layout.itemSpacing!=null&&parts.push("gap-"+Math.round(layout.itemSpacing));var p2=layout.paddingLeft||layout.paddingRight||layout.paddingTop||layout.paddingBottom;p2!=null&&parts.push("p-"+Math.round(p2))}else outputHint==="react"?(parts.push("flex "+dir),layout.itemSpacing!=null&&parts.push("gap "+layout.itemSpacing),layout.paddingLeft!=null&&parts.push("paddingLeft "+layout.paddingLeft),layout.paddingTop!=null&&parts.push("paddingTop "+layout.paddingTop)):(parts.push(dir==="grid"?"grid":"flex "+dir),layout.itemSpacing!=null&&parts.push("gap "+layout.itemSpacing),(layout.paddingLeft!=null||layout.paddingTop!=null)&&parts.push("padding "+(layout.paddingLeft||0)+"/"+(layout.paddingTop||0)));return parts.join(", ")}async function resolveVariableNames(aliases){if(!aliases)return[];for(var list=Array.isArray(aliases)?aliases:aliases.id?[aliases]:[],names=[],i2=0;i2<list.length;i2++){var alias=list[i2];if(alias&&alias.id)try{var v2=await figma.variables.getVariableByIdAsync(alias.id);v2&&v2.name&&names.push(v2.name)}catch{}}return names}async function buildNodePayload(node2,currentDepth,maxDepth,opts2){if(currentDepth>maxDepth)return null;var verbosity2=opts2.verbosity||"summary",includeLayout=opts2.includeLayout===!0||verbosity2==="full",includeVisual=opts2.includeVisual===!0||verbosity2==="full",includeTypography=opts2.includeTypography===!0||verbosity2==="full",includeCodeReady=opts2.includeCodeReady!==!1&&(includeLayout||includeVisual),outputHint=opts2.outputHint||null,out2={id:node2.id,name:node2.name,type:node2.type};verbosity2!=="inventory"&&verbosity2!=="summary"&&(node2.absoluteBoundingBox&&(out2.absoluteBoundingBox=node2.absoluteBoundingBox),node2.width!==void 0&&(out2.width=node2.width),node2.height!==void 0&&(out2.height=node2.height),node2.type==="TEXT"&&node2.characters!==void 0&&(out2.characters=node2.characters));var incompleteReasons=[];node2.description!==void 0&&node2.description!==""&&(out2.description=node2.description);var suiName=nameToSuiComponent(node2.name,node2.description);if(suiName&&(out2.roleHint=suiName,out2.suiComponent=suiName),node2.type==="INSTANCE"&&"componentProperties"in node2&&node2.componentProperties){var props2=node2.componentProperties,propNames=Object.keys(props2);if(propNames.length>0){out2.suggestedProps={};for(var summaryParts=[],i2=0;i2<propNames.length;i2++){var pn2=propNames[i2],v2=props2[pn2];out2.suggestedProps[pn2]=v2,summaryParts.push(pn2+"="+(typeof v2=="string"?v2:String(v2)))}out2.variantSummary=summaryParts.join(", ")}}if(includeLayout){if("constraints"in node2&&node2.constraints&&(out2.constraints={horizontal:node2.constraints.horizontal,vertical:node2.constraints.vertical}),"layoutMode"in node2&&node2.layoutMode&&node2.layoutMode!=="NONE"){var layout={layoutMode:node2.layoutMode};node2.paddingLeft!==void 0&&(layout.paddingLeft=node2.paddingLeft),node2.paddingRight!==void 0&&(layout.paddingRight=node2.paddingRight),node2.paddingTop!==void 0&&(layout.paddingTop=node2.paddingTop),node2.paddingBottom!==void 0&&(layout.paddingBottom=node2.paddingBottom),node2.itemSpacing!==void 0&&(layout.itemSpacing=node2.itemSpacing),node2.primaryAxisAlignItems!==void 0&&(layout.primaryAxisAlignItems=node2.primaryAxisAlignItems),node2.counterAxisAlignItems!==void 0&&(layout.counterAxisAlignItems=node2.counterAxisAlignItems),node2.primaryAxisSizingMode!==void 0&&(layout.primaryAxisSizingMode=node2.primaryAxisSizingMode),node2.counterAxisSizingMode!==void 0&&(layout.counterAxisSizingMode=node2.counterAxisSizingMode),node2.layoutWrap!==void 0&&(layout.layoutWrap=node2.layoutWrap),node2.counterAxisSpacing!=null&&(layout.counterAxisSpacing=node2.counterAxisSpacing),node2.layoutMode==="GRID"&&(node2.gridRowCount!==void 0&&(layout.gridRowCount=node2.gridRowCount),node2.gridColumnCount!==void 0&&(layout.gridColumnCount=node2.gridColumnCount),node2.gridRowGap!==void 0&&(layout.gridRowGap=node2.gridRowGap),node2.gridColumnGap!==void 0&&(layout.gridColumnGap=node2.gridColumnGap)),out2.layout=layout,includeCodeReady&&(out2.layoutSummary=buildLayoutSummary(layout,outputHint))}"layoutAlign"in node2&&(out2.layoutAlign=node2.layoutAlign),"layoutGrow"in node2&&(out2.layoutGrow=node2.layoutGrow),"layoutPositioning"in node2&&(out2.layoutPositioning=node2.layoutPositioning),"layoutSizingHorizontal"in node2&&(out2.layoutSizingHorizontal=node2.layoutSizingHorizontal),"layoutSizingVertical"in node2&&(out2.layoutSizingVertical=node2.layoutSizingVertical),node2.minWidth!=null&&(out2.minWidth=node2.minWidth),node2.maxWidth!=null&&(out2.maxWidth=node2.maxWidth),node2.minHeight!=null&&(out2.minHeight=node2.minHeight),node2.maxHeight!=null&&(out2.maxHeight=node2.maxHeight)}var isMixed=typeof figma<"u"&&figma.mixed!==void 0?function(v3){return v3===figma.mixed}:function(){return!1};if(includeVisual){var hasImageFill=!1;if("fills"in node2&&node2.fills!==void 0&&!isMixed(node2.fills))try{var fillsCopy=JSON.parse(JSON.stringify(node2.fills));if(out2.fills=fillsCopy,Array.isArray(fillsCopy)&&fillsCopy.length>0){var first=fillsCopy[0];if(first&&first.type==="SOLID"&&first.color){var hex=rgbaToHex(first.color);hex&&(out2.colorHex=hex,out2.primaryColorHex=hex)}for(var f=0;f<fillsCopy.length;f++)(fillsCopy[f].type==="IMAGE"||fillsCopy[f].imageRef)&&(hasImageFill=!0)}}catch{out2.fills=[]}else"fills"in node2&&node2.fills!==void 0&&(out2.fills="mixed",incompleteReasons.push("mixed fills"));if(hasImageFill&&(out2.hasImageFill=!0,incompleteReasons.push("image fill")),"strokes"in node2&&node2.strokes!==void 0&&!isMixed(node2.strokes))try{out2.strokes=JSON.parse(JSON.stringify(node2.strokes))}catch{out2.strokes=[]}else"strokes"in node2&&node2.strokes!==void 0&&(out2.strokes="mixed",incompleteReasons.push("mixed stroke"));if("effects"in node2&&node2.effects&&node2.effects.length>0)try{out2.effects=JSON.parse(JSON.stringify(node2.effects))}catch{out2.effects=[]}if("opacity"in node2&&node2.opacity!==void 0&&(out2.opacity=node2.opacity),"cornerRadius"in node2&&node2.cornerRadius!==void 0&&!isMixed(node2.cornerRadius)&&(out2.cornerRadius=node2.cornerRadius),"strokeWeight"in node2&&node2.strokeWeight!==void 0&&!isMixed(node2.strokeWeight)&&(out2.strokeWeight=node2.strokeWeight),"strokeAlign"in node2&&(out2.strokeAlign=node2.strokeAlign),verbosity2==="full"&&"boundVariables"in node2&&node2.boundVariables){var bv=node2.boundVariables;if(bv.fills&&(Array.isArray(bv.fills)?bv.fills.length:bv.fills&&bv.fills.id)){var fillNames=await resolveVariableNames(bv.fills);fillNames.length&&(out2.fillVariableNames=fillNames)}if(bv.strokes&&(Array.isArray(bv.strokes)?bv.strokes.length:bv.strokes&&bv.strokes.id)){var strokeNames=await resolveVariableNames(bv.strokes);strokeNames.length&&(out2.strokeVariableNames=strokeNames)}}}if(includeTypography&&node2.type==="TEXT"&&(node2.fontName!==void 0&&!isMixed(node2.fontName)?out2.fontName=node2.fontName:node2.type==="TEXT"&&incompleteReasons.push("font not loaded"),node2.fontSize!==void 0&&!isMixed(node2.fontSize)&&(out2.fontSize=node2.fontSize),node2.lineHeight!==void 0&&!isMixed(node2.lineHeight)&&(out2.lineHeight=node2.lineHeight),node2.textStyleId!==void 0&&!isMixed(node2.textStyleId)&&node2.textStyleId&&(out2.textStyleId=node2.textStyleId)),incompleteReasons.length>0&&(out2.incompleteReasons=incompleteReasons),node2.children&&node2.children.length>0&&currentDepth<maxDepth){var childPayloads=await Promise.all(node2.children.map(function(c){return buildNodePayload(c,currentDepth+1,maxDepth,opts2)}));out2.children=childPayloads.filter(Boolean),(verbosity2==="summary"||verbosity2==="inventory")&&(out2.childCount=node2.children.length)}return out2}if(msg.type==="EXECUTE_CODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Executing code, length:",msg.code.length);var wrappedCode=`(async function() {
2
- `+msg.code+`
3
- })()`;console.log("\u{1F309} [F-MCP ATezer Bridge] Wrapped code for eval");var timeoutMs=msg.timeout||5e3,timeoutPromise=new Promise(function(_,reject){setTimeout(function(){reject(new Error("Execution timed out after "+timeoutMs+"ms"))},timeoutMs)}),codePromise;try{codePromise=eval(wrappedCode)}catch(syntaxError){var syntaxErrorMsg=syntaxError&&syntaxError.message?syntaxError.message:String(syntaxError);console.error("\u{1F309} [F-MCP ATezer Bridge] Syntax error in code:",syntaxErrorMsg),figma.ui.postMessage({type:"EXECUTE_CODE_RESULT",requestId:msg.requestId,success:!1,error:"Syntax error: "+syntaxErrorMsg});return}var result=await Promise.race([codePromise,timeoutPromise]);console.log("\u{1F309} [F-MCP ATezer Bridge] Code executed successfully, result type:",typeof result);var resultAnalysis={type:typeof result,isNull:result===null,isUndefined:result===void 0,isEmpty:!1,warning:null};if(Array.isArray(result))resultAnalysis.isEmpty=result.length===0,resultAnalysis.isEmpty&&(resultAnalysis.warning="Code returned an empty array. If you were searching for nodes, none were found.");else if(result!==null&&typeof result=="object"){var keys=Object.keys(result);resultAnalysis.isEmpty=keys.length===0,resultAnalysis.isEmpty&&(resultAnalysis.warning="Code returned an empty object. The operation may not have found what it was looking for."),(result.length===0||result.count===0||result.foundCount===0||result.nodes&&result.nodes.length===0)&&(resultAnalysis.warning="Code returned a result indicating nothing was found (count/length is 0).")}else result===null?resultAnalysis.warning="Code returned null. The requested node or resource may not exist.":result===void 0&&(resultAnalysis.warning="Code returned undefined. Make sure your code has a return statement.");resultAnalysis.warning&&console.warn("\u{1F309} [F-MCP ATezer Bridge] \u26A0\uFE0F Result warning:",resultAnalysis.warning),figma.ui.postMessage({type:"EXECUTE_CODE_RESULT",requestId:msg.requestId,success:!0,result,resultAnalysis,fileContext:{fileName:figma.root.name,fileKey:figma.fileKey||null}})}catch(error){var errorName=error&&error.name?error.name:"Error",errorMsg=error&&error.message?error.message:String(error),errorStack=error&&error.stack?error.stack:"";console.error("\u{1F309} [F-MCP ATezer Bridge] Code execution error: ["+errorName+"] "+errorMsg),errorStack&&console.error("\u{1F309} [F-MCP ATezer Bridge] Stack:",errorStack),figma.ui.postMessage({type:"EXECUTE_CODE_RESULT",requestId:msg.requestId,success:!1,error:errorName+": "+errorMsg})}else if(msg.type==="UPDATE_VARIABLE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Updating variable:",msg.variableId);var variable=await figma.variables.getVariableByIdAsync(msg.variableId);if(!variable)throw new Error("Variable not found: "+msg.variableId);var value=msg.value;typeof value=="string"&&value.startsWith("VariableID:")?(value={type:"VARIABLE_ALIAS",id:value},console.log("\u{1F309} [F-MCP ATezer Bridge] Converting to variable alias:",value.id)):variable.resolvedType==="COLOR"&&typeof value=="string"&&(value=hexToFigmaRGB(value)),variable.setValueForMode(msg.modeId,value),console.log("\u{1F309} [F-MCP ATezer Bridge] Variable updated successfully"),figma.ui.postMessage({type:"UPDATE_VARIABLE_RESULT",requestId:msg.requestId,success:!0,variable:serializeVariable(variable)})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Update variable error:",error),figma.ui.postMessage({type:"UPDATE_VARIABLE_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="CREATE_VARIABLE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Creating variable:",msg.name);var collection=await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);if(!collection)throw new Error("Collection not found: "+msg.collectionId);var variable=figma.variables.createVariable(msg.name,collection,msg.resolvedType);if(msg.valuesByMode)for(var modeId in msg.valuesByMode){var value=msg.valuesByMode[modeId];msg.resolvedType==="COLOR"&&typeof value=="string"&&(value=hexToFigmaRGB(value)),variable.setValueForMode(modeId,value)}msg.description&&(variable.description=msg.description),msg.scopes&&(variable.scopes=msg.scopes),console.log("\u{1F309} [F-MCP ATezer Bridge] Variable created:",variable.id),figma.ui.postMessage({type:"CREATE_VARIABLE_RESULT",requestId:msg.requestId,success:!0,variable:serializeVariable(variable)})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Create variable error:",error),figma.ui.postMessage({type:"CREATE_VARIABLE_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="CREATE_VARIABLE_COLLECTION")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Creating collection:",msg.name);var collection=figma.variables.createVariableCollection(msg.name);if(msg.initialModeName&&collection.modes.length>0&&collection.renameMode(collection.modes[0].modeId,msg.initialModeName),msg.additionalModes&&msg.additionalModes.length>0)for(var i=0;i<msg.additionalModes.length;i++)collection.addMode(msg.additionalModes[i]);console.log("\u{1F309} [F-MCP ATezer Bridge] Collection created:",collection.id),figma.ui.postMessage({type:"CREATE_VARIABLE_COLLECTION_RESULT",requestId:msg.requestId,success:!0,collection:serializeCollection(collection)})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Create collection error:",error),figma.ui.postMessage({type:"CREATE_VARIABLE_COLLECTION_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="DELETE_VARIABLE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Deleting variable:",msg.variableId);var variable=await figma.variables.getVariableByIdAsync(msg.variableId);if(!variable)throw new Error("Variable not found: "+msg.variableId);var deletedInfo={id:variable.id,name:variable.name};variable.remove(),console.log("\u{1F309} [F-MCP ATezer Bridge] Variable deleted"),figma.ui.postMessage({type:"DELETE_VARIABLE_RESULT",requestId:msg.requestId,success:!0,deleted:deletedInfo})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Delete variable error:",error),figma.ui.postMessage({type:"DELETE_VARIABLE_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="DELETE_VARIABLE_COLLECTION")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Deleting collection:",msg.collectionId);var collection=await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);if(!collection)throw new Error("Collection not found: "+msg.collectionId);var deletedInfo={id:collection.id,name:collection.name,variableCount:collection.variableIds.length};collection.remove(),console.log("\u{1F309} [F-MCP ATezer Bridge] Collection deleted"),figma.ui.postMessage({type:"DELETE_VARIABLE_COLLECTION_RESULT",requestId:msg.requestId,success:!0,deleted:deletedInfo})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Delete collection error:",error),figma.ui.postMessage({type:"DELETE_VARIABLE_COLLECTION_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="RENAME_VARIABLE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Renaming variable:",msg.variableId,"to",msg.newName);var variable=await figma.variables.getVariableByIdAsync(msg.variableId);if(!variable)throw new Error("Variable not found: "+msg.variableId);var oldName=variable.name;variable.name=msg.newName,console.log('\u{1F309} [F-MCP ATezer Bridge] Variable renamed from "'+oldName+'" to "'+msg.newName+'"'),figma.ui.postMessage({type:"RENAME_VARIABLE_RESULT",requestId:msg.requestId,success:!0,variable:serializeVariable(variable),oldName})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Rename variable error:",error),figma.ui.postMessage({type:"RENAME_VARIABLE_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="ADD_MODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Adding mode to collection:",msg.collectionId);var collection=await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);if(!collection)throw new Error("Collection not found: "+msg.collectionId);var newModeId=collection.addMode(msg.modeName);console.log('\u{1F309} [F-MCP ATezer Bridge] Mode "'+msg.modeName+'" added with ID:',newModeId),figma.ui.postMessage({type:"ADD_MODE_RESULT",requestId:msg.requestId,success:!0,collection:serializeCollection(collection),newMode:{modeId:newModeId,name:msg.modeName}})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Add mode error:",error),figma.ui.postMessage({type:"ADD_MODE_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="RENAME_MODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Renaming mode:",msg.modeId,"in collection:",msg.collectionId);var collection=await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);if(!collection)throw new Error("Collection not found: "+msg.collectionId);var currentMode=collection.modes.find(function(m2){return m2.modeId===msg.modeId});if(!currentMode)throw new Error("Mode not found: "+msg.modeId);var oldName=currentMode.name;collection.renameMode(msg.modeId,msg.newName),console.log('\u{1F309} [F-MCP ATezer Bridge] Mode renamed from "'+oldName+'" to "'+msg.newName+'"'),figma.ui.postMessage({type:"RENAME_MODE_RESULT",requestId:msg.requestId,success:!0,collection:serializeCollection(collection),oldName})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Rename mode error:",error),figma.ui.postMessage({type:"RENAME_MODE_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="REFRESH_VARIABLES")try{if(console.log("\u{1F309} [F-MCP ATezer Bridge] Refreshing variables data..."),!figma.variables||typeof figma.variables.getLocalVariablesAsync!="function"){figma.ui.postMessage({type:"REFRESH_VARIABLES_RESULT",requestId:msg.requestId,success:!0,data:{success:!0,timestamp:Date.now(),fileKey:figma.fileKey||null,variables:[],variableCollections:[]}});return}var variables=await figma.variables.getLocalVariablesAsync(),collections=await figma.variables.getLocalVariableCollectionsAsync(),variablesData={success:!0,timestamp:Date.now(),fileKey:figma.fileKey||null,variables:variables.map(serializeVariable),variableCollections:collections.map(serializeCollection)};figma.ui.postMessage({type:"VARIABLES_DATA",data:variablesData}),figma.ui.postMessage({type:"REFRESH_VARIABLES_RESULT",requestId:msg.requestId,success:!0,data:variablesData}),console.log("\u{1F309} [F-MCP ATezer Bridge] Variables refreshed:",variables.length,"variables in",collections.length,"collections")}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Refresh variables error:",error),figma.ui.postMessage({type:"REFRESH_VARIABLES_RESULT",requestId:msg.requestId,success:!1,error:error.message||String(error)})}else if(msg.type==="GET_COMPONENT")try{console.log(`\u{1F309} [F-MCP ATezer Bridge] Fetching component: ${msg.nodeId}`);const node2=await figma.getNodeByIdAsync(msg.nodeId);if(!node2)throw new Error(`Node not found: ${msg.nodeId}`);if(node2.type!=="COMPONENT"&&node2.type!=="COMPONENT_SET"&&node2.type!=="INSTANCE")throw new Error(`Node is not a component. Type: ${node2.type}`);const isVariant=node2.type==="COMPONENT"&&node2.parent&&node2.parent.type==="COMPONENT_SET",componentData={success:!0,timestamp:Date.now(),nodeId:msg.nodeId,component:{id:node2.id,name:node2.name,type:node2.type,description:node2.description||null,descriptionMarkdown:node2.descriptionMarkdown||null,visible:node2.visible,locked:node2.locked,annotations:node2.annotations||[],isVariant,componentPropertyDefinitions:node2.type==="COMPONENT_SET"||node2.type==="COMPONENT"&&!isVariant?node2.componentPropertyDefinitions:void 0,children:node2.children?node2.children.map(child2=>{var c={id:child2.id,name:child2.name,type:child2.type};return child2.type==="TEXT"&&child2.characters!==void 0&&(c.characters=child2.characters),c}):void 0}};console.log(`\u{1F309} [F-MCP ATezer Bridge] Component data ready. Has description: ${!!componentData.component.description}, annotations: ${componentData.component.annotations.length}`),figma.ui.postMessage({type:"COMPONENT_DATA",requestId:msg.requestId,data:componentData})}catch(error){console.error("\u{1F309} [F-MCP ATezer Bridge] Error fetching component:",error),figma.ui.postMessage({type:"COMPONENT_ERROR",requestId:msg.requestId,error:error.message||String(error)})}else if(msg.type==="GET_LOCAL_COMPONENTS")try{let extractComponentData2=function(node2,isPartOfSet){var data={key:node2.key,nodeId:node2.id,name:node2.name,type:node2.type,description:node2.description||null,width:node2.width,height:node2.height};if(!isPartOfSet&&node2.componentPropertyDefinitions){data.properties=[];var propDefs=node2.componentPropertyDefinitions;for(var propName2 in propDefs)if(propDefs.hasOwnProperty(propName2)){var propDef=propDefs[propName2];data.properties.push({name:propName2,type:propDef.type,defaultValue:propDef.defaultValue})}}return data},extractComponentSetData2=function(node2){var variantAxes={},variants=[];node2.children&&node2.children.forEach(function(child2){if(child2.type==="COMPONENT"){var variantProps={},parts=child2.name.split(",").map(function(p2){return p2.trim()});parts.forEach(function(part){var kv=part.split("=");if(kv.length===2){var key=kv[0].trim(),value2=kv[1].trim();variantProps[key]=value2,variantAxes[key]||(variantAxes[key]=[]),variantAxes[key].indexOf(value2)===-1&&variantAxes[key].push(value2)}}),variants.push({key:child2.key,nodeId:child2.id,name:child2.name,description:child2.description||null,variantProperties:variantProps,width:child2.width,height:child2.height})}});var axes=[];for(var axisName in variantAxes)variantAxes.hasOwnProperty(axisName)&&axes.push({name:axisName,values:variantAxes[axisName]});return{key:node2.key,nodeId:node2.id,name:node2.name,type:"COMPONENT_SET",description:node2.description||null,variantAxes:axes,variants,defaultVariant:variants.length>0?variants[0]:null,properties:node2.componentPropertyDefinitions?Object.keys(node2.componentPropertyDefinitions).map(function(propName2){var propDef=node2.componentPropertyDefinitions[propName2];return{name:propName2,type:propDef.type,defaultValue:propDef.defaultValue}}):[]}};var extractComponentData=extractComponentData2,extractComponentSetData=extractComponentSetData2,currentPageOnly=msg.currentPageOnly!==!1,limit=msg.limit!=null?Math.max(0,parseInt(msg.limit,10)||0):0;console.log("\u{1F309} [F-MCP ATezer Bridge] Fetching local components (currentPageOnly:",currentPageOnly,", limit:",limit||"none",")...");var components=[],componentSets=[],hitLimit=!1;async function processNodeList(nodes2){for(var i2=0;i2<nodes2.length&&!hitLimit;i2++){if(limit>0&&components.length+componentSets.length>=limit){hitLimit=!0;break}var node2=nodes2[i2];node2&&(node2.loadAsync&&await node2.loadAsync(),node2.type==="COMPONENT_SET"?componentSets.push(extractComponentSetData2(node2)):node2.type==="COMPONENT"&&(!node2.parent||node2.parent.type!=="COMPONENT_SET")&&components.push(extractComponentData2(node2,!1)))}}if(currentPageOnly){var page=figma.currentPage;if(page){await(page.loadAsync&&page.loadAsync());var nodes=page.findAllWithCriteria?page.findAllWithCriteria({types:["COMPONENT","COMPONENT_SET"]}):[];await processNodeList(nodes)}}else{console.log("\u{1F309} [F-MCP ATezer Bridge] Loading all pages..."),await figma.loadAllPagesAsync(),console.log("\u{1F309} [F-MCP ATezer Bridge] All pages loaded, searching for components...");for(var pages=figma.root.children,p=0;p<pages.length&&!hitLimit;p++){var pg=pages[p];pg&&pg.loadAsync&&await pg.loadAsync();var pageNodes=pg&&pg.findAllWithCriteria?pg.findAllWithCriteria({types:["COMPONENT","COMPONENT_SET"]}):[];await processNodeList(pageNodes)}}console.log("\u{1F309} [F-MCP ATezer Bridge] Found "+components.length+" components and "+componentSets.length+" component sets"),figma.ui.postMessage({type:"GET_LOCAL_COMPONENTS_RESULT",requestId:msg.requestId,success:!0,data:{components,componentSets,totalComponents:components.length,totalComponentSets:componentSets.length,currentPageOnly,truncatedByLimit:hitLimit&&limit>0,fileName:figma.root.name,fileKey:figma.fileKey||null,timestamp:Date.now()}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Get local components error:",errorMsg),figma.ui.postMessage({type:"GET_LOCAL_COMPONENTS_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SEARCH_LIBRARY_ASSETS")try{var q=(msg.query||"").toLowerCase().trim(),assetTypes=msg.assetTypes&&msg.assetTypes.length?msg.assetTypes:["variables","components"],limit=Math.min(Math.max(parseInt(msg.limit,10)||25,1),80),currentPageOnly=msg.currentPageOnly!==!1,out={success:!0,query:msg.query||"",variables:[],components:[],componentSets:[],librariesScanned:[],notes:[]};if(assetTypes.indexOf("variables")>=0&&figma.teamLibrary&&typeof figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync=="function")try{for(var cols=await figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync(),ci=0;ci<cols.length&&out.variables.length<limit;ci++){var col=cols[ci];out.librariesScanned.push({kind:"variableCollection",name:col.name||"",key:col.key||""});for(var libVars=await figma.teamLibrary.getVariablesInLibraryCollectionAsync(col.key),vi=0;vi<libVars.length&&out.variables.length<limit;vi++){var lv=libVars[vi],nm=(lv.name||"").toLowerCase();(!q||nm.indexOf(q)>=0)&&out.variables.push({name:lv.name,key:lv.key,resolvedType:lv.resolvedType,libraryCollectionKey:col.key,libraryCollectionName:col.name})}}}catch(ve){out.variableLibraryError=ve&&ve.message?ve.message:String(ve)}else assetTypes.indexOf("variables")>=0&&out.notes.push("Library variables skipped: teamLibrary API unavailable or permission missing (manifest permissions: teamlibrary).");if(assetTypes.indexOf("components")>=0){let extractComponentData2=function(node2,fromSet){return{id:node2.id,name:node2.name,key:node2.key,description:node2.description||null,fromLibraryComponentSet:!!fromSet}},extractComponentSetData2=function(node2){return{id:node2.id,name:node2.name,key:node2.key,description:node2.description||null,variantCount:node2.children?node2.children.length:0}};var extractComponentData=extractComponentData2,extractComponentSetData=extractComponentSetData2,components=[],componentSets=[],hitLimit=!1;async function processNodeList(nodes2){for(var i2=0;i2<nodes2.length&&!hitLimit;i2++){if(components.length+componentSets.length>=limit){hitLimit=!0;break}var node2=nodes2[i2];if(node2){node2.loadAsync&&await node2.loadAsync();var nname=(node2.name||"").toLowerCase(),ndesc=(node2.description||"").toLowerCase(),match=!q||nname.indexOf(q)>=0||ndesc.indexOf(q)>=0;node2.type==="COMPONENT_SET"?match&&componentSets.push(extractComponentSetData2(node2)):node2.type==="COMPONENT"&&(!node2.parent||node2.parent.type!=="COMPONENT_SET")&&match&&components.push(extractComponentData2(node2,!1))}}}if(currentPageOnly){var page=figma.currentPage;if(page){page.loadAsync&&await page.loadAsync();var pn=page.findAllWithCriteria?page.findAllWithCriteria({types:["COMPONENT","COMPONENT_SET"]}):[];await processNodeList(pn)}}else{await figma.loadAllPagesAsync();for(var pages=figma.root.children,p=0;p<pages.length&&!hitLimit;p++){var pg=pages[p];pg&&pg.loadAsync&&await pg.loadAsync();var pageNodes=pg&&pg.findAllWithCriteria?pg.findAllWithCriteria({types:["COMPONENT","COMPONENT_SET"]}):[];await processNodeList(pageNodes)}}out.components=components,out.componentSets=componentSets,out.truncatedByLimit=hitLimit,out.currentPageOnly=currentPageOnly,out.notes.push("Components are from the current file (local + published keys via component.key). Full remote catalog may require REST API.")}figma.ui.postMessage({type:"SEARCH_LIBRARY_ASSETS_RESULT",requestId:msg.requestId,success:!0,data:out})}catch(error){var emsg=error&&error.message?error.message:String(error);figma.ui.postMessage({type:"SEARCH_LIBRARY_ASSETS_RESULT",requestId:msg.requestId,success:!1,error:emsg})}else if(msg.type==="GET_CODE_CONNECT_HINTS")try{var ids=msg.nodeIds&&msg.nodeIds.length?msg.nodeIds:[],scanPage=msg.scanCurrentPage===!0,maxNodes=Math.min(parseInt(msg.maxNodes,10)||40,120),nodes=[];async function pushNodeEntry(n2){if(n2){n2.loadAsync&&await n2.loadAsync();var entry={nodeId:n2.id,name:n2.name,type:n2.type};(n2.type==="COMPONENT"||n2.type==="COMPONENT_SET")&&(entry.componentKey=n2.key),"description"in n2&&n2.description&&(entry.description=n2.description),"documentationLinks"in n2&&n2.documentationLinks&&n2.documentationLinks.length&&(entry.documentationLinks=n2.documentationLinks.map(function(link){return{uri:link.uri,type:link.type}})),nodes.push(entry)}}if(ids.length>0)for(var ii=0;ii<ids.length&&nodes.length<maxNodes;ii++){var node=await figma.getNodeByIdAsync(ids[ii]);await pushNodeEntry(node)}else if(scanPage){var cur=figma.currentPage;cur&&cur.loadAsync&&await cur.loadAsync();for(var found=cur&&cur.findAllWithCriteria?cur.findAllWithCriteria({types:["COMPONENT","COMPONENT_SET","INSTANCE"]}):[],fi=0;fi<found.length&&nodes.length<maxNodes;fi++)await pushNodeEntry(found[fi])}else throw new Error("Provide nodeIds array or set scanCurrentPage=true");figma.ui.postMessage({type:"GET_CODE_CONNECT_HINTS_RESULT",requestId:msg.requestId,success:!0,data:{nodes,note:"Full Code Connect source paths live in repo figma.config / CLI; use Figma official MCP for native get_code_connect_map when needed."}})}catch(error2){var emsg2=error2&&error2.message?error2.message:String(error2);figma.ui.postMessage({type:"GET_CODE_CONNECT_HINTS_RESULT",requestId:msg.requestId,success:!1,error:emsg2})}else if(msg.type==="INSTANTIATE_COMPONENT")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Instantiating component:",msg.componentKey||msg.nodeId);var component=null,instance=null;if(msg.componentKey)try{component=await figma.importComponentByKeyAsync(msg.componentKey)}catch{console.log("\u{1F309} [F-MCP ATezer Bridge] Not a published component, trying local...")}if(!component&&msg.nodeId){var node=await figma.getNodeByIdAsync(msg.nodeId);if(node){if(node.type==="COMPONENT")component=node;else if(node.type==="COMPONENT_SET"){if(msg.variant&&node.children&&node.children.length>0){var variantParts=[];for(var prop in msg.variant)msg.variant.hasOwnProperty(prop)&&variantParts.push(prop+"="+msg.variant[prop]);var targetVariantName=variantParts.join(", ");console.log("\u{1F309} [F-MCP ATezer Bridge] Looking for variant:",targetVariantName);for(var i=0;i<node.children.length;i++){var child=node.children[i];if(child.type==="COMPONENT"&&child.name===targetVariantName){component=child,console.log("\u{1F309} [F-MCP ATezer Bridge] Found exact variant match");break}}if(!component)for(var i=0;i<node.children.length;i++){var child=node.children[i];if(child.type==="COMPONENT"){var matches=!0;for(var prop in msg.variant)if(msg.variant.hasOwnProperty(prop)){var expected=prop+"="+msg.variant[prop];if(child.name.indexOf(expected)===-1){matches=!1;break}}if(matches){component=child,console.log("\u{1F309} [F-MCP ATezer Bridge] Found partial variant match:",child.name);break}}}}!component&&node.children&&node.children.length>0&&(component=node.children[0],console.log("\u{1F309} [F-MCP ATezer Bridge] Using default variant:",component.name))}}}if(!component){var errorParts=["Component not found."];throw msg.componentKey&&errorParts.push('Published component key "'+msg.componentKey+'" could not be imported - it may have been unpublished or deleted from the library.'),msg.nodeId&&errorParts.push('Local nodeId "'+msg.nodeId+'" does not exist in this file - nodeIds are session-specific and may be stale.'),!msg.componentKey&&!msg.nodeId&&errorParts.push("No componentKey or nodeId was provided."),errorParts.push("SUGGESTION: Use figma_search_components to get current component identifiers before instantiating."),new Error(errorParts.join(" "))}if(instance=component.createInstance(),msg.position&&(instance.x=msg.position.x||0,instance.y=msg.position.y||0),msg.size&&instance.resize(msg.size.width,msg.size.height),msg.overrides){for(var propName in msg.overrides)if(msg.overrides.hasOwnProperty(propName))try{instance.setProperties({[propName]:msg.overrides[propName]})}catch(propError){console.warn("\u{1F309} [F-MCP ATezer Bridge] Could not set property "+propName+":",propError.message)}}if(msg.variant)try{instance.setProperties(msg.variant)}catch(variantError){console.warn("\u{1F309} [F-MCP ATezer Bridge] Could not set variant:",variantError.message)}if(msg.parentId){var parent=await figma.getNodeByIdAsync(msg.parentId);parent&&"appendChild"in parent&&parent.appendChild(instance)}console.log("\u{1F309} [F-MCP ATezer Bridge] Component instantiated:",instance.id),figma.ui.postMessage({type:"INSTANTIATE_COMPONENT_RESULT",requestId:msg.requestId,success:!0,instance:{id:instance.id,name:instance.name,x:instance.x,y:instance.y,width:instance.width,height:instance.height}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Instantiate component error:",errorMsg),figma.ui.postMessage({type:"INSTANTIATE_COMPONENT_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SET_NODE_DESCRIPTION")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Setting description on node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("description"in node))throw new Error("Node type "+node.type+" does not support description");node.description=msg.description||"",msg.descriptionMarkdown&&"descriptionMarkdown"in node&&(node.descriptionMarkdown=msg.descriptionMarkdown),console.log("\u{1F309} [F-MCP ATezer Bridge] Description set successfully"),figma.ui.postMessage({type:"SET_NODE_DESCRIPTION_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name,description:node.description}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Set description error:",errorMsg),figma.ui.postMessage({type:"SET_NODE_DESCRIPTION_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="ADD_COMPONENT_PROPERTY")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Adding component property:",msg.propertyName,"type:",msg.propertyType);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(node.type!=="COMPONENT"&&node.type!=="COMPONENT_SET")throw new Error("Node must be a COMPONENT or COMPONENT_SET. Got: "+node.type);if(node.type==="COMPONENT"&&node.parent&&node.parent.type==="COMPONENT_SET")throw new Error("Cannot add properties to variant components. Add to the parent COMPONENT_SET instead.");var options=void 0;msg.preferredValues&&(options={preferredValues:msg.preferredValues});var propertyNameWithId=node.addComponentProperty(msg.propertyName,msg.propertyType,msg.defaultValue,options);console.log("\u{1F309} [F-MCP ATezer Bridge] Property added:",propertyNameWithId),figma.ui.postMessage({type:"ADD_COMPONENT_PROPERTY_RESULT",requestId:msg.requestId,success:!0,propertyName:propertyNameWithId})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Add component property error:",errorMsg),figma.ui.postMessage({type:"ADD_COMPONENT_PROPERTY_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="EDIT_COMPONENT_PROPERTY")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Editing component property:",msg.propertyName);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(node.type!=="COMPONENT"&&node.type!=="COMPONENT_SET")throw new Error("Node must be a COMPONENT or COMPONENT_SET. Got: "+node.type);var propertyNameWithId=node.editComponentProperty(msg.propertyName,msg.newValue);console.log("\u{1F309} [F-MCP ATezer Bridge] Property edited:",propertyNameWithId),figma.ui.postMessage({type:"EDIT_COMPONENT_PROPERTY_RESULT",requestId:msg.requestId,success:!0,propertyName:propertyNameWithId})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Edit component property error:",errorMsg),figma.ui.postMessage({type:"EDIT_COMPONENT_PROPERTY_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="DELETE_COMPONENT_PROPERTY")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Deleting component property:",msg.propertyName);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(node.type!=="COMPONENT"&&node.type!=="COMPONENT_SET")throw new Error("Node must be a COMPONENT or COMPONENT_SET. Got: "+node.type);node.deleteComponentProperty(msg.propertyName),console.log("\u{1F309} [F-MCP ATezer Bridge] Property deleted"),figma.ui.postMessage({type:"DELETE_COMPONENT_PROPERTY_RESULT",requestId:msg.requestId,success:!0})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Delete component property error:",errorMsg),figma.ui.postMessage({type:"DELETE_COMPONENT_PROPERTY_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="RESIZE_NODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Resizing node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("resize"in node))throw new Error("Node type "+node.type+" does not support resize");msg.withConstraints?node.resize(msg.width,msg.height):node.resizeWithoutConstraints(msg.width,msg.height),console.log("\u{1F309} [F-MCP ATezer Bridge] Node resized to:",msg.width,"x",msg.height),figma.ui.postMessage({type:"RESIZE_NODE_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name,width:node.width,height:node.height}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Resize node error:",errorMsg),figma.ui.postMessage({type:"RESIZE_NODE_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="MOVE_NODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Moving node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("x"in node))throw new Error("Node type "+node.type+" does not support positioning");node.x=msg.x,node.y=msg.y,console.log("\u{1F309} [F-MCP ATezer Bridge] Node moved to:",msg.x,",",msg.y),figma.ui.postMessage({type:"MOVE_NODE_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name,x:node.x,y:node.y}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Move node error:",errorMsg),figma.ui.postMessage({type:"MOVE_NODE_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SET_NODE_FILLS")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Setting fills on node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("fills"in node))throw new Error("Node type "+node.type+" does not support fills");var processedFills=msg.fills.map(function(fill){if(fill.type==="SOLID"&&typeof fill.color=="string"){var rgb=hexToFigmaRGB(fill.color);return{type:"SOLID",color:{r:rgb.r,g:rgb.g,b:rgb.b},opacity:rgb.a!==void 0?rgb.a:fill.opacity!==void 0?fill.opacity:1}}return fill});node.fills=processedFills,console.log("\u{1F309} [F-MCP ATezer Bridge] Fills set successfully"),figma.ui.postMessage({type:"SET_NODE_FILLS_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Set fills error:",errorMsg),figma.ui.postMessage({type:"SET_NODE_FILLS_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SET_NODE_STROKES")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Setting strokes on node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("strokes"in node))throw new Error("Node type "+node.type+" does not support strokes");var processedStrokes=msg.strokes.map(function(stroke){if(stroke.type==="SOLID"&&typeof stroke.color=="string"){var rgb=hexToFigmaRGB(stroke.color);return{type:"SOLID",color:{r:rgb.r,g:rgb.g,b:rgb.b},opacity:rgb.a!==void 0?rgb.a:stroke.opacity!==void 0?stroke.opacity:1}}return stroke});node.strokes=processedStrokes,msg.strokeWeight!==void 0&&(node.strokeWeight=msg.strokeWeight),console.log("\u{1F309} [F-MCP ATezer Bridge] Strokes set successfully"),figma.ui.postMessage({type:"SET_NODE_STROKES_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Set strokes error:",errorMsg),figma.ui.postMessage({type:"SET_NODE_STROKES_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SET_NODE_OPACITY")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Setting opacity on node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("opacity"in node))throw new Error("Node type "+node.type+" does not support opacity");node.opacity=Math.max(0,Math.min(1,msg.opacity)),console.log("\u{1F309} [F-MCP ATezer Bridge] Opacity set to:",node.opacity),figma.ui.postMessage({type:"SET_NODE_OPACITY_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name,opacity:node.opacity}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Set opacity error:",errorMsg),figma.ui.postMessage({type:"SET_NODE_OPACITY_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SET_NODE_CORNER_RADIUS")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Setting corner radius on node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("cornerRadius"in node))throw new Error("Node type "+node.type+" does not support corner radius");node.cornerRadius=msg.radius,console.log("\u{1F309} [F-MCP ATezer Bridge] Corner radius set to:",msg.radius),figma.ui.postMessage({type:"SET_NODE_CORNER_RADIUS_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name,cornerRadius:node.cornerRadius}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Set corner radius error:",errorMsg),figma.ui.postMessage({type:"SET_NODE_CORNER_RADIUS_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="CLONE_NODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Cloning node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("clone"in node))throw new Error("Node type "+node.type+" does not support cloning");var clonedNode=node.clone();console.log("\u{1F309} [F-MCP ATezer Bridge] Node cloned:",clonedNode.id),figma.ui.postMessage({type:"CLONE_NODE_RESULT",requestId:msg.requestId,success:!0,node:{id:clonedNode.id,name:clonedNode.name,x:clonedNode.x,y:clonedNode.y}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Clone node error:",errorMsg),figma.ui.postMessage({type:"CLONE_NODE_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="DELETE_NODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Deleting node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);var deletedInfo={id:node.id,name:node.name};node.remove(),console.log("\u{1F309} [F-MCP ATezer Bridge] Node deleted"),figma.ui.postMessage({type:"DELETE_NODE_RESULT",requestId:msg.requestId,success:!0,deleted:deletedInfo})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Delete node error:",errorMsg),figma.ui.postMessage({type:"DELETE_NODE_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="RENAME_NODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Renaming node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);var oldName=node.name;node.name=msg.newName,console.log('\u{1F309} [F-MCP ATezer Bridge] Node renamed from "'+oldName+'" to "'+msg.newName+'"'),figma.ui.postMessage({type:"RENAME_NODE_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name,oldName}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Rename node error:",errorMsg),figma.ui.postMessage({type:"RENAME_NODE_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SET_TEXT_CONTENT")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Setting text content on node:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(node.type!=="TEXT")throw new Error("Node must be a TEXT node. Got: "+node.type);await figma.loadFontAsync(node.fontName),node.characters=msg.text,msg.fontSize&&(node.fontSize=msg.fontSize),console.log("\u{1F309} [F-MCP ATezer Bridge] Text content set"),figma.ui.postMessage({type:"SET_TEXT_CONTENT_RESULT",requestId:msg.requestId,success:!0,node:{id:node.id,name:node.name,characters:node.characters}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Set text content error:",errorMsg),figma.ui.postMessage({type:"SET_TEXT_CONTENT_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="CREATE_CHILD_NODE")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Creating child node of type:",msg.nodeType);var parent=await figma.getNodeByIdAsync(msg.parentId);if(!parent)throw new Error("Parent node not found: "+msg.parentId);if(!("appendChild"in parent))throw new Error("Parent node type "+parent.type+" does not support children");var newNode,props=msg.properties||{};switch(msg.nodeType){case"RECTANGLE":newNode=figma.createRectangle();break;case"ELLIPSE":newNode=figma.createEllipse();break;case"FRAME":newNode=figma.createFrame();break;case"TEXT":newNode=figma.createText(),await figma.loadFontAsync({family:"Inter",style:"Regular"}),newNode.fontName={family:"Inter",style:"Regular"},props.text&&(newNode.characters=props.text);break;case"LINE":newNode=figma.createLine();break;case"POLYGON":newNode=figma.createPolygon();break;case"STAR":newNode=figma.createStar();break;case"VECTOR":newNode=figma.createVector();break;default:throw new Error("Unsupported node type: "+msg.nodeType)}if(props.name&&(newNode.name=props.name),props.x!==void 0&&(newNode.x=props.x),props.y!==void 0&&(newNode.y=props.y),props.width!==void 0&&props.height!==void 0&&newNode.resize(props.width,props.height),props.fills){var processedFills=props.fills.map(function(fill){if(fill.type==="SOLID"&&typeof fill.color=="string"){var rgb=hexToFigmaRGB(fill.color);return{type:"SOLID",color:{r:rgb.r,g:rgb.g,b:rgb.b},opacity:rgb.a!==void 0?rgb.a:1}}return fill});newNode.fills=processedFills}parent.appendChild(newNode),console.log("\u{1F309} [F-MCP ATezer Bridge] Child node created:",newNode.id),figma.ui.postMessage({type:"CREATE_CHILD_NODE_RESULT",requestId:msg.requestId,success:!0,node:{id:newNode.id,name:newNode.name,type:newNode.type,x:newNode.x,y:newNode.y,width:newNode.width,height:newNode.height}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Create child node error:",errorMsg),figma.ui.postMessage({type:"CREATE_CHILD_NODE_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="CAPTURE_SCREENSHOT")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Capturing screenshot for node:",msg.nodeId);var node=msg.nodeId?await figma.getNodeByIdAsync(msg.nodeId):figma.currentPage;if(!node)throw new Error("Node not found: "+msg.nodeId);if(!("exportAsync"in node))throw new Error("Node type "+node.type+" does not support export");var format=msg.format||"PNG",scale=msg.scale||2,exportSettings={format,constraint:{type:"SCALE",value:scale}},bytes=await node.exportAsync(exportSettings),base64=figma.base64Encode(bytes),bounds=null;"absoluteBoundingBox"in node&&(bounds=node.absoluteBoundingBox),console.log("\u{1F309} [F-MCP ATezer Bridge] Screenshot captured:",bytes.length,"bytes"),figma.ui.postMessage({type:"CAPTURE_SCREENSHOT_RESULT",requestId:msg.requestId,success:!0,image:{base64,format,scale,byteLength:bytes.length,node:{id:node.id,name:node.name,type:node.type},bounds}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Screenshot capture error:",errorMsg),figma.ui.postMessage({type:"CAPTURE_SCREENSHOT_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="SET_INSTANCE_PROPERTIES")try{console.log("\u{1F309} [F-MCP ATezer Bridge] Setting instance properties on:",msg.nodeId);var node=await figma.getNodeByIdAsync(msg.nodeId);if(!node)throw new Error("Node not found: "+msg.nodeId);if(node.type!=="INSTANCE")throw new Error("Node must be an INSTANCE. Got: "+node.type);var currentProps=node.componentProperties;console.log("\u{1F309} [F-MCP ATezer Bridge] Current properties:",JSON.stringify(Object.keys(currentProps)));var propsToSet={},propUpdates=msg.properties||{};for(var propName in propUpdates){var newValue=propUpdates[propName];if(currentProps[propName]!==void 0)propsToSet[propName]=newValue,console.log("\u{1F309} [F-MCP ATezer Bridge] Setting property:",propName,"=",newValue);else{var foundMatch=!1;for(var existingProp in currentProps)if(existingProp.startsWith(propName+"#")){propsToSet[existingProp]=newValue,console.log("\u{1F309} [F-MCP ATezer Bridge] Found suffixed property:",existingProp,"=",newValue),foundMatch=!0;break}foundMatch||console.warn("\u{1F309} [F-MCP ATezer Bridge] Property not found:",propName,"- Available:",Object.keys(currentProps).join(", "))}}if(Object.keys(propsToSet).length===0)throw new Error("No valid properties to set. Available properties: "+Object.keys(currentProps).join(", "));node.setProperties(propsToSet);var updatedProps=node.componentProperties;console.log("\u{1F309} [F-MCP ATezer Bridge] Instance properties updated"),figma.ui.postMessage({type:"SET_INSTANCE_PROPERTIES_RESULT",requestId:msg.requestId,success:!0,instance:{id:node.id,name:node.name,componentId:node.mainComponent?node.mainComponent.id:null,propertiesSet:Object.keys(propsToSet),currentProperties:Object.keys(updatedProps).reduce(function(acc,key){return acc[key]={type:updatedProps[key].type,value:updatedProps[key].value},acc},{})}})}catch(error){var errorMsg=error&&error.message?error.message:String(error);console.error("\u{1F309} [F-MCP ATezer Bridge] Set instance properties error:",errorMsg),figma.ui.postMessage({type:"SET_INSTANCE_PROPERTIES_RESULT",requestId:msg.requestId,success:!1,error:errorMsg})}else if(msg.type==="GET_DOCUMENT_STRUCTURE")try{await figma.loadAllPagesAsync();var depth=Math.min(Math.max(msg.depth||1,0),3),verbosity=msg.verbosity||"summary",opts={verbosity,includeLayout:msg.includeLayout===!0,includeVisual:msg.includeVisual===!0,includeTypography:msg.includeTypography===!0,includeCodeReady:msg.includeCodeReady!==!1,outputHint:msg.outputHint||null},document={name:figma.root.name,id:figma.root.id,type:"DOCUMENT",fileKey:figma.fileKey||null,children:figma.root.children?(await Promise.all(figma.root.children.map(function(p2){return buildNodePayload(p2,1,depth,opts)}))).filter(Boolean):[]};figma.ui.postMessage({type:"GET_DOCUMENT_STRUCTURE_RESULT",requestId:msg.requestId,success:!0,data:{document,fileKey:figma.fileKey,fileName:figma.root.name}})}catch(error){var errMsg=error&&error.message?error.message:String(error);figma.ui.postMessage({type:"GET_DOCUMENT_STRUCTURE_RESULT",requestId:msg.requestId,success:!1,error:errMsg})}else if(msg.type==="GET_NODE_CONTEXT")try{var nodeId=msg.nodeId;if(!nodeId)figma.ui.postMessage({type:"GET_NODE_CONTEXT_RESULT",requestId:msg.requestId,success:!1,error:"nodeId is required"});else{var targetNode=await figma.getNodeByIdAsync(nodeId);if(!targetNode)figma.ui.postMessage({type:"GET_NODE_CONTEXT_RESULT",requestId:msg.requestId,success:!1,error:"Node not found: "+nodeId});else{var depthNode=Math.min(Math.max(msg.depth||2,0),3),verbosityNode=msg.verbosity||"standard",optsNode={verbosity:verbosityNode,includeLayout:msg.includeLayout===!0,includeVisual:msg.includeVisual===!0,includeTypography:msg.includeTypography===!0,includeCodeReady:msg.includeCodeReady!==!1,outputHint:msg.outputHint||null},nodeTree=await buildNodePayload(targetNode,0,depthNode,optsNode);figma.ui.postMessage({type:"GET_NODE_CONTEXT_RESULT",requestId:msg.requestId,success:!0,data:{node:nodeTree,fileKey:figma.fileKey||null,fileName:figma.root.name}})}}}catch(error){var errMsg=error&&error.message?error.message:String(error);figma.ui.postMessage({type:"GET_NODE_CONTEXT_RESULT",requestId:msg.requestId,success:!1,error:errMsg})}else if(msg.type==="GET_CONSOLE_LOGS"){var limit=Math.min(Math.max(1,parseInt(msg.limit,10)||50),200),slice=__consoleLogBuffer.slice(-limit);figma.ui.postMessage({type:"CONSOLE_LOGS_RESULT",requestId:msg.requestId,success:!0,data:{logs:slice,total:__consoleLogBuffer.length}})}else if(msg.type==="CLEAR_CONSOLE")__consoleLogBuffer.length=0,figma.ui.postMessage({type:"CLEAR_CONSOLE_RESULT",requestId:msg.requestId,success:!0});else if(msg.type==="BATCH_CREATE_VARIABLES"){var items=msg.items||[];items.length>100&&(items=items.slice(0,100));for(var created=[],failed=[],i=0;i<items.length;i++){var it=items[i];try{var coll=await figma.variables.getVariableCollectionByIdAsync(it.collectionId);if(!coll)throw new Error("Collection not found");var valuesByMode={};it.modeId!=null&&it.value!==void 0?valuesByMode[it.modeId]=it.value:it.valuesByMode&&(valuesByMode=it.valuesByMode);var v=figma.variables.createVariable(it.name,coll,it.resolvedType);if(Object.keys(valuesByMode).length){var modeId=it.modeId||Object.keys(valuesByMode)[0];modeId&&v.setValueForMode(modeId,valuesByMode[modeId])}created.push({name:it.name,id:v.id})}catch(e){failed.push({name:it.name,error:e.message||String(e)})}}figma.ui.postMessage({type:"BATCH_CREATE_VARIABLES_RESULT",requestId:msg.requestId,success:!0,data:{created,failed}})}else if(msg.type==="BATCH_UPDATE_VARIABLES"){var updates=msg.items||[];updates.length>100&&(updates=updates.slice(0,100));for(var updated=[],failed=[],j=0;j<updates.length;j++){var u=updates[j];try{var variable=await figma.variables.getVariableByIdAsync(u.variableId);if(!variable)throw new Error("Variable not found");variable.setValueForMode(u.modeId,u.value),updated.push({variableId:u.variableId})}catch(e){failed.push({variableId:u.variableId,error:e.message||String(e)})}}figma.ui.postMessage({type:"BATCH_UPDATE_VARIABLES_RESULT",requestId:msg.requestId,success:!0,data:{updated,failed}})}else if(msg.type==="SETUP_DESIGN_TOKENS"){var collectionName=msg.collectionName||"Design Tokens",modes=msg.modes||["Default"],tokens=msg.tokens||[],createdVarIds=[],collection=null;try{if(collection=figma.variables.createVariableCollection(collectionName),modes.length>1)for(var m=1;m<modes.length;m++)collection.addMode(modes[m]);for(var defaultModeId=collection.modes[0].modeId,t=0;t<tokens.length;t++){var tok=tokens[t],name=typeof tok=="object"?tok.name:String(tok),type=(typeof tok=="object"?tok.type:null)||"STRING",values=typeof tok=="object"&&tok.values?tok.values:typeof tok=="object"?tok.value:void 0,valsByMode=typeof values=="object"&&!Array.isArray(values)?values:{[defaultModeId]:values},variable=figma.variables.createVariable(name,collection,type);for(var mid in valsByMode)variable.setValueForMode(mid,valsByMode[mid]);createdVarIds.push(variable.id)}figma.ui.postMessage({type:"SETUP_DESIGN_TOKENS_RESULT",requestId:msg.requestId,success:!0,data:{collectionId:collection.id,collectionName:collection.name,variableIds:createdVarIds,modeCount:modes.length}})}catch(err){if(collection){for(var d=0;d<createdVarIds.length;d++)try{var vv=await figma.variables.getVariableByIdAsync(createdVarIds[d]);vv&&vv.remove()}catch{}try{collection.remove()}catch{}}figma.ui.postMessage({type:"SETUP_DESIGN_TOKENS_RESULT",requestId:msg.requestId,success:!1,error:err.message||String(err)})}}else if(msg.type==="ARRANGE_COMPONENT_SET")try{var nodeIds=msg.nodeIds||[];if(nodeIds.length<2)throw new Error("At least 2 component node IDs required");for(var nodes=[],parent=null,n=0;n<nodeIds.length;n++){var nd=figma.getNodeById(nodeIds[n]);if(!nd)throw new Error("Node not found: "+nodeIds[n]);if(nd.type!=="COMPONENT")throw new Error("Node is not a COMPONENT: "+nodeIds[n]);nodes.push(nd),parent||(parent=nd.parent)}if(!parent||!parent.appendChild)throw new Error("Parent does not support children");var componentSet=figma.combineAsVariants(nodes,parent);figma.ui.postMessage({type:"ARRANGE_COMPONENT_SET_RESULT",requestId:msg.requestId,success:!0,data:{nodeId:componentSet.id,name:componentSet.name}})}catch(err){figma.ui.postMessage({type:"ARRANGE_COMPONENT_SET_RESULT",requestId:msg.requestId,success:!1,error:err.message||String(err)})}else if(msg.type==="GET_LOCAL_STYLES")try{let styleToSummary2=function(s){return{id:s.id,name:s.name}},styleToFull2=function(s){var o={id:s.id,name:s.name,description:s.description||""};return s.paints&&(o.paints=s.paints),s.fontName&&(o.fontName=s.fontName),s.fontSize!==void 0&&(o.fontSize=s.fontSize),s.effects&&(o.effects=s.effects),o};var styleToSummary=styleToSummary2,styleToFull=styleToFull2,verbosity=msg.verbosity||"summary",paints=await figma.getLocalPaintStylesAsync(),texts=await figma.getLocalTextStylesAsync(),effects=await figma.getLocalEffectStylesAsync(),mapFn=verbosity==="full"?styleToFull2:styleToSummary2;figma.ui.postMessage({type:"GET_LOCAL_STYLES_RESULT",requestId:msg.requestId,success:!0,data:{paintStyles:paints.map(mapFn),textStyles:texts.map(mapFn),effectStyles:effects.map(mapFn)}})}catch(error){var errMsg=error&&error.message?error.message:String(error);figma.ui.postMessage({type:"GET_LOCAL_STYLES_RESULT",requestId:msg.requestId,success:!1,error:errMsg})}},console.log("\u{1F309} [F-MCP ATezer Bridge] Ready to handle component requests"),console.log("\u{1F309} [F-MCP ATezer Bridge] Plugin will stay open until manually closed");
1
+ // F-MCP ATezer Bridge - MCP Plugin
2
+ // Bridges Figma API to MCP clients via plugin UI window
3
+ // Supports: Variables, Components, Styles, and more
4
+ // Uses postMessage to communicate with UI, bypassing worker sandbox limitations
5
+ // Puppeteer can access UI iframe's window context to retrieve data
6
+
7
+ // Console log buffer for figma_get_console_logs (no CDP)
8
+ var __consoleLogBuffer = [];
9
+ var __consoleLogLimit = 200;
10
+ var _origLog = console.log, _origWarn = console.warn, _origError = console.error;
11
+ function _pushLog(level, args) {
12
+ var arr = Array.prototype.slice.call(args);
13
+ __consoleLogBuffer.push({ level: level, time: Date.now(), args: arr });
14
+ if (__consoleLogBuffer.length > __consoleLogLimit) __consoleLogBuffer.shift();
15
+ }
16
+ console.log = function() { _pushLog('log', arguments); _origLog.apply(console, arguments); };
17
+ console.warn = function() { _pushLog('warn', arguments); _origWarn.apply(console, arguments); };
18
+ console.error = function() { _pushLog('error', arguments); _origError.apply(console, arguments); };
19
+
20
+ console.log('🌉 [F-MCP ATezer Bridge] Plugin loaded and ready');
21
+
22
+ // Start compact; UI asks for dynamic resize based on content.
23
+ figma.showUI(__html__, { width: 280, height: 96, visible: true, themeColors: true });
24
+
25
+ // Send file identity to UI so it can include it in the WebSocket handshake
26
+ figma.ui.postMessage({
27
+ type: 'FILE_IDENTITY',
28
+ fileKey: figma.fileKey || null,
29
+ fileName: figma.root.name || null
30
+ });
31
+
32
+ // Restore saved Figma REST API token from clientStorage (persists across plugin restarts)
33
+ (async () => {
34
+ try {
35
+ var saved = await figma.clientStorage.getAsync('figmaRestToken');
36
+ if (saved && typeof saved === 'object' && saved.token) {
37
+ figma.ui.postMessage({
38
+ type: 'RESTORE_TOKEN',
39
+ token: saved.token,
40
+ savedAt: saved.savedAt || 0,
41
+ expiresAt: saved.expiresAt || 0
42
+ });
43
+ console.log('🌉 [F-MCP] Restored saved REST API token from clientStorage');
44
+ }
45
+ } catch (e) {
46
+ console.warn('🌉 [F-MCP] Could not restore token:', e && e.message ? e.message : String(e));
47
+ }
48
+ })();
49
+
50
+ // Immediately fetch and send variables data to UI
51
+ (async () => {
52
+ try {
53
+ console.log('🌉 [F-MCP ATezer Bridge] Fetching variables...');
54
+
55
+ // FigJam does not support figma.variables API
56
+ if (!figma.variables || typeof figma.variables.getLocalVariablesAsync !== 'function') {
57
+ console.log('🌉 [F-MCP ATezer Bridge] Variables API not available (FigJam or limited editor), skipping variable load');
58
+ figma.ui.postMessage({
59
+ type: 'VARIABLES_DATA',
60
+ data: {
61
+ success: true,
62
+ timestamp: Date.now(),
63
+ fileKey: figma.fileKey || null,
64
+ variables: [],
65
+ variableCollections: [],
66
+ _note: 'Variables API not available in this editor type'
67
+ }
68
+ });
69
+ return;
70
+ }
71
+
72
+ // Get all local variables and collections
73
+ const variables = await figma.variables.getLocalVariablesAsync();
74
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
75
+
76
+ console.log(`🌉 [F-MCP ATezer Bridge] Found ${variables.length} variables in ${collections.length} collections`);
77
+
78
+ // Format the data
79
+ const variablesData = {
80
+ success: true,
81
+ timestamp: Date.now(),
82
+ fileKey: figma.fileKey || null,
83
+ variables: variables.map(v => ({
84
+ id: v.id,
85
+ name: v.name,
86
+ key: v.key,
87
+ resolvedType: v.resolvedType,
88
+ valuesByMode: v.valuesByMode,
89
+ variableCollectionId: v.variableCollectionId,
90
+ scopes: v.scopes,
91
+ description: v.description,
92
+ hiddenFromPublishing: v.hiddenFromPublishing
93
+ })),
94
+ variableCollections: collections.map(c => ({
95
+ id: c.id,
96
+ name: c.name,
97
+ key: c.key,
98
+ modes: c.modes,
99
+ defaultModeId: c.defaultModeId,
100
+ variableIds: c.variableIds
101
+ }))
102
+ };
103
+
104
+ // Send to UI via postMessage
105
+ figma.ui.postMessage({
106
+ type: 'VARIABLES_DATA',
107
+ data: variablesData
108
+ });
109
+
110
+ console.log('🌉 [F-MCP ATezer Bridge] Variables data sent to UI successfully');
111
+ console.log('🌉 [F-MCP ATezer Bridge] UI iframe now has variables data accessible via window.__figmaVariablesData');
112
+
113
+ } catch (error) {
114
+ console.error('🌉 [F-MCP ATezer Bridge] Error fetching variables:', error);
115
+ figma.ui.postMessage({
116
+ type: 'ERROR',
117
+ error: error.message || String(error)
118
+ });
119
+ }
120
+ })();
121
+
122
+ // Helper function to serialize a variable for response
123
+ function serializeVariable(v) {
124
+ return {
125
+ id: v.id,
126
+ name: v.name,
127
+ key: v.key,
128
+ resolvedType: v.resolvedType,
129
+ valuesByMode: v.valuesByMode,
130
+ variableCollectionId: v.variableCollectionId,
131
+ scopes: v.scopes,
132
+ description: v.description,
133
+ hiddenFromPublishing: v.hiddenFromPublishing
134
+ };
135
+ }
136
+
137
+ // Helper function to serialize a collection for response
138
+ function serializeCollection(c) {
139
+ return {
140
+ id: c.id,
141
+ name: c.name,
142
+ key: c.key,
143
+ modes: c.modes,
144
+ defaultModeId: c.defaultModeId,
145
+ variableIds: c.variableIds
146
+ };
147
+ }
148
+
149
+ // Helper to convert hex color to Figma RGB (0-1 range)
150
+ function hexToFigmaRGB(hex) {
151
+ // Remove # if present
152
+ hex = hex.replace(/^#/, '');
153
+
154
+ // Validate hex characters BEFORE parsing (prevents NaN values)
155
+ if (!/^[0-9A-Fa-f]+$/.test(hex)) {
156
+ throw new Error('Invalid hex color: "' + hex + '" contains non-hex characters. Use only 0-9 and A-F.');
157
+ }
158
+
159
+ // Parse hex values
160
+ var r, g, b, a = 1;
161
+
162
+ if (hex.length === 3) {
163
+ // #RGB format
164
+ r = parseInt(hex[0] + hex[0], 16) / 255;
165
+ g = parseInt(hex[1] + hex[1], 16) / 255;
166
+ b = parseInt(hex[2] + hex[2], 16) / 255;
167
+ } else if (hex.length === 4) {
168
+ // #RGBA format (CSS4 shorthand)
169
+ r = parseInt(hex[0] + hex[0], 16) / 255;
170
+ g = parseInt(hex[1] + hex[1], 16) / 255;
171
+ b = parseInt(hex[2] + hex[2], 16) / 255;
172
+ a = parseInt(hex[3] + hex[3], 16) / 255;
173
+ } else if (hex.length === 6) {
174
+ // #RRGGBB format
175
+ r = parseInt(hex.substring(0, 2), 16) / 255;
176
+ g = parseInt(hex.substring(2, 4), 16) / 255;
177
+ b = parseInt(hex.substring(4, 6), 16) / 255;
178
+ } else if (hex.length === 8) {
179
+ // #RRGGBBAA format
180
+ r = parseInt(hex.substring(0, 2), 16) / 255;
181
+ g = parseInt(hex.substring(2, 4), 16) / 255;
182
+ b = parseInt(hex.substring(4, 6), 16) / 255;
183
+ a = parseInt(hex.substring(6, 8), 16) / 255;
184
+ } else {
185
+ throw new Error('Invalid hex color format: "' + hex + '". Expected 3, 4, 6, or 8 hex characters (e.g., #RGB, #RGBA, #RRGGBB, #RRGGBBAA).');
186
+ }
187
+
188
+ return { r: r, g: g, b: b, a: a };
189
+ }
190
+
191
+ // Listen for requests from UI (e.g., component data requests, write operations)
192
+ figma.ui.onmessage = async (msg) => {
193
+ // Token persistence via figma.clientStorage (survives plugin close/reopen)
194
+ if (msg.type === 'SAVE_TOKEN') {
195
+ try {
196
+ await figma.clientStorage.setAsync('figmaRestToken', {
197
+ token: msg.token,
198
+ savedAt: Date.now(),
199
+ expiresAt: msg.expiresAt || 0
200
+ });
201
+ figma.ui.postMessage({ type: 'TOKEN_SAVED', success: true });
202
+ console.log('🌉 [F-MCP] Token saved to clientStorage');
203
+ } catch (e) {
204
+ figma.ui.postMessage({ type: 'TOKEN_SAVED', success: false, error: String(e) });
205
+ }
206
+ return;
207
+ }
208
+ if (msg.type === 'DELETE_TOKEN') {
209
+ try {
210
+ await figma.clientStorage.deleteAsync('figmaRestToken');
211
+ figma.ui.postMessage({ type: 'TOKEN_DELETED', success: true });
212
+ console.log('🌉 [F-MCP] Token deleted from clientStorage');
213
+ } catch (e) {
214
+ figma.ui.postMessage({ type: 'TOKEN_DELETED', success: false });
215
+ }
216
+ return;
217
+ }
218
+ if (msg.type === 'RESIZE_UI') {
219
+ try {
220
+ var requestedWidth = Number(msg.width);
221
+ var requestedHeight = Number(msg.height);
222
+ var width = Math.max(220, Math.min(520, Math.round(isNaN(requestedWidth) ? 280 : requestedWidth)));
223
+ var height = Math.max(72, Math.min(420, Math.round(isNaN(requestedHeight) ? 96 : requestedHeight)));
224
+ figma.ui.resize(width, height);
225
+ } catch (error) {
226
+ console.warn('🌉 [F-MCP ATezer Bridge] UI resize failed:', error && error.message ? error.message : String(error));
227
+ }
228
+ return;
229
+ }
230
+
231
+ function rgbaToHex(color) {
232
+ if (!color || typeof color !== 'object') return null;
233
+ var r = Math.round((Number(color.r) !== undefined ? Number(color.r) : 0) * 255);
234
+ var g = Math.round((Number(color.g) !== undefined ? Number(color.g) : 0) * 255);
235
+ var b = Math.round((Number(color.b) !== undefined ? Number(color.b) : 0) * 255);
236
+ return '#' + [r, g, b].map(function(x) { return x.toString(16).padStart(2, '0'); }).join('');
237
+ }
238
+
239
+ function nameToSuiComponent(name, description) {
240
+ var raw = (name || '') + (description ? ' ' + description : '');
241
+ raw = raw.replace(/[0-9_\-\s]+/g, ' ').trim().split(/\s+/);
242
+ if (raw.length === 0 || (raw.length === 1 && !raw[0])) return null;
243
+ return raw.map(function(w) { return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase(); }).join('');
244
+ }
245
+
246
+ function buildLayoutSummary(layout, outputHint) {
247
+ if (!layout || !layout.layoutMode) return null;
248
+ var parts = [];
249
+ var dir = layout.layoutMode === 'HORIZONTAL' ? 'row' : layout.layoutMode === 'VERTICAL' ? 'col' : 'grid';
250
+ if (outputHint === 'tailwind') {
251
+ parts.push('flex' + (dir === 'row' ? '' : '-col'));
252
+ if (layout.itemSpacing != null) parts.push('gap-' + Math.round(layout.itemSpacing));
253
+ var p = layout.paddingLeft || layout.paddingRight || layout.paddingTop || layout.paddingBottom;
254
+ if (p != null) parts.push('p-' + Math.round(p));
255
+ } else if (outputHint === 'react') {
256
+ parts.push('flex ' + dir);
257
+ if (layout.itemSpacing != null) parts.push('gap ' + layout.itemSpacing);
258
+ if (layout.paddingLeft != null) parts.push('paddingLeft ' + layout.paddingLeft);
259
+ if (layout.paddingTop != null) parts.push('paddingTop ' + layout.paddingTop);
260
+ } else {
261
+ parts.push(dir === 'grid' ? 'grid' : 'flex ' + dir);
262
+ if (layout.itemSpacing != null) parts.push('gap ' + layout.itemSpacing);
263
+ if (layout.paddingLeft != null || layout.paddingTop != null) parts.push('padding ' + (layout.paddingLeft || 0) + '/' + (layout.paddingTop || 0));
264
+ }
265
+ return parts.join(', ');
266
+ }
267
+
268
+ // Resolve variable alias(es) to names (SUI token reference — 2.3)
269
+ async function resolveVariableNames(aliases) {
270
+ if (!aliases) return [];
271
+ var list = Array.isArray(aliases) ? aliases : (aliases.id ? [aliases] : []);
272
+ var names = [];
273
+ for (var i = 0; i < list.length; i++) {
274
+ var alias = list[i];
275
+ if (alias && alias.id) {
276
+ try {
277
+ var v = await figma.variables.getVariableByIdAsync(alias.id);
278
+ if (v && v.name) names.push(v.name);
279
+ } catch (e) { /* skip */ }
280
+ }
281
+ }
282
+ return names;
283
+ }
284
+
285
+ // Build node payload for GET_DOCUMENT_STRUCTURE / GET_NODE_CONTEXT (layout, constraints, visual, typography, code-ready, SUI)
286
+ async function buildNodePayload(node, currentDepth, maxDepth, opts) {
287
+ if (currentDepth > maxDepth) return null;
288
+ var verbosity = opts.verbosity || 'summary';
289
+ var includeLayout = opts.includeLayout === true || verbosity === 'full';
290
+ var includeVisual = opts.includeVisual === true || verbosity === 'full';
291
+ var includeTypography = opts.includeTypography === true || verbosity === 'full';
292
+ var includeCodeReady = opts.includeCodeReady !== false && (includeLayout || includeVisual);
293
+ var outputHint = opts.outputHint || null;
294
+
295
+ var out = { id: node.id, name: node.name, type: node.type };
296
+
297
+ if (verbosity !== 'inventory' && verbosity !== 'summary') {
298
+ if (node.absoluteBoundingBox) out.absoluteBoundingBox = node.absoluteBoundingBox;
299
+ if (node.width !== undefined) out.width = node.width;
300
+ if (node.height !== undefined) out.height = node.height;
301
+ if (node.type === 'TEXT' && node.characters !== undefined) out.characters = node.characters;
302
+ }
303
+
304
+ var incompleteReasons = [];
305
+ if (node.description !== undefined && node.description !== '') out.description = node.description;
306
+ var suiName = nameToSuiComponent(node.name, node.description);
307
+ if (suiName) { out.roleHint = suiName; out.suiComponent = suiName; }
308
+
309
+ if (node.type === 'INSTANCE' && 'componentProperties' in node && node.componentProperties) {
310
+ var props = node.componentProperties;
311
+ var propNames = Object.keys(props);
312
+ if (propNames.length > 0) {
313
+ out.suggestedProps = {};
314
+ var summaryParts = [];
315
+ for (var i = 0; i < propNames.length; i++) {
316
+ var pn = propNames[i];
317
+ var v = props[pn];
318
+ out.suggestedProps[pn] = v;
319
+ summaryParts.push(pn + '=' + (typeof v === 'string' ? v : String(v)));
320
+ }
321
+ out.variantSummary = summaryParts.join(', ');
322
+ }
323
+ }
324
+
325
+ if (includeLayout) {
326
+ if ('constraints' in node && node.constraints) {
327
+ out.constraints = { horizontal: node.constraints.horizontal, vertical: node.constraints.vertical };
328
+ }
329
+ if ('layoutMode' in node && node.layoutMode && node.layoutMode !== 'NONE') {
330
+ var layout = { layoutMode: node.layoutMode };
331
+ if (node.paddingLeft !== undefined) layout.paddingLeft = node.paddingLeft;
332
+ if (node.paddingRight !== undefined) layout.paddingRight = node.paddingRight;
333
+ if (node.paddingTop !== undefined) layout.paddingTop = node.paddingTop;
334
+ if (node.paddingBottom !== undefined) layout.paddingBottom = node.paddingBottom;
335
+ if (node.itemSpacing !== undefined) layout.itemSpacing = node.itemSpacing;
336
+ if (node.primaryAxisAlignItems !== undefined) layout.primaryAxisAlignItems = node.primaryAxisAlignItems;
337
+ if (node.counterAxisAlignItems !== undefined) layout.counterAxisAlignItems = node.counterAxisAlignItems;
338
+ if (node.primaryAxisSizingMode !== undefined) layout.primaryAxisSizingMode = node.primaryAxisSizingMode;
339
+ if (node.counterAxisSizingMode !== undefined) layout.counterAxisSizingMode = node.counterAxisSizingMode;
340
+ if (node.layoutWrap !== undefined) layout.layoutWrap = node.layoutWrap;
341
+ if (node.counterAxisSpacing != null) layout.counterAxisSpacing = node.counterAxisSpacing;
342
+ if (node.layoutMode === 'GRID') {
343
+ if (node.gridRowCount !== undefined) layout.gridRowCount = node.gridRowCount;
344
+ if (node.gridColumnCount !== undefined) layout.gridColumnCount = node.gridColumnCount;
345
+ if (node.gridRowGap !== undefined) layout.gridRowGap = node.gridRowGap;
346
+ if (node.gridColumnGap !== undefined) layout.gridColumnGap = node.gridColumnGap;
347
+ }
348
+ out.layout = layout;
349
+ if (includeCodeReady) out.layoutSummary = buildLayoutSummary(layout, outputHint);
350
+ }
351
+ if ('layoutAlign' in node) out.layoutAlign = node.layoutAlign;
352
+ if ('layoutGrow' in node) out.layoutGrow = node.layoutGrow;
353
+ if ('layoutPositioning' in node) out.layoutPositioning = node.layoutPositioning;
354
+ if ('layoutSizingHorizontal' in node) out.layoutSizingHorizontal = node.layoutSizingHorizontal;
355
+ if ('layoutSizingVertical' in node) out.layoutSizingVertical = node.layoutSizingVertical;
356
+ if (node.minWidth != null) out.minWidth = node.minWidth;
357
+ if (node.maxWidth != null) out.maxWidth = node.maxWidth;
358
+ if (node.minHeight != null) out.minHeight = node.minHeight;
359
+ if (node.maxHeight != null) out.maxHeight = node.maxHeight;
360
+ }
361
+
362
+ var isMixed = typeof figma !== 'undefined' && figma.mixed !== undefined ? function(v) { return v === figma.mixed; } : function() { return false; };
363
+ if (includeVisual) {
364
+ var hasImageFill = false;
365
+ if ('fills' in node && node.fills !== undefined && !isMixed(node.fills)) {
366
+ try {
367
+ var fillsCopy = JSON.parse(JSON.stringify(node.fills));
368
+ out.fills = fillsCopy;
369
+ if (Array.isArray(fillsCopy) && fillsCopy.length > 0) {
370
+ var first = fillsCopy[0];
371
+ if (first && first.type === 'SOLID' && first.color) {
372
+ var hex = rgbaToHex(first.color);
373
+ if (hex) { out.colorHex = hex; out.primaryColorHex = hex; }
374
+ }
375
+ for (var f = 0; f < fillsCopy.length; f++) {
376
+ if (fillsCopy[f].type === 'IMAGE' || fillsCopy[f].imageRef) hasImageFill = true;
377
+ }
378
+ }
379
+ } catch (e) { out.fills = []; }
380
+ } else if ('fills' in node && node.fills !== undefined) {
381
+ out.fills = 'mixed';
382
+ incompleteReasons.push('mixed fills');
383
+ }
384
+ if (hasImageFill) { out.hasImageFill = true; incompleteReasons.push('image fill'); }
385
+ if ('strokes' in node && node.strokes !== undefined && !isMixed(node.strokes)) {
386
+ try { out.strokes = JSON.parse(JSON.stringify(node.strokes)); } catch (e) { out.strokes = []; }
387
+ } else if ('strokes' in node && node.strokes !== undefined) {
388
+ out.strokes = 'mixed';
389
+ incompleteReasons.push('mixed stroke');
390
+ }
391
+ if ('effects' in node && node.effects && node.effects.length > 0) {
392
+ try { out.effects = JSON.parse(JSON.stringify(node.effects)); } catch (e) { out.effects = []; }
393
+ }
394
+ if ('opacity' in node && node.opacity !== undefined) out.opacity = node.opacity;
395
+ if ('cornerRadius' in node && node.cornerRadius !== undefined && !isMixed(node.cornerRadius)) out.cornerRadius = node.cornerRadius;
396
+ if ('strokeWeight' in node && node.strokeWeight !== undefined && !isMixed(node.strokeWeight)) out.strokeWeight = node.strokeWeight;
397
+ if ('strokeAlign' in node) out.strokeAlign = node.strokeAlign;
398
+ /* Variable adı çözümlemesi sadece verbosity full'da (büyük dosyada binlerce getVariableByIdAsync timeout'a yol açar) */
399
+ if (verbosity === 'full' && 'boundVariables' in node && node.boundVariables) {
400
+ var bv = node.boundVariables;
401
+ if (bv.fills && (Array.isArray(bv.fills) ? bv.fills.length : (bv.fills && bv.fills.id))) {
402
+ var fillNames = await resolveVariableNames(bv.fills);
403
+ if (fillNames.length) out.fillVariableNames = fillNames;
404
+ }
405
+ if (bv.strokes && (Array.isArray(bv.strokes) ? bv.strokes.length : (bv.strokes && bv.strokes.id))) {
406
+ var strokeNames = await resolveVariableNames(bv.strokes);
407
+ if (strokeNames.length) out.strokeVariableNames = strokeNames;
408
+ }
409
+ }
410
+ }
411
+
412
+ if (includeTypography && node.type === 'TEXT') {
413
+ if (node.fontName !== undefined && !isMixed(node.fontName)) out.fontName = node.fontName;
414
+ else if (node.type === 'TEXT') incompleteReasons.push('font not loaded');
415
+ if (node.fontSize !== undefined && !isMixed(node.fontSize)) out.fontSize = node.fontSize;
416
+ if (node.lineHeight !== undefined && !isMixed(node.lineHeight)) out.lineHeight = node.lineHeight;
417
+ if (node.textStyleId !== undefined && !isMixed(node.textStyleId) && node.textStyleId) out.textStyleId = node.textStyleId;
418
+ }
419
+
420
+ if (incompleteReasons.length > 0) out.incompleteReasons = incompleteReasons;
421
+
422
+ if (node.children && node.children.length > 0 && currentDepth < maxDepth) {
423
+ var childPayloads = await Promise.all(node.children.map(function(c) { return buildNodePayload(c, currentDepth + 1, maxDepth, opts); }));
424
+ out.children = childPayloads.filter(Boolean);
425
+ if (verbosity === 'summary' || verbosity === 'inventory') out.childCount = node.children.length;
426
+ }
427
+ return out;
428
+ }
429
+
430
+ // ============================================================================
431
+ // EXECUTE_CODE - Arbitrary code execution (Power Tool)
432
+ // ============================================================================
433
+ if (msg.type === 'EXECUTE_CODE') {
434
+ try {
435
+ console.log('🌉 [F-MCP ATezer Bridge] Executing code, length:', msg.code.length);
436
+
437
+ // Use eval with async IIFE wrapper instead of AsyncFunction constructor
438
+ // AsyncFunction is restricted in Figma's plugin sandbox, but eval works
439
+ // See: https://developers.figma.com/docs/plugins/resource-links
440
+
441
+ // Wrap user code in an async IIFE that returns a Promise
442
+ // This allows async/await in user code while using eval
443
+ var wrappedCode = "(async function() {\n" + msg.code + "\n})()";
444
+
445
+ console.log('🌉 [F-MCP ATezer Bridge] Wrapped code for eval');
446
+
447
+ // Execute with timeout
448
+ var timeoutMs = msg.timeout || 5000;
449
+ var timeoutPromise = new Promise(function(_, reject) {
450
+ setTimeout(function() {
451
+ reject(new Error('Execution timed out after ' + timeoutMs + 'ms'));
452
+ }, timeoutMs);
453
+ });
454
+
455
+ var codePromise;
456
+ try {
457
+ // eval returns the Promise from the async IIFE
458
+ codePromise = eval(wrappedCode);
459
+ } catch (syntaxError) {
460
+ // Log the actual syntax error message
461
+ var syntaxErrorMsg = syntaxError && syntaxError.message ? syntaxError.message : String(syntaxError);
462
+ console.error('🌉 [F-MCP ATezer Bridge] Syntax error in code:', syntaxErrorMsg);
463
+ figma.ui.postMessage({
464
+ type: 'EXECUTE_CODE_RESULT',
465
+ requestId: msg.requestId,
466
+ success: false,
467
+ error: 'Syntax error: ' + syntaxErrorMsg
468
+ });
469
+ return;
470
+ }
471
+
472
+ var result = await Promise.race([
473
+ codePromise,
474
+ timeoutPromise
475
+ ]);
476
+
477
+ console.log('🌉 [F-MCP ATezer Bridge] Code executed successfully, result type:', typeof result);
478
+
479
+ // Analyze result for potential silent failures
480
+ var resultAnalysis = {
481
+ type: typeof result,
482
+ isNull: result === null,
483
+ isUndefined: result === undefined,
484
+ isEmpty: false,
485
+ warning: null
486
+ };
487
+
488
+ // Check for empty results that might indicate a failed search/operation
489
+ if (Array.isArray(result)) {
490
+ resultAnalysis.isEmpty = result.length === 0;
491
+ if (resultAnalysis.isEmpty) {
492
+ resultAnalysis.warning = 'Code returned an empty array. If you were searching for nodes, none were found.';
493
+ }
494
+ } else if (result !== null && typeof result === 'object') {
495
+ var keys = Object.keys(result);
496
+ resultAnalysis.isEmpty = keys.length === 0;
497
+ if (resultAnalysis.isEmpty) {
498
+ resultAnalysis.warning = 'Code returned an empty object. The operation may not have found what it was looking for.';
499
+ }
500
+ // Check for common "found nothing" patterns
501
+ if (result.length === 0 || result.count === 0 || result.foundCount === 0 || (result.nodes && result.nodes.length === 0)) {
502
+ resultAnalysis.warning = 'Code returned a result indicating nothing was found (count/length is 0).';
503
+ }
504
+ } else if (result === null) {
505
+ resultAnalysis.warning = 'Code returned null. The requested node or resource may not exist.';
506
+ } else if (result === undefined) {
507
+ resultAnalysis.warning = 'Code returned undefined. Make sure your code has a return statement.';
508
+ }
509
+
510
+ if (resultAnalysis.warning) {
511
+ console.warn('🌉 [F-MCP ATezer Bridge] ⚠️ Result warning:', resultAnalysis.warning);
512
+ }
513
+
514
+ figma.ui.postMessage({
515
+ type: 'EXECUTE_CODE_RESULT',
516
+ requestId: msg.requestId,
517
+ success: true,
518
+ result: result,
519
+ resultAnalysis: resultAnalysis,
520
+ // Include file context so users know which file this executed against
521
+ fileContext: {
522
+ fileName: figma.root.name,
523
+ fileKey: figma.fileKey || null
524
+ }
525
+ });
526
+
527
+ } catch (error) {
528
+ // Extract error message explicitly - don't rely on console.error serialization
529
+ var errorName = error && error.name ? error.name : 'Error';
530
+ var errorMsg = error && error.message ? error.message : String(error);
531
+ var errorStack = error && error.stack ? error.stack : '';
532
+
533
+ // Log error details as strings so they show up properly in Puppeteer
534
+ console.error('🌉 [F-MCP ATezer Bridge] Code execution error: [' + errorName + '] ' + errorMsg);
535
+ if (errorStack) {
536
+ console.error('🌉 [F-MCP ATezer Bridge] Stack:', errorStack);
537
+ }
538
+
539
+ figma.ui.postMessage({
540
+ type: 'EXECUTE_CODE_RESULT',
541
+ requestId: msg.requestId,
542
+ success: false,
543
+ error: errorName + ': ' + errorMsg
544
+ });
545
+ }
546
+ }
547
+
548
+ // ============================================================================
549
+ // UPDATE_VARIABLE - Update a variable's value in a specific mode
550
+ // ============================================================================
551
+ else if (msg.type === 'UPDATE_VARIABLE') {
552
+ try {
553
+ console.log('🌉 [F-MCP ATezer Bridge] Updating variable:', msg.variableId);
554
+
555
+ var variable = await figma.variables.getVariableByIdAsync(msg.variableId);
556
+ if (!variable) {
557
+ throw new Error('Variable not found: ' + msg.variableId);
558
+ }
559
+
560
+ // Convert value based on variable type
561
+ var value = msg.value;
562
+
563
+ // Check if value is a variable alias (string starting with "VariableID:")
564
+ if (typeof value === 'string' && value.startsWith('VariableID:')) {
565
+ // Convert to VARIABLE_ALIAS format
566
+ value = {
567
+ type: 'VARIABLE_ALIAS',
568
+ id: value
569
+ };
570
+ console.log('🌉 [F-MCP ATezer Bridge] Converting to variable alias:', value.id);
571
+ } else if (variable.resolvedType === 'COLOR' && typeof value === 'string') {
572
+ // Convert hex string to Figma color
573
+ value = hexToFigmaRGB(value);
574
+ }
575
+
576
+ // Set the value for the specified mode
577
+ variable.setValueForMode(msg.modeId, value);
578
+
579
+ console.log('🌉 [F-MCP ATezer Bridge] Variable updated successfully');
580
+
581
+ figma.ui.postMessage({
582
+ type: 'UPDATE_VARIABLE_RESULT',
583
+ requestId: msg.requestId,
584
+ success: true,
585
+ variable: serializeVariable(variable)
586
+ });
587
+
588
+ } catch (error) {
589
+ console.error('🌉 [F-MCP ATezer Bridge] Update variable error:', error);
590
+ figma.ui.postMessage({
591
+ type: 'UPDATE_VARIABLE_RESULT',
592
+ requestId: msg.requestId,
593
+ success: false,
594
+ error: error.message || String(error)
595
+ });
596
+ }
597
+ }
598
+
599
+ // ============================================================================
600
+ // CREATE_VARIABLE - Create a new variable in a collection
601
+ // ============================================================================
602
+ else if (msg.type === 'CREATE_VARIABLE') {
603
+ try {
604
+ console.log('🌉 [F-MCP ATezer Bridge] Creating variable:', msg.name);
605
+
606
+ // Get the collection
607
+ var collection = await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);
608
+ if (!collection) {
609
+ throw new Error('Collection not found: ' + msg.collectionId);
610
+ }
611
+
612
+ // Create the variable
613
+ var variable = figma.variables.createVariable(msg.name, collection, msg.resolvedType);
614
+
615
+ // Set initial values if provided
616
+ if (msg.valuesByMode) {
617
+ for (var modeId in msg.valuesByMode) {
618
+ var value = msg.valuesByMode[modeId];
619
+ // Convert hex colors
620
+ if (msg.resolvedType === 'COLOR' && typeof value === 'string') {
621
+ value = hexToFigmaRGB(value);
622
+ }
623
+ variable.setValueForMode(modeId, value);
624
+ }
625
+ }
626
+
627
+ // Set description if provided
628
+ if (msg.description) {
629
+ variable.description = msg.description;
630
+ }
631
+
632
+ // Set scopes if provided
633
+ if (msg.scopes) {
634
+ variable.scopes = msg.scopes;
635
+ }
636
+
637
+ console.log('🌉 [F-MCP ATezer Bridge] Variable created:', variable.id);
638
+
639
+ figma.ui.postMessage({
640
+ type: 'CREATE_VARIABLE_RESULT',
641
+ requestId: msg.requestId,
642
+ success: true,
643
+ variable: serializeVariable(variable)
644
+ });
645
+
646
+ } catch (error) {
647
+ console.error('🌉 [F-MCP ATezer Bridge] Create variable error:', error);
648
+ figma.ui.postMessage({
649
+ type: 'CREATE_VARIABLE_RESULT',
650
+ requestId: msg.requestId,
651
+ success: false,
652
+ error: error.message || String(error)
653
+ });
654
+ }
655
+ }
656
+
657
+ // ============================================================================
658
+ // CREATE_VARIABLE_COLLECTION - Create a new variable collection
659
+ // ============================================================================
660
+ else if (msg.type === 'CREATE_VARIABLE_COLLECTION') {
661
+ try {
662
+ console.log('🌉 [F-MCP ATezer Bridge] Creating collection:', msg.name);
663
+
664
+ // Create the collection
665
+ var collection = figma.variables.createVariableCollection(msg.name);
666
+
667
+ // Rename the default mode if a name is provided
668
+ if (msg.initialModeName && collection.modes.length > 0) {
669
+ collection.renameMode(collection.modes[0].modeId, msg.initialModeName);
670
+ }
671
+
672
+ // Add additional modes if provided
673
+ if (msg.additionalModes && msg.additionalModes.length > 0) {
674
+ for (var i = 0; i < msg.additionalModes.length; i++) {
675
+ collection.addMode(msg.additionalModes[i]);
676
+ }
677
+ }
678
+
679
+ console.log('🌉 [F-MCP ATezer Bridge] Collection created:', collection.id);
680
+
681
+ figma.ui.postMessage({
682
+ type: 'CREATE_VARIABLE_COLLECTION_RESULT',
683
+ requestId: msg.requestId,
684
+ success: true,
685
+ collection: serializeCollection(collection)
686
+ });
687
+
688
+ } catch (error) {
689
+ console.error('🌉 [F-MCP ATezer Bridge] Create collection error:', error);
690
+ figma.ui.postMessage({
691
+ type: 'CREATE_VARIABLE_COLLECTION_RESULT',
692
+ requestId: msg.requestId,
693
+ success: false,
694
+ error: error.message || String(error)
695
+ });
696
+ }
697
+ }
698
+
699
+ // ============================================================================
700
+ // DELETE_VARIABLE - Delete a variable
701
+ // ============================================================================
702
+ else if (msg.type === 'DELETE_VARIABLE') {
703
+ try {
704
+ console.log('🌉 [F-MCP ATezer Bridge] Deleting variable:', msg.variableId);
705
+
706
+ var variable = await figma.variables.getVariableByIdAsync(msg.variableId);
707
+ if (!variable) {
708
+ throw new Error('Variable not found: ' + msg.variableId);
709
+ }
710
+
711
+ var deletedInfo = {
712
+ id: variable.id,
713
+ name: variable.name
714
+ };
715
+
716
+ variable.remove();
717
+
718
+ console.log('🌉 [F-MCP ATezer Bridge] Variable deleted');
719
+
720
+ figma.ui.postMessage({
721
+ type: 'DELETE_VARIABLE_RESULT',
722
+ requestId: msg.requestId,
723
+ success: true,
724
+ deleted: deletedInfo
725
+ });
726
+
727
+ } catch (error) {
728
+ console.error('🌉 [F-MCP ATezer Bridge] Delete variable error:', error);
729
+ figma.ui.postMessage({
730
+ type: 'DELETE_VARIABLE_RESULT',
731
+ requestId: msg.requestId,
732
+ success: false,
733
+ error: error.message || String(error)
734
+ });
735
+ }
736
+ }
737
+
738
+ // ============================================================================
739
+ // DELETE_VARIABLE_COLLECTION - Delete a variable collection
740
+ // ============================================================================
741
+ else if (msg.type === 'DELETE_VARIABLE_COLLECTION') {
742
+ try {
743
+ console.log('🌉 [F-MCP ATezer Bridge] Deleting collection:', msg.collectionId);
744
+
745
+ var collection = await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);
746
+ if (!collection) {
747
+ throw new Error('Collection not found: ' + msg.collectionId);
748
+ }
749
+
750
+ var deletedInfo = {
751
+ id: collection.id,
752
+ name: collection.name,
753
+ variableCount: collection.variableIds.length
754
+ };
755
+
756
+ collection.remove();
757
+
758
+ console.log('🌉 [F-MCP ATezer Bridge] Collection deleted');
759
+
760
+ figma.ui.postMessage({
761
+ type: 'DELETE_VARIABLE_COLLECTION_RESULT',
762
+ requestId: msg.requestId,
763
+ success: true,
764
+ deleted: deletedInfo
765
+ });
766
+
767
+ } catch (error) {
768
+ console.error('🌉 [F-MCP ATezer Bridge] Delete collection error:', error);
769
+ figma.ui.postMessage({
770
+ type: 'DELETE_VARIABLE_COLLECTION_RESULT',
771
+ requestId: msg.requestId,
772
+ success: false,
773
+ error: error.message || String(error)
774
+ });
775
+ }
776
+ }
777
+
778
+ // ============================================================================
779
+ // RENAME_VARIABLE - Rename a variable
780
+ // ============================================================================
781
+ else if (msg.type === 'RENAME_VARIABLE') {
782
+ try {
783
+ console.log('🌉 [F-MCP ATezer Bridge] Renaming variable:', msg.variableId, 'to', msg.newName);
784
+
785
+ var variable = await figma.variables.getVariableByIdAsync(msg.variableId);
786
+ if (!variable) {
787
+ throw new Error('Variable not found: ' + msg.variableId);
788
+ }
789
+
790
+ var oldName = variable.name;
791
+ variable.name = msg.newName;
792
+
793
+ console.log('🌉 [F-MCP ATezer Bridge] Variable renamed from "' + oldName + '" to "' + msg.newName + '"');
794
+
795
+ figma.ui.postMessage({
796
+ type: 'RENAME_VARIABLE_RESULT',
797
+ requestId: msg.requestId,
798
+ success: true,
799
+ variable: serializeVariable(variable),
800
+ oldName: oldName
801
+ });
802
+
803
+ } catch (error) {
804
+ console.error('🌉 [F-MCP ATezer Bridge] Rename variable error:', error);
805
+ figma.ui.postMessage({
806
+ type: 'RENAME_VARIABLE_RESULT',
807
+ requestId: msg.requestId,
808
+ success: false,
809
+ error: error.message || String(error)
810
+ });
811
+ }
812
+ }
813
+
814
+ // ============================================================================
815
+ // ADD_MODE - Add a mode to a variable collection
816
+ // ============================================================================
817
+ else if (msg.type === 'ADD_MODE') {
818
+ try {
819
+ console.log('🌉 [F-MCP ATezer Bridge] Adding mode to collection:', msg.collectionId);
820
+
821
+ var collection = await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);
822
+ if (!collection) {
823
+ throw new Error('Collection not found: ' + msg.collectionId);
824
+ }
825
+
826
+ // Add the mode (returns the new mode ID)
827
+ var newModeId = collection.addMode(msg.modeName);
828
+
829
+ console.log('🌉 [F-MCP ATezer Bridge] Mode "' + msg.modeName + '" added with ID:', newModeId);
830
+
831
+ figma.ui.postMessage({
832
+ type: 'ADD_MODE_RESULT',
833
+ requestId: msg.requestId,
834
+ success: true,
835
+ collection: serializeCollection(collection),
836
+ newMode: {
837
+ modeId: newModeId,
838
+ name: msg.modeName
839
+ }
840
+ });
841
+
842
+ } catch (error) {
843
+ console.error('🌉 [F-MCP ATezer Bridge] Add mode error:', error);
844
+ figma.ui.postMessage({
845
+ type: 'ADD_MODE_RESULT',
846
+ requestId: msg.requestId,
847
+ success: false,
848
+ error: error.message || String(error)
849
+ });
850
+ }
851
+ }
852
+
853
+ // ============================================================================
854
+ // RENAME_MODE - Rename a mode in a variable collection
855
+ // ============================================================================
856
+ else if (msg.type === 'RENAME_MODE') {
857
+ try {
858
+ console.log('🌉 [F-MCP ATezer Bridge] Renaming mode:', msg.modeId, 'in collection:', msg.collectionId);
859
+
860
+ var collection = await figma.variables.getVariableCollectionByIdAsync(msg.collectionId);
861
+ if (!collection) {
862
+ throw new Error('Collection not found: ' + msg.collectionId);
863
+ }
864
+
865
+ // Find the current mode name
866
+ var currentMode = collection.modes.find(function(m) { return m.modeId === msg.modeId; });
867
+ if (!currentMode) {
868
+ throw new Error('Mode not found: ' + msg.modeId);
869
+ }
870
+
871
+ var oldName = currentMode.name;
872
+ collection.renameMode(msg.modeId, msg.newName);
873
+
874
+ console.log('🌉 [F-MCP ATezer Bridge] Mode renamed from "' + oldName + '" to "' + msg.newName + '"');
875
+
876
+ figma.ui.postMessage({
877
+ type: 'RENAME_MODE_RESULT',
878
+ requestId: msg.requestId,
879
+ success: true,
880
+ collection: serializeCollection(collection),
881
+ oldName: oldName
882
+ });
883
+
884
+ } catch (error) {
885
+ console.error('🌉 [F-MCP ATezer Bridge] Rename mode error:', error);
886
+ figma.ui.postMessage({
887
+ type: 'RENAME_MODE_RESULT',
888
+ requestId: msg.requestId,
889
+ success: false,
890
+ error: error.message || String(error)
891
+ });
892
+ }
893
+ }
894
+
895
+ // ============================================================================
896
+ // REFRESH_VARIABLES - Re-fetch and send all variables data
897
+ // ============================================================================
898
+ else if (msg.type === 'REFRESH_VARIABLES') {
899
+ try {
900
+ console.log('🌉 [F-MCP ATezer Bridge] Refreshing variables data...');
901
+
902
+ if (!figma.variables || typeof figma.variables.getLocalVariablesAsync !== 'function') {
903
+ figma.ui.postMessage({
904
+ type: 'REFRESH_VARIABLES_RESULT',
905
+ requestId: msg.requestId,
906
+ success: true,
907
+ data: { success: true, timestamp: Date.now(), fileKey: figma.fileKey || null, variables: [], variableCollections: [] }
908
+ });
909
+ return;
910
+ }
911
+
912
+ var variables = await figma.variables.getLocalVariablesAsync();
913
+ var collections = await figma.variables.getLocalVariableCollectionsAsync();
914
+
915
+ var variablesData = {
916
+ success: true,
917
+ timestamp: Date.now(),
918
+ fileKey: figma.fileKey || null,
919
+ variables: variables.map(serializeVariable),
920
+ variableCollections: collections.map(serializeCollection)
921
+ };
922
+
923
+ // Update the UI's cached data
924
+ figma.ui.postMessage({
925
+ type: 'VARIABLES_DATA',
926
+ data: variablesData
927
+ });
928
+
929
+ // Also send as a response to the request
930
+ figma.ui.postMessage({
931
+ type: 'REFRESH_VARIABLES_RESULT',
932
+ requestId: msg.requestId,
933
+ success: true,
934
+ data: variablesData
935
+ });
936
+
937
+ console.log('🌉 [F-MCP ATezer Bridge] Variables refreshed:', variables.length, 'variables in', collections.length, 'collections');
938
+
939
+ } catch (error) {
940
+ console.error('🌉 [F-MCP ATezer Bridge] Refresh variables error:', error);
941
+ figma.ui.postMessage({
942
+ type: 'REFRESH_VARIABLES_RESULT',
943
+ requestId: msg.requestId,
944
+ success: false,
945
+ error: error.message || String(error)
946
+ });
947
+ }
948
+ }
949
+
950
+ // ============================================================================
951
+ // GET_COMPONENT - Existing read operation
952
+ // ============================================================================
953
+ else if (msg.type === 'GET_COMPONENT') {
954
+ try {
955
+ console.log(`🌉 [F-MCP ATezer Bridge] Fetching component: ${msg.nodeId}`);
956
+
957
+ const node = await figma.getNodeByIdAsync(msg.nodeId);
958
+
959
+ if (!node) {
960
+ throw new Error(`Node not found: ${msg.nodeId}`);
961
+ }
962
+
963
+ if (node.type !== 'COMPONENT' && node.type !== 'COMPONENT_SET' && node.type !== 'INSTANCE') {
964
+ throw new Error(`Node is not a component. Type: ${node.type}`);
965
+ }
966
+
967
+ // Detect if this is a variant (COMPONENT inside a COMPONENT_SET)
968
+ // Note: Can't use optional chaining (?.) - Figma plugin sandbox doesn't support it
969
+ const isVariant = node.type === 'COMPONENT' && node.parent && node.parent.type === 'COMPONENT_SET';
970
+
971
+ // Extract component data including description fields and annotations
972
+ const componentData = {
973
+ success: true,
974
+ timestamp: Date.now(),
975
+ nodeId: msg.nodeId,
976
+ component: {
977
+ id: node.id,
978
+ name: node.name,
979
+ type: node.type,
980
+ // Variants CAN have their own description
981
+ description: node.description || null,
982
+ descriptionMarkdown: node.descriptionMarkdown || null,
983
+ visible: node.visible,
984
+ locked: node.locked,
985
+ // Dev Mode annotations
986
+ annotations: node.annotations || [],
987
+ // Flag to indicate if this is a variant
988
+ isVariant: isVariant,
989
+ // For component sets and non-variant components only (variants cannot access this)
990
+ componentPropertyDefinitions: (node.type === 'COMPONENT_SET' || (node.type === 'COMPONENT' && !isVariant))
991
+ ? node.componentPropertyDefinitions
992
+ : undefined,
993
+ // Get children info (include text content for TEXT nodes)
994
+ children: node.children ? node.children.map(child => {
995
+ var c = { id: child.id, name: child.name, type: child.type };
996
+ if (child.type === 'TEXT' && child.characters !== undefined) c.characters = child.characters;
997
+ return c;
998
+ }) : undefined
999
+ }
1000
+ };
1001
+
1002
+ console.log(`🌉 [F-MCP ATezer Bridge] Component data ready. Has description: ${!!componentData.component.description}, annotations: ${componentData.component.annotations.length}`);
1003
+
1004
+ // Send to UI
1005
+ figma.ui.postMessage({
1006
+ type: 'COMPONENT_DATA',
1007
+ requestId: msg.requestId, // Echo back the request ID
1008
+ data: componentData
1009
+ });
1010
+
1011
+ } catch (error) {
1012
+ console.error(`🌉 [F-MCP ATezer Bridge] Error fetching component:`, error);
1013
+ figma.ui.postMessage({
1014
+ type: 'COMPONENT_ERROR',
1015
+ requestId: msg.requestId,
1016
+ error: error.message || String(error)
1017
+ });
1018
+ }
1019
+ }
1020
+
1021
+ // ============================================================================
1022
+ // GET_LOCAL_COMPONENTS - Get all local components for design system manifest
1023
+ // ============================================================================
1024
+ else if (msg.type === 'GET_LOCAL_COMPONENTS') {
1025
+ try {
1026
+ // Default true: avoid full-doc scan (timeout on large files). Only scan all pages when explicitly false.
1027
+ var currentPageOnly = msg.currentPageOnly !== false;
1028
+ var limit = msg.limit != null ? Math.max(0, parseInt(msg.limit, 10) || 0) : 0;
1029
+ console.log('🌉 [F-MCP ATezer Bridge] Fetching local components (currentPageOnly:', currentPageOnly, ', limit:', limit || 'none', ')...');
1030
+
1031
+ // Find all component sets and standalone components in the file
1032
+ var components = [];
1033
+ var componentSets = [];
1034
+ var hitLimit = false;
1035
+
1036
+ // Helper to extract component data
1037
+ function extractComponentData(node, isPartOfSet) {
1038
+ var data = {
1039
+ key: node.key,
1040
+ nodeId: node.id,
1041
+ name: node.name,
1042
+ type: node.type,
1043
+ description: node.description || null,
1044
+ width: node.width,
1045
+ height: node.height
1046
+ };
1047
+
1048
+ // Get property definitions for non-variant components
1049
+ if (!isPartOfSet && node.componentPropertyDefinitions) {
1050
+ data.properties = [];
1051
+ var propDefs = node.componentPropertyDefinitions;
1052
+ for (var propName in propDefs) {
1053
+ if (propDefs.hasOwnProperty(propName)) {
1054
+ var propDef = propDefs[propName];
1055
+ data.properties.push({
1056
+ name: propName,
1057
+ type: propDef.type,
1058
+ defaultValue: propDef.defaultValue
1059
+ });
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ return data;
1065
+ }
1066
+
1067
+ // Helper to extract component set data with all variants
1068
+ function extractComponentSetData(node) {
1069
+ var variantAxes = {};
1070
+ var variants = [];
1071
+
1072
+ // Parse variant properties from children names
1073
+ if (node.children) {
1074
+ node.children.forEach(function(child) {
1075
+ if (child.type === 'COMPONENT') {
1076
+ // Parse variant name (e.g., "Size=md, State=default")
1077
+ var variantProps = {};
1078
+ var parts = child.name.split(',').map(function(p) { return p.trim(); });
1079
+ parts.forEach(function(part) {
1080
+ var kv = part.split('=');
1081
+ if (kv.length === 2) {
1082
+ var key = kv[0].trim();
1083
+ var value = kv[1].trim();
1084
+ variantProps[key] = value;
1085
+
1086
+ // Track all values for each axis
1087
+ if (!variantAxes[key]) {
1088
+ variantAxes[key] = [];
1089
+ }
1090
+ if (variantAxes[key].indexOf(value) === -1) {
1091
+ variantAxes[key].push(value);
1092
+ }
1093
+ }
1094
+ });
1095
+
1096
+ variants.push({
1097
+ key: child.key,
1098
+ nodeId: child.id,
1099
+ name: child.name,
1100
+ description: child.description || null,
1101
+ variantProperties: variantProps,
1102
+ width: child.width,
1103
+ height: child.height
1104
+ });
1105
+ }
1106
+ });
1107
+ }
1108
+
1109
+ // Convert variantAxes object to array format
1110
+ var axes = [];
1111
+ for (var axisName in variantAxes) {
1112
+ if (variantAxes.hasOwnProperty(axisName)) {
1113
+ axes.push({
1114
+ name: axisName,
1115
+ values: variantAxes[axisName]
1116
+ });
1117
+ }
1118
+ }
1119
+
1120
+ return {
1121
+ key: node.key,
1122
+ nodeId: node.id,
1123
+ name: node.name,
1124
+ type: 'COMPONENT_SET',
1125
+ description: node.description || null,
1126
+ variantAxes: axes,
1127
+ variants: variants,
1128
+ defaultVariant: variants.length > 0 ? variants[0] : null,
1129
+ properties: node.componentPropertyDefinitions ? Object.keys(node.componentPropertyDefinitions).map(function(propName) {
1130
+ var propDef = node.componentPropertyDefinitions[propName];
1131
+ return {
1132
+ name: propName,
1133
+ type: propDef.type,
1134
+ defaultValue: propDef.defaultValue
1135
+ };
1136
+ }) : []
1137
+ };
1138
+ }
1139
+
1140
+ // Use findAllWithCriteria (async-friendly, no sync tree walk) — works in dynamic-page
1141
+ async function processNodeList(nodes) {
1142
+ for (var i = 0; i < nodes.length && !hitLimit; i++) {
1143
+ if (limit > 0 && components.length + componentSets.length >= limit) {
1144
+ hitLimit = true;
1145
+ break;
1146
+ }
1147
+ var node = nodes[i];
1148
+ if (!node) continue;
1149
+ if (node.loadAsync) await node.loadAsync();
1150
+ if (node.type === 'COMPONENT_SET') {
1151
+ componentSets.push(extractComponentSetData(node));
1152
+ } else if (node.type === 'COMPONENT') {
1153
+ if (!node.parent || node.parent.type !== 'COMPONENT_SET') {
1154
+ components.push(extractComponentData(node, false));
1155
+ }
1156
+ }
1157
+ }
1158
+ }
1159
+
1160
+ if (currentPageOnly) {
1161
+ var page = figma.currentPage;
1162
+ if (page) {
1163
+ await (page.loadAsync && page.loadAsync());
1164
+ var nodes = page.findAllWithCriteria ? page.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1165
+ await processNodeList(nodes);
1166
+ }
1167
+ } else {
1168
+ console.log('🌉 [F-MCP ATezer Bridge] Loading all pages...');
1169
+ await figma.loadAllPagesAsync();
1170
+ console.log('🌉 [F-MCP ATezer Bridge] All pages loaded, searching for components...');
1171
+ var pages = figma.root.children;
1172
+ for (var p = 0; p < pages.length && !hitLimit; p++) {
1173
+ var pg = pages[p];
1174
+ if (pg && pg.loadAsync) await pg.loadAsync();
1175
+ var pageNodes = pg && pg.findAllWithCriteria ? pg.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1176
+ await processNodeList(pageNodes);
1177
+ }
1178
+ }
1179
+
1180
+ console.log('🌉 [F-MCP ATezer Bridge] Found ' + components.length + ' components and ' + componentSets.length + ' component sets');
1181
+
1182
+ figma.ui.postMessage({
1183
+ type: 'GET_LOCAL_COMPONENTS_RESULT',
1184
+ requestId: msg.requestId,
1185
+ success: true,
1186
+ data: {
1187
+ components: components,
1188
+ componentSets: componentSets,
1189
+ totalComponents: components.length,
1190
+ totalComponentSets: componentSets.length,
1191
+ currentPageOnly: currentPageOnly,
1192
+ truncatedByLimit: hitLimit && limit > 0,
1193
+ fileName: figma.root.name,
1194
+ fileKey: figma.fileKey || null,
1195
+ timestamp: Date.now()
1196
+ }
1197
+ });
1198
+
1199
+ } catch (error) {
1200
+ var errorMsg = error && error.message ? error.message : String(error);
1201
+ console.error('🌉 [F-MCP ATezer Bridge] Get local components error:', errorMsg);
1202
+ figma.ui.postMessage({
1203
+ type: 'GET_LOCAL_COMPONENTS_RESULT',
1204
+ requestId: msg.requestId,
1205
+ success: false,
1206
+ error: errorMsg
1207
+ });
1208
+ }
1209
+ }
1210
+
1211
+ // ============================================================================
1212
+ // SEARCH_LIBRARY_ASSETS — enabled library variables + file components (query filter)
1213
+ // ============================================================================
1214
+ else if (msg.type === 'SEARCH_LIBRARY_ASSETS') {
1215
+ try {
1216
+ var q = (msg.query || '').toLowerCase().trim();
1217
+ var assetTypes = msg.assetTypes && msg.assetTypes.length ? msg.assetTypes : ['variables', 'components'];
1218
+ var limit = Math.min(Math.max(parseInt(msg.limit, 10) || 25, 1), 80);
1219
+ var currentPageOnly = msg.currentPageOnly !== false;
1220
+ var out = {
1221
+ success: true,
1222
+ query: msg.query || '',
1223
+ variables: [],
1224
+ components: [],
1225
+ componentSets: [],
1226
+ librariesScanned: [],
1227
+ notes: []
1228
+ };
1229
+
1230
+ if (assetTypes.indexOf('variables') >= 0 && figma.teamLibrary && typeof figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync === 'function') {
1231
+ try {
1232
+ var cols = await figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync();
1233
+ for (var ci = 0; ci < cols.length && out.variables.length < limit; ci++) {
1234
+ var col = cols[ci];
1235
+ out.librariesScanned.push({ kind: 'variableCollection', name: col.name || '', key: col.key || '' });
1236
+ var libVars = await figma.teamLibrary.getVariablesInLibraryCollectionAsync(col.key);
1237
+ for (var vi = 0; vi < libVars.length && out.variables.length < limit; vi++) {
1238
+ var lv = libVars[vi];
1239
+ var nm = (lv.name || '').toLowerCase();
1240
+ if (!q || nm.indexOf(q) >= 0) {
1241
+ out.variables.push({
1242
+ name: lv.name,
1243
+ key: lv.key,
1244
+ resolvedType: lv.resolvedType,
1245
+ libraryCollectionKey: col.key,
1246
+ libraryCollectionName: col.name
1247
+ });
1248
+ }
1249
+ }
1250
+ }
1251
+ } catch (ve) {
1252
+ out.variableLibraryError = ve && ve.message ? ve.message : String(ve);
1253
+ }
1254
+ } else if (assetTypes.indexOf('variables') >= 0) {
1255
+ out.notes.push('Library variables skipped: teamLibrary API unavailable or permission missing (manifest permissions: teamlibrary).');
1256
+ }
1257
+
1258
+ if (assetTypes.indexOf('components') >= 0) {
1259
+ var components = [];
1260
+ var componentSets = [];
1261
+ var hitLimit = false;
1262
+ function extractComponentData(node, fromSet) {
1263
+ return {
1264
+ id: node.id,
1265
+ name: node.name,
1266
+ key: node.key,
1267
+ description: node.description || null,
1268
+ fromLibraryComponentSet: !!fromSet
1269
+ };
1270
+ }
1271
+ function extractComponentSetData(node) {
1272
+ return {
1273
+ id: node.id,
1274
+ name: node.name,
1275
+ key: node.key,
1276
+ description: node.description || null,
1277
+ variantCount: node.children ? node.children.length : 0
1278
+ };
1279
+ }
1280
+ async function processNodeList(nodes) {
1281
+ for (var i = 0; i < nodes.length && !hitLimit; i++) {
1282
+ if (components.length + componentSets.length >= limit) {
1283
+ hitLimit = true;
1284
+ break;
1285
+ }
1286
+ var node = nodes[i];
1287
+ if (!node) continue;
1288
+ if (node.loadAsync) await node.loadAsync();
1289
+ var nname = (node.name || '').toLowerCase();
1290
+ var ndesc = (node.description || '').toLowerCase();
1291
+ var match = !q || nname.indexOf(q) >= 0 || ndesc.indexOf(q) >= 0;
1292
+ if (node.type === 'COMPONENT_SET') {
1293
+ if (match) componentSets.push(extractComponentSetData(node));
1294
+ } else if (node.type === 'COMPONENT') {
1295
+ if (!node.parent || node.parent.type !== 'COMPONENT_SET') {
1296
+ if (match) components.push(extractComponentData(node, false));
1297
+ }
1298
+ }
1299
+ }
1300
+ }
1301
+ if (currentPageOnly) {
1302
+ var page = figma.currentPage;
1303
+ if (page) {
1304
+ if (page.loadAsync) await page.loadAsync();
1305
+ var pn = page.findAllWithCriteria ? page.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1306
+ await processNodeList(pn);
1307
+ }
1308
+ } else {
1309
+ await figma.loadAllPagesAsync();
1310
+ var pages = figma.root.children;
1311
+ for (var p = 0; p < pages.length && !hitLimit; p++) {
1312
+ var pg = pages[p];
1313
+ if (pg && pg.loadAsync) await pg.loadAsync();
1314
+ var pageNodes = pg && pg.findAllWithCriteria ? pg.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1315
+ await processNodeList(pageNodes);
1316
+ }
1317
+ }
1318
+ out.components = components;
1319
+ out.componentSets = componentSets;
1320
+ out.truncatedByLimit = hitLimit;
1321
+ out.currentPageOnly = currentPageOnly;
1322
+ out.notes.push('Components are from the current file (local + published keys via component.key). Full remote catalog may require REST API.');
1323
+ }
1324
+
1325
+ figma.ui.postMessage({
1326
+ type: 'SEARCH_LIBRARY_ASSETS_RESULT',
1327
+ requestId: msg.requestId,
1328
+ success: true,
1329
+ data: out
1330
+ });
1331
+ } catch (error) {
1332
+ var emsg = error && error.message ? error.message : String(error);
1333
+ figma.ui.postMessage({
1334
+ type: 'SEARCH_LIBRARY_ASSETS_RESULT',
1335
+ requestId: msg.requestId,
1336
+ success: false,
1337
+ error: emsg
1338
+ });
1339
+ }
1340
+ }
1341
+
1342
+ // ============================================================================
1343
+ // GET_CODE_CONNECT_HINTS — documentationLinks + component keys (not full Code Connect file map)
1344
+ // ============================================================================
1345
+ else if (msg.type === 'GET_CODE_CONNECT_HINTS') {
1346
+ try {
1347
+ var ids = msg.nodeIds && msg.nodeIds.length ? msg.nodeIds : [];
1348
+ var scanPage = msg.scanCurrentPage === true;
1349
+ var maxNodes = Math.min(parseInt(msg.maxNodes, 10) || 40, 120);
1350
+ var nodes = [];
1351
+
1352
+ async function pushNodeEntry(n) {
1353
+ if (!n) return;
1354
+ if (n.loadAsync) await n.loadAsync();
1355
+ var entry = { nodeId: n.id, name: n.name, type: n.type };
1356
+ if (n.type === 'COMPONENT' || n.type === 'COMPONENT_SET') {
1357
+ entry.componentKey = n.key;
1358
+ }
1359
+ if ('description' in n && n.description) entry.description = n.description;
1360
+ if ('documentationLinks' in n && n.documentationLinks && n.documentationLinks.length) {
1361
+ entry.documentationLinks = n.documentationLinks.map(function (link) {
1362
+ return { uri: link.uri, type: link.type };
1363
+ });
1364
+ }
1365
+ nodes.push(entry);
1366
+ }
1367
+
1368
+ if (ids.length > 0) {
1369
+ for (var ii = 0; ii < ids.length && nodes.length < maxNodes; ii++) {
1370
+ var node = await figma.getNodeByIdAsync(ids[ii]);
1371
+ await pushNodeEntry(node);
1372
+ }
1373
+ } else if (scanPage) {
1374
+ var cur = figma.currentPage;
1375
+ if (cur && cur.loadAsync) await cur.loadAsync();
1376
+ var found = cur && cur.findAllWithCriteria
1377
+ ? cur.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET', 'INSTANCE'] })
1378
+ : [];
1379
+ for (var fi = 0; fi < found.length && nodes.length < maxNodes; fi++) {
1380
+ await pushNodeEntry(found[fi]);
1381
+ }
1382
+ } else {
1383
+ throw new Error('Provide nodeIds array or set scanCurrentPage=true');
1384
+ }
1385
+
1386
+ figma.ui.postMessage({
1387
+ type: 'GET_CODE_CONNECT_HINTS_RESULT',
1388
+ requestId: msg.requestId,
1389
+ success: true,
1390
+ data: {
1391
+ nodes: nodes,
1392
+ note: 'Full Code Connect source paths live in repo figma.config / CLI; use Figma official MCP for native get_code_connect_map when needed.'
1393
+ }
1394
+ });
1395
+ } catch (error2) {
1396
+ var emsg2 = error2 && error2.message ? error2.message : String(error2);
1397
+ figma.ui.postMessage({
1398
+ type: 'GET_CODE_CONNECT_HINTS_RESULT',
1399
+ requestId: msg.requestId,
1400
+ success: false,
1401
+ error: emsg2
1402
+ });
1403
+ }
1404
+ }
1405
+
1406
+ // ============================================================================
1407
+ // INSTANTIATE_COMPONENT - Create a component instance with overrides
1408
+ // ============================================================================
1409
+ else if (msg.type === 'INSTANTIATE_COMPONENT') {
1410
+ try {
1411
+ console.log('🌉 [F-MCP ATezer Bridge] Instantiating component:', msg.componentKey || msg.nodeId);
1412
+
1413
+ var component = null;
1414
+ var instance = null;
1415
+
1416
+ // Try published library first (by key), then fall back to local component (by nodeId)
1417
+ if (msg.componentKey) {
1418
+ try {
1419
+ component = await figma.importComponentByKeyAsync(msg.componentKey);
1420
+ } catch (importError) {
1421
+ console.log('🌉 [F-MCP ATezer Bridge] Not a published component, trying local...');
1422
+ }
1423
+ }
1424
+
1425
+ // Fall back to local component by nodeId
1426
+ if (!component && msg.nodeId) {
1427
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1428
+ if (node) {
1429
+ if (node.type === 'COMPONENT') {
1430
+ component = node;
1431
+ } else if (node.type === 'COMPONENT_SET') {
1432
+ // For component sets, find the right variant or use default
1433
+ if (msg.variant && node.children && node.children.length > 0) {
1434
+ // Build variant name from properties (e.g., "Type=Simple, State=Default")
1435
+ var variantParts = [];
1436
+ for (var prop in msg.variant) {
1437
+ if (msg.variant.hasOwnProperty(prop)) {
1438
+ variantParts.push(prop + '=' + msg.variant[prop]);
1439
+ }
1440
+ }
1441
+ var targetVariantName = variantParts.join(', ');
1442
+ console.log('🌉 [F-MCP ATezer Bridge] Looking for variant:', targetVariantName);
1443
+
1444
+ // Find matching variant
1445
+ for (var i = 0; i < node.children.length; i++) {
1446
+ var child = node.children[i];
1447
+ if (child.type === 'COMPONENT' && child.name === targetVariantName) {
1448
+ component = child;
1449
+ console.log('🌉 [F-MCP ATezer Bridge] Found exact variant match');
1450
+ break;
1451
+ }
1452
+ }
1453
+
1454
+ // If no exact match, try partial match
1455
+ if (!component) {
1456
+ for (var i = 0; i < node.children.length; i++) {
1457
+ var child = node.children[i];
1458
+ if (child.type === 'COMPONENT') {
1459
+ var matches = true;
1460
+ for (var prop in msg.variant) {
1461
+ if (msg.variant.hasOwnProperty(prop)) {
1462
+ var expected = prop + '=' + msg.variant[prop];
1463
+ if (child.name.indexOf(expected) === -1) {
1464
+ matches = false;
1465
+ break;
1466
+ }
1467
+ }
1468
+ }
1469
+ if (matches) {
1470
+ component = child;
1471
+ console.log('🌉 [F-MCP ATezer Bridge] Found partial variant match:', child.name);
1472
+ break;
1473
+ }
1474
+ }
1475
+ }
1476
+ }
1477
+ }
1478
+
1479
+ // Default to first variant if no match
1480
+ if (!component && node.children && node.children.length > 0) {
1481
+ component = node.children[0];
1482
+ console.log('🌉 [F-MCP ATezer Bridge] Using default variant:', component.name);
1483
+ }
1484
+ }
1485
+ }
1486
+ }
1487
+
1488
+ if (!component) {
1489
+ // Build detailed error message with actionable guidance
1490
+ var errorParts = ['Component not found.'];
1491
+
1492
+ if (msg.componentKey) {
1493
+ errorParts.push('Published component key "' + msg.componentKey + '" could not be imported - it may have been unpublished or deleted from the library.');
1494
+ }
1495
+
1496
+ if (msg.nodeId) {
1497
+ errorParts.push('Local nodeId "' + msg.nodeId + '" does not exist in this file - nodeIds are session-specific and may be stale.');
1498
+ }
1499
+
1500
+ if (!msg.componentKey && !msg.nodeId) {
1501
+ errorParts.push('No componentKey or nodeId was provided.');
1502
+ }
1503
+
1504
+ errorParts.push('SUGGESTION: Use figma_search_components to get current component identifiers before instantiating.');
1505
+
1506
+ throw new Error(errorParts.join(' '));
1507
+ }
1508
+
1509
+ // Create the instance
1510
+ instance = component.createInstance();
1511
+
1512
+ // Apply position if specified
1513
+ if (msg.position) {
1514
+ instance.x = msg.position.x || 0;
1515
+ instance.y = msg.position.y || 0;
1516
+ }
1517
+
1518
+ // Apply size override if specified
1519
+ if (msg.size) {
1520
+ instance.resize(msg.size.width, msg.size.height);
1521
+ }
1522
+
1523
+ // Apply property overrides
1524
+ if (msg.overrides) {
1525
+ for (var propName in msg.overrides) {
1526
+ if (msg.overrides.hasOwnProperty(propName)) {
1527
+ try {
1528
+ instance.setProperties({ [propName]: msg.overrides[propName] });
1529
+ } catch (propError) {
1530
+ console.warn('🌉 [F-MCP ATezer Bridge] Could not set property ' + propName + ':', propError.message);
1531
+ }
1532
+ }
1533
+ }
1534
+ }
1535
+
1536
+ // Apply variant selection if specified
1537
+ if (msg.variant) {
1538
+ try {
1539
+ instance.setProperties(msg.variant);
1540
+ } catch (variantError) {
1541
+ console.warn('🌉 [F-MCP ATezer Bridge] Could not set variant:', variantError.message);
1542
+ }
1543
+ }
1544
+
1545
+ // Append to parent if specified
1546
+ if (msg.parentId) {
1547
+ var parent = await figma.getNodeByIdAsync(msg.parentId);
1548
+ if (parent && 'appendChild' in parent) {
1549
+ parent.appendChild(instance);
1550
+ }
1551
+ }
1552
+
1553
+ console.log('🌉 [F-MCP ATezer Bridge] Component instantiated:', instance.id);
1554
+
1555
+ figma.ui.postMessage({
1556
+ type: 'INSTANTIATE_COMPONENT_RESULT',
1557
+ requestId: msg.requestId,
1558
+ success: true,
1559
+ instance: {
1560
+ id: instance.id,
1561
+ name: instance.name,
1562
+ x: instance.x,
1563
+ y: instance.y,
1564
+ width: instance.width,
1565
+ height: instance.height
1566
+ }
1567
+ });
1568
+
1569
+ } catch (error) {
1570
+ var errorMsg = error && error.message ? error.message : String(error);
1571
+ console.error('🌉 [F-MCP ATezer Bridge] Instantiate component error:', errorMsg);
1572
+ figma.ui.postMessage({
1573
+ type: 'INSTANTIATE_COMPONENT_RESULT',
1574
+ requestId: msg.requestId,
1575
+ success: false,
1576
+ error: errorMsg
1577
+ });
1578
+ }
1579
+ }
1580
+
1581
+ // ============================================================================
1582
+ // SET_NODE_DESCRIPTION - Set description on component/style
1583
+ // ============================================================================
1584
+ else if (msg.type === 'SET_NODE_DESCRIPTION') {
1585
+ try {
1586
+ console.log('🌉 [F-MCP ATezer Bridge] Setting description on node:', msg.nodeId);
1587
+
1588
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1589
+ if (!node) {
1590
+ throw new Error('Node not found: ' + msg.nodeId);
1591
+ }
1592
+
1593
+ // Check if node supports description
1594
+ if (!('description' in node)) {
1595
+ throw new Error('Node type ' + node.type + ' does not support description');
1596
+ }
1597
+
1598
+ // Set description (and markdown if supported)
1599
+ node.description = msg.description || '';
1600
+ if (msg.descriptionMarkdown && 'descriptionMarkdown' in node) {
1601
+ node.descriptionMarkdown = msg.descriptionMarkdown;
1602
+ }
1603
+
1604
+ console.log('🌉 [F-MCP ATezer Bridge] Description set successfully');
1605
+
1606
+ figma.ui.postMessage({
1607
+ type: 'SET_NODE_DESCRIPTION_RESULT',
1608
+ requestId: msg.requestId,
1609
+ success: true,
1610
+ node: { id: node.id, name: node.name, description: node.description }
1611
+ });
1612
+
1613
+ } catch (error) {
1614
+ var errorMsg = error && error.message ? error.message : String(error);
1615
+ console.error('🌉 [F-MCP ATezer Bridge] Set description error:', errorMsg);
1616
+ figma.ui.postMessage({
1617
+ type: 'SET_NODE_DESCRIPTION_RESULT',
1618
+ requestId: msg.requestId,
1619
+ success: false,
1620
+ error: errorMsg
1621
+ });
1622
+ }
1623
+ }
1624
+
1625
+ // ============================================================================
1626
+ // ADD_COMPONENT_PROPERTY - Add property to component
1627
+ // ============================================================================
1628
+ else if (msg.type === 'ADD_COMPONENT_PROPERTY') {
1629
+ try {
1630
+ console.log('🌉 [F-MCP ATezer Bridge] Adding component property:', msg.propertyName, 'type:', msg.propertyType);
1631
+
1632
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1633
+ if (!node) {
1634
+ throw new Error('Node not found: ' + msg.nodeId);
1635
+ }
1636
+
1637
+ if (node.type !== 'COMPONENT' && node.type !== 'COMPONENT_SET') {
1638
+ throw new Error('Node must be a COMPONENT or COMPONENT_SET. Got: ' + node.type);
1639
+ }
1640
+
1641
+ // Check if it's a variant (can't add properties to variants)
1642
+ if (node.type === 'COMPONENT' && node.parent && node.parent.type === 'COMPONENT_SET') {
1643
+ throw new Error('Cannot add properties to variant components. Add to the parent COMPONENT_SET instead.');
1644
+ }
1645
+
1646
+ // Build options if preferredValues provided
1647
+ var options = undefined;
1648
+ if (msg.preferredValues) {
1649
+ options = { preferredValues: msg.preferredValues };
1650
+ }
1651
+
1652
+ // Use msg.propertyType (not msg.type which is the message type 'ADD_COMPONENT_PROPERTY')
1653
+ var propertyNameWithId = node.addComponentProperty(msg.propertyName, msg.propertyType, msg.defaultValue, options);
1654
+
1655
+ console.log('🌉 [F-MCP ATezer Bridge] Property added:', propertyNameWithId);
1656
+
1657
+ figma.ui.postMessage({
1658
+ type: 'ADD_COMPONENT_PROPERTY_RESULT',
1659
+ requestId: msg.requestId,
1660
+ success: true,
1661
+ propertyName: propertyNameWithId
1662
+ });
1663
+
1664
+ } catch (error) {
1665
+ var errorMsg = error && error.message ? error.message : String(error);
1666
+ console.error('🌉 [F-MCP ATezer Bridge] Add component property error:', errorMsg);
1667
+ figma.ui.postMessage({
1668
+ type: 'ADD_COMPONENT_PROPERTY_RESULT',
1669
+ requestId: msg.requestId,
1670
+ success: false,
1671
+ error: errorMsg
1672
+ });
1673
+ }
1674
+ }
1675
+
1676
+ // ============================================================================
1677
+ // EDIT_COMPONENT_PROPERTY - Edit existing component property
1678
+ // ============================================================================
1679
+ else if (msg.type === 'EDIT_COMPONENT_PROPERTY') {
1680
+ try {
1681
+ console.log('🌉 [F-MCP ATezer Bridge] Editing component property:', msg.propertyName);
1682
+
1683
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1684
+ if (!node) {
1685
+ throw new Error('Node not found: ' + msg.nodeId);
1686
+ }
1687
+
1688
+ if (node.type !== 'COMPONENT' && node.type !== 'COMPONENT_SET') {
1689
+ throw new Error('Node must be a COMPONENT or COMPONENT_SET. Got: ' + node.type);
1690
+ }
1691
+
1692
+ var propertyNameWithId = node.editComponentProperty(msg.propertyName, msg.newValue);
1693
+
1694
+ console.log('🌉 [F-MCP ATezer Bridge] Property edited:', propertyNameWithId);
1695
+
1696
+ figma.ui.postMessage({
1697
+ type: 'EDIT_COMPONENT_PROPERTY_RESULT',
1698
+ requestId: msg.requestId,
1699
+ success: true,
1700
+ propertyName: propertyNameWithId
1701
+ });
1702
+
1703
+ } catch (error) {
1704
+ var errorMsg = error && error.message ? error.message : String(error);
1705
+ console.error('🌉 [F-MCP ATezer Bridge] Edit component property error:', errorMsg);
1706
+ figma.ui.postMessage({
1707
+ type: 'EDIT_COMPONENT_PROPERTY_RESULT',
1708
+ requestId: msg.requestId,
1709
+ success: false,
1710
+ error: errorMsg
1711
+ });
1712
+ }
1713
+ }
1714
+
1715
+ // ============================================================================
1716
+ // DELETE_COMPONENT_PROPERTY - Delete a component property
1717
+ // ============================================================================
1718
+ else if (msg.type === 'DELETE_COMPONENT_PROPERTY') {
1719
+ try {
1720
+ console.log('🌉 [F-MCP ATezer Bridge] Deleting component property:', msg.propertyName);
1721
+
1722
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1723
+ if (!node) {
1724
+ throw new Error('Node not found: ' + msg.nodeId);
1725
+ }
1726
+
1727
+ if (node.type !== 'COMPONENT' && node.type !== 'COMPONENT_SET') {
1728
+ throw new Error('Node must be a COMPONENT or COMPONENT_SET. Got: ' + node.type);
1729
+ }
1730
+
1731
+ node.deleteComponentProperty(msg.propertyName);
1732
+
1733
+ console.log('🌉 [F-MCP ATezer Bridge] Property deleted');
1734
+
1735
+ figma.ui.postMessage({
1736
+ type: 'DELETE_COMPONENT_PROPERTY_RESULT',
1737
+ requestId: msg.requestId,
1738
+ success: true
1739
+ });
1740
+
1741
+ } catch (error) {
1742
+ var errorMsg = error && error.message ? error.message : String(error);
1743
+ console.error('🌉 [F-MCP ATezer Bridge] Delete component property error:', errorMsg);
1744
+ figma.ui.postMessage({
1745
+ type: 'DELETE_COMPONENT_PROPERTY_RESULT',
1746
+ requestId: msg.requestId,
1747
+ success: false,
1748
+ error: errorMsg
1749
+ });
1750
+ }
1751
+ }
1752
+
1753
+ // ============================================================================
1754
+ // RESIZE_NODE - Resize any node
1755
+ // ============================================================================
1756
+ else if (msg.type === 'RESIZE_NODE') {
1757
+ try {
1758
+ console.log('🌉 [F-MCP ATezer Bridge] Resizing node:', msg.nodeId);
1759
+
1760
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1761
+ if (!node) {
1762
+ throw new Error('Node not found: ' + msg.nodeId);
1763
+ }
1764
+
1765
+ if (!('resize' in node)) {
1766
+ throw new Error('Node type ' + node.type + ' does not support resize');
1767
+ }
1768
+
1769
+ if (msg.withConstraints) {
1770
+ node.resize(msg.width, msg.height);
1771
+ } else {
1772
+ node.resizeWithoutConstraints(msg.width, msg.height);
1773
+ }
1774
+
1775
+ console.log('🌉 [F-MCP ATezer Bridge] Node resized to:', msg.width, 'x', msg.height);
1776
+
1777
+ figma.ui.postMessage({
1778
+ type: 'RESIZE_NODE_RESULT',
1779
+ requestId: msg.requestId,
1780
+ success: true,
1781
+ node: { id: node.id, name: node.name, width: node.width, height: node.height }
1782
+ });
1783
+
1784
+ } catch (error) {
1785
+ var errorMsg = error && error.message ? error.message : String(error);
1786
+ console.error('🌉 [F-MCP ATezer Bridge] Resize node error:', errorMsg);
1787
+ figma.ui.postMessage({
1788
+ type: 'RESIZE_NODE_RESULT',
1789
+ requestId: msg.requestId,
1790
+ success: false,
1791
+ error: errorMsg
1792
+ });
1793
+ }
1794
+ }
1795
+
1796
+ // ============================================================================
1797
+ // MOVE_NODE - Move/position a node
1798
+ // ============================================================================
1799
+ else if (msg.type === 'MOVE_NODE') {
1800
+ try {
1801
+ console.log('🌉 [F-MCP ATezer Bridge] Moving node:', msg.nodeId);
1802
+
1803
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1804
+ if (!node) {
1805
+ throw new Error('Node not found: ' + msg.nodeId);
1806
+ }
1807
+
1808
+ if (!('x' in node)) {
1809
+ throw new Error('Node type ' + node.type + ' does not support positioning');
1810
+ }
1811
+
1812
+ node.x = msg.x;
1813
+ node.y = msg.y;
1814
+
1815
+ console.log('🌉 [F-MCP ATezer Bridge] Node moved to:', msg.x, ',', msg.y);
1816
+
1817
+ figma.ui.postMessage({
1818
+ type: 'MOVE_NODE_RESULT',
1819
+ requestId: msg.requestId,
1820
+ success: true,
1821
+ node: { id: node.id, name: node.name, x: node.x, y: node.y }
1822
+ });
1823
+
1824
+ } catch (error) {
1825
+ var errorMsg = error && error.message ? error.message : String(error);
1826
+ console.error('🌉 [F-MCP ATezer Bridge] Move node error:', errorMsg);
1827
+ figma.ui.postMessage({
1828
+ type: 'MOVE_NODE_RESULT',
1829
+ requestId: msg.requestId,
1830
+ success: false,
1831
+ error: errorMsg
1832
+ });
1833
+ }
1834
+ }
1835
+
1836
+ // ============================================================================
1837
+ // SET_NODE_FILLS - Set fills (colors) on a node
1838
+ // ============================================================================
1839
+ else if (msg.type === 'SET_NODE_FILLS') {
1840
+ try {
1841
+ console.log('🌉 [F-MCP ATezer Bridge] Setting fills on node:', msg.nodeId);
1842
+
1843
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1844
+ if (!node) {
1845
+ throw new Error('Node not found: ' + msg.nodeId);
1846
+ }
1847
+
1848
+ if (!('fills' in node)) {
1849
+ throw new Error('Node type ' + node.type + ' does not support fills');
1850
+ }
1851
+
1852
+ // Process fills - convert hex colors if needed
1853
+ var processedFills = msg.fills.map(function(fill) {
1854
+ if (fill.type === 'SOLID' && typeof fill.color === 'string') {
1855
+ // Convert hex to RGB
1856
+ var rgb = hexToFigmaRGB(fill.color);
1857
+ return {
1858
+ type: 'SOLID',
1859
+ color: { r: rgb.r, g: rgb.g, b: rgb.b },
1860
+ opacity: rgb.a !== undefined ? rgb.a : (fill.opacity !== undefined ? fill.opacity : 1)
1861
+ };
1862
+ }
1863
+ return fill;
1864
+ });
1865
+
1866
+ node.fills = processedFills;
1867
+
1868
+ console.log('🌉 [F-MCP ATezer Bridge] Fills set successfully');
1869
+
1870
+ figma.ui.postMessage({
1871
+ type: 'SET_NODE_FILLS_RESULT',
1872
+ requestId: msg.requestId,
1873
+ success: true,
1874
+ node: { id: node.id, name: node.name }
1875
+ });
1876
+
1877
+ } catch (error) {
1878
+ var errorMsg = error && error.message ? error.message : String(error);
1879
+ console.error('🌉 [F-MCP ATezer Bridge] Set fills error:', errorMsg);
1880
+ figma.ui.postMessage({
1881
+ type: 'SET_NODE_FILLS_RESULT',
1882
+ requestId: msg.requestId,
1883
+ success: false,
1884
+ error: errorMsg
1885
+ });
1886
+ }
1887
+ }
1888
+
1889
+ // ============================================================================
1890
+ // SET_NODE_STROKES - Set strokes on a node
1891
+ // ============================================================================
1892
+ else if (msg.type === 'SET_NODE_STROKES') {
1893
+ try {
1894
+ console.log('🌉 [F-MCP ATezer Bridge] Setting strokes on node:', msg.nodeId);
1895
+
1896
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1897
+ if (!node) {
1898
+ throw new Error('Node not found: ' + msg.nodeId);
1899
+ }
1900
+
1901
+ if (!('strokes' in node)) {
1902
+ throw new Error('Node type ' + node.type + ' does not support strokes');
1903
+ }
1904
+
1905
+ // Process strokes - convert hex colors if needed
1906
+ var processedStrokes = msg.strokes.map(function(stroke) {
1907
+ if (stroke.type === 'SOLID' && typeof stroke.color === 'string') {
1908
+ var rgb = hexToFigmaRGB(stroke.color);
1909
+ return {
1910
+ type: 'SOLID',
1911
+ color: { r: rgb.r, g: rgb.g, b: rgb.b },
1912
+ opacity: rgb.a !== undefined ? rgb.a : (stroke.opacity !== undefined ? stroke.opacity : 1)
1913
+ };
1914
+ }
1915
+ return stroke;
1916
+ });
1917
+
1918
+ node.strokes = processedStrokes;
1919
+
1920
+ if (msg.strokeWeight !== undefined) {
1921
+ node.strokeWeight = msg.strokeWeight;
1922
+ }
1923
+
1924
+ console.log('🌉 [F-MCP ATezer Bridge] Strokes set successfully');
1925
+
1926
+ figma.ui.postMessage({
1927
+ type: 'SET_NODE_STROKES_RESULT',
1928
+ requestId: msg.requestId,
1929
+ success: true,
1930
+ node: { id: node.id, name: node.name }
1931
+ });
1932
+
1933
+ } catch (error) {
1934
+ var errorMsg = error && error.message ? error.message : String(error);
1935
+ console.error('🌉 [F-MCP ATezer Bridge] Set strokes error:', errorMsg);
1936
+ figma.ui.postMessage({
1937
+ type: 'SET_NODE_STROKES_RESULT',
1938
+ requestId: msg.requestId,
1939
+ success: false,
1940
+ error: errorMsg
1941
+ });
1942
+ }
1943
+ }
1944
+
1945
+ // ============================================================================
1946
+ // SET_NODE_OPACITY - Set opacity on a node
1947
+ // ============================================================================
1948
+ else if (msg.type === 'SET_NODE_OPACITY') {
1949
+ try {
1950
+ console.log('🌉 [F-MCP ATezer Bridge] Setting opacity on node:', msg.nodeId);
1951
+
1952
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1953
+ if (!node) {
1954
+ throw new Error('Node not found: ' + msg.nodeId);
1955
+ }
1956
+
1957
+ if (!('opacity' in node)) {
1958
+ throw new Error('Node type ' + node.type + ' does not support opacity');
1959
+ }
1960
+
1961
+ node.opacity = Math.max(0, Math.min(1, msg.opacity));
1962
+
1963
+ console.log('🌉 [F-MCP ATezer Bridge] Opacity set to:', node.opacity);
1964
+
1965
+ figma.ui.postMessage({
1966
+ type: 'SET_NODE_OPACITY_RESULT',
1967
+ requestId: msg.requestId,
1968
+ success: true,
1969
+ node: { id: node.id, name: node.name, opacity: node.opacity }
1970
+ });
1971
+
1972
+ } catch (error) {
1973
+ var errorMsg = error && error.message ? error.message : String(error);
1974
+ console.error('🌉 [F-MCP ATezer Bridge] Set opacity error:', errorMsg);
1975
+ figma.ui.postMessage({
1976
+ type: 'SET_NODE_OPACITY_RESULT',
1977
+ requestId: msg.requestId,
1978
+ success: false,
1979
+ error: errorMsg
1980
+ });
1981
+ }
1982
+ }
1983
+
1984
+ // ============================================================================
1985
+ // SET_NODE_CORNER_RADIUS - Set corner radius on a node
1986
+ // ============================================================================
1987
+ else if (msg.type === 'SET_NODE_CORNER_RADIUS') {
1988
+ try {
1989
+ console.log('🌉 [F-MCP ATezer Bridge] Setting corner radius on node:', msg.nodeId);
1990
+
1991
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
1992
+ if (!node) {
1993
+ throw new Error('Node not found: ' + msg.nodeId);
1994
+ }
1995
+
1996
+ if (!('cornerRadius' in node)) {
1997
+ throw new Error('Node type ' + node.type + ' does not support corner radius');
1998
+ }
1999
+
2000
+ node.cornerRadius = msg.radius;
2001
+
2002
+ console.log('🌉 [F-MCP ATezer Bridge] Corner radius set to:', msg.radius);
2003
+
2004
+ figma.ui.postMessage({
2005
+ type: 'SET_NODE_CORNER_RADIUS_RESULT',
2006
+ requestId: msg.requestId,
2007
+ success: true,
2008
+ node: { id: node.id, name: node.name, cornerRadius: node.cornerRadius }
2009
+ });
2010
+
2011
+ } catch (error) {
2012
+ var errorMsg = error && error.message ? error.message : String(error);
2013
+ console.error('🌉 [F-MCP ATezer Bridge] Set corner radius error:', errorMsg);
2014
+ figma.ui.postMessage({
2015
+ type: 'SET_NODE_CORNER_RADIUS_RESULT',
2016
+ requestId: msg.requestId,
2017
+ success: false,
2018
+ error: errorMsg
2019
+ });
2020
+ }
2021
+ }
2022
+
2023
+ // ============================================================================
2024
+ // CLONE_NODE - Clone/duplicate a node
2025
+ // ============================================================================
2026
+ else if (msg.type === 'CLONE_NODE') {
2027
+ try {
2028
+ console.log('🌉 [F-MCP ATezer Bridge] Cloning node:', msg.nodeId);
2029
+
2030
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
2031
+ if (!node) {
2032
+ throw new Error('Node not found: ' + msg.nodeId);
2033
+ }
2034
+
2035
+ if (!('clone' in node)) {
2036
+ throw new Error('Node type ' + node.type + ' does not support cloning');
2037
+ }
2038
+
2039
+ var clonedNode = node.clone();
2040
+
2041
+ console.log('🌉 [F-MCP ATezer Bridge] Node cloned:', clonedNode.id);
2042
+
2043
+ figma.ui.postMessage({
2044
+ type: 'CLONE_NODE_RESULT',
2045
+ requestId: msg.requestId,
2046
+ success: true,
2047
+ node: { id: clonedNode.id, name: clonedNode.name, x: clonedNode.x, y: clonedNode.y }
2048
+ });
2049
+
2050
+ } catch (error) {
2051
+ var errorMsg = error && error.message ? error.message : String(error);
2052
+ console.error('🌉 [F-MCP ATezer Bridge] Clone node error:', errorMsg);
2053
+ figma.ui.postMessage({
2054
+ type: 'CLONE_NODE_RESULT',
2055
+ requestId: msg.requestId,
2056
+ success: false,
2057
+ error: errorMsg
2058
+ });
2059
+ }
2060
+ }
2061
+
2062
+ // ============================================================================
2063
+ // DELETE_NODE - Delete a node
2064
+ // ============================================================================
2065
+ else if (msg.type === 'DELETE_NODE') {
2066
+ try {
2067
+ console.log('🌉 [F-MCP ATezer Bridge] Deleting node:', msg.nodeId);
2068
+
2069
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
2070
+ if (!node) {
2071
+ throw new Error('Node not found: ' + msg.nodeId);
2072
+ }
2073
+
2074
+ var deletedInfo = { id: node.id, name: node.name };
2075
+
2076
+ node.remove();
2077
+
2078
+ console.log('🌉 [F-MCP ATezer Bridge] Node deleted');
2079
+
2080
+ figma.ui.postMessage({
2081
+ type: 'DELETE_NODE_RESULT',
2082
+ requestId: msg.requestId,
2083
+ success: true,
2084
+ deleted: deletedInfo
2085
+ });
2086
+
2087
+ } catch (error) {
2088
+ var errorMsg = error && error.message ? error.message : String(error);
2089
+ console.error('🌉 [F-MCP ATezer Bridge] Delete node error:', errorMsg);
2090
+ figma.ui.postMessage({
2091
+ type: 'DELETE_NODE_RESULT',
2092
+ requestId: msg.requestId,
2093
+ success: false,
2094
+ error: errorMsg
2095
+ });
2096
+ }
2097
+ }
2098
+
2099
+ // ============================================================================
2100
+ // RENAME_NODE - Rename a node
2101
+ // ============================================================================
2102
+ else if (msg.type === 'RENAME_NODE') {
2103
+ try {
2104
+ console.log('🌉 [F-MCP ATezer Bridge] Renaming node:', msg.nodeId);
2105
+
2106
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
2107
+ if (!node) {
2108
+ throw new Error('Node not found: ' + msg.nodeId);
2109
+ }
2110
+
2111
+ var oldName = node.name;
2112
+ node.name = msg.newName;
2113
+
2114
+ console.log('🌉 [F-MCP ATezer Bridge] Node renamed from "' + oldName + '" to "' + msg.newName + '"');
2115
+
2116
+ figma.ui.postMessage({
2117
+ type: 'RENAME_NODE_RESULT',
2118
+ requestId: msg.requestId,
2119
+ success: true,
2120
+ node: { id: node.id, name: node.name, oldName: oldName }
2121
+ });
2122
+
2123
+ } catch (error) {
2124
+ var errorMsg = error && error.message ? error.message : String(error);
2125
+ console.error('🌉 [F-MCP ATezer Bridge] Rename node error:', errorMsg);
2126
+ figma.ui.postMessage({
2127
+ type: 'RENAME_NODE_RESULT',
2128
+ requestId: msg.requestId,
2129
+ success: false,
2130
+ error: errorMsg
2131
+ });
2132
+ }
2133
+ }
2134
+
2135
+ // ============================================================================
2136
+ // SET_TEXT_CONTENT - Set text on a text node
2137
+ // ============================================================================
2138
+ else if (msg.type === 'SET_TEXT_CONTENT') {
2139
+ try {
2140
+ console.log('🌉 [F-MCP ATezer Bridge] Setting text content on node:', msg.nodeId);
2141
+
2142
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
2143
+ if (!node) {
2144
+ throw new Error('Node not found: ' + msg.nodeId);
2145
+ }
2146
+
2147
+ if (node.type !== 'TEXT') {
2148
+ throw new Error('Node must be a TEXT node. Got: ' + node.type);
2149
+ }
2150
+
2151
+ // Load the font first
2152
+ await figma.loadFontAsync(node.fontName);
2153
+
2154
+ node.characters = msg.text;
2155
+
2156
+ // Apply font properties if specified
2157
+ if (msg.fontSize) {
2158
+ node.fontSize = msg.fontSize;
2159
+ }
2160
+
2161
+ console.log('🌉 [F-MCP ATezer Bridge] Text content set');
2162
+
2163
+ figma.ui.postMessage({
2164
+ type: 'SET_TEXT_CONTENT_RESULT',
2165
+ requestId: msg.requestId,
2166
+ success: true,
2167
+ node: { id: node.id, name: node.name, characters: node.characters }
2168
+ });
2169
+
2170
+ } catch (error) {
2171
+ var errorMsg = error && error.message ? error.message : String(error);
2172
+ console.error('🌉 [F-MCP ATezer Bridge] Set text content error:', errorMsg);
2173
+ figma.ui.postMessage({
2174
+ type: 'SET_TEXT_CONTENT_RESULT',
2175
+ requestId: msg.requestId,
2176
+ success: false,
2177
+ error: errorMsg
2178
+ });
2179
+ }
2180
+ }
2181
+
2182
+ // ============================================================================
2183
+ // CREATE_CHILD_NODE - Create a new child node
2184
+ // ============================================================================
2185
+ else if (msg.type === 'CREATE_CHILD_NODE') {
2186
+ try {
2187
+ console.log('🌉 [F-MCP ATezer Bridge] Creating child node of type:', msg.nodeType);
2188
+
2189
+ var parent = await figma.getNodeByIdAsync(msg.parentId);
2190
+ if (!parent) {
2191
+ throw new Error('Parent node not found: ' + msg.parentId);
2192
+ }
2193
+
2194
+ if (!('appendChild' in parent)) {
2195
+ throw new Error('Parent node type ' + parent.type + ' does not support children');
2196
+ }
2197
+
2198
+ var newNode;
2199
+ var props = msg.properties || {};
2200
+
2201
+ switch (msg.nodeType) {
2202
+ case 'RECTANGLE':
2203
+ newNode = figma.createRectangle();
2204
+ break;
2205
+ case 'ELLIPSE':
2206
+ newNode = figma.createEllipse();
2207
+ break;
2208
+ case 'FRAME':
2209
+ newNode = figma.createFrame();
2210
+ break;
2211
+ case 'TEXT':
2212
+ newNode = figma.createText();
2213
+ // Load default font
2214
+ await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
2215
+ newNode.fontName = { family: 'Inter', style: 'Regular' };
2216
+ if (props.text) {
2217
+ newNode.characters = props.text;
2218
+ }
2219
+ break;
2220
+ case 'LINE':
2221
+ newNode = figma.createLine();
2222
+ break;
2223
+ case 'POLYGON':
2224
+ newNode = figma.createPolygon();
2225
+ break;
2226
+ case 'STAR':
2227
+ newNode = figma.createStar();
2228
+ break;
2229
+ case 'VECTOR':
2230
+ newNode = figma.createVector();
2231
+ break;
2232
+ default:
2233
+ throw new Error('Unsupported node type: ' + msg.nodeType);
2234
+ }
2235
+
2236
+ // Apply common properties
2237
+ if (props.name) newNode.name = props.name;
2238
+ if (props.x !== undefined) newNode.x = props.x;
2239
+ if (props.y !== undefined) newNode.y = props.y;
2240
+ if (props.width !== undefined && props.height !== undefined) {
2241
+ newNode.resize(props.width, props.height);
2242
+ }
2243
+
2244
+ // Apply fills if specified
2245
+ if (props.fills) {
2246
+ var processedFills = props.fills.map(function(fill) {
2247
+ if (fill.type === 'SOLID' && typeof fill.color === 'string') {
2248
+ var rgb = hexToFigmaRGB(fill.color);
2249
+ return {
2250
+ type: 'SOLID',
2251
+ color: { r: rgb.r, g: rgb.g, b: rgb.b },
2252
+ opacity: rgb.a !== undefined ? rgb.a : 1
2253
+ };
2254
+ }
2255
+ return fill;
2256
+ });
2257
+ newNode.fills = processedFills;
2258
+ }
2259
+
2260
+ // Add to parent
2261
+ parent.appendChild(newNode);
2262
+
2263
+ console.log('🌉 [F-MCP ATezer Bridge] Child node created:', newNode.id);
2264
+
2265
+ figma.ui.postMessage({
2266
+ type: 'CREATE_CHILD_NODE_RESULT',
2267
+ requestId: msg.requestId,
2268
+ success: true,
2269
+ node: {
2270
+ id: newNode.id,
2271
+ name: newNode.name,
2272
+ type: newNode.type,
2273
+ x: newNode.x,
2274
+ y: newNode.y,
2275
+ width: newNode.width,
2276
+ height: newNode.height
2277
+ }
2278
+ });
2279
+
2280
+ } catch (error) {
2281
+ var errorMsg = error && error.message ? error.message : String(error);
2282
+ console.error('🌉 [F-MCP ATezer Bridge] Create child node error:', errorMsg);
2283
+ figma.ui.postMessage({
2284
+ type: 'CREATE_CHILD_NODE_RESULT',
2285
+ requestId: msg.requestId,
2286
+ success: false,
2287
+ error: errorMsg
2288
+ });
2289
+ }
2290
+ }
2291
+
2292
+ // ============================================================================
2293
+ // CAPTURE_SCREENSHOT - Capture node screenshot using plugin exportAsync
2294
+ // This captures the CURRENT plugin runtime state (not cloud state like REST API)
2295
+ // ============================================================================
2296
+ else if (msg.type === 'CAPTURE_SCREENSHOT') {
2297
+ try {
2298
+ console.log('🌉 [F-MCP ATezer Bridge] Capturing screenshot for node:', msg.nodeId);
2299
+
2300
+ var node = msg.nodeId ? await figma.getNodeByIdAsync(msg.nodeId) : figma.currentPage;
2301
+ if (!node) {
2302
+ throw new Error('Node not found: ' + msg.nodeId);
2303
+ }
2304
+
2305
+ // Verify node supports export
2306
+ if (!('exportAsync' in node)) {
2307
+ throw new Error('Node type ' + node.type + ' does not support export');
2308
+ }
2309
+
2310
+ // Configure export settings
2311
+ var format = msg.format || 'PNG';
2312
+ var scale = msg.scale || 2;
2313
+
2314
+ var exportSettings = {
2315
+ format: format,
2316
+ constraint: { type: 'SCALE', value: scale }
2317
+ };
2318
+
2319
+ // Export the node
2320
+ var bytes = await node.exportAsync(exportSettings);
2321
+
2322
+ // Convert to base64
2323
+ var base64 = figma.base64Encode(bytes);
2324
+
2325
+ // Get node bounds for context
2326
+ var bounds = null;
2327
+ if ('absoluteBoundingBox' in node) {
2328
+ bounds = node.absoluteBoundingBox;
2329
+ }
2330
+
2331
+ console.log('🌉 [F-MCP ATezer Bridge] Screenshot captured:', bytes.length, 'bytes');
2332
+
2333
+ figma.ui.postMessage({
2334
+ type: 'CAPTURE_SCREENSHOT_RESULT',
2335
+ requestId: msg.requestId,
2336
+ success: true,
2337
+ image: {
2338
+ base64: base64,
2339
+ format: format,
2340
+ scale: scale,
2341
+ byteLength: bytes.length,
2342
+ node: {
2343
+ id: node.id,
2344
+ name: node.name,
2345
+ type: node.type
2346
+ },
2347
+ bounds: bounds
2348
+ }
2349
+ });
2350
+
2351
+ } catch (error) {
2352
+ var errorMsg = error && error.message ? error.message : String(error);
2353
+ console.error('🌉 [F-MCP ATezer Bridge] Screenshot capture error:', errorMsg);
2354
+ figma.ui.postMessage({
2355
+ type: 'CAPTURE_SCREENSHOT_RESULT',
2356
+ requestId: msg.requestId,
2357
+ success: false,
2358
+ error: errorMsg
2359
+ });
2360
+ }
2361
+ }
2362
+
2363
+ // ============================================================================
2364
+ // SET_INSTANCE_PROPERTIES - Update component properties on an instance
2365
+ // Uses instance.setProperties() to update TEXT, BOOLEAN, INSTANCE_SWAP, VARIANT
2366
+ // ============================================================================
2367
+ else if (msg.type === 'SET_INSTANCE_PROPERTIES') {
2368
+ try {
2369
+ console.log('🌉 [F-MCP ATezer Bridge] Setting instance properties on:', msg.nodeId);
2370
+
2371
+ var node = await figma.getNodeByIdAsync(msg.nodeId);
2372
+ if (!node) {
2373
+ throw new Error('Node not found: ' + msg.nodeId);
2374
+ }
2375
+
2376
+ if (node.type !== 'INSTANCE') {
2377
+ throw new Error('Node must be an INSTANCE. Got: ' + node.type);
2378
+ }
2379
+
2380
+ // Get current properties for reference
2381
+ var currentProps = node.componentProperties;
2382
+ console.log('🌉 [F-MCP ATezer Bridge] Current properties:', JSON.stringify(Object.keys(currentProps)));
2383
+
2384
+ // Build the properties object
2385
+ // Note: TEXT, BOOLEAN, INSTANCE_SWAP properties use the format "PropertyName#nodeId"
2386
+ // VARIANT properties use just "PropertyName"
2387
+ var propsToSet = {};
2388
+ var propUpdates = msg.properties || {};
2389
+
2390
+ for (var propName in propUpdates) {
2391
+ var newValue = propUpdates[propName];
2392
+
2393
+ // Check if this exact property name exists
2394
+ if (currentProps[propName] !== undefined) {
2395
+ propsToSet[propName] = newValue;
2396
+ console.log('🌉 [F-MCP ATezer Bridge] Setting property:', propName, '=', newValue);
2397
+ } else {
2398
+ // Try to find a matching property with a suffix (for TEXT/BOOLEAN/INSTANCE_SWAP)
2399
+ var foundMatch = false;
2400
+ for (var existingProp in currentProps) {
2401
+ // Check if this is the base property name with a node ID suffix
2402
+ if (existingProp.startsWith(propName + '#')) {
2403
+ propsToSet[existingProp] = newValue;
2404
+ console.log('🌉 [F-MCP ATezer Bridge] Found suffixed property:', existingProp, '=', newValue);
2405
+ foundMatch = true;
2406
+ break;
2407
+ }
2408
+ }
2409
+
2410
+ if (!foundMatch) {
2411
+ console.warn('🌉 [F-MCP ATezer Bridge] Property not found:', propName, '- Available:', Object.keys(currentProps).join(', '));
2412
+ }
2413
+ }
2414
+ }
2415
+
2416
+ if (Object.keys(propsToSet).length === 0) {
2417
+ throw new Error('No valid properties to set. Available properties: ' + Object.keys(currentProps).join(', '));
2418
+ }
2419
+
2420
+ // Apply the properties
2421
+ node.setProperties(propsToSet);
2422
+
2423
+ // Get updated properties
2424
+ var updatedProps = node.componentProperties;
2425
+
2426
+ console.log('🌉 [F-MCP ATezer Bridge] Instance properties updated');
2427
+
2428
+ figma.ui.postMessage({
2429
+ type: 'SET_INSTANCE_PROPERTIES_RESULT',
2430
+ requestId: msg.requestId,
2431
+ success: true,
2432
+ instance: {
2433
+ id: node.id,
2434
+ name: node.name,
2435
+ componentId: node.mainComponent ? node.mainComponent.id : null,
2436
+ propertiesSet: Object.keys(propsToSet),
2437
+ currentProperties: Object.keys(updatedProps).reduce(function(acc, key) {
2438
+ acc[key] = {
2439
+ type: updatedProps[key].type,
2440
+ value: updatedProps[key].value
2441
+ };
2442
+ return acc;
2443
+ }, {})
2444
+ }
2445
+ });
2446
+
2447
+ } catch (error) {
2448
+ var errorMsg = error && error.message ? error.message : String(error);
2449
+ console.error('🌉 [F-MCP ATezer Bridge] Set instance properties error:', errorMsg);
2450
+ figma.ui.postMessage({
2451
+ type: 'SET_INSTANCE_PROPERTIES_RESULT',
2452
+ requestId: msg.requestId,
2453
+ success: false,
2454
+ error: errorMsg
2455
+ });
2456
+ }
2457
+ }
2458
+
2459
+ // ============================================================================
2460
+ // GET_DOCUMENT_STRUCTURE - File tree without REST API (token-friendly)
2461
+ // ============================================================================
2462
+ else if (msg.type === 'GET_DOCUMENT_STRUCTURE') {
2463
+ try {
2464
+ await figma.loadAllPagesAsync();
2465
+
2466
+ var depth = Math.min(Math.max(msg.depth || 1, 0), 3);
2467
+ var verbosity = msg.verbosity || 'summary';
2468
+ var opts = {
2469
+ verbosity: verbosity,
2470
+ includeLayout: msg.includeLayout === true,
2471
+ includeVisual: msg.includeVisual === true,
2472
+ includeTypography: msg.includeTypography === true,
2473
+ includeCodeReady: msg.includeCodeReady !== false,
2474
+ outputHint: msg.outputHint || null
2475
+ };
2476
+
2477
+ var document = {
2478
+ name: figma.root.name,
2479
+ id: figma.root.id,
2480
+ type: 'DOCUMENT',
2481
+ fileKey: figma.fileKey || null,
2482
+ children: figma.root.children ? (await Promise.all(figma.root.children.map(function(p) { return buildNodePayload(p, 1, depth, opts); }))).filter(Boolean) : []
2483
+ };
2484
+
2485
+ figma.ui.postMessage({
2486
+ type: 'GET_DOCUMENT_STRUCTURE_RESULT',
2487
+ requestId: msg.requestId,
2488
+ success: true,
2489
+ data: { document: document, fileKey: figma.fileKey, fileName: figma.root.name }
2490
+ });
2491
+ } catch (error) {
2492
+ var errMsg = error && error.message ? error.message : String(error);
2493
+ figma.ui.postMessage({
2494
+ type: 'GET_DOCUMENT_STRUCTURE_RESULT',
2495
+ requestId: msg.requestId,
2496
+ success: false,
2497
+ error: errMsg
2498
+ });
2499
+ }
2500
+ }
2501
+
2502
+ // ============================================================================
2503
+ // GET_NODE_CONTEXT - Subtree for one node with text content (token-efficient design context)
2504
+ // ============================================================================
2505
+ else if (msg.type === 'GET_NODE_CONTEXT') {
2506
+ try {
2507
+ var nodeId = msg.nodeId;
2508
+ if (!nodeId) {
2509
+ figma.ui.postMessage({
2510
+ type: 'GET_NODE_CONTEXT_RESULT',
2511
+ requestId: msg.requestId,
2512
+ success: false,
2513
+ error: 'nodeId is required'
2514
+ });
2515
+ } else {
2516
+ var targetNode = await figma.getNodeByIdAsync(nodeId);
2517
+ if (!targetNode) {
2518
+ figma.ui.postMessage({
2519
+ type: 'GET_NODE_CONTEXT_RESULT',
2520
+ requestId: msg.requestId,
2521
+ success: false,
2522
+ error: 'Node not found: ' + nodeId
2523
+ });
2524
+ } else {
2525
+ var depthNode = Math.min(Math.max(msg.depth || 2, 0), 3);
2526
+ var verbosityNode = msg.verbosity || 'standard';
2527
+ var optsNode = {
2528
+ verbosity: verbosityNode,
2529
+ includeLayout: msg.includeLayout === true,
2530
+ includeVisual: msg.includeVisual === true,
2531
+ includeTypography: msg.includeTypography === true,
2532
+ includeCodeReady: msg.includeCodeReady !== false,
2533
+ outputHint: msg.outputHint || null
2534
+ };
2535
+
2536
+ var nodeTree = await buildNodePayload(targetNode, 0, depthNode, optsNode);
2537
+ figma.ui.postMessage({
2538
+ type: 'GET_NODE_CONTEXT_RESULT',
2539
+ requestId: msg.requestId,
2540
+ success: true,
2541
+ data: {
2542
+ node: nodeTree,
2543
+ fileKey: figma.fileKey || null,
2544
+ fileName: figma.root.name
2545
+ }
2546
+ });
2547
+ }
2548
+ }
2549
+ } catch (error) {
2550
+ var errMsg = error && error.message ? error.message : String(error);
2551
+ figma.ui.postMessage({
2552
+ type: 'GET_NODE_CONTEXT_RESULT',
2553
+ requestId: msg.requestId,
2554
+ success: false,
2555
+ error: errMsg
2556
+ });
2557
+ }
2558
+ }
2559
+
2560
+ // ============================================================================
2561
+ // GET_CONSOLE_LOGS - Plugin console buffer (no CDP)
2562
+ else if (msg.type === 'GET_CONSOLE_LOGS') {
2563
+ var limit = Math.min(Math.max(1, parseInt(msg.limit, 10) || 50), 200);
2564
+ var slice = __consoleLogBuffer.slice(-limit);
2565
+ figma.ui.postMessage({
2566
+ type: 'CONSOLE_LOGS_RESULT',
2567
+ requestId: msg.requestId,
2568
+ success: true,
2569
+ data: { logs: slice, total: __consoleLogBuffer.length }
2570
+ });
2571
+ }
2572
+ else if (msg.type === 'CLEAR_CONSOLE') {
2573
+ __consoleLogBuffer.length = 0;
2574
+ figma.ui.postMessage({
2575
+ type: 'CLEAR_CONSOLE_RESULT',
2576
+ requestId: msg.requestId,
2577
+ success: true
2578
+ });
2579
+ }
2580
+
2581
+ // BATCH_CREATE_VARIABLES - Up to 100 variables, partial success
2582
+ else if (msg.type === 'BATCH_CREATE_VARIABLES') {
2583
+ var items = msg.items || [];
2584
+ if (items.length > 100) items = items.slice(0, 100);
2585
+ var created = [], failed = [];
2586
+ for (var i = 0; i < items.length; i++) {
2587
+ var it = items[i];
2588
+ try {
2589
+ var coll = await figma.variables.getVariableCollectionByIdAsync(it.collectionId);
2590
+ if (!coll) throw new Error('Collection not found');
2591
+ var valuesByMode = {};
2592
+ if (it.modeId != null && it.value !== undefined) valuesByMode[it.modeId] = it.value;
2593
+ else if (it.valuesByMode) valuesByMode = it.valuesByMode;
2594
+ var v = figma.variables.createVariable(it.name, coll, it.resolvedType);
2595
+ if (Object.keys(valuesByMode).length) {
2596
+ var modeId = it.modeId || Object.keys(valuesByMode)[0];
2597
+ if (modeId) v.setValueForMode(modeId, valuesByMode[modeId]);
2598
+ }
2599
+ created.push({ name: it.name, id: v.id });
2600
+ } catch (e) {
2601
+ failed.push({ name: it.name, error: e.message || String(e) });
2602
+ }
2603
+ }
2604
+ figma.ui.postMessage({
2605
+ type: 'BATCH_CREATE_VARIABLES_RESULT',
2606
+ requestId: msg.requestId,
2607
+ success: true,
2608
+ data: { created, failed }
2609
+ });
2610
+ }
2611
+
2612
+ // BATCH_UPDATE_VARIABLES - Up to 100 updates, partial success
2613
+ else if (msg.type === 'BATCH_UPDATE_VARIABLES') {
2614
+ var updates = msg.items || [];
2615
+ if (updates.length > 100) updates = updates.slice(0, 100);
2616
+ var updated = [], failed = [];
2617
+ for (var j = 0; j < updates.length; j++) {
2618
+ var u = updates[j];
2619
+ try {
2620
+ var variable = await figma.variables.getVariableByIdAsync(u.variableId);
2621
+ if (!variable) throw new Error('Variable not found');
2622
+ variable.setValueForMode(u.modeId, u.value);
2623
+ updated.push({ variableId: u.variableId });
2624
+ } catch (e) {
2625
+ failed.push({ variableId: u.variableId, error: e.message || String(e) });
2626
+ }
2627
+ }
2628
+ figma.ui.postMessage({
2629
+ type: 'BATCH_UPDATE_VARIABLES_RESULT',
2630
+ requestId: msg.requestId,
2631
+ success: true,
2632
+ data: { updated, failed }
2633
+ });
2634
+ }
2635
+
2636
+ // SETUP_DESIGN_TOKENS - Atomic: collection + modes + variables; rollback on error
2637
+ else if (msg.type === 'SETUP_DESIGN_TOKENS') {
2638
+ var collectionName = msg.collectionName || 'Design Tokens';
2639
+ var modes = msg.modes || ['Default'];
2640
+ var tokens = msg.tokens || [];
2641
+ var createdVarIds = [];
2642
+ var collection = null;
2643
+ try {
2644
+ collection = figma.variables.createVariableCollection(collectionName);
2645
+ if (modes.length > 1) {
2646
+ for (var m = 1; m < modes.length; m++) collection.addMode(modes[m]);
2647
+ }
2648
+ var defaultModeId = collection.modes[0].modeId;
2649
+ for (var t = 0; t < tokens.length; t++) {
2650
+ var tok = tokens[t];
2651
+ var name = typeof tok === 'object' ? tok.name : String(tok);
2652
+ var type = (typeof tok === 'object' ? tok.type : null) || 'STRING';
2653
+ var values = (typeof tok === 'object' && tok.values) ? tok.values : (typeof tok === 'object' ? tok.value : undefined);
2654
+ var valsByMode = typeof values === 'object' && !Array.isArray(values) ? values : { [defaultModeId]: values };
2655
+ var variable = figma.variables.createVariable(name, collection, type);
2656
+ for (var mid in valsByMode) variable.setValueForMode(mid, valsByMode[mid]);
2657
+ createdVarIds.push(variable.id);
2658
+ }
2659
+ figma.ui.postMessage({
2660
+ type: 'SETUP_DESIGN_TOKENS_RESULT',
2661
+ requestId: msg.requestId,
2662
+ success: true,
2663
+ data: { collectionId: collection.id, collectionName: collection.name, variableIds: createdVarIds, modeCount: modes.length }
2664
+ });
2665
+ } catch (err) {
2666
+ if (collection) {
2667
+ for (var d = 0; d < createdVarIds.length; d++) {
2668
+ try {
2669
+ var vv = await figma.variables.getVariableByIdAsync(createdVarIds[d]);
2670
+ if (vv) vv.remove();
2671
+ } catch (ignore) {}
2672
+ }
2673
+ try { collection.remove(); } catch (ignore) {}
2674
+ }
2675
+ figma.ui.postMessage({
2676
+ type: 'SETUP_DESIGN_TOKENS_RESULT',
2677
+ requestId: msg.requestId,
2678
+ success: false,
2679
+ error: err.message || String(err)
2680
+ });
2681
+ }
2682
+ }
2683
+
2684
+ // ARRANGE_COMPONENT_SET - combineAsVariants
2685
+ else if (msg.type === 'ARRANGE_COMPONENT_SET') {
2686
+ try {
2687
+ var nodeIds = msg.nodeIds || [];
2688
+ if (nodeIds.length < 2) {
2689
+ throw new Error('At least 2 component node IDs required');
2690
+ }
2691
+ var nodes = [];
2692
+ var parent = null;
2693
+ for (var n = 0; n < nodeIds.length; n++) {
2694
+ var nd = figma.getNodeById(nodeIds[n]);
2695
+ if (!nd) throw new Error('Node not found: ' + nodeIds[n]);
2696
+ if (nd.type !== 'COMPONENT') throw new Error('Node is not a COMPONENT: ' + nodeIds[n]);
2697
+ nodes.push(nd);
2698
+ if (!parent) parent = nd.parent;
2699
+ }
2700
+ if (!parent || !parent.appendChild) throw new Error('Parent does not support children');
2701
+ var componentSet = figma.combineAsVariants(nodes, parent);
2702
+ figma.ui.postMessage({
2703
+ type: 'ARRANGE_COMPONENT_SET_RESULT',
2704
+ requestId: msg.requestId,
2705
+ success: true,
2706
+ data: { nodeId: componentSet.id, name: componentSet.name }
2707
+ });
2708
+ } catch (err) {
2709
+ figma.ui.postMessage({
2710
+ type: 'ARRANGE_COMPONENT_SET_RESULT',
2711
+ requestId: msg.requestId,
2712
+ success: false,
2713
+ error: err.message || String(err)
2714
+ });
2715
+ }
2716
+ }
2717
+
2718
+ // GET_LOCAL_STYLES - Paint, text, effect styles without REST API
2719
+ // ============================================================================
2720
+ else if (msg.type === 'GET_LOCAL_STYLES') {
2721
+ try {
2722
+ var verbosity = msg.verbosity || 'summary';
2723
+ var paints = await figma.getLocalPaintStylesAsync();
2724
+ var texts = await figma.getLocalTextStylesAsync();
2725
+ var effects = await figma.getLocalEffectStylesAsync();
2726
+
2727
+ function styleToSummary(s) {
2728
+ return { id: s.id, name: s.name };
2729
+ }
2730
+ function styleToFull(s) {
2731
+ var o = { id: s.id, name: s.name, description: s.description || '' };
2732
+ if (s.paints) o.paints = s.paints;
2733
+ if (s.fontName) o.fontName = s.fontName;
2734
+ if (s.fontSize !== undefined) o.fontSize = s.fontSize;
2735
+ if (s.effects) o.effects = s.effects;
2736
+ return o;
2737
+ }
2738
+
2739
+ var mapFn = verbosity === 'full' ? styleToFull : styleToSummary;
2740
+
2741
+ figma.ui.postMessage({
2742
+ type: 'GET_LOCAL_STYLES_RESULT',
2743
+ requestId: msg.requestId,
2744
+ success: true,
2745
+ data: {
2746
+ paintStyles: paints.map(mapFn),
2747
+ textStyles: texts.map(mapFn),
2748
+ effectStyles: effects.map(mapFn)
2749
+ }
2750
+ });
2751
+ } catch (error) {
2752
+ var errMsg = error && error.message ? error.message : String(error);
2753
+ figma.ui.postMessage({
2754
+ type: 'GET_LOCAL_STYLES_RESULT',
2755
+ requestId: msg.requestId,
2756
+ success: false,
2757
+ error: errMsg
2758
+ });
2759
+ }
2760
+ }
2761
+ };
2762
+
2763
+ console.log('🌉 [F-MCP ATezer Bridge] Ready to handle component requests');
2764
+ console.log('🌉 [F-MCP ATezer Bridge] Plugin will stay open until manually closed');
2765
+
2766
+ // Plugin stays open - no auto-close
2767
+ // UI iframe remains accessible for Puppeteer to read data from window object