@atezer/figma-mcp-bridge 1.7.23 → 1.7.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.claude-plugin/plugin.json +37 -0
  2. package/.cursor-plugin/plugin.json +21 -0
  3. package/CHANGELOG.md +30 -0
  4. package/README.md +4 -3
  5. package/agents/ds-auditor.md +29 -0
  6. package/agents/screen-builder.md +29 -0
  7. package/agents/token-syncer.md +26 -0
  8. package/assets/logo.png +0 -0
  9. package/commands/add-library.md +122 -0
  10. package/commands/ds-add.md +255 -0
  11. package/commands/ds-sync.md +314 -0
  12. package/commands/implement.md +43 -0
  13. package/commands/install-library.md +73 -0
  14. package/commands/setup.md +26 -0
  15. package/commands/test.md +39 -0
  16. package/commands/update.md +25 -0
  17. package/dist/core/config.d.ts +1 -5
  18. package/dist/core/config.d.ts.map +1 -1
  19. package/dist/core/config.js +11 -111
  20. package/dist/core/config.js.map +1 -1
  21. package/dist/core/plugin-bridge-server.d.ts.map +1 -1
  22. package/dist/core/plugin-bridge-server.js +1 -2
  23. package/dist/core/plugin-bridge-server.js.map +1 -1
  24. package/dist/core/response-guard.d.ts +1 -1
  25. package/dist/core/response-guard.js +1 -1
  26. package/dist/core/types/index.d.ts +2 -98
  27. package/dist/core/types/index.d.ts.map +1 -1
  28. package/dist/core/version.d.ts +1 -1
  29. package/dist/core/version.js +1 -1
  30. package/dist/local-plugin-only.d.ts.map +1 -1
  31. package/dist/local-plugin-only.js +14 -13
  32. package/dist/local-plugin-only.js.map +1 -1
  33. package/f-mcp-plugin/README.md +8 -15
  34. package/f-mcp-plugin/manifest.json +1 -3
  35. package/hooks/hooks.json +26 -0
  36. package/package.json +15 -31
  37. package/skills/BRAND_PROFILE_SCHEMA.md +113 -0
  38. package/skills/SKILL_INDEX.md +194 -0
  39. package/skills/TOOL_MAPPING.md +111 -0
  40. package/skills/ai-handoff-export/SKILL.md +254 -0
  41. package/skills/apply-figma-design-system/SKILL.md +104 -0
  42. package/skills/audit-figma-design-system/SKILL.md +278 -0
  43. package/skills/code-design-mapper/SKILL.md +370 -0
  44. package/skills/component-documentation/SKILL.md +190 -0
  45. package/skills/design-drift-detector/SKILL.md +407 -0
  46. package/skills/design-system-rules/SKILL.md +407 -0
  47. package/skills/design-token-pipeline/SKILL.md +619 -0
  48. package/skills/ds-impact-analysis/SKILL.md +266 -0
  49. package/skills/figjam-diagram-builder/SKILL.md +172 -0
  50. package/skills/figma-a11y-audit/SKILL.md +587 -0
  51. package/skills/figma-canvas-ops/SKILL.md +325 -0
  52. package/skills/figma-screen-analyzer/SKILL.md +235 -0
  53. package/skills/fix-figma-design-system-finding/SKILL.md +117 -0
  54. package/skills/fmcp-project-rules/SKILL.md +93 -0
  55. package/skills/generate-figma-library/SKILL.md +598 -0
  56. package/skills/generate-figma-screen/SKILL.md +689 -0
  57. package/skills/implement-design/SKILL.md +473 -0
  58. package/skills/ux-copy-guidance/SKILL.md +373 -0
  59. package/skills/visual-qa-compare/SKILL.md +166 -0
  60. package/dist/browser/base.d.ts +0 -50
  61. package/dist/browser/base.d.ts.map +0 -1
  62. package/dist/browser/base.js +0 -6
  63. package/dist/browser/base.js.map +0 -1
  64. package/dist/browser/local.d.ts +0 -81
  65. package/dist/browser/local.d.ts.map +0 -1
  66. package/dist/browser/local.js +0 -283
  67. package/dist/browser/local.js.map +0 -1
  68. package/dist/core/console-monitor.d.ts +0 -82
  69. package/dist/core/console-monitor.d.ts.map +0 -1
  70. package/dist/core/console-monitor.js +0 -428
  71. package/dist/core/console-monitor.js.map +0 -1
  72. package/dist/core/design-system-manifest.d.ts +0 -272
  73. package/dist/core/design-system-manifest.d.ts.map +0 -1
  74. package/dist/core/design-system-manifest.js +0 -261
  75. package/dist/core/design-system-manifest.js.map +0 -1
  76. package/dist/core/enrichment/enrichment-service.d.ts +0 -52
  77. package/dist/core/enrichment/enrichment-service.d.ts.map +0 -1
  78. package/dist/core/enrichment/enrichment-service.js +0 -272
  79. package/dist/core/enrichment/enrichment-service.js.map +0 -1
  80. package/dist/core/enrichment/index.d.ts +0 -8
  81. package/dist/core/enrichment/index.d.ts.map +0 -1
  82. package/dist/core/enrichment/index.js +0 -8
  83. package/dist/core/enrichment/index.js.map +0 -1
  84. package/dist/core/enrichment/relationship-mapper.d.ts +0 -106
  85. package/dist/core/enrichment/relationship-mapper.d.ts.map +0 -1
  86. package/dist/core/enrichment/relationship-mapper.js +0 -352
  87. package/dist/core/enrichment/relationship-mapper.js.map +0 -1
  88. package/dist/core/enrichment/style-resolver.d.ts +0 -80
  89. package/dist/core/enrichment/style-resolver.d.ts.map +0 -1
  90. package/dist/core/enrichment/style-resolver.js +0 -327
  91. package/dist/core/enrichment/style-resolver.js.map +0 -1
  92. package/dist/core/figma-api.d.ts +0 -137
  93. package/dist/core/figma-api.d.ts.map +0 -1
  94. package/dist/core/figma-api.js +0 -274
  95. package/dist/core/figma-api.js.map +0 -1
  96. package/dist/core/figma-desktop-connector.d.ts +0 -242
  97. package/dist/core/figma-desktop-connector.d.ts.map +0 -1
  98. package/dist/core/figma-desktop-connector.js +0 -1042
  99. package/dist/core/figma-desktop-connector.js.map +0 -1
  100. package/dist/core/figma-reconstruction-spec.d.ts +0 -162
  101. package/dist/core/figma-reconstruction-spec.d.ts.map +0 -1
  102. package/dist/core/figma-reconstruction-spec.js +0 -387
  103. package/dist/core/figma-reconstruction-spec.js.map +0 -1
  104. package/dist/core/figma-tools.d.ts +0 -21
  105. package/dist/core/figma-tools.d.ts.map +0 -1
  106. package/dist/core/figma-tools.js +0 -2920
  107. package/dist/core/figma-tools.js.map +0 -1
  108. package/dist/core/snippet-injector.d.ts +0 -24
  109. package/dist/core/snippet-injector.d.ts.map +0 -1
  110. package/dist/core/snippet-injector.js +0 -97
  111. package/dist/core/snippet-injector.js.map +0 -1
  112. package/dist/core/types/enriched.d.ts +0 -213
  113. package/dist/core/types/enriched.d.ts.map +0 -1
  114. package/dist/core/types/enriched.js +0 -6
  115. package/dist/core/types/enriched.js.map +0 -1
  116. package/dist/local.d.ts +0 -73
  117. package/dist/local.d.ts.map +0 -1
  118. package/dist/local.js +0 -2605
  119. package/dist/local.js.map +0 -1
