@mp3wizard/figma-console-mcp 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +816 -0
  3. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts +14 -0
  4. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts.map +1 -0
  5. package/dist/apps/design-system-dashboard/scoring/accessibility.js +278 -0
  6. package/dist/apps/design-system-dashboard/scoring/accessibility.js.map +1 -0
  7. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts +29 -0
  8. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts.map +1 -0
  9. package/dist/apps/design-system-dashboard/scoring/component-metadata.js +358 -0
  10. package/dist/apps/design-system-dashboard/scoring/component-metadata.js.map +1 -0
  11. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts +14 -0
  12. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts.map +1 -0
  13. package/dist/apps/design-system-dashboard/scoring/consistency.js +342 -0
  14. package/dist/apps/design-system-dashboard/scoring/consistency.js.map +1 -0
  15. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts +14 -0
  16. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts.map +1 -0
  17. package/dist/apps/design-system-dashboard/scoring/coverage.js +231 -0
  18. package/dist/apps/design-system-dashboard/scoring/coverage.js.map +1 -0
  19. package/dist/apps/design-system-dashboard/scoring/engine.d.ts +27 -0
  20. package/dist/apps/design-system-dashboard/scoring/engine.d.ts.map +1 -0
  21. package/dist/apps/design-system-dashboard/scoring/engine.js +93 -0
  22. package/dist/apps/design-system-dashboard/scoring/engine.js.map +1 -0
  23. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts +14 -0
  24. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts.map +1 -0
  25. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js +309 -0
  26. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js.map +1 -0
  27. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts +14 -0
  28. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts.map +1 -0
  29. package/dist/apps/design-system-dashboard/scoring/token-architecture.js +350 -0
  30. package/dist/apps/design-system-dashboard/scoring/token-architecture.js.map +1 -0
  31. package/dist/apps/design-system-dashboard/scoring/types.d.ts +89 -0
  32. package/dist/apps/design-system-dashboard/scoring/types.d.ts.map +1 -0
  33. package/dist/apps/design-system-dashboard/scoring/types.js +41 -0
  34. package/dist/apps/design-system-dashboard/scoring/types.js.map +1 -0
  35. package/dist/apps/design-system-dashboard/server.d.ts +24 -0
  36. package/dist/apps/design-system-dashboard/server.d.ts.map +1 -0
  37. package/dist/apps/design-system-dashboard/server.js +160 -0
  38. package/dist/apps/design-system-dashboard/server.js.map +1 -0
  39. package/dist/apps/token-browser/server.d.ts +26 -0
  40. package/dist/apps/token-browser/server.d.ts.map +1 -0
  41. package/dist/apps/token-browser/server.js +137 -0
  42. package/dist/apps/token-browser/server.js.map +1 -0
  43. package/dist/browser/base.d.ts +58 -0
  44. package/dist/browser/base.d.ts.map +1 -0
  45. package/dist/browser/base.js +6 -0
  46. package/dist/browser/base.js.map +1 -0
  47. package/dist/browser/local.d.ts +87 -0
  48. package/dist/browser/local.d.ts.map +1 -0
  49. package/dist/browser/local.js +318 -0
  50. package/dist/browser/local.js.map +1 -0
  51. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +277 -0
  52. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +357 -0
  53. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +341 -0
  54. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +230 -0
  55. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +92 -0
  56. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +308 -0
  57. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +349 -0
  58. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +40 -0
  59. package/dist/cloudflare/apps/design-system-dashboard/server.js +159 -0
  60. package/dist/cloudflare/apps/token-browser/server.js +136 -0
  61. package/dist/cloudflare/browser/base.js +5 -0
  62. package/dist/cloudflare/browser/cloudflare.js +156 -0
  63. package/dist/cloudflare/browser-manager.js +157 -0
  64. package/dist/cloudflare/core/cloud-websocket-connector.js +267 -0
  65. package/dist/cloudflare/core/cloud-websocket-relay.js +199 -0
  66. package/dist/cloudflare/core/comment-tools.js +292 -0
  67. package/dist/cloudflare/core/config.js +161 -0
  68. package/dist/cloudflare/core/console-monitor.js +427 -0
  69. package/dist/cloudflare/core/design-code-tools.js +2504 -0
  70. package/dist/cloudflare/core/design-system-manifest.js +260 -0
  71. package/dist/cloudflare/core/design-system-tools.js +863 -0
  72. package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
  73. package/dist/cloudflare/core/enrichment/index.js +7 -0
  74. package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
  75. package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
  76. package/dist/cloudflare/core/figma-api.js +409 -0
  77. package/dist/cloudflare/core/figma-connector.js +7 -0
  78. package/dist/cloudflare/core/figma-desktop-connector.js +1184 -0
  79. package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
  80. package/dist/cloudflare/core/figma-style-extractor.js +311 -0
  81. package/dist/cloudflare/core/figma-tools.js +2947 -0
  82. package/dist/cloudflare/core/logger.js +53 -0
  83. package/dist/cloudflare/core/port-discovery.js +282 -0
  84. package/dist/cloudflare/core/snippet-injector.js +96 -0
  85. package/dist/cloudflare/core/types/design-code.js +4 -0
  86. package/dist/cloudflare/core/types/enriched.js +5 -0
  87. package/dist/cloudflare/core/types/index.js +4 -0
  88. package/dist/cloudflare/core/websocket-connector.js +256 -0
  89. package/dist/cloudflare/core/websocket-server.js +646 -0
  90. package/dist/cloudflare/core/write-tools.js +2091 -0
  91. package/dist/cloudflare/index.js +2899 -0
  92. package/dist/cloudflare/test-browser.js +88 -0
  93. package/dist/core/comment-tools.d.ts +11 -0
  94. package/dist/core/comment-tools.d.ts.map +1 -0
  95. package/dist/core/comment-tools.js +293 -0
  96. package/dist/core/comment-tools.js.map +1 -0
  97. package/dist/core/config.d.ts +17 -0
  98. package/dist/core/config.d.ts.map +1 -0
  99. package/dist/core/config.js +162 -0
  100. package/dist/core/config.js.map +1 -0
  101. package/dist/core/console-monitor.d.ts +82 -0
  102. package/dist/core/console-monitor.d.ts.map +1 -0
  103. package/dist/core/console-monitor.js +428 -0
  104. package/dist/core/console-monitor.js.map +1 -0
  105. package/dist/core/design-code-tools.d.ts +127 -0
  106. package/dist/core/design-code-tools.d.ts.map +1 -0
  107. package/dist/core/design-code-tools.js +2505 -0
  108. package/dist/core/design-code-tools.js.map +1 -0
  109. package/dist/core/design-system-manifest.d.ts +272 -0
  110. package/dist/core/design-system-manifest.d.ts.map +1 -0
  111. package/dist/core/design-system-manifest.js +261 -0
  112. package/dist/core/design-system-manifest.js.map +1 -0
  113. package/dist/core/design-system-tools.d.ts +17 -0
  114. package/dist/core/design-system-tools.d.ts.map +1 -0
  115. package/dist/core/design-system-tools.js +864 -0
  116. package/dist/core/design-system-tools.js.map +1 -0
  117. package/dist/core/enrichment/enrichment-service.d.ts +52 -0
  118. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
  119. package/dist/core/enrichment/enrichment-service.js +273 -0
  120. package/dist/core/enrichment/enrichment-service.js.map +1 -0
  121. package/dist/core/enrichment/index.d.ts +8 -0
  122. package/dist/core/enrichment/index.d.ts.map +1 -0
  123. package/dist/core/enrichment/index.js +8 -0
  124. package/dist/core/enrichment/index.js.map +1 -0
  125. package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
  126. package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
  127. package/dist/core/enrichment/relationship-mapper.js +352 -0
  128. package/dist/core/enrichment/relationship-mapper.js.map +1 -0
  129. package/dist/core/enrichment/style-resolver.d.ts +80 -0
  130. package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
  131. package/dist/core/enrichment/style-resolver.js +327 -0
  132. package/dist/core/enrichment/style-resolver.js.map +1 -0
  133. package/dist/core/figma-api.d.ts +201 -0
  134. package/dist/core/figma-api.d.ts.map +1 -0
  135. package/dist/core/figma-api.js +410 -0
  136. package/dist/core/figma-api.js.map +1 -0
  137. package/dist/core/figma-connector.d.ts +48 -0
  138. package/dist/core/figma-connector.d.ts.map +1 -0
  139. package/dist/core/figma-connector.js +8 -0
  140. package/dist/core/figma-connector.js.map +1 -0
  141. package/dist/core/figma-desktop-connector.d.ts +265 -0
  142. package/dist/core/figma-desktop-connector.d.ts.map +1 -0
  143. package/dist/core/figma-desktop-connector.js +1184 -0
  144. package/dist/core/figma-desktop-connector.js.map +1 -0
  145. package/dist/core/figma-reconstruction-spec.d.ts +166 -0
  146. package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
  147. package/dist/core/figma-reconstruction-spec.js +403 -0
  148. package/dist/core/figma-reconstruction-spec.js.map +1 -0
  149. package/dist/core/figma-style-extractor.d.ts +76 -0
  150. package/dist/core/figma-style-extractor.d.ts.map +1 -0
  151. package/dist/core/figma-style-extractor.js +312 -0
  152. package/dist/core/figma-style-extractor.js.map +1 -0
  153. package/dist/core/figma-tools.d.ts +23 -0
  154. package/dist/core/figma-tools.d.ts.map +1 -0
  155. package/dist/core/figma-tools.js +2948 -0
  156. package/dist/core/figma-tools.js.map +1 -0
  157. package/dist/core/logger.d.ts +22 -0
  158. package/dist/core/logger.d.ts.map +1 -0
  159. package/dist/core/logger.js +54 -0
  160. package/dist/core/logger.js.map +1 -0
  161. package/dist/core/port-discovery.d.ts +110 -0
  162. package/dist/core/port-discovery.d.ts.map +1 -0
  163. package/dist/core/port-discovery.js +283 -0
  164. package/dist/core/port-discovery.js.map +1 -0
  165. package/dist/core/snippet-injector.d.ts +24 -0
  166. package/dist/core/snippet-injector.d.ts.map +1 -0
  167. package/dist/core/snippet-injector.js +97 -0
  168. package/dist/core/snippet-injector.js.map +1 -0
  169. package/dist/core/types/design-code.d.ts +262 -0
  170. package/dist/core/types/design-code.d.ts.map +1 -0
  171. package/dist/core/types/design-code.js +5 -0
  172. package/dist/core/types/design-code.js.map +1 -0
  173. package/dist/core/types/enriched.d.ts +213 -0
  174. package/dist/core/types/enriched.d.ts.map +1 -0
  175. package/dist/core/types/enriched.js +6 -0
  176. package/dist/core/types/enriched.js.map +1 -0
  177. package/dist/core/types/index.d.ts +112 -0
  178. package/dist/core/types/index.d.ts.map +1 -0
  179. package/dist/core/types/index.js +5 -0
  180. package/dist/core/types/index.js.map +1 -0
  181. package/dist/core/websocket-connector.d.ts +55 -0
  182. package/dist/core/websocket-connector.d.ts.map +1 -0
  183. package/dist/core/websocket-connector.js +257 -0
  184. package/dist/core/websocket-connector.js.map +1 -0
  185. package/dist/core/websocket-server.d.ts +191 -0
  186. package/dist/core/websocket-server.d.ts.map +1 -0
  187. package/dist/core/websocket-server.js +647 -0
  188. package/dist/core/websocket-server.js.map +1 -0
  189. package/dist/core/write-tools.d.ts +7 -0
  190. package/dist/core/write-tools.d.ts.map +1 -0
  191. package/dist/core/write-tools.js +2092 -0
  192. package/dist/core/write-tools.js.map +1 -0
  193. package/dist/local.d.ts +84 -0
  194. package/dist/local.d.ts.map +1 -0
  195. package/dist/local.js +5039 -0
  196. package/dist/local.js.map +1 -0
  197. package/figma-desktop-bridge/README.md +313 -0
  198. package/figma-desktop-bridge/code.js +2818 -0
  199. package/figma-desktop-bridge/manifest.json +67 -0
  200. package/figma-desktop-bridge/ui.html +1236 -0
  201. package/package.json +87 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Cloudflare Browser Manager
