figma-console-mcp 1.19.0 → 1.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/cloudflare/core/cloud-websocket-connector.js +6 -0
- package/dist/cloudflare/core/deep-component-tools.js +128 -0
- package/dist/cloudflare/core/design-code-tools.js +1 -1
- package/dist/cloudflare/core/enrichment/enrichment-service.js +108 -12
- package/dist/cloudflare/core/figma-api.js +2 -2
- package/dist/cloudflare/core/figma-desktop-connector.js +32 -0
- package/dist/cloudflare/core/figma-tools.js +328 -20
- package/dist/cloudflare/core/websocket-connector.js +6 -0
- package/dist/cloudflare/index.js +137 -41
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +5 -2
- package/dist/core/figma-api.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +11 -6
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/resolve-package-root.d.ts +2 -0
- package/dist/core/resolve-package-root.d.ts.map +1 -0
- package/dist/core/resolve-package-root.js +12 -0
- package/dist/core/resolve-package-root.js.map +1 -0
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +7 -9
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts +6 -0
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +22 -0
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +103 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,9 +51,9 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
51
51
|
| Real-time monitoring (console, selection) | ✅ | ❌ | ❌ |
|
|
52
52
|
| Desktop Bridge plugin | ✅ | ✅ | ❌ |
|
|
53
53
|
| Requires Node.js | Yes | **No** | No |
|
|
54
|
-
| **Total tools available** | **
|
|
54
|
+
| **Total tools available** | **90+** | **43** | **22** |
|
|
55
55
|
|
|
56
|
-
> **Bottom line:** Remote SSE is **read-only** with ~38% of the tools. **Cloud Mode** unlocks write access from web AI clients without Node.js. NPX/Local Git gives the full
|
|
56
|
+
> **Bottom line:** Remote SSE is **read-only** with ~38% of the tools. **Cloud Mode** unlocks write access from web AI clients without Node.js. NPX/Local Git gives the full 90+ tools with real-time monitoring.
|
|
57
57
|
|
|
58
58
|
---
|
|
59
59
|
|
|
@@ -61,7 +61,7 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
61
61
|
|
|
62
62
|
**Best for:** Designers who want full AI-assisted design capabilities.
|
|
63
63
|
|
|
64
|
-
**What you get:** All
|
|
64
|
+
**What you get:** All 90+ tools including design creation, variable management, and component instantiation.
|
|
65
65
|
|
|
66
66
|
#### Prerequisites
|
|
67
67
|
|
|
@@ -123,7 +123,7 @@ If you're not sure where to put the JSON configuration above, here's where each
|
|
|
123
123
|
#### Step 3: Connect to Figma Desktop
|
|
124
124
|
|
|
125
125
|
**Desktop Bridge Plugin:**
|
|
126
|
-
1. Open Figma Desktop normally (no special flags needed)
|
|
126
|
+
1. Open Figma Desktop normally (no special flags needed) and open a file
|
|
127
127
|
2. Go to **Plugins → Development → Import plugin from manifest...**
|
|
128
128
|
3. Select `~/.figma-console-mcp/plugin/manifest.json` (stable path, auto-created by the MCP server)
|
|
129
129
|
4. Run the plugin in your Figma file — the bootloader finds the MCP server and loads the latest UI automatically
|
|
@@ -156,7 +156,7 @@ Create a simple frame with a blue background
|
|
|
156
156
|
|
|
157
157
|
**Best for:** Developers who want to modify source code or contribute to the project.
|
|
158
158
|
|
|
159
|
-
**What you get:** Same
|
|
159
|
+
**What you get:** Same 90+ tools as NPX, plus full source code access.
|
|
160
160
|
|
|
161
161
|
#### Quick Setup
|
|
162
162
|
|
|
@@ -302,7 +302,7 @@ AI Client → Cloud MCP Server → Durable Object Relay → Desktop Bridge Plugi
|
|
|
302
302
|
| Feature | NPX (Recommended) | Cloud Mode | Local Git | Remote SSE |
|
|
303
303
|
|---------|-------------------|------------|-----------|------------|
|
|
304
304
|
| **Setup time** | ~10 minutes | ~5 minutes | ~15 minutes | ~2 minutes |
|
|
305
|
-
| **Total tools** | **
|
|
305
|
+
| **Total tools** | **90+** | **43** | **90+** | **22** (read-only) |
|
|
306
306
|
| **Design creation** | ✅ | ✅ | ✅ | ❌ |
|
|
307
307
|
| **Variable management** | ✅ | ✅ | ✅ | ❌ |
|
|
308
308
|
| **Component instantiation** | ✅ | ✅ | ✅ | ❌ |
|
|
@@ -317,7 +317,7 @@ AI Client → Cloud MCP Server → Durable Object Relay → Desktop Bridge Plugi
|
|
|
317
317
|
| **Automatic updates** | ✅ (`@latest`) | ✅ | Manual (`git pull`) | ✅ |
|
|
318
318
|
| **Source code access** | ❌ | ❌ | ✅ | ❌ |
|
|
319
319
|
|
|
320
|
-
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full
|
|
320
|
+
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full 90+ tools.
|
|
321
321
|
|
|
322
322
|
**📖 [Complete Feature Comparison](docs/mode-comparison.md)**
|
|
323
323
|
|
|
@@ -649,7 +649,7 @@ The **Figma Desktop Bridge** plugin is the recommended way to connect Figma to t
|
|
|
649
649
|
- The MCP server communicates via **WebSocket** through the Desktop Bridge plugin
|
|
650
650
|
- The server tries port 9223 first, then automatically falls back through ports 9224–9232 if needed
|
|
651
651
|
- The plugin scans all ports in the range and connects to every active server it finds
|
|
652
|
-
- All
|
|
652
|
+
- All 90+ tools work through the WebSocket transport
|
|
653
653
|
|
|
654
654
|
**Multiple files:** The WebSocket server supports multiple simultaneous plugin connections — one per open Figma file. Each connection is tracked by file key with independent state (selection, document changes, console logs).
|
|
655
655
|
|
|
@@ -786,7 +786,7 @@ The architecture supports adding new apps with minimal boilerplate — each app
|
|
|
786
786
|
|
|
787
787
|
## 🛤️ Roadmap
|
|
788
788
|
|
|
789
|
-
**Current Status:** v1.17.0 (Stable) - Production-ready with FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking,
|
|
789
|
+
**Current Status:** v1.17.0 (Stable) - Production-ready with FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, 90+ tools, Comments API, and MCP Apps
|
|
790
790
|
|
|
791
791
|
**Recent Releases:**
|
|
792
792
|
- [x] **v1.17.0** - Figma Slides Support: 15 new tools for managing presentations — slides, transitions, content, reordering, and navigation. Inspired by Toni Haidamous (PR #11).
|
|
@@ -142,6 +142,12 @@ export class CloudWebSocketConnector {
|
|
|
142
142
|
async getAnnotationCategories() {
|
|
143
143
|
return this.sendCommand('GET_ANNOTATION_CATEGORIES', {}, 5000);
|
|
144
144
|
}
|
|
145
|
+
async deepGetComponent(nodeId, depth) {
|
|
146
|
+
return this.sendCommand('DEEP_GET_COMPONENT', { nodeId, depth: depth || 10 }, 30000);
|
|
147
|
+
}
|
|
148
|
+
async analyzeComponentSet(nodeId) {
|
|
149
|
+
return this.sendCommand('ANALYZE_COMPONENT_SET', { nodeId }, 30000);
|
|
150
|
+
}
|
|
145
151
|
async addComponentProperty(nodeId, propertyName, type, defaultValue, options) {
|
|
146
152
|
const params = { nodeId, propertyName, propertyType: type, defaultValue };
|
|
147
153
|
if (options?.preferredValues)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Component Extraction MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Provides unlimited-depth component tree extraction via the Desktop Bridge
|
|
5
|
+
* Plugin API. Returns full visual properties, resolved design token names,
|
|
6
|
+
* instance references (mainComponent), prototype reactions, and annotations
|
|
7
|
+
* at every level of the tree.
|
|
8
|
+
*
|
|
9
|
+
* This complements figma_get_component_for_development (REST API, depth 4)
|
|
10
|
+
* with deeper, richer data when the Desktop Bridge plugin is connected.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { createChildLogger } from "./logger.js";
|
|
14
|
+
const logger = createChildLogger({ component: "deep-component-tools" });
|
|
15
|
+
export function registerDeepComponentTools(server, getDesktopConnector) {
|
|
16
|
+
server.tool("figma_get_component_for_development_deep", "Get a deeply nested component tree with full visual properties, resolved design token names, instance references, prototype interactions, and annotations at every level. Uses the Desktop Bridge Plugin API for unlimited depth traversal — essential for complex components like data tables, nested menus, date pickers, and compound form fields where the standard depth-4 REST API tool misses deeper structure. Returns boundVariables resolved to actual token names (not just IDs), mainComponent references for INSTANCE nodes, and reactions for interaction states. Requires Desktop Bridge plugin. For simpler components (depth ≤ 4), use figma_get_component_for_development instead.", {
|
|
17
|
+
nodeId: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("Component node ID to extract (e.g., '695:313')"),
|
|
20
|
+
depth: z.preprocess((v) => (typeof v === "string" ? Number(v) : v), z.number().optional().default(10)).describe("Maximum tree depth to traverse (default: 10, max: 20). Use higher values for deeply nested components."),
|
|
21
|
+
}, async ({ nodeId, depth = 10 }) => {
|
|
22
|
+
try {
|
|
23
|
+
const clampedDepth = Math.min(Math.max(depth, 1), 20);
|
|
24
|
+
logger.info({ nodeId, depth: clampedDepth }, "Deep component extraction");
|
|
25
|
+
const connector = await getDesktopConnector();
|
|
26
|
+
const result = await connector.deepGetComponent(nodeId, clampedDepth);
|
|
27
|
+
if (!result || (result.success === false)) {
|
|
28
|
+
throw new Error(result?.error || "Failed to extract component");
|
|
29
|
+
}
|
|
30
|
+
const data = result.data || result;
|
|
31
|
+
// Measure response size and warn if large
|
|
32
|
+
const responseJson = JSON.stringify(data);
|
|
33
|
+
const sizeKB = Math.round(responseJson.length / 1024);
|
|
34
|
+
const response = {
|
|
35
|
+
nodeId,
|
|
36
|
+
component: data,
|
|
37
|
+
metadata: {
|
|
38
|
+
purpose: "deep_component_development",
|
|
39
|
+
treeDepth: clampedDepth,
|
|
40
|
+
responseSizeKB: sizeKB,
|
|
41
|
+
variablesResolved: data._variableMapSize || 0,
|
|
42
|
+
note: [
|
|
43
|
+
`Deep component tree extracted via Plugin API (depth ${clampedDepth}).`,
|
|
44
|
+
"boundVariables are resolved to token names, collections, and codeSyntax.",
|
|
45
|
+
"INSTANCE nodes include mainComponent references (key, name, component set).",
|
|
46
|
+
"Use this data to generate production-quality, token-aware, accessible code.",
|
|
47
|
+
sizeKB > 200 ? "Response is large — consider targeting a specific child node for deeper analysis." : null,
|
|
48
|
+
].filter(Boolean).join(" "),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify(response),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
logger.error({ error }, "Deep component extraction failed");
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify({
|
|
68
|
+
error: "deep_component_failed",
|
|
69
|
+
message: `Cannot extract deep component. ${message}`,
|
|
70
|
+
hint: "This tool requires the Desktop Bridge plugin to be running in Figma. For REST API fallback (depth 4), use figma_get_component_for_development.",
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// -----------------------------------------------------------------------
|
|
79
|
+
// Tool: figma_analyze_component_set
|
|
80
|
+
// -----------------------------------------------------------------------
|
|
81
|
+
server.tool("figma_analyze_component_set", "Analyze a Figma COMPONENT_SET to extract variant state machine and cross-variant diffs for code generation. Returns: (1) variant axes (size, state) with all values, (2) CSS pseudo-class mappings for interaction states (hover→:hover, focus→:focus-visible, disabled→:disabled, error→[aria-invalid]), (3) visual diff from default state per variant (only changed properties — fill token, stroke token, stroke weight, text color, opacity, effects, visibility), (4) component property definitions mapped to code props (BOOLEAN→boolean, TEXT→string, INSTANCE_SWAP→slot/ReactNode). Use this on the parent COMPONENT_SET node, not individual variants. Requires Desktop Bridge plugin.", {
|
|
82
|
+
nodeId: z
|
|
83
|
+
.string()
|
|
84
|
+
.describe("COMPONENT_SET node ID (the parent of all variants, e.g., '214:274')"),
|
|
85
|
+
}, async ({ nodeId }) => {
|
|
86
|
+
try {
|
|
87
|
+
logger.info({ nodeId }, "Analyzing component set");
|
|
88
|
+
const connector = await getDesktopConnector();
|
|
89
|
+
const result = await connector.analyzeComponentSet(nodeId);
|
|
90
|
+
if (!result || (result.success === false)) {
|
|
91
|
+
throw new Error(result?.error || "Failed to analyze component set");
|
|
92
|
+
}
|
|
93
|
+
const data = result.data || result;
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify({
|
|
99
|
+
nodeId,
|
|
100
|
+
analysis: data,
|
|
101
|
+
metadata: {
|
|
102
|
+
purpose: "variant_state_machine",
|
|
103
|
+
note: "Use cssMapping to implement interaction states as CSS pseudo-classes/attributes. diffFromDefault shows only what changes per variant — apply as style overrides. componentProps maps to your component's TypeScript interface.",
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
logger.error({ error }, "Component set analysis failed");
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: JSON.stringify({
|
|
118
|
+
error: "analyze_component_set_failed",
|
|
119
|
+
message: `Cannot analyze component set. ${message}`,
|
|
120
|
+
hint: "This tool requires the Desktop Bridge plugin and a COMPONENT_SET node ID (not an individual variant). Use figma_search_components to find component sets.",
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -2127,7 +2127,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2127
2127
|
logger.info({ fileKey, nodeId, canonicalSource, enrich }, "Starting design-code parity check");
|
|
2128
2128
|
const api = await getFigmaAPI();
|
|
2129
2129
|
// Fetch component node
|
|
2130
|
-
const nodesResponse = await api.getNodes(fileKey, [nodeId], { depth:
|
|
2130
|
+
const nodesResponse = await api.getNodes(fileKey, [nodeId], { depth: 4 });
|
|
2131
2131
|
const nodeData = nodesResponse?.nodes?.[nodeId];
|
|
2132
2132
|
if (!nodeData?.document) {
|
|
2133
2133
|
throw new Error(`Node ${nodeId} not found in file ${fileKey}`);
|
|
@@ -152,20 +152,116 @@ export class EnrichmentService {
|
|
|
152
152
|
}
|
|
153
153
|
enriched.styles_used = stylesUsed;
|
|
154
154
|
}
|
|
155
|
-
// Extract variables used
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
155
|
+
// Extract variables used and detect hardcoded values by walking the node tree
|
|
156
|
+
const varsUsed = [];
|
|
157
|
+
const hardcodedValues = [];
|
|
158
|
+
const variables = this.extractVariablesMap(data);
|
|
159
|
+
// Walk node tree to find boundVariables and hardcoded values
|
|
160
|
+
const walkForTokens = (node, path = "") => {
|
|
161
|
+
if (!node)
|
|
162
|
+
return;
|
|
163
|
+
const nodePath = path ? `${path} > ${node.name || node.id}` : (node.name || node.id);
|
|
164
|
+
const bv = node.boundVariables || {};
|
|
165
|
+
// Check fills
|
|
166
|
+
if (node.fills && Array.isArray(node.fills)) {
|
|
167
|
+
for (let i = 0; i < node.fills.length; i++) {
|
|
168
|
+
const fill = node.fills[i];
|
|
169
|
+
if (fill.type === "SOLID" && fill.visible !== false) {
|
|
170
|
+
const fillBv = bv.fills;
|
|
171
|
+
if (fillBv && (Array.isArray(fillBv) ? fillBv[i] : fillBv)) {
|
|
172
|
+
const varRef = Array.isArray(fillBv) ? fillBv[i] : fillBv;
|
|
173
|
+
if (varRef?.id) {
|
|
174
|
+
const varInfo = variables.get(varRef.id);
|
|
175
|
+
varsUsed.push({
|
|
176
|
+
variableId: varRef.id,
|
|
177
|
+
variableName: varInfo?.name || varRef.id,
|
|
178
|
+
property: "fill",
|
|
179
|
+
nodePath,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Hardcoded fill
|
|
185
|
+
const c = fill.color;
|
|
186
|
+
if (c) {
|
|
187
|
+
hardcodedValues.push({
|
|
188
|
+
property: "fill",
|
|
189
|
+
value: `rgb(${Math.round(c.r * 255)}, ${Math.round(c.g * 255)}, ${Math.round(c.b * 255)})`,
|
|
190
|
+
nodePath,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Check strokes
|
|
198
|
+
if (node.strokes && Array.isArray(node.strokes)) {
|
|
199
|
+
for (let i = 0; i < node.strokes.length; i++) {
|
|
200
|
+
const stroke = node.strokes[i];
|
|
201
|
+
if (stroke.type === "SOLID" && stroke.visible !== false) {
|
|
202
|
+
const strokeBv = bv.strokes;
|
|
203
|
+
if (strokeBv && (Array.isArray(strokeBv) ? strokeBv[i] : strokeBv)) {
|
|
204
|
+
const varRef = Array.isArray(strokeBv) ? strokeBv[i] : strokeBv;
|
|
205
|
+
if (varRef?.id) {
|
|
206
|
+
const varInfo = variables.get(varRef.id);
|
|
207
|
+
varsUsed.push({
|
|
208
|
+
variableId: varRef.id,
|
|
209
|
+
variableName: varInfo?.name || varRef.id,
|
|
210
|
+
property: "stroke",
|
|
211
|
+
nodePath,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const c = stroke.color;
|
|
217
|
+
if (c) {
|
|
218
|
+
hardcodedValues.push({
|
|
219
|
+
property: "stroke",
|
|
220
|
+
value: `rgb(${Math.round(c.r * 255)}, ${Math.round(c.g * 255)}, ${Math.round(c.b * 255)})`,
|
|
221
|
+
nodePath,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Check spacing/sizing tokens
|
|
229
|
+
const spacingProps = ["itemSpacing", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "cornerRadius"];
|
|
230
|
+
for (const prop of spacingProps) {
|
|
231
|
+
if (node[prop] !== undefined && node[prop] !== 0) {
|
|
232
|
+
if (bv[prop]?.id) {
|
|
233
|
+
const varInfo = variables.get(bv[prop].id);
|
|
234
|
+
varsUsed.push({
|
|
235
|
+
variableId: bv[prop].id,
|
|
236
|
+
variableName: varInfo?.name || bv[prop].id,
|
|
237
|
+
property: prop,
|
|
238
|
+
nodePath,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
hardcodedValues.push({
|
|
243
|
+
property: prop,
|
|
244
|
+
value: node[prop],
|
|
245
|
+
nodePath,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Recurse into children
|
|
251
|
+
if (node.children && Array.isArray(node.children)) {
|
|
252
|
+
for (const child of node.children) {
|
|
253
|
+
walkForTokens(child, nodePath);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
walkForTokens(component);
|
|
258
|
+
enriched.variables_used = varsUsed;
|
|
259
|
+
enriched.hardcoded_values = hardcodedValues;
|
|
164
260
|
// Calculate token coverage
|
|
165
|
-
const totalProps =
|
|
166
|
-
const tokenProps =
|
|
261
|
+
const totalProps = varsUsed.length + (enriched.styles_used?.length || 0) + hardcodedValues.length;
|
|
262
|
+
const tokenProps = varsUsed.length + (enriched.styles_used?.length || 0);
|
|
167
263
|
enriched.token_coverage =
|
|
168
|
-
totalProps > 0 ? Math.round((tokenProps / totalProps) * 100) :
|
|
264
|
+
totalProps > 0 ? Math.round((tokenProps / totalProps) * 100) : 100;
|
|
169
265
|
this.logger.info({ componentId: component.id, coverage: enriched.token_coverage }, "Component enrichment complete");
|
|
170
266
|
return enriched;
|
|
171
267
|
}
|
|
@@ -341,8 +341,8 @@ export class FigmaAPI {
|
|
|
341
341
|
/**
|
|
342
342
|
* Helper: Get component metadata with properties
|
|
343
343
|
*/
|
|
344
|
-
async getComponentData(fileKey, nodeId) {
|
|
345
|
-
const response = await this.getNodes(fileKey, [nodeId], { depth
|
|
344
|
+
async getComponentData(fileKey, nodeId, depth = 4) {
|
|
345
|
+
const response = await this.getNodes(fileKey, [nodeId], { depth });
|
|
346
346
|
return response.nodes?.[nodeId];
|
|
347
347
|
}
|
|
348
348
|
/**
|
|
@@ -937,6 +937,38 @@ export class FigmaDesktopConnector {
|
|
|
937
937
|
throw error;
|
|
938
938
|
}
|
|
939
939
|
}
|
|
940
|
+
/**
|
|
941
|
+
* Analyze a component set — variant state machine + cross-variant diff
|
|
942
|
+
*/
|
|
943
|
+
async analyzeComponentSet(nodeId) {
|
|
944
|
+
logger.info({ nodeId }, 'Analyzing component set via plugin UI');
|
|
945
|
+
const frame = await this.findPluginUIFrame();
|
|
946
|
+
try {
|
|
947
|
+
const result = await frame.evaluate(`window.analyzeComponentSet(${JSON.stringify(nodeId)})`);
|
|
948
|
+
logger.info({ success: result?.success }, 'Component set analysis complete');
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
logger.error({ error, nodeId }, 'Component set analysis failed');
|
|
953
|
+
throw error;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Deep component extraction via plugin UI
|
|
958
|
+
*/
|
|
959
|
+
async deepGetComponent(nodeId, depth) {
|
|
960
|
+
logger.info({ nodeId, depth }, 'Deep component fetch via plugin UI');
|
|
961
|
+
const frame = await this.findPluginUIFrame();
|
|
962
|
+
try {
|
|
963
|
+
const result = await frame.evaluate(`window.deepGetComponent(${JSON.stringify(nodeId)}, ${JSON.stringify(depth || 10)})`);
|
|
964
|
+
logger.info({ success: result?.success }, 'Deep component data retrieved');
|
|
965
|
+
return result;
|
|
966
|
+
}
|
|
967
|
+
catch (error) {
|
|
968
|
+
logger.error({ error, nodeId }, 'Deep component fetch failed');
|
|
969
|
+
throw error;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
940
972
|
/**
|
|
941
973
|
* Add a component property
|
|
942
974
|
*/
|