@@ -1,1042 +0,0 @@
1
- /**
2
- * Figma Desktop Connector
3
- *
4
- * This service connects directly to Figma Desktop's plugin context
5
- * to execute code with access to the full Figma Plugin API,
6
- * including variables without Enterprise access.
7
- *
8
- * Uses Puppeteer's Worker API to directly access plugin workers,
9
- * bypassing CDP context enumeration limitations.
10
- */
11
- import { logger } from './logger.js';
12
- export class FigmaDesktopConnector {
13
- constructor(page) {
14
- this.page = page;
15
- }
16
- /**
17
- * Initialize connection to Figma Desktop's plugin context
18
- * No setup needed - Puppeteer handles worker access automatically
19
- */
20
- async initialize() {
21
- logger.info('Figma Desktop connector initialized (using Puppeteer Worker API)');
22
- }
23
- /**
24
- * Execute code in Figma's plugin context where the figma API is available
25
- * Uses Puppeteer's direct worker access instead of CDP context enumeration
26
- */
27
- async executeInPluginContext(code) {
28
- try {
29
- // Use Puppeteer's worker API directly - this can access plugin workers
30
- // that CDP's Runtime.getExecutionContexts cannot enumerate
31
- const workers = this.page.workers();
32
- // Log to browser console so MCP can capture it
33
- await this.page.evaluate((count, urls) => {
34
- console.log(`[DESKTOP_CONNECTOR] Found ${count} workers via Puppeteer API:`, urls);
35
- }, workers.length, workers.map(w => w.url()));
36
- logger.info({
37
- workerCount: workers.length,
38
- workerUrls: workers.map(w => w.url())
39
- }, 'Found workers via Puppeteer API');
40
- // Try each worker to find one with figma API
41
- for (const worker of workers) {
42
- try {
43
- // Log to browser console
44
- await this.page.evaluate((url) => {
45
- console.log(`[DESKTOP_CONNECTOR] Checking worker: ${url}`);
46
- }, worker.url());
47
- // Check if this worker has the figma API
48
- // Use string evaluation to avoid TypeScript errors about figma global
49
- const hasFigmaApi = await worker.evaluate('typeof figma !== "undefined"');
50
- // Log result to browser console
51
- await this.page.evaluate((url, hasApi) => {
52
- console.log(`[DESKTOP_CONNECTOR] Worker ${url} has figma API: ${hasApi}`);
53
- }, worker.url(), hasFigmaApi);
54
- if (hasFigmaApi) {
55
- logger.info({ workerUrl: worker.url() }, 'Found worker with Figma API');
56
- await this.page.evaluate((url) => {
57
- console.log(`[DESKTOP_CONNECTOR] ✅ SUCCESS! Found worker with Figma API: ${url}`);
58
- }, worker.url());
59
- // Execute the code in this worker context
60
- // Wrap the code in a function to ensure proper evaluation
61
- const wrappedCode = `(${code})`;
62
- const result = await worker.evaluate(wrappedCode);
63
- return result;
64
- }
65
- }
66
- catch (workerError) {
67
- // This worker doesn't have figma API or evaluation failed, try next
68
- await this.page.evaluate((url, err) => {
69
- console.error(`[DESKTOP_CONNECTOR] ❌ Worker ${url} check failed:`, err);
70
- }, worker.url(), workerError instanceof Error ? workerError.message : String(workerError));
71
- logger.error({ error: workerError, workerUrl: worker.url() }, 'Worker check failed, trying next');
72
- continue;
73
- }
74
- }
75
- // If no worker found with figma API, throw error
76
- throw new Error('No plugin worker found with Figma API. Make sure a plugin is running in Figma Desktop.');
77
- }
78
- catch (error) {
79
- logger.error({ error, code: code.substring(0, 200) }, 'Failed to execute in plugin context');
80
- throw error;
81
- }
82
- }
83
- /**
84
- * Get Figma variables from plugin UI window object
85
- * This bypasses Figma's plugin sandbox security restrictions
86
- * by accessing data that the plugin posted to its UI iframe
87
- */
88
- async getVariablesFromPluginUI(fileKey) {
89
- try {
90
- // Log to browser console
91
- await this.page.evaluate((key) => {
92
- console.log(`[DESKTOP_CONNECTOR] 🚀 getVariablesFromPluginUI() called, fileKey: ${key}`);
93
- }, fileKey);
94
- logger.info({ fileKey }, 'Getting variables from plugin UI iframe');
95
- // Get all frames (iframes) in the page
96
- const frames = this.page.frames();
97
- await this.page.evaluate((count) => {
98
- console.log(`[DESKTOP_CONNECTOR] Found ${count} frames (iframes)`);
99
- }, frames.length);
100
- logger.info({ frameCount: frames.length }, 'Found frames in page');
101
- // Try to find plugin UI iframe with variables data
102
- for (const frame of frames) {
103
- try {
104
- const frameUrl = frame.url();
105
- await this.page.evaluate((url) => {
106
- console.log(`[DESKTOP_CONNECTOR] Checking frame: ${url}`);
107
- }, frameUrl);
108
- // Check if this frame is our plugin UI (cached data or refreshVariables for dynamic-page)
109
- const hasData = await frame.evaluate('typeof window.__figmaVariablesData !== "undefined" && window.__figmaVariablesReady === true');
110
- const hasRefresh = await frame.evaluate('typeof window.refreshVariables === "function"');
111
- await this.page.evaluate((url, has, hasRef) => {
112
- console.log(`[DESKTOP_CONNECTOR] Frame ${url} has variables data: ${has}, refreshVariables: ${hasRef}`);
113
- }, frameUrl, hasData, hasRefresh);
114
- if (hasData || hasRefresh) {
115
- logger.info({ frameUrl }, 'Found frame with variables data');
116
- await this.page.evaluate((url) => {
117
- console.log(`[DESKTOP_CONNECTOR] ✅ SUCCESS! Found plugin UI with variables data: ${url}`);
118
- }, frameUrl);
119
- // Refresh first (async API in plugin) so variables work in dynamic-page context, then get data
120
- const result = (await frame.evaluate(() => new Promise((resolve, reject) => {
121
- const win = globalThis;
122
- if (typeof win.refreshVariables === 'function') {
123
- win.refreshVariables().then(() => resolve(win.__figmaVariablesData)).catch(reject);
124
- }
125
- else {
126
- resolve(win.__figmaVariablesData);
127
- }
128
- })));
129
- logger.info({
130
- variableCount: result.variables?.length,
131
- collectionCount: result.variableCollections?.length
132
- }, 'Successfully retrieved variables from plugin UI');
133
- await this.page.evaluate((varCount, collCount) => {
134
- console.log(`[DESKTOP_CONNECTOR] ✅ Retrieved ${varCount} variables in ${collCount} collections`);
135
- }, result.variables?.length || 0, result.variableCollections?.length || 0);
136
- return result;
137
- }
138
- }
139
- catch (frameError) {
140
- await this.page.evaluate((url, err) => {
141
- console.log(`[DESKTOP_CONNECTOR] Frame ${url} check failed: ${err}`);
142
- }, frame.url(), frameError instanceof Error ? frameError.message : String(frameError));
143
- logger.debug({ error: frameError, frameUrl: frame.url() }, 'Frame check failed, trying next');
144
- continue;
145
- }
146
- }
147
- throw new Error('No plugin UI found with variables data. Make sure the F-MCP ATezer Bridge plugin is running.');
148
- }
149
- catch (error) {
150
- logger.error({ error }, 'Failed to get variables from plugin UI');
151
- await this.page.evaluate((msg) => {
152
- console.error('[DESKTOP_CONNECTOR] ❌ getVariablesFromPluginUI failed:', msg);
153
- }, error instanceof Error ? error.message : String(error));
154
- throw error;
155
- }
156
- }
157
- /**
158
- * Get component data by node ID from plugin UI window object
159
- * This bypasses the REST API bug where descriptions are missing
160
- * by accessing data from the F-MCP ATezer Bridge plugin via its UI iframe
161
- */
162
- async getComponentFromPluginUI(nodeId) {
163
- try {
164
- // Log to browser console
165
- await this.page.evaluate((id) => {
166
- console.log(`[DESKTOP_CONNECTOR] 🎯 getComponentFromPluginUI() called, nodeId: ${id}`);
167
- }, nodeId);
168
- logger.info({ nodeId }, 'Getting component from plugin UI iframe');
169
- // Get all frames (iframes) in the page
170
- const frames = this.page.frames();
171
- await this.page.evaluate((count) => {
172
- console.log(`[DESKTOP_CONNECTOR] Found ${count} frames (iframes)`);
173
- }, frames.length);
174
- logger.info({ frameCount: frames.length }, 'Found frames in page');
175
- // Try to find plugin UI iframe with requestComponentData function
176
- for (const frame of frames) {
177
- try {
178
- const frameUrl = frame.url();
179
- await this.page.evaluate((url) => {
180
- console.log(`[DESKTOP_CONNECTOR] Checking frame: ${url}`);
181
- }, frameUrl);
182
- // Check if this frame has our requestComponentData function
183
- const hasFunction = await frame.evaluate('typeof window.requestComponentData === "function"');
184
- await this.page.evaluate((url, has) => {
185
- console.log(`[DESKTOP_CONNECTOR] Frame ${url} has requestComponentData: ${has}`);
186
- }, frameUrl, hasFunction);
187
- if (hasFunction) {
188
- logger.info({ frameUrl }, 'Found frame with requestComponentData function');
189
- await this.page.evaluate((url) => {
190
- console.log(`[DESKTOP_CONNECTOR] ✅ SUCCESS! Found plugin UI with requestComponentData: ${url}`);
191
- }, frameUrl);
192
- // Call the function with the nodeId - it returns a Promise
193
- // Use JSON.stringify to safely pass the nodeId as a string literal
194
- const result = await frame.evaluate(`window.requestComponentData(${JSON.stringify(nodeId)})`);
195
- logger.info({
196
- nodeId,
197
- componentName: result.component?.name,
198
- hasDescription: !!result.component?.description
199
- }, 'Successfully retrieved component from plugin UI');
200
- await this.page.evaluate((name, hasDesc) => {
201
- console.log(`[DESKTOP_CONNECTOR] ✅ Retrieved component "${name}", has description: ${hasDesc}`);
202
- }, result.component?.name, !!result.component?.description);
203
- return result;
204
- }
205
- }
206
- catch (frameError) {
207
- await this.page.evaluate((url, err) => {
208
- console.log(`[DESKTOP_CONNECTOR] Frame ${url} check failed: ${err}`);
209
- }, frame.url(), frameError instanceof Error ? frameError.message : String(frameError));
210
- logger.debug({ error: frameError, frameUrl: frame.url() }, 'Frame check failed, trying next');
211
- continue;
212
- }
213
- }
214
- // If no frame found with function, throw error
215
- throw new Error('No plugin UI found with requestComponentData function. Make sure the F-MCP ATezer Bridge plugin is running.');
216
- }
217
- catch (error) {
218
- logger.error({ error, nodeId }, 'Failed to get component from plugin UI');
219
- await this.page.evaluate((msg) => {
220
- console.error('[DESKTOP_CONNECTOR] ❌ getComponentFromPluginUI failed:', msg);
221
- }, error instanceof Error ? error.message : String(error));
222
- throw error;
223
- }
224
- }
225
- /**
226
- * Get Figma variables using the desktop connection
227
- * This bypasses the Enterprise requirement!
228
- */
229
- async getVariables(fileKey) {
230
- // Log to browser console
231
- await this.page.evaluate((key) => {
232
- console.log(`[DESKTOP_CONNECTOR] 🚀 getVariables() called, fileKey: ${key}`);
233
- }, fileKey);
234
- logger.info({ fileKey }, 'Getting variables via Desktop connection');
235
- const code = `
236
- (async () => {
237
- try {
238
- // Check if we're in the right context
239
- if (typeof figma === 'undefined') {
240
- throw new Error('Figma API not available in this context');
241
- }
242
-
243
- // Get variables just like the official MCP does
244
- const variables = await figma.variables.getLocalVariablesAsync();
245
- const collections = await figma.variables.getLocalVariableCollectionsAsync();
246
-
247
- // Format the response with file metadata for context verification
248
- const result = {
249
- success: true,
250
- timestamp: Date.now(),
251
- // Include file metadata so we can verify we're querying the right file
252
- fileMetadata: {
253
- fileName: figma.root.name,
254
- fileKey: figma.fileKey || null
255
- },
256
- variables: variables.map(v => ({
257
- id: v.id,
258
- name: v.name,
259
- key: v.key,
260
- resolvedType: v.resolvedType,
261
- valuesByMode: v.valuesByMode,
262
- variableCollectionId: v.variableCollectionId,
263
- scopes: v.scopes,
264
- description: v.description,
265
- hiddenFromPublishing: v.hiddenFromPublishing
266
- })),
267
- variableCollections: collections.map(c => ({
268
- id: c.id,
269
- name: c.name,
270
- key: c.key,
271
- modes: c.modes,
272
- defaultModeId: c.defaultModeId,
273
- variableIds: c.variableIds
274
- }))
275
- };
276
-
277
- return result;
278
- } catch (error) {
279
- return {
280
- success: false,
281
- error: error.message
282
- };
283
- }
284
- })()
285
- `;
286
- try {
287
- const result = await this.executeInPluginContext(code);
288
- if (!result.success) {
289
- throw new Error(result.error || 'Failed to get variables');
290
- }
291
- logger.info({
292
- variableCount: result.variables?.length,
293
- collectionCount: result.variableCollections?.length
294
- }, 'Successfully retrieved variables via Desktop');
295
- return result;
296
- }
297
- catch (error) {
298
- logger.error({ error }, 'Failed to get variables via Desktop');
299
- throw error;
300
- }
301
- }
302
- /**
303
- * Clean up resources (no-op since we use Puppeteer's built-in worker management)
304
- */
305
- /**
306
- * Get component data by node ID using Plugin API
307
- * This bypasses the REST API bug where descriptions are missing
308
- */
309
- async getComponentByNodeId(nodeId) {
310
- await this.page.evaluate((id) => {
311
- console.log(`[DESKTOP_CONNECTOR] 🎯 getComponentByNodeId() called, nodeId: ${id}`);
312
- }, nodeId);
313
- logger.info({ nodeId }, 'Getting component via Desktop Plugin API');
314
- const code = `
315
- (async () => {
316
- try {
317
- // Check if we're in the right context
318
- if (typeof figma === 'undefined') {
319
- throw new Error('Figma API not available in this context');
320
- }
321
-
322
- // Get the node by ID
323
- const node = figma.getNodeById('${nodeId}');
324
-
325
- if (!node) {
326
- throw new Error('Node not found with ID: ${nodeId}');
327
- }
328
-
329
- // Check if it's a component-like node
330
- if (node.type !== 'COMPONENT' && node.type !== 'COMPONENT_SET' && node.type !== 'INSTANCE') {
331
- throw new Error('Node is not a component, component set, or instance. Type: ' + node.type);
332
- }
333
-
334
- // Detect if this is a variant (COMPONENT inside a COMPONENT_SET)
335
- // Note: Can't use optional chaining (?.) - Figma plugin sandbox doesn't support it
336
- const isVariant = node.type === 'COMPONENT' && node.parent && node.parent.type === 'COMPONENT_SET';
337
-
338
- // Extract component data including description fields
339
- const result = {
340
- success: true,
341
- timestamp: Date.now(),
342
- component: {
343
- id: node.id,
344
- name: node.name,
345
- type: node.type,
346
- // Variants CAN have their own description
347
- description: node.description || null,
348
- descriptionMarkdown: node.descriptionMarkdown || null,
349
- // Include other useful properties
350
- visible: node.visible,
351
- locked: node.locked,
352
- // Flag to indicate if this is a variant
353
- isVariant: isVariant,
354
- // For component sets and non-variant components only (variants cannot access this)
355
- componentPropertyDefinitions: node.type === 'COMPONENT_SET' || (node.type === 'COMPONENT' && !isVariant)
356
- ? node.componentPropertyDefinitions
357
- : undefined,
358
- // Get children info (lightweight)
359
- children: node.children ? node.children.map(child => ({
360
- id: child.id,
361
- name: child.name,
362
- type: child.type
363
- })) : undefined
364
- }
365
- };
366
-
367
- return result;
368
- } catch (error) {
369
- return {
370
- success: false,
371
- error: error.message,
372
- stack: error.stack
373
- };
374
- }
375
- })()
376
- `;
377
- try {
378
- const result = await this.executeInPluginContext(code);
379
- if (!result.success) {
380
- throw new Error(result.error || 'Failed to get component data');
381
- }
382
- logger.info({
383
- nodeId,
384
- componentName: result.component?.name,
385
- hasDescription: !!result.component?.description
386
- }, 'Successfully retrieved component via Desktop Plugin API');
387
- await this.page.evaluate((name, hasDesc) => {
388
- console.log(`[DESKTOP_CONNECTOR] ✅ Retrieved component "${name}", has description: ${hasDesc}`);
389
- }, result.component?.name, !!result.component?.description);
390
- return result;
391
- }
392
- catch (error) {
393
- logger.error({ error, nodeId }, 'Failed to get component via Desktop Plugin API');
394
- await this.page.evaluate((id, err) => {
395
- console.error(`[DESKTOP_CONNECTOR] ❌ getComponentByNodeId failed for ${id}:`, err);
396
- }, nodeId, error instanceof Error ? error.message : String(error));
397
- throw error;
398
- }
399
- }
400
- async dispose() {
401
- logger.info('Figma Desktop connector disposed');
402
- }
403
- // ============================================================================
404
- // WRITE OPERATIONS - Execute commands via Plugin UI iframe
405
- // ============================================================================
406
- /**
407
- * Find the F-MCP ATezer Bridge plugin UI iframe
408
- * Returns the frame that has the write operation functions
409
- * Handles detached frame errors gracefully
410
- */
411
- async findPluginUIFrame() {
412
- // Get fresh frames - don't cache this
413
- const frames = this.page.frames();
414
- logger.debug({ frameCount: frames.length }, 'Searching for F-MCP ATezer Bridge plugin UI frame');
415
- for (const frame of frames) {
416
- try {
417
- // Skip detached frames
418
- if (frame.isDetached()) {
419
- continue;
420
- }
421
- // Check if this frame has the executeCode function (our F-MCP ATezer Bridge plugin)
422
- const hasWriteOps = await frame.evaluate('typeof window.executeCode === "function"');
423
- if (hasWriteOps) {
424
- logger.info({ frameUrl: frame.url() }, 'Found F-MCP ATezer Bridge plugin UI frame');
425
- return frame;
426
- }
427
- }
428
- catch (error) {
429
- // Frame might be inaccessible or detached, continue to next
430
- const errorMsg = error instanceof Error ? error.message : String(error);
431
- if (errorMsg.includes('detached')) {
432
- logger.debug({ frameUrl: frame.url() }, 'Frame was detached, skipping');
433
- }
434
- continue;
435
- }
436
- }
437
- throw new Error('F-MCP ATezer Bridge plugin UI not found. Make sure the F-MCP ATezer Bridge plugin is running in Figma. ' +
438
- 'The plugin must be open for write operations to work.');
439
- }
440
- /**
441
- * Execute arbitrary code in Figma's plugin context
442
- * This is the power tool that can run any Figma Plugin API code
443
- * Includes retry logic for detached frame errors
444
- */
445
- async executeCodeViaUI(code, timeout = 5000) {
446
- // Log to console (but don't fail if page is stale - this is just for debugging)
447
- try {
448
- await this.page.evaluate((codeStr, timeoutMs) => {
449
- console.log(`[DESKTOP_CONNECTOR] executeCodeViaUI() called, code length: ${codeStr.length}, timeout: ${timeoutMs}ms`);
450
- }, code, timeout);
451
- }
452
- catch (logError) {
453
- // If even page logging fails, the page is likely stale
454
- const errorMsg = logError instanceof Error ? logError.message : String(logError);
455
- if (errorMsg.includes('detached')) {
456
- throw new Error(`Page or frame is detached. Please refresh the Figma page or reconnect. Original error: ${errorMsg}`);
457
- }
458
- // For other errors, just continue - logging is not critical
459
- logger.warn({ error: errorMsg }, 'Failed to log to page console');
460
- }
461
- logger.info({ codeLength: code.length, timeout }, 'Executing code via plugin UI');
462
- // Retry logic for detached frame errors
463
- const maxRetries = 2;
464
- let lastError = null;
465
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
466
- try {
467
- const frame = await this.findPluginUIFrame();
468
- // Check if frame is still valid before using it
469
- if (frame.isDetached()) {
470
- throw new Error('Frame became detached');
471
- }
472
- // Call the executeCode function in the UI iframe
473
- const result = await frame.evaluate(`window.executeCode(${JSON.stringify(code)}, ${timeout})`);
474
- logger.info({ success: result.success, error: result.error }, 'Code execution completed');
475
- await this.page.evaluate((success, errorMsg) => {
476
- if (success) {
477
- console.log('[DESKTOP_CONNECTOR] ✅ Code execution succeeded');
478
- }
479
- else {
480
- console.log('[DESKTOP_CONNECTOR] ⚠️ Code execution returned error:', errorMsg);
481
- }
482
- }, result.success, result.error || '');
483
- return result;
484
- }
485
- catch (error) {
486
- const errorMsg = error instanceof Error ? error.message : String(error);
487
- lastError = error instanceof Error ? error : new Error(errorMsg);
488
- // Check if it's a detached frame error
489
- if (errorMsg.includes('detached') && attempt < maxRetries) {
490
- logger.warn({ attempt, maxRetries }, 'Frame detached, retrying with fresh frames');
491
- // Small delay before retry
492
- await new Promise(resolve => setTimeout(resolve, 100));
493
- continue;
494
- }
495
- // Not a detached frame error or we've exhausted retries
496
- logger.error({ error: errorMsg, attempt }, 'Code execution failed');
497
- try {
498
- await this.page.evaluate((err) => {
499
- console.error('[DESKTOP_CONNECTOR] ❌ executeCodeViaUI failed:', err);
500
- }, errorMsg);
501
- }
502
- catch {
503
- // Ignore errors from logging
504
- }
505
- throw lastError;
506
- }
507
- }
508
- throw lastError || new Error('Execution failed after retries');
509
- }
510
- /**
511
- * Update a variable's value in a specific mode
512
- */
513
- async updateVariable(variableId, modeId, value) {
514
- await this.page.evaluate((vId, mId) => {
515
- console.log(`[DESKTOP_CONNECTOR] updateVariable() called: ${vId} mode ${mId}`);
516
- }, variableId, modeId);
517
- logger.info({ variableId, modeId }, 'Updating variable via plugin UI');
518
- const frame = await this.findPluginUIFrame();
519
- try {
520
- const result = await frame.evaluate(`window.updateVariable(${JSON.stringify(variableId)}, ${JSON.stringify(modeId)}, ${JSON.stringify(value)})`);
521
- logger.info({ success: result.success, variableName: result.variable?.name }, 'Variable updated');
522
- await this.page.evaluate((name) => {
523
- console.log(`[DESKTOP_CONNECTOR] ✅ Variable "${name}" updated successfully`);
524
- }, result.variable?.name || variableId);
525
- return result;
526
- }
527
- catch (error) {
528
- logger.error({ error, variableId }, 'Update variable failed');
529
- throw error;
530
- }
531
- }
532
- /**
533
- * Create a new variable in a collection
534
- */
535
- async createVariable(name, collectionId, resolvedType, options) {
536
- await this.page.evaluate((n, cId, type) => {
537
- console.log(`[DESKTOP_CONNECTOR] createVariable() called: "${n}" in collection ${cId}, type: ${type}`);
538
- }, name, collectionId, resolvedType);
539
- logger.info({ name, collectionId, resolvedType }, 'Creating variable via plugin UI');
540
- const frame = await this.findPluginUIFrame();
541
- try {
542
- const result = await frame.evaluate(`window.createVariable(${JSON.stringify(name)}, ${JSON.stringify(collectionId)}, ${JSON.stringify(resolvedType)}, ${JSON.stringify(options || {})})`);
543
- logger.info({ success: result.success, variableId: result.variable?.id }, 'Variable created');
544
- await this.page.evaluate((id, n) => {
545
- console.log(`[DESKTOP_CONNECTOR] ✅ Variable "${n}" created with ID: ${id}`);
546
- }, result.variable?.id || 'unknown', name);
547
- return result;
548
- }
549
- catch (error) {
550
- logger.error({ error, name }, 'Create variable failed');
551
- throw error;
552
- }
553
- }
554
- /**
555
- * Create a new variable collection
556
- */
557
- async createVariableCollection(name, options) {
558
- await this.page.evaluate((n) => {
559
- console.log(`[DESKTOP_CONNECTOR] createVariableCollection() called: "${n}"`);
560
- }, name);
561
- logger.info({ name, options }, 'Creating variable collection via plugin UI');
562
- const frame = await this.findPluginUIFrame();
563
- try {
564
- const result = await frame.evaluate(`window.createVariableCollection(${JSON.stringify(name)}, ${JSON.stringify(options || {})})`);
565
- logger.info({ success: result.success, collectionId: result.collection?.id }, 'Collection created');
566
- await this.page.evaluate((id, n) => {
567
- console.log(`[DESKTOP_CONNECTOR] ✅ Collection "${n}" created with ID: ${id}`);
568
- }, result.collection?.id || 'unknown', name);
569
- return result;
570
- }
571
- catch (error) {
572
- logger.error({ error, name }, 'Create collection failed');
573
- throw error;
574
- }
575
- }
576
- /**
577
- * Delete a variable
578
- */
579
- async deleteVariable(variableId) {
580
- await this.page.evaluate((vId) => {
581
- console.log(`[DESKTOP_CONNECTOR] deleteVariable() called: ${vId}`);
582
- }, variableId);
583
- logger.info({ variableId }, 'Deleting variable via plugin UI');
584
- const frame = await this.findPluginUIFrame();
585
- try {
586
- const result = await frame.evaluate(`window.deleteVariable(${JSON.stringify(variableId)})`);
587
- logger.info({ success: result.success, deletedName: result.deleted?.name }, 'Variable deleted');
588
- await this.page.evaluate((name) => {
589
- console.log(`[DESKTOP_CONNECTOR] ✅ Variable "${name}" deleted`);
590
- }, result.deleted?.name || variableId);
591
- return result;
592
- }
593
- catch (error) {
594
- logger.error({ error, variableId }, 'Delete variable failed');
595
- throw error;
596
- }
597
- }
598
- /**
599
- * Delete a variable collection
600
- */
601
- async deleteVariableCollection(collectionId) {
602
- await this.page.evaluate((cId) => {
603
- console.log(`[DESKTOP_CONNECTOR] deleteVariableCollection() called: ${cId}`);
604
- }, collectionId);
605
- logger.info({ collectionId }, 'Deleting collection via plugin UI');
606
- const frame = await this.findPluginUIFrame();
607
- try {
608
- const result = await frame.evaluate(`window.deleteVariableCollection(${JSON.stringify(collectionId)})`);
609
- logger.info({ success: result.success, deletedName: result.deleted?.name }, 'Collection deleted');
610
- await this.page.evaluate((name, count) => {
611
- console.log(`[DESKTOP_CONNECTOR] ✅ Collection "${name}" deleted (had ${count} variables)`);
612
- }, result.deleted?.name || collectionId, result.deleted?.variableCount || 0);
613
- return result;
614
- }
615
- catch (error) {
616
- logger.error({ error, collectionId }, 'Delete collection failed');
617
- throw error;
618
- }
619
- }
620
- /**
621
- * Refresh variables data from Figma
622
- */
623
- async refreshVariables() {
624
- await this.page.evaluate(() => {
625
- console.log('[DESKTOP_CONNECTOR] refreshVariables() called');
626
- });
627
- logger.info('Refreshing variables via plugin UI');
628
- const frame = await this.findPluginUIFrame();
629
- try {
630
- const result = await frame.evaluate('window.refreshVariables()');
631
- logger.info({
632
- success: result.success,
633
- variableCount: result.data?.variables?.length,
634
- collectionCount: result.data?.variableCollections?.length
635
- }, 'Variables refreshed');
636
- await this.page.evaluate((vCount, cCount) => {
637
- console.log(`[DESKTOP_CONNECTOR] ✅ Variables refreshed: ${vCount} variables in ${cCount} collections`);
638
- }, result.data?.variables?.length || 0, result.data?.variableCollections?.length || 0);
639
- return result;
640
- }
641
- catch (error) {
642
- logger.error({ error }, 'Refresh variables failed');
643
- throw error;
644
- }
645
- }
646
- /**
647
- * Rename a variable
648
- */
649
- async renameVariable(variableId, newName) {
650
- await this.page.evaluate((vId, name) => {
651
- console.log(`[DESKTOP_CONNECTOR] renameVariable() called: ${vId} -> "${name}"`);
652
- }, variableId, newName);
653
- logger.info({ variableId, newName }, 'Renaming variable via plugin UI');
654
- const frame = await this.findPluginUIFrame();
655
- try {
656
- const result = await frame.evaluate(`window.renameVariable(${JSON.stringify(variableId)}, ${JSON.stringify(newName)})`);
657
- logger.info({ success: result.success, oldName: result.oldName, newName: result.variable?.name }, 'Variable renamed');
658
- await this.page.evaluate((oldN, newN) => {
659
- console.log(`[DESKTOP_CONNECTOR] ✅ Variable renamed from "${oldN}" to "${newN}"`);
660
- }, result.oldName || 'unknown', result.variable?.name || newName);
661
- return result;
662
- }
663
- catch (error) {
664
- logger.error({ error, variableId }, 'Rename variable failed');
665
- throw error;
666
- }
667
- }
668
- /**
669
- * Add a mode to a variable collection
670
- */
671
- async addMode(collectionId, modeName) {
672
- await this.page.evaluate((cId, name) => {
673
- console.log(`[DESKTOP_CONNECTOR] addMode() called: "${name}" to collection ${cId}`);
674
- }, collectionId, modeName);
675
- logger.info({ collectionId, modeName }, 'Adding mode via plugin UI');
676
- const frame = await this.findPluginUIFrame();
677
- try {
678
- const result = await frame.evaluate(`window.addMode(${JSON.stringify(collectionId)}, ${JSON.stringify(modeName)})`);
679
- logger.info({ success: result.success, newModeId: result.newMode?.modeId }, 'Mode added');
680
- await this.page.evaluate((name, modeId) => {
681
- console.log(`[DESKTOP_CONNECTOR] ✅ Mode "${name}" added with ID: ${modeId}`);
682
- }, modeName, result.newMode?.modeId || 'unknown');
683
- return result;
684
- }
685
- catch (error) {
686
- logger.error({ error, collectionId }, 'Add mode failed');
687
- throw error;
688
- }
689
- }
690
- /**
691
- * Rename a mode in a variable collection
692
- */
693
- async renameMode(collectionId, modeId, newName) {
694
- await this.page.evaluate((cId, mId, name) => {
695
- console.log(`[DESKTOP_CONNECTOR] renameMode() called: mode ${mId} in collection ${cId} -> "${name}"`);
696
- }, collectionId, modeId, newName);
697
- logger.info({ collectionId, modeId, newName }, 'Renaming mode via plugin UI');
698
- const frame = await this.findPluginUIFrame();
699
- try {
700
- const result = await frame.evaluate(`window.renameMode(${JSON.stringify(collectionId)}, ${JSON.stringify(modeId)}, ${JSON.stringify(newName)})`);
701
- logger.info({ success: result.success, oldName: result.oldName, newName }, 'Mode renamed');
702
- await this.page.evaluate((oldN, newN) => {
703
- console.log(`[DESKTOP_CONNECTOR] ✅ Mode renamed from "${oldN}" to "${newN}"`);
704
- }, result.oldName || 'unknown', newName);
705
- return result;
706
- }
707
- catch (error) {
708
- logger.error({ error, collectionId, modeId }, 'Rename mode failed');
709
- throw error;
710
- }
711
- }
712
- /**
713
- * Get all local components for design system manifest generation.
714
- * Defaults to currentPageOnly: true to avoid timeout on large files (dynamic-page).
715
- */
716
- async getLocalComponents(opts) {
717
- const currentPageOnly = opts?.currentPageOnly !== false;
718
- const limit = opts?.limit ?? 0;
719
- await this.page.evaluate((cp, lim) => {
720
- console.log('[DESKTOP_CONNECTOR] getLocalComponents() called, currentPageOnly:', cp, 'limit:', lim);
721
- }, currentPageOnly, limit);
722
- logger.info({ currentPageOnly, limit }, 'Getting local components via plugin UI');
723
- const frame = await this.findPluginUIFrame();
724
- try {
725
- // Runs in browser iframe; globalThis is the window there
726
- const result = await frame.evaluate((cp, lim) => globalThis.getLocalComponents(cp, lim), currentPageOnly, limit);
727
- logger.info({
728
- success: result.success,
729
- componentCount: result.data?.totalComponents,
730
- componentSetCount: result.data?.totalComponentSets
731
- }, 'Local components retrieved');
732
- await this.page.evaluate((cCount, csCount) => {
733
- console.log(`[DESKTOP_CONNECTOR] ✅ Found ${cCount} components and ${csCount} component sets`);
734
- }, result.data?.totalComponents || 0, result.data?.totalComponentSets || 0);
735
- return result;
736
- }
737
- catch (error) {
738
- logger.error({ error }, 'Get local components failed');
739
- throw error;
740
- }
741
- }
742
- /**
743
- * Instantiate a component with overrides
744
- * Supports both published library components (by key) and local components (by nodeId)
745
- */
746
- async instantiateComponent(componentKey, options) {
747
- await this.page.evaluate((key, nodeId) => {
748
- console.log(`[DESKTOP_CONNECTOR] instantiateComponent() called: key=${key}, nodeId=${nodeId}`);
749
- }, componentKey, options?.nodeId || null);
750
- logger.info({ componentKey, nodeId: options?.nodeId, options }, 'Instantiating component via plugin UI');
751
- const frame = await this.findPluginUIFrame();
752
- try {
753
- const result = await frame.evaluate(`window.instantiateComponent(${JSON.stringify(componentKey)}, ${JSON.stringify(options || {})})`);
754
- logger.info({ success: result.success, instanceId: result.instance?.id }, 'Component instantiated');
755
- await this.page.evaluate((instanceId, name) => {
756
- console.log(`[DESKTOP_CONNECTOR] ✅ Component instantiated: ${name} (${instanceId})`);
757
- }, result.instance?.id || 'unknown', result.instance?.name || 'unknown');
758
- return result;
759
- }
760
- catch (error) {
761
- logger.error({ error, componentKey }, 'Instantiate component failed');
762
- throw error;
763
- }
764
- }
765
- // ============================================================================
766
- // NEW: COMPONENT PROPERTY MANAGEMENT
767
- // ============================================================================
768
- /**
769
- * Set description on a component or style
770
- */
771
- async setNodeDescription(nodeId, description, descriptionMarkdown) {
772
- logger.info({ nodeId, descriptionLength: description.length }, 'Setting node description via plugin UI');
773
- const frame = await this.findPluginUIFrame();
774
- try {
775
- const result = await frame.evaluate(`window.setNodeDescription(${JSON.stringify(nodeId)}, ${JSON.stringify(description)}, ${JSON.stringify(descriptionMarkdown)})`);
776
- logger.info({ success: result.success, nodeName: result.node?.name }, 'Description set');
777
- return result;
778
- }
779
- catch (error) {
780
- logger.error({ error, nodeId }, 'Set description failed');
781
- throw error;
782
- }
783
- }
784
- /**
785
- * Add a component property
786
- */
787
- async addComponentProperty(nodeId, propertyName, type, defaultValue, options) {
788
- logger.info({ nodeId, propertyName, type }, 'Adding component property via plugin UI');
789
- const frame = await this.findPluginUIFrame();
790
- try {
791
- const result = await frame.evaluate(`window.addComponentProperty(${JSON.stringify(nodeId)}, ${JSON.stringify(propertyName)}, ${JSON.stringify(type)}, ${JSON.stringify(defaultValue)}, ${JSON.stringify(options || {})})`);
792
- logger.info({ success: result.success, propertyName: result.propertyName }, 'Property added');
793
- return result;
794
- }
795
- catch (error) {
796
- logger.error({ error, nodeId, propertyName }, 'Add component property failed');
797
- throw error;
798
- }
799
- }
800
- /**
801
- * Edit an existing component property
802
- */
803
- async editComponentProperty(nodeId, propertyName, newValue) {
804
- logger.info({ nodeId, propertyName }, 'Editing component property via plugin UI');
805
- const frame = await this.findPluginUIFrame();
806
- try {
807
- const result = await frame.evaluate(`window.editComponentProperty(${JSON.stringify(nodeId)}, ${JSON.stringify(propertyName)}, ${JSON.stringify(newValue)})`);
808
- logger.info({ success: result.success, propertyName: result.propertyName }, 'Property edited');
809
- return result;
810
- }
811
- catch (error) {
812
- logger.error({ error, nodeId, propertyName }, 'Edit component property failed');
813
- throw error;
814
- }
815
- }
816
- /**
817
- * Delete a component property
818
- */
819
- async deleteComponentProperty(nodeId, propertyName) {
820
- logger.info({ nodeId, propertyName }, 'Deleting component property via plugin UI');
821
- const frame = await this.findPluginUIFrame();
822
- try {
823
- const result = await frame.evaluate(`window.deleteComponentProperty(${JSON.stringify(nodeId)}, ${JSON.stringify(propertyName)})`);
824
- logger.info({ success: result.success }, 'Property deleted');
825
- return result;
826
- }
827
- catch (error) {
828
- logger.error({ error, nodeId, propertyName }, 'Delete component property failed');
829
- throw error;
830
- }
831
- }
832
- // ============================================================================
833
- // NEW: NODE MANIPULATION
834
- // ============================================================================
835
- /**
836
- * Resize a node
837
- */
838
- async resizeNode(nodeId, width, height, withConstraints = true) {
839
- logger.info({ nodeId, width, height, withConstraints }, 'Resizing node via plugin UI');
840
- const frame = await this.findPluginUIFrame();
841
- try {
842
- const result = await frame.evaluate(`window.resizeNode(${JSON.stringify(nodeId)}, ${width}, ${height}, ${withConstraints})`);
843
- logger.info({ success: result.success, nodeId: result.node?.id }, 'Node resized');
844
- return result;
845
- }
846
- catch (error) {
847
- logger.error({ error, nodeId }, 'Resize node failed');
848
- throw error;
849
- }
850
- }
851
- /**
852
- * Move/position a node
853
- */
854
- async moveNode(nodeId, x, y) {
855
- logger.info({ nodeId, x, y }, 'Moving node via plugin UI');
856
- const frame = await this.findPluginUIFrame();
857
- try {
858
- const result = await frame.evaluate(`window.moveNode(${JSON.stringify(nodeId)}, ${x}, ${y})`);
859
- logger.info({ success: result.success, nodeId: result.node?.id }, 'Node moved');
860
- return result;
861
- }
862
- catch (error) {
863
- logger.error({ error, nodeId }, 'Move node failed');
864
- throw error;
865
- }
866
- }
867
- /**
868
- * Set fills (colors) on a node
869
- */
870
- async setNodeFills(nodeId, fills) {
871
- logger.info({ nodeId, fillCount: fills.length }, 'Setting node fills via plugin UI');
872
- const frame = await this.findPluginUIFrame();
873
- try {
874
- const result = await frame.evaluate(`window.setNodeFills(${JSON.stringify(nodeId)}, ${JSON.stringify(fills)})`);
875
- logger.info({ success: result.success, nodeId: result.node?.id }, 'Fills set');
876
- return result;
877
- }
878
- catch (error) {
879
- logger.error({ error, nodeId }, 'Set fills failed');
880
- throw error;
881
- }
882
- }
883
- /**
884
- * Set strokes on a node
885
- */
886
- async setNodeStrokes(nodeId, strokes, strokeWeight) {
887
- logger.info({ nodeId, strokeCount: strokes.length, strokeWeight }, 'Setting node strokes via plugin UI');
888
- const frame = await this.findPluginUIFrame();
889
- try {
890
- const result = await frame.evaluate(`window.setNodeStrokes(${JSON.stringify(nodeId)}, ${JSON.stringify(strokes)}, ${strokeWeight})`);
891
- logger.info({ success: result.success, nodeId: result.node?.id }, 'Strokes set');
892
- return result;
893
- }
894
- catch (error) {
895
- logger.error({ error, nodeId }, 'Set strokes failed');
896
- throw error;
897
- }
898
- }
899
- /**
900
- * Set opacity on a node
901
- */
902
- async setNodeOpacity(nodeId, opacity) {
903
- logger.info({ nodeId, opacity }, 'Setting node opacity via plugin UI');
904
- const frame = await this.findPluginUIFrame();
905
- try {
906
- const result = await frame.evaluate(`window.setNodeOpacity(${JSON.stringify(nodeId)}, ${opacity})`);
907
- logger.info({ success: result.success, opacity: result.node?.opacity }, 'Opacity set');
908
- return result;
909
- }
910
- catch (error) {
911
- logger.error({ error, nodeId }, 'Set opacity failed');
912
- throw error;
913
- }
914
- }
915
- /**
916
- * Set corner radius on a node
917
- */
918
- async setNodeCornerRadius(nodeId, radius) {
919
- logger.info({ nodeId, radius }, 'Setting node corner radius via plugin UI');
920
- const frame = await this.findPluginUIFrame();
921
- try {
922
- const result = await frame.evaluate(`window.setNodeCornerRadius(${JSON.stringify(nodeId)}, ${radius})`);
923
- logger.info({ success: result.success, radius: result.node?.cornerRadius }, 'Corner radius set');
924
- return result;
925
- }
926
- catch (error) {
927
- logger.error({ error, nodeId }, 'Set corner radius failed');
928
- throw error;
929
- }
930
- }
931
- /**
932
- * Clone/duplicate a node
933
- */
934
- async cloneNode(nodeId) {
935
- logger.info({ nodeId }, 'Cloning node via plugin UI');
936
- const frame = await this.findPluginUIFrame();
937
- try {
938
- const result = await frame.evaluate(`window.cloneNode(${JSON.stringify(nodeId)})`);
939
- logger.info({ success: result.success, clonedId: result.node?.id }, 'Node cloned');
940
- return result;
941
- }
942
- catch (error) {
943
- logger.error({ error, nodeId }, 'Clone node failed');
944
- throw error;
945
- }
946
- }
947
- /**
948
- * Delete a node
949
- */
950
- async deleteNode(nodeId) {
951
- logger.info({ nodeId }, 'Deleting node via plugin UI');
952
- const frame = await this.findPluginUIFrame();
953
- try {
954
- const result = await frame.evaluate(`window.deleteNode(${JSON.stringify(nodeId)})`);
955
- logger.info({ success: result.success, deletedName: result.deleted?.name }, 'Node deleted');
956
- return result;
957
- }
958
- catch (error) {
959
- logger.error({ error, nodeId }, 'Delete node failed');
960
- throw error;
961
- }
962
- }
963
- /**
964
- * Rename a node
965
- */
966
- async renameNode(nodeId, newName) {
967
- logger.info({ nodeId, newName }, 'Renaming node via plugin UI');
968
- const frame = await this.findPluginUIFrame();
969
- try {
970
- const result = await frame.evaluate(`window.renameNode(${JSON.stringify(nodeId)}, ${JSON.stringify(newName)})`);
971
- logger.info({ success: result.success, oldName: result.node?.oldName, newName: result.node?.name }, 'Node renamed');
972
- return result;
973
- }
974
- catch (error) {
975
- logger.error({ error, nodeId }, 'Rename node failed');
976
- throw error;
977
- }
978
- }
979
- /**
980
- * Set text content on a text node
981
- */
982
- async setTextContent(nodeId, text, options) {
983
- logger.info({ nodeId, textLength: text.length }, 'Setting text content via plugin UI');
984
- const frame = await this.findPluginUIFrame();
985
- try {
986
- const result = await frame.evaluate(`window.setTextContent(${JSON.stringify(nodeId)}, ${JSON.stringify(text)}, ${JSON.stringify(options || {})})`);
987
- logger.info({ success: result.success, characters: result.node?.characters?.substring(0, 50) }, 'Text content set');
988
- return result;
989
- }
990
- catch (error) {
991
- logger.error({ error, nodeId }, 'Set text content failed');
992
- throw error;
993
- }
994
- }
995
- /**
996
- * Create a child node
997
- */
998
- async createChildNode(parentId, nodeType, properties) {
999
- logger.info({ parentId, nodeType, properties }, 'Creating child node via plugin UI');
1000
- const frame = await this.findPluginUIFrame();
1001
- try {
1002
- const result = await frame.evaluate(`window.createChildNode(${JSON.stringify(parentId)}, ${JSON.stringify(nodeType)}, ${JSON.stringify(properties || {})})`);
1003
- logger.info({ success: result.success, nodeId: result.node?.id, nodeType: result.node?.type }, 'Child node created');
1004
- return result;
1005
- }
1006
- catch (error) {
1007
- logger.error({ error, parentId, nodeType }, 'Create child node failed');
1008
- throw error;
1009
- }
1010
- }
1011
- /**
1012
- * Capture screenshot via plugin UI (for visual validation)
1013
- */
1014
- async captureScreenshot(nodeId, options) {
1015
- logger.info({ nodeId, options }, 'Capturing screenshot via plugin UI');
1016
- const frame = await this.findPluginUIFrame();
1017
- try {
1018
- const result = await frame.evaluate(`window.captureScreenshot(${JSON.stringify(nodeId)}, ${JSON.stringify(options || {})})`);
1019
- return result;
1020
- }
1021
- catch (error) {
1022
- logger.error({ error, nodeId }, 'Capture screenshot failed');
1023
- throw error;
1024
- }
1025
- }
1026
- /**
1027
- * Set instance properties via plugin UI
1028
- */
1029
- async setInstanceProperties(nodeId, properties) {
1030
- logger.info({ nodeId, properties }, 'Setting instance properties via plugin UI');
1031
- const frame = await this.findPluginUIFrame();
1032
- try {
1033
- const result = await frame.evaluate(`window.setInstanceProperties(${JSON.stringify(nodeId)}, ${JSON.stringify(properties)})`);
1034
- return result;
1035
- }
1036
- catch (error) {
1037
- logger.error({ error, nodeId }, 'Set instance properties failed');
1038
- throw error;
1039
- }
1040
- }
1041
- }
1042
- //# sourceMappingURL=figma-desktop-connector.js.map