3
+ * Manages Puppeteer browser instance lifecycle for Cloudflare Browser Rendering API
4
+ */
5
+ import puppeteer from '@cloudflare/puppeteer';
6
+ import { createChildLogger } from '../core/logger.js';
7
+ const logger = createChildLogger({ component: 'cloudflare-browser' });
8
+ /**
9
+ * Cloudflare Browser Manager
10
+ * Implements IBrowserManager for Cloudflare Browser Rendering API
11
+ */
12
+ export class CloudflareBrowserManager {
13
+ constructor(env, config) {
14
+ this.browser = null;
15
+ this.page = null;
16
+ this.env = env;
17
+ this.config = config;
18
+ }
19
+ /**
20
+ * Launch browser instance via Cloudflare Browser Rendering API
21
+ */
22
+ async launch() {
23
+ if (this.browser) {
24
+ logger.info('Browser already running, reusing instance');
25
+ return;
26
+ }
27
+ logger.info('Launching browser with Cloudflare Browser Rendering API');
28
+ try {
29
+ this.browser = await puppeteer.launch(this.env.BROWSER, {
30
+ keep_alive: 600000, // Keep alive for 10 minutes
31
+ });
32
+ logger.info('Browser launched successfully');
33
+ }
34
+ catch (error) {
35
+ logger.error({ error }, 'Failed to launch browser');
36
+ throw new Error(`Browser launch failed: ${error}`);
37
+ }
38
+ }
39
+ /**
40
+ * Get or create a page instance
41
+ */
42
+ async getPage() {
43
+ if (!this.browser) {
44
+ await this.launch();
45
+ }
46
+ if (this.page && !this.page.isClosed()) {
47
+ return this.page;
48
+ }
49
+ logger.info('Creating new browser page');
50
+ this.page = await this.browser.newPage();
51
+ // Set viewport size
52
+ await this.page.setViewport({
53
+ width: 1920,
54
+ height: 1080,
55
+ deviceScaleFactor: 1,
56
+ });
57
+ logger.info('Browser page created');
58
+ return this.page;
59
+ }
60
+ /**
61
+ * Navigate to Figma URL
62
+ */
63
+ async navigateToFigma(figmaUrl) {
64
+ const page = await this.getPage();
65
+ // Default to Figma homepage if no URL provided
66
+ const url = figmaUrl || 'https://www.figma.com';
67
+ logger.info({ url }, 'Navigating to Figma');
68
+ try {
69
+ await page.goto(url, {
70
+ waitUntil: 'networkidle2',
71
+ timeout: 30000,
72
+ });
73
+ logger.info({ url }, 'Navigation successful');
74
+ return { page, action: 'navigated', url };
75
+ }
76
+ catch (error) {
77
+ logger.error({ error, url }, 'Navigation failed');
78
+ throw new Error(`Failed to navigate to ${url}: ${error}`);
79
+ }
80
+ }
81
+ /**
82
+ * Reload current page
83
+ */
84
+ async reload(hardReload = false) {
85
+ if (!this.page || this.page.isClosed()) {
86
+ throw new Error('No active page to reload');
87
+ }
88
+ logger.info({ hardReload }, 'Reloading page');
89
+ try {
90
+ await this.page.reload({
91
+ waitUntil: 'networkidle2',
92
+ timeout: 30000,
93
+ });
94
+ logger.info('Page reloaded successfully');
95
+ }
96
+ catch (error) {
97
+ logger.error({ error }, 'Page reload failed');
98
+ throw new Error(`Page reload failed: ${error}`);
99
+ }
100
+ }
101
+ /**
102
+ * Execute JavaScript in page context
103
+ */
104
+ async evaluate(fn) {
105
+ const page = await this.getPage();
106
+ return page.evaluate(fn);
107
+ }
108
+ // Screenshot functionality removed - use Figma REST API's getImages() instead
109
+ // See: figma_take_screenshot and figma_get_component_image tools
110
+ /**
111
+ * Check if browser is running
112
+ */
113
+ isRunning() {
114
+ return this.browser !== null && this.browser.isConnected();
115
+ }
116
+ /**
117
+ * Close browser instance
118
+ */
119
+ async close() {
120
+ if (!this.browser) {
121
+ return;
122
+ }
123
+ logger.info('Closing browser');
124
+ try {
125
+ await this.browser.close();
126
+ this.browser = null;
127
+ this.page = null;
128
+ logger.info('Browser closed successfully');
129
+ }
130
+ catch (error) {
131
+ logger.error({ error }, 'Failed to close browser');
132
+ throw error;
133
+ }
134
+ }
135
+ /**
136
+ * Get current page URL
137
+ */
138
+ getCurrentUrl() {
139
+ if (!this.page || this.page.isClosed()) {
140
+ return null;
141
+ }
142
+ return this.page.url();
143
+ }
144
+ /**
145
+ * Wait for navigation
146
+ */
147
+ async waitForNavigation(timeout = 30000) {
148
+ if (!this.page || this.page.isClosed()) {
149
+ throw new Error('No active page');
150
+ }
151
+ await this.page.waitForNavigation({
152
+ waitUntil: 'networkidle2',
153
+ timeout,
154
+ });
155
+ }
156
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Browser Manager
3
+ * Manages Puppeteer browser instance lifecycle for Cloudflare Browser Rendering API
4
+ */
5
+ import puppeteer from '@cloudflare/puppeteer';
6
+ import { createChildLogger } from './core/logger.js';
7
+ const logger = createChildLogger({ component: 'browser-manager' });
8
+ /**
9
+ * Browser Manager
10
+ * Handles browser instance creation, page management, and navigation
11
+ */
12
+ export class BrowserManager {
13
+ constructor(env, config) {
14
+ this.browser = null;
15
+ this.page = null;
16
+ this.env = env;
17
+ this.config = config;
18
+ }
19
+ /**
20
+ * Launch browser instance
21
+ */
22
+ async launch() {
23
+ if (this.browser) {
24
+ logger.info('Browser already running, reusing instance');
25
+ return this.browser;
26
+ }
27
+ logger.info('Launching browser with Cloudflare Browser Rendering API');
28
+ try {
29
+ this.browser = await puppeteer.launch(this.env.BROWSER, {
30
+ keep_alive: 600000, // Keep alive for 10 minutes
31
+ });
32
+ logger.info('Browser launched successfully');
33
+ return this.browser;
34
+ }
35
+ catch (error) {
36
+ logger.error({ error }, 'Failed to launch browser');
37
+ throw new Error(`Browser launch failed: ${error}`);
38
+ }
39
+ }
40
+ /**
41
+ * Get or create a page instance
42
+ */
43
+ async getPage() {
44
+ if (!this.browser) {
45
+ await this.launch();
46
+ }
47
+ if (this.page && !this.page.isClosed()) {
48
+ return this.page;
49
+ }
50
+ logger.info('Creating new browser page');
51
+ this.page = await this.browser.newPage();
52
+ // Set viewport size
53
+ await this.page.setViewport({
54
+ width: 1920,
55
+ height: 1080,
56
+ deviceScaleFactor: 1,
57
+ });
58
+ logger.info('Browser page created');
59
+ return this.page;
60
+ }
61
+ /**
62
+ * Navigate to Figma URL
63
+ */
64
+ async navigateToFigma(figmaUrl) {
65
+ const page = await this.getPage();
66
+ // Default to Figma homepage if no URL provided
67
+ const url = figmaUrl || 'https://www.figma.com';
68
+ logger.info({ url }, 'Navigating to Figma');
69
+ try {
70
+ await page.goto(url, {
71
+ waitUntil: 'networkidle2',
72
+ timeout: 30000,
73
+ });
74
+ logger.info({ url }, 'Navigation successful');
75
+ return { page, action: 'navigated', url };
76
+ }
77
+ catch (error) {
78
+ logger.error({ error, url }, 'Navigation failed');
79
+ throw new Error(`Failed to navigate to ${url}: ${error}`);
80
+ }
81
+ }
82
+ /**
83
+ * Reload current page
84
+ */
85
+ async reload(hardReload = false) {
86
+ if (!this.page || this.page.isClosed()) {
87
+ throw new Error('No active page to reload');
88
+ }
89
+ logger.info({ hardReload }, 'Reloading page');
90
+ try {
91
+ await this.page.reload({
92
+ waitUntil: 'networkidle2',
93
+ timeout: 30000,
94
+ });
95
+ logger.info('Page reloaded successfully');
96
+ }
97
+ catch (error) {
98
+ logger.error({ error }, 'Page reload failed');
99
+ throw new Error(`Page reload failed: ${error}`);
100
+ }
101
+ }
102
+ /**
103
+ * Execute JavaScript in page context
104
+ */
105
+ async evaluate(fn) {
106
+ const page = await this.getPage();
107
+ return page.evaluate(fn);
108
+ }
109
+ // Screenshot functionality removed - use Figma REST API's getImages() instead
110
+ // See: figma_take_screenshot and figma_get_component_image tools
111
+ /**
112
+ * Check if browser is running
113
+ */
114
+ isRunning() {
115
+ return this.browser !== null && this.browser.isConnected();
116
+ }
117
+ /**
118
+ * Close browser instance
119
+ */
120
+ async close() {
121
+ if (!this.browser) {
122
+ return;
123
+ }
124
+ logger.info('Closing browser');
125
+ try {
126
+ await this.browser.close();
127
+ this.browser = null;
128
+ this.page = null;
129
+ logger.info('Browser closed successfully');
130
+ }
131
+ catch (error) {
132
+ logger.error({ error }, 'Failed to close browser');
133
+ throw error;
134
+ }
135
+ }
136
+ /**
137
+ * Get current page URL
138
+ */
139
+ getCurrentUrl() {
140
+ if (!this.page || this.page.isClosed()) {
141
+ return null;
142
+ }
143
+ return this.page.url();
144
+ }
145
+ /**
146
+ * Wait for navigation
147
+ */
148
+ async waitForNavigation(timeout = 30000) {
149
+ if (!this.page || this.page.isClosed()) {
150
+ throw new Error('No active page');
151
+ }
152
+ await this.page.waitForNavigation({
153
+ waitUntil: 'networkidle2',
154
+ timeout,
155
+ });
156
+ }
157
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Cloud WebSocket Connector
3
+ *
4
+ * Implements IFigmaConnector by routing commands through the PluginRelayDO
5
+ * Durable Object. Each method maps to a command sent via fetch() RPC to
6
+ * the relay, which forwards it to the Figma Desktop Bridge plugin over
7
+ * WebSocket.
8
+ *
9
+ * Structurally mirrors WebSocketConnector — same methods, different transport.
10
+ */
11
+ export class CloudWebSocketConnector {
12
+ constructor(relayStub) {
13
+ this.relayStub = relayStub;
14
+ }
15
+ async initialize() {
16
+ const res = await this.relayStub.fetch('https://relay/relay/status');
17
+ const status = await res.json();
18
+ if (!status.connected) {
19
+ throw new Error('No plugin connected to cloud relay. User must pair the Desktop Bridge plugin first (use figma_pair_plugin tool).');
20
+ }
21
+ }
22
+ getTransportType() {
23
+ return 'websocket';
24
+ }
25
+ // ============================================================================
26
+ // Core execution
27
+ // ============================================================================
28
+ async executeInPluginContext(code) {
29
+ return this.sendCommand('EXECUTE_CODE', { code, timeout: 5000 }, 7000);
30
+ }
31
+ async getVariablesFromPluginUI(fileKey) {
32
+ return this.sendCommand('GET_VARIABLES_DATA', {}, 10000);
33
+ }
34
+ async getVariables(fileKey) {
35
+ const code = `
36
+ (async () => {
37
+ try {
38
+ if (typeof figma === 'undefined') {
39
+ throw new Error('Figma API not available in this context');
40
+ }
41
+ const variables = await figma.variables.getLocalVariablesAsync();
42
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
43
+ return {
44
+ success: true,
45
+ timestamp: Date.now(),
46
+ fileMetadata: { fileName: figma.root.name, fileKey: figma.fileKey || null },
47
+ variables: variables.map(function(v) { return { id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType, valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId, scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing }; }),
48
+ variableCollections: collections.map(function(c) { return { id: c.id, name: c.name, key: c.key, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds }; })
49
+ };
50
+ } catch (error) {
51
+ return { success: false, error: error.message };
52
+ }
53
+ })()
54
+ `;
55
+ return this.sendCommand('EXECUTE_CODE', { code, timeout: 30000 }, 32000);
56
+ }
57
+ async executeCodeViaUI(code, timeoutMs = 5000) {
58
+ return this.sendCommand('EXECUTE_CODE', { code, timeout: timeoutMs }, timeoutMs + 2000);
59
+ }
60
+ // ============================================================================
61
+ // Variable operations
62
+ // ============================================================================
63
+ async updateVariable(variableId, modeId, value) {
64
+ return this.sendCommand('UPDATE_VARIABLE', { variableId, modeId, value });
65
+ }
66
+ async createVariable(name, collectionId, resolvedType, options) {
67
+ const params = { name, collectionId, resolvedType };
68
+ if (options) {
69
+ if (options.valuesByMode)
70
+ params.valuesByMode = options.valuesByMode;
71
+ if (options.description)
72
+ params.description = options.description;
73
+ if (options.scopes)
74
+ params.scopes = options.scopes;
75
+ }
76
+ return this.sendCommand('CREATE_VARIABLE', params);
77
+ }
78
+ async deleteVariable(variableId) {
79
+ return this.sendCommand('DELETE_VARIABLE', { variableId });
80
+ }
81
+ async refreshVariables() {
82
+ return this.sendCommand('REFRESH_VARIABLES', {}, 300000);
83
+ }
84
+ async renameVariable(variableId, newName) {
85
+ const result = await this.sendCommand('RENAME_VARIABLE', { variableId, newName });
86
+ if (!result.oldName && result.variable?.oldName)
87
+ result.oldName = result.variable.oldName;
88
+ return result;
89
+ }
90
+ async setVariableDescription(variableId, description) {
91
+ return this.sendCommand('SET_VARIABLE_DESCRIPTION', { variableId, description });
92
+ }
93
+ // ============================================================================
94
+ // Mode operations
95
+ // ============================================================================
96
+ async addMode(collectionId, modeName) {
97
+ return this.sendCommand('ADD_MODE', { collectionId, modeName });
98
+ }
99
+ async renameMode(collectionId, modeId, newName) {
100
+ const result = await this.sendCommand('RENAME_MODE', { collectionId, modeId, newName });
101
+ if (!result.oldName && result.collection?.oldName)
102
+ result.oldName = result.collection.oldName;
103
+ return result;
104
+ }
105
+ // ============================================================================
106
+ // Collection operations
107
+ // ============================================================================
108
+ async createVariableCollection(name, options) {
109
+ const params = { name };
110
+ if (options) {
111
+ if (options.initialModeName)
112
+ params.initialModeName = options.initialModeName;
113
+ if (options.additionalModes)
114
+ params.additionalModes = options.additionalModes;
115
+ }
116
+ return this.sendCommand('CREATE_VARIABLE_COLLECTION', params);
117
+ }
118
+ async deleteVariableCollection(collectionId) {
119
+ return this.sendCommand('DELETE_VARIABLE_COLLECTION', { collectionId });
120
+ }
121
+ // ============================================================================
122
+ // Component operations
123
+ // ============================================================================
124
+ async getComponentFromPluginUI(nodeId) {
125
+ return this.sendCommand('GET_COMPONENT', { nodeId }, 10000);
126
+ }
127
+ async getLocalComponents() {
128
+ return this.sendCommand('GET_LOCAL_COMPONENTS', {}, 300000);
129
+ }
130
+ async setNodeDescription(nodeId, description, descriptionMarkdown) {
131
+ return this.sendCommand('SET_NODE_DESCRIPTION', { nodeId, description, descriptionMarkdown });
132
+ }
133
+ async addComponentProperty(nodeId, propertyName, type, defaultValue, options) {
134
+ const params = { nodeId, propertyName, propertyType: type, defaultValue };
135
+ if (options?.preferredValues)
136
+ params.preferredValues = options.preferredValues;
137
+ return this.sendCommand('ADD_COMPONENT_PROPERTY', params);
138
+ }
139
+ async editComponentProperty(nodeId, propertyName, newValue) {
140
+ return this.sendCommand('EDIT_COMPONENT_PROPERTY', { nodeId, propertyName, newValue });
141
+ }
142
+ async deleteComponentProperty(nodeId, propertyName) {
143
+ return this.sendCommand('DELETE_COMPONENT_PROPERTY', { nodeId, propertyName });
144
+ }
145
+ async instantiateComponent(componentKey, options) {
146
+ const params = { componentKey };
147
+ if (options) {
148
+ if (options.nodeId)
149
+ params.nodeId = options.nodeId;
150
+ if (options.position)
151
+ params.position = options.position;
152
+ if (options.size)
153
+ params.size = options.size;
154
+ if (options.overrides)
155
+ params.overrides = options.overrides;
156
+ if (options.variant)
157
+ params.variant = options.variant;
158
+ if (options.parentId)
159
+ params.parentId = options.parentId;
160
+ }
161
+ return this.sendCommand('INSTANTIATE_COMPONENT', params);
162
+ }
163
+ // ============================================================================
164
+ // Node manipulation
165
+ // ============================================================================
166
+ async resizeNode(nodeId, width, height, withConstraints = true) {
167
+ return this.sendCommand('RESIZE_NODE', { nodeId, width, height, withConstraints });
168
+ }
169
+ async moveNode(nodeId, x, y) {
170
+ return this.sendCommand('MOVE_NODE', { nodeId, x, y });
171
+ }
172
+ async setNodeFills(nodeId, fills) {
173
+ return this.sendCommand('SET_NODE_FILLS', { nodeId, fills });
174
+ }
175
+ async setNodeStrokes(nodeId, strokes, strokeWeight) {
176
+ const params = { nodeId, strokes };
177
+ if (strokeWeight !== undefined)
178
+ params.strokeWeight = strokeWeight;
179
+ return this.sendCommand('SET_NODE_STROKES', params);
180
+ }
181
+ async setNodeOpacity(nodeId, opacity) {
182
+ return this.sendCommand('SET_NODE_OPACITY', { nodeId, opacity });
183
+ }
184
+ async setNodeCornerRadius(nodeId, radius) {
185
+ return this.sendCommand('SET_NODE_CORNER_RADIUS', { nodeId, radius });
186
+ }
187
+ async cloneNode(nodeId) {
188
+ return this.sendCommand('CLONE_NODE', { nodeId });
189
+ }
190
+ async deleteNode(nodeId) {
191
+ return this.sendCommand('DELETE_NODE', { nodeId });
192
+ }
193
+ async renameNode(nodeId, newName) {
194
+ return this.sendCommand('RENAME_NODE', { nodeId, newName });
195
+ }
196
+ async setTextContent(nodeId, characters, options) {
197
+ const params = { nodeId, text: characters };
198
+ if (options) {
199
+ if (options.fontSize)
200
+ params.fontSize = options.fontSize;
201
+ if (options.fontWeight)
202
+ params.fontWeight = options.fontWeight;
203
+ if (options.fontFamily)
204
+ params.fontFamily = options.fontFamily;
205
+ }
206
+ return this.sendCommand('SET_TEXT_CONTENT', params);
207
+ }
208
+ async createChildNode(parentId, nodeType, properties) {
209
+ return this.sendCommand('CREATE_CHILD_NODE', { parentId, nodeType, properties: properties || {} });
210
+ }
211
+ // ============================================================================
212
+ // Screenshot & instance properties
213
+ // ============================================================================
214
+ async captureScreenshot(nodeId, options) {
215
+ const params = { nodeId };
216
+ if (options?.format)
217
+ params.format = options.format;
218
+ if (options?.scale)
219
+ params.scale = options.scale;
220
+ return this.sendCommand('CAPTURE_SCREENSHOT', params, 30000);
221
+ }
222
+ async setInstanceProperties(nodeId, properties) {
223
+ return this.sendCommand('SET_INSTANCE_PROPERTIES', { nodeId, properties });
224
+ }
225
+ // ============================================================================
226
+ // Image fill
227
+ // ============================================================================
228
+ async setImageFill(nodeIds, imageData, scaleMode = 'FILL') {
229
+ return this.sendCommand('SET_IMAGE_FILL', { nodeIds, imageData, scaleMode }, 60000);
230
+ }
231
+ // ============================================================================
232
+ // Design lint
233
+ // ============================================================================
234
+ async lintDesign(nodeId, rules, maxDepth, maxFindings) {
235
+ const params = {};
236
+ if (nodeId)
237
+ params.nodeId = nodeId;
238
+ if (rules)
239
+ params.rules = rules;
240
+ if (maxDepth !== undefined)
241
+ params.maxDepth = maxDepth;
242
+ if (maxFindings !== undefined)
243
+ params.maxFindings = maxFindings;
244
+ return this.sendCommand('LINT_DESIGN', params, 120000);
245
+ }
246
+ // ============================================================================
247
+ // Cache management (no-op for cloud relay)
248
+ // ============================================================================
249
+ clearFrameCache() {
250
+ // No frame cache in cloud relay mode
251
+ }
252
+ // ============================================================================
253
+ // Transport — fetch-based RPC to the relay DO
254
+ // ============================================================================
255
+ async sendCommand(method, params = {}, timeoutMs = 15000) {
256
+ const res = await this.relayStub.fetch('https://relay/relay/command', {
257
+ method: 'POST',
258
+ headers: { 'Content-Type': 'application/json' },
259
+ body: JSON.stringify({ method, params, timeoutMs }),
260
+ });
261
+ const data = await res.json();
262
+ if (data.error) {
263
+ throw new Error(data.error);
264
+ }
265
+ return data.result;
266
+ }
267
+ }