@sarjallab09/figma-intelligence 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/README.md +67 -36
  2. package/dist/bin/cli.js +2 -0
  3. package/dist/design-bridge/bridge.js +2 -0
  4. package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
  5. package/dist/figma-bridge-plugin/code.js +1 -0
  6. package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
  7. package/dist/figma-bridge-plugin/ui.html +4970 -0
  8. package/dist/figma-intelligence-layer/dist/index.js +2 -0
  9. package/dist/scripts/clean-existing-chunks.js +2 -0
  10. package/dist/scripts/connect-ai-tool.js +2 -0
  11. package/dist/scripts/convert-hub-pdfs.js +2 -0
  12. package/dist/scripts/figma-mcp-status.js +2 -0
  13. package/dist/scripts/register-codex-mcp.js +2 -0
  14. package/dist/scripts/test-copilot-chat.js +2 -0
  15. package/package.json +11 -8
  16. package/bin/cli.js +0 -859
  17. package/design-bridge/bridge.js +0 -196
  18. package/design-bridge/lib/assets.js +0 -367
  19. package/design-bridge/lib/prompt.js +0 -85
  20. package/design-bridge/lib/server.js +0 -66
  21. package/design-bridge/lib/stitch.js +0 -37
  22. package/design-bridge/lib/tokens.js +0 -82
  23. package/design-bridge/package-lock.json +0 -579
  24. package/figma-bridge-plugin/README.md +0 -97
  25. package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
  26. package/figma-bridge-plugin/bridge-relay.js +0 -2505
  27. package/figma-bridge-plugin/chat-runner.js +0 -485
  28. package/figma-bridge-plugin/code.js +0 -1534
  29. package/figma-bridge-plugin/codex-runner.js +0 -505
  30. package/figma-bridge-plugin/component-schemas.js +0 -110
  31. package/figma-bridge-plugin/content-context.js +0 -869
  32. package/figma-bridge-plugin/create-button.js +0 -216
  33. package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
  34. package/figma-bridge-plugin/gemini-runner.js +0 -187
  35. package/figma-bridge-plugin/html-to-figma.js +0 -927
  36. package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
  37. package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
  38. package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
  39. package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
  40. package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
  41. package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
  42. package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
  43. package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
  44. package/figma-bridge-plugin/perplexity-runner.js +0 -188
  45. package/figma-bridge-plugin/references/SKILL.md +0 -178
  46. package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
  47. package/figma-bridge-plugin/references/api-spec.md +0 -162
  48. package/figma-bridge-plugin/references/color-spec.md +0 -148
  49. package/figma-bridge-plugin/references/full-spec-template.md +0 -314
  50. package/figma-bridge-plugin/references/property-spec.md +0 -175
  51. package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
  52. package/figma-bridge-plugin/references/structure-spec.md +0 -165
  53. package/figma-bridge-plugin/shared-prompt-config.js +0 -645
  54. package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
  55. package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
  56. package/figma-bridge-plugin/spec-helpers/index.js +0 -35
  57. package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
  58. package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
  59. package/figma-bridge-plugin/stitch-auth.js +0 -322
  60. package/figma-bridge-plugin/stitch-runner.js +0 -1427
  61. package/figma-bridge-plugin/token-resolver.js +0 -107
  62. package/figma-bridge-plugin/ui.html +0 -4542
  63. package/figma-intelligence-layer/.env.example +0 -39
  64. package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
  65. package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
  66. package/figma-intelligence-layer/jest.config.js +0 -14
  67. package/figma-intelligence-layer/mcp-config.json +0 -19
  68. package/figma-intelligence-layer/package-lock.json +0 -5892
  69. package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
  70. package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
  71. package/figma-intelligence-layer/src/index.ts +0 -2233
  72. package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
  73. package/figma-intelligence-layer/src/shared/cache.ts +0 -187
  74. package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
  75. package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
  76. package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
  77. package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
  78. package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
  79. package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
  80. package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
  81. package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
  82. package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
  83. package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
  84. package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
  85. package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
  86. package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
  87. package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1418
  88. package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
  89. package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
  90. package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
  91. package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
  92. package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
  93. package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
  94. package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
  95. package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
  96. package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
  97. package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
  98. package/figma-intelligence-layer/src/shared/types.ts +0 -346
  99. package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
  100. package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
  101. package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
  102. package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
  103. package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
  104. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
  105. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
  106. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
  107. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
  108. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
  109. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
  110. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
  111. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
  112. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
  113. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
  114. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
  115. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
  116. package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
  117. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
  118. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
  119. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
  120. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
  121. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
  122. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
  123. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
  124. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
  125. package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
  126. package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
  127. package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
  128. package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
  129. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
  130. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
  131. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
  132. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
  133. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
  134. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
  135. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
  136. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
  137. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
  138. package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
  139. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
  140. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
  141. package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
  142. package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
  143. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
  144. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
  145. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
  146. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
  147. package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
  148. package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
  149. package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
  150. package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
  151. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
  152. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
  153. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
  154. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
  155. package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
  156. package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
  157. package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
  158. package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
  159. package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
  160. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
  161. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
  162. package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
  163. package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
  164. package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
  165. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
  166. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
  167. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
  168. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
  169. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
  170. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
  171. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
  172. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
  173. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
  174. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
  175. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
  176. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
  177. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
  178. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
  179. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
  180. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
  181. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
  182. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
  183. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
  184. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
  185. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
  186. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
  187. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
  188. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
  189. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
  190. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
  191. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
  192. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
  193. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
  194. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
  195. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
  196. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
  197. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
  198. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
  199. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
  200. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
  201. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
  202. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
  203. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
  204. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
  205. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
  206. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
  207. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
  208. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
  209. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
  210. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
  211. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
  212. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
  213. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
  214. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
  215. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
  216. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
  217. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
  218. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
  219. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
  220. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
  221. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
  222. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
  223. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
  224. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
  225. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
  226. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
  227. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
  228. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
  229. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
  230. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
  231. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
  232. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
  233. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
  234. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
  235. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
  236. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
  237. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
  238. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
  239. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
  240. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
  241. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
  242. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
  243. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
  244. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
  245. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
  246. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
  247. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
  248. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
  249. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
  250. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
  251. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
  252. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
  253. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
  254. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
  255. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
  256. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
  257. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
  258. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
  259. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
  260. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
  261. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
  262. package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
  263. package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
  264. package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
  265. package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
  266. package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
  267. package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
  268. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
  269. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
  270. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
  271. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
  272. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
  273. package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
  274. package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
  275. package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
  276. package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
  277. package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
  278. package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
  279. package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
  280. package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
  281. package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
  282. package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
  283. package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
  284. package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
  285. package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
  286. package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
  287. package/figma-intelligence-layer/tsconfig.json +0 -19
  288. package/scripts/clean-existing-chunks.js +0 -179
  289. package/scripts/connect-ai-tool.js +0 -490
  290. package/scripts/convert-hub-pdfs.js +0 -425
  291. package/scripts/figma-mcp-status.js +0 -349
  292. package/scripts/register-codex-mcp.js +0 -96
  293. /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
  294. /package/{design-bridge → dist/design-bridge}/package.json +0 -0
  295. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
  296. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
  297. /package/{figma-intelligence-layer → dist/figma-intelligence-layer}/package.json +0 -0
@@ -1,1427 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * stitch-runner.js — Uses the official @google/stitch-sdk to generate UI designs,
4
- * then converts the Tailwind HTML output to native Figma frames.
5
- *
6
- * SDK: @google/stitch-sdk (ESM-only, loaded via dynamic import)
7
- * MCP endpoint: https://stitch.googleapis.com/mcp
8
- * Auth: API key from Stitch Settings (stitch.withgoogle.com)
9
- *
10
- * Pipeline (mirrors actual Stitch environment):
11
- * 1. StitchToolClient.connect() → authenticate
12
- * 2. Stitch.createProject() → workspace (or reuse existing)
13
- * 3. Project.generate(prompt) → Screen with HTML + screenshot
14
- * 4. extract_design_context → Design DNA (colors, typography, spacing, components)
15
- * 5. Save as design.md → persist for future screen consistency
16
- * 6. Screen.getHtml() → download URL → fetch HTML
17
- * 7. htmlToFigmaCommands() → convert Tailwind HTML to Figma frames
18
- *
19
- * The design.md file is saved per-project so subsequent screen generations
20
- * in the same workspace reuse the same design DNA — maintaining visual
21
- * consistency across screens (same palette, fonts, spacing, patterns).
22
- *
23
- * Emits the same event shape as chat-runner.js / perplexity-runner.js
24
- * so bridge-relay can use it interchangeably.
25
- */
26
-
27
- const http = require("http");
28
- const https = require("https");
29
- const { EventEmitter } = require("events");
30
- const { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } = require("fs");
31
- const { join } = require("path");
32
- const { homedir } = require("os");
33
- const { htmlToFigmaExecuteCode } = require("./html-to-figma");
34
- const { getStitchAccessToken, hasStitchAuth } = require("./stitch-auth");
35
- const STITCH_LOG = join(homedir(), ".claude", "stitch", "debug.log");
36
- function stitchLog(msg, data) {
37
- try {
38
- const ts = new Date().toISOString();
39
- const line = `[${ts}] ${msg}${data !== undefined ? ": " + JSON.stringify(data, null, 2) : ""}\n`;
40
- if (!existsSync(join(homedir(), ".claude", "stitch"))) mkdirSync(join(homedir(), ".claude", "stitch"), { recursive: true });
41
- appendFileSync(STITCH_LOG, line);
42
- } catch {}
43
- }
44
-
45
- // ── Local preview server ──────────────────────────────────────────────────────
46
-
47
- const PREVIEW_DIR = join(homedir(), ".claude", "stitch", "previews");
48
- let _previewServer = null;
49
- let _previewPort = null;
50
- const _previewFiles = new Map(); // slug → { html, title, createdAt }
51
-
52
- function ensurePreviewServer() {
53
- return new Promise((resolve, reject) => {
54
- if (_previewServer && _previewPort) {
55
- resolve(_previewPort);
56
- return;
57
- }
58
-
59
- if (!existsSync(PREVIEW_DIR)) mkdirSync(PREVIEW_DIR, { recursive: true });
60
-
61
- const server = http.createServer((req, res) => {
62
- const url = new URL(req.url, `http://localhost`);
63
- const slug = url.pathname.slice(1) || "index";
64
-
65
- // Serve the index listing all previews
66
- if (slug === "index") {
67
- const items = Array.from(_previewFiles.entries())
68
- .sort((a, b) => b[1].createdAt - a[1].createdAt)
69
- .map(([s, f]) => `<li style="margin:8px 0"><a href="/${s}" style="color:#4ade80;font-size:18px">${f.title || s}</a> <span style="color:#666;font-size:13px">${new Date(f.createdAt).toLocaleTimeString()}</span></li>`)
70
- .join("");
71
- res.writeHead(200, { "Content-Type": "text/html" });
72
- res.end(`<!DOCTYPE html><html><head><title>Stitch Previews</title>
73
- <style>body{font-family:-apple-system,sans-serif;background:#0a0a0a;color:#fff;padding:40px;max-width:600px;margin:0 auto}
74
- a{text-decoration:none}a:hover{text-decoration:underline}h1{color:#4ade80}ul{list-style:none;padding:0}</style></head>
75
- <body><h1>Stitch Previews</h1><ul>${items || "<li style='color:#666'>No previews yet</li>"}</ul></body></html>`);
76
- return;
77
- }
78
-
79
- const entry = _previewFiles.get(slug);
80
- if (!entry) {
81
- res.writeHead(404, { "Content-Type": "text/html" });
82
- res.end("<h1>Preview not found</h1><p><a href='/'>View all previews</a></p>");
83
- return;
84
- }
85
-
86
- res.writeHead(200, {
87
- "Content-Type": "text/html",
88
- "Cache-Control": "no-cache",
89
- });
90
- res.end(entry.html);
91
- });
92
-
93
- server.listen(0, "127.0.0.1", () => {
94
- _previewPort = server.address().port;
95
- _previewServer = server;
96
- console.log(` Stitch preview server running at http://localhost:${_previewPort}`);
97
- resolve(_previewPort);
98
- });
99
-
100
- server.on("error", (err) => {
101
- console.error(" Stitch preview server error:", err.message);
102
- reject(err);
103
- });
104
- });
105
- }
106
-
107
- function addPreview(html, title) {
108
- const slug = (title || "screen")
109
- .toLowerCase()
110
- .replace(/[^a-z0-9]+/g, "-")
111
- .replace(/^-|-$/g, "")
112
- .slice(0, 60)
113
- + "-" + Date.now().toString(36);
114
-
115
- _previewFiles.set(slug, { html, title, createdAt: Date.now() });
116
-
117
- // Also save to disk for persistence
118
- try {
119
- writeFileSync(join(PREVIEW_DIR, slug + ".html"), html);
120
- } catch {}
121
-
122
- return slug;
123
- }
124
-
125
- // ── SDK loader (ESM → CJS bridge) ─────────────────────────────────────────────
126
-
127
- let _sdkPromise = null;
128
-
129
- function loadSdk() {
130
- if (!_sdkPromise) {
131
- _sdkPromise = import("@google/stitch-sdk");
132
- }
133
- return _sdkPromise;
134
- }
135
-
136
- // ── Design context (design.md) persistence ─────────────────────────────────────
137
-
138
- /**
139
- * Directory where per-project design.md files are stored.
140
- * ~/.claude/stitch/<sanitized-project-name>/design.md
141
- */
142
- const STITCH_DIR = join(homedir(), ".claude", "stitch");
143
-
144
- function sanitizeName(name) {
145
- return (name || "default").replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 60);
146
- }
147
-
148
- function getDesignMdPath(projectName) {
149
- const dir = join(STITCH_DIR, sanitizeName(projectName));
150
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
151
- return join(dir, "design.md");
152
- }
153
-
154
- /**
155
- * Load an existing design.md for a project. Returns null if not found.
156
- */
157
- function loadDesignMd(projectName) {
158
- const filePath = getDesignMdPath(projectName);
159
- try {
160
- if (existsSync(filePath)) return readFileSync(filePath, "utf8");
161
- } catch {}
162
- return null;
163
- }
164
-
165
- /**
166
- * Save design.md for a project.
167
- */
168
- function saveDesignMd(projectName, content) {
169
- const filePath = getDesignMdPath(projectName);
170
- try {
171
- writeFileSync(filePath, content, "utf8");
172
- return filePath;
173
- } catch (err) {
174
- console.error(" ⚠ Could not save design.md:", err.message);
175
- return null;
176
- }
177
- }
178
-
179
- /**
180
- * Build a design.md from the extract_design_context result.
181
- * The result is typically structured JSON with colors, typography, spacing, etc.
182
- */
183
- function buildDesignMd(context, projectName, screenPrompt, theme) {
184
- const lines = [];
185
- lines.push(`# Design System — ${projectName}`);
186
- lines.push("");
187
- lines.push(`> Extracted by Google Stitch from: "${screenPrompt}"`);
188
- lines.push(`> Generated: ${new Date().toISOString().split("T")[0]}`);
189
- lines.push("");
190
-
191
- if (typeof context === "string") {
192
- // If Stitch returned plain text/markdown, use it directly
193
- lines.push(context);
194
-
195
- // Append structured Figma Variables section from the theme's namedColors
196
- // so the .md parser can create proper variable collections
197
- if (theme?.namedColors && typeof theme.namedColors === "object") {
198
- lines.push("");
199
- lines.push("---");
200
- lines.push("");
201
- lines.push("## Primitives");
202
- lines.push("");
203
- lines.push("### Colors");
204
- lines.push("");
205
- for (const [name, hex] of Object.entries(theme.namedColors)) {
206
- lines.push(`- **color/${name.replace(/_/g, "/")}**: \`${hex}\``);
207
- }
208
- lines.push("");
209
-
210
- // Add fonts
211
- if (theme.font || theme.bodyFont || theme.headlineFont) {
212
- lines.push("### Strings");
213
- lines.push("");
214
- if (theme.font) lines.push(`- **fontFamily/primary**: \`${theme.font}\``);
215
- if (theme.bodyFont && theme.bodyFont !== theme.font) lines.push(`- **fontFamily/body**: \`${theme.bodyFont}\``);
216
- if (theme.headlineFont && theme.headlineFont !== theme.font) lines.push(`- **fontFamily/headline**: \`${theme.headlineFont}\``);
217
- lines.push("");
218
- }
219
-
220
- // Add spacing scale
221
- if (theme.spacingScale) {
222
- lines.push("### spacing");
223
- lines.push("");
224
- const base = [0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48];
225
- const names = ["0", "0.5", "1", "1.5", "2", "2.5", "3", "4", "5", "6", "8", "10", "12"];
226
- base.forEach((val, i) => {
227
- const scaled = Math.round(val * (theme.spacingScale || 1));
228
- lines.push(`- **spacing/${names[i]}**: \`${scaled}px\``);
229
- });
230
- lines.push("");
231
- }
232
-
233
- // Semantic aliases for common roles
234
- lines.push("## Semantic");
235
- lines.push("");
236
- lines.push("### Colors");
237
- lines.push("");
238
- const semanticMap = {
239
- "color/action/primary": "color/primary",
240
- "color/action/primary/hover": "color/primary/container",
241
- "color/text/primary": "color/on/surface",
242
- "color/text/secondary": "color/on/surface/variant",
243
- "color/text/onAction": "color/on/primary",
244
- "color/surface/default": "color/surface",
245
- "color/surface/muted": "color/surface/container",
246
- "color/border/default": "color/outline",
247
- "color/border/muted": "color/outline/variant",
248
- "color/action/danger": "color/error",
249
- "color/action/success": "color/tertiary",
250
- };
251
- for (const [sem, prim] of Object.entries(semanticMap)) {
252
- // Only add if the target primitive exists in namedColors
253
- const primKey = prim.replace("color/", "").replace(/\//g, "_");
254
- if (theme.namedColors[primKey]) {
255
- lines.push(`- **${sem}**: → \`${prim}\``);
256
- }
257
- }
258
- lines.push("");
259
- }
260
-
261
- return lines.join("\n");
262
- }
263
-
264
- // Structured context — format as readable markdown
265
- if (context.colors || context.colorPalette || context.palette) {
266
- lines.push("## Colors");
267
- lines.push("");
268
- const colors = context.colors || context.colorPalette || context.palette;
269
- if (Array.isArray(colors)) {
270
- for (const c of colors) {
271
- const name = c.name || c.role || c.label || "Color";
272
- const value = c.value || c.hex || c.color || "";
273
- lines.push(`- **${name}**: \`${value}\``);
274
- }
275
- } else if (typeof colors === "object") {
276
- for (const [key, val] of Object.entries(colors)) {
277
- lines.push(`- **${key}**: \`${typeof val === "object" ? val.hex || val.value || JSON.stringify(val) : val}\``);
278
- }
279
- }
280
- lines.push("");
281
- }
282
-
283
- if (context.typography || context.fonts || context.textStyles) {
284
- lines.push("## Typography");
285
- lines.push("");
286
- const typo = context.typography || context.fonts || context.textStyles;
287
- if (Array.isArray(typo)) {
288
- for (const t of typo) {
289
- const name = t.name || t.role || "Text";
290
- const family = t.fontFamily || t.family || "";
291
- const size = t.fontSize || t.size || "";
292
- const weight = t.fontWeight || t.weight || "";
293
- lines.push(`- **${name}**: ${family} ${size}${size ? "px" : ""} / ${weight}`);
294
- }
295
- } else if (typeof typo === "object") {
296
- for (const [key, val] of Object.entries(typo)) {
297
- lines.push(`- **${key}**: ${typeof val === "object" ? JSON.stringify(val) : val}`);
298
- }
299
- }
300
- lines.push("");
301
- }
302
-
303
- if (context.spacing || context.layout) {
304
- lines.push("## Spacing & Layout");
305
- lines.push("");
306
- const sp = context.spacing || context.layout;
307
- if (typeof sp === "object") {
308
- for (const [key, val] of Object.entries(sp)) {
309
- lines.push(`- **${key}**: ${typeof val === "object" ? JSON.stringify(val) : val}`);
310
- }
311
- }
312
- lines.push("");
313
- }
314
-
315
- if (context.components || context.patterns || context.componentPatterns) {
316
- lines.push("## Component Patterns");
317
- lines.push("");
318
- const comps = context.components || context.patterns || context.componentPatterns;
319
- if (Array.isArray(comps)) {
320
- for (const c of comps) {
321
- const name = c.name || c.type || "Component";
322
- const desc = c.description || c.style || "";
323
- lines.push(`- **${name}**: ${desc}`);
324
- }
325
- } else if (typeof comps === "object") {
326
- for (const [key, val] of Object.entries(comps)) {
327
- lines.push(`- **${key}**: ${typeof val === "object" ? JSON.stringify(val) : val}`);
328
- }
329
- }
330
- lines.push("");
331
- }
332
-
333
- if (context.borderRadius || context.radius || context.radii) {
334
- lines.push("## Border Radius");
335
- lines.push("");
336
- const radii = context.borderRadius || context.radius || context.radii;
337
- if (typeof radii === "object") {
338
- for (const [key, val] of Object.entries(radii)) {
339
- lines.push(`- **${key}**: ${val}`);
340
- }
341
- }
342
- lines.push("");
343
- }
344
-
345
- if (context.shadows || context.effects) {
346
- lines.push("## Shadows & Effects");
347
- lines.push("");
348
- const effects = context.shadows || context.effects;
349
- if (typeof effects === "object") {
350
- for (const [key, val] of Object.entries(effects)) {
351
- lines.push(`- **${key}**: ${typeof val === "object" ? JSON.stringify(val) : val}`);
352
- }
353
- }
354
- lines.push("");
355
- }
356
-
357
- // Dump any remaining top-level keys we didn't handle
358
- const handled = new Set(["colors","colorPalette","palette","typography","fonts","textStyles","spacing","layout","components","patterns","componentPatterns","borderRadius","radius","radii","shadows","effects"]);
359
- const remaining = Object.keys(context).filter(k => !handled.has(k));
360
- if (remaining.length > 0) {
361
- lines.push("## Additional Properties");
362
- lines.push("");
363
- for (const key of remaining) {
364
- const val = context[key];
365
- if (val !== null && val !== undefined) {
366
- lines.push(`- **${key}**: ${typeof val === "object" ? JSON.stringify(val) : val}`);
367
- }
368
- }
369
- lines.push("");
370
- }
371
-
372
- return lines.join("\n");
373
- }
374
-
375
- // ── GCP project auto-detection ──────────────────────────────────────────────
376
-
377
- /**
378
- * Auto-detect the user's Google Cloud project ID from their account.
379
- * Queries the Cloud Resource Manager API and returns the first active project.
380
- */
381
- function detectGcpProject(accessToken) {
382
- return new Promise((resolve) => {
383
- https.get("https://cloudresourcemanager.googleapis.com/v1/projects?pageSize=5", {
384
- headers: { "Authorization": `Bearer ${accessToken}` },
385
- }, (res) => {
386
- let data = "";
387
- res.on("data", (c) => { data += c; });
388
- res.on("end", () => {
389
- try {
390
- const json = JSON.parse(data);
391
- const active = (json.projects || []).find(p => p.lifecycleState === "ACTIVE");
392
- if (active) {
393
- console.log(` Stitch: auto-detected GCP project: ${active.projectId}`);
394
- resolve(active.projectId);
395
- } else {
396
- resolve("figma-plugin");
397
- }
398
- } catch {
399
- resolve("figma-plugin");
400
- }
401
- });
402
- }).on("error", () => resolve("figma-plugin"));
403
- });
404
- }
405
-
406
- // ── Intent detection ────────────────────────────────────────────────────────────
407
-
408
- function detectStitchIntent(message) {
409
- const m = message.toLowerCase();
410
-
411
- // List projects — broad patterns
412
- if (/list\s+(all\s+)?(my\s+)?(stitch\s+)?projects|show\s+(all\s+)?(me\s+)?(my\s+)?(stitch\s+)?projects|stitch\s+projects|what\s+projects|my\s+projects/.test(m))
413
- return { type: "list_projects" };
414
-
415
- // Use/convert an existing screen to something (React, webpage, Figma, interactive, etc.)
416
- // Key signal: user mentions "screen" + an action/conversion word
417
- // Catches: "use my stitch screen", "create interactive web with the selected screen",
418
- // "convert stitch to react", "make the screen interactive", "web app from my screen", etc.
419
- if (/use\s+(my|the|this|that|selected|current|latest)?\s*(stitch\s+)?screen/.test(m) ||
420
- /from\s+(my\s+)?stitch.*(?:create|build|make|convert|generate)/.test(m) ||
421
- /(?:create|build|make|convert|generate)\b.*\b(?:from|using|with)\s+(?:my\s+)?(?:the\s+)?(?:stitch\s+)?(?:selected\s+)?screen/.test(m) ||
422
- /(?:with|from|using)\s+(?:the\s+)?(?:my\s+)?(?:selected\s+|current\s+|latest\s+)?(?:stitch\s+)?screen/.test(m) ||
423
- /stitch\s+(?:screen\s+)?(?:to|into)\s+(?:react|figma|webpage|html|code|web|interactive)/.test(m) ||
424
- /(?:screen|design)\s+(?:i|I)\s+(?:selected|created|made|have)/.test(m) ||
425
- /take\s+(?:the|my|this)?\s*(?:stitch\s+)?screen/.test(m) ||
426
- /(?:selected|current|latest)\s+screen\b/.test(m) ||
427
- /\bscreen\b.*(?:interactive|react|web\s*app|webpage|website|convert|code)/.test(m) ||
428
- /(?:interactive|react|web\s*app|webpage|website|convert)\b.*\bscreen\b/.test(m))
429
- return { type: "get_screen", screenHint: extractScreenHint(m), projectHint: extractProjectHint(m), wantsCode: detectCodeIntent(m) };
430
-
431
- // Get screen HTML / fetch specific screen by name
432
- if (/get\s+(the\s+)?html|screen\s+code|fetch\s+screen|download\s+screen|html\s+from\s+screen/.test(m))
433
- return { type: "get_screen", screenHint: extractScreenHint(m), projectHint: extractProjectHint(m), wantsCode: detectCodeIntent(m) };
434
-
435
- // Show screenshot / preview — check BEFORE list_screens
436
- if (/show\s+screenshot|preview\s+screen|screen\s+image|screenshot\s+of/.test(m))
437
- return { type: "get_screen_image", screenHint: extractScreenHint(m), projectHint: extractProjectHint(m) };
438
-
439
- // List screens in a project
440
- if (/list\s+(all\s+)?screens|show\s+(all\s+)?screens|screens?\s+(in|of|from)\s+/.test(m))
441
- return { type: "list_screens", projectHint: extractProjectHint(m) };
442
-
443
- // Default: generation
444
- return { type: "generate" };
445
- }
446
-
447
- /**
448
- * Detect if the user wants code output (React, HTML, etc.) vs Figma frames.
449
- */
450
- function detectCodeIntent(message) {
451
- if (/react|component|webpage|web\s*page|web\s*app|website|html\s+code|code|next\.?js|typescript|jsx|tsx|interactive\s+web|interactive\s+page|interactive\s+app|live\s+preview|preview\s+link|run\s+locally|localhost|interactive\b.*\b(?:using|with|from|create|build|make|generate)/.test(message))
452
- return "react";
453
- return null; // default: Figma frames
454
- }
455
-
456
- function extractProjectHint(message) {
457
- // Match: "in project X", "from project X", "project named X", "in X project"
458
- let match = message.match(/(?:in|from|of)\s+(?:project\s+)?["']?([^"',\n]+?)["']?\s*(?:project)?(?:\s*$|\s+(?:to|and|screen|create|get|show|list))/i);
459
- if (match) { const h = stripArticles(match[1].trim()); if (h) return h; }
460
- match = message.match(/project\s+(?:named?\s+)?["']?([^"',\n]+?)["']?(?:\s*$|\s+(?:to|and|screen|create|get|show))/i);
461
- if (match) { const h = stripArticles(match[1].trim()); if (h) return h; }
462
- return null;
463
- }
464
-
465
- /**
466
- * Strip leading articles (the, my, this, that, a, an) from hints.
467
- */
468
- function stripArticles(s) {
469
- // Remove leading articles and also filter out "stitch" (provider name, not a project)
470
- return s.replace(/^(the|my|this|that|a|an)\s+/i, "").replace(/^stitch\s*/i, "").trim();
471
- }
472
-
473
- /**
474
- * Get a human-readable label for a screen.
475
- * The API often only returns a `name` like "projects/123/screens/abc" with no displayName.
476
- */
477
- function getScreenLabel(screen, index) {
478
- const d = screen.data || {};
479
- if (d.displayName) return d.displayName;
480
- if (d.title) return d.title;
481
- // Extract short ID from name like "projects/123/screens/abc..."
482
- const id = screen.id || screen.screenId || "";
483
- return `Screen ${typeof index === "number" ? index : 1}` + (id ? ` (${id.slice(0, 8)}...)` : "");
484
- }
485
-
486
- function extractScreenHint(message) {
487
- // Match: "screen X", "the X screen", "screen named X", "use X"
488
- let match = message.match(/(?:screen\s+(?:named?\s+)?|use\s+(?:the\s+)?)["']?([^"',\n]+?)["']?\s*(?:screen)?(?:\s*$|\s+(?:to|from|in|and|create))/i);
489
- if (match) return match[1].trim();
490
- return null;
491
- }
492
-
493
- async function findProject(stitchApi, hint) {
494
- const projects = await stitchApi.projects();
495
- if (!hint) return { projects, matched: null };
496
-
497
- const lower = stripArticles(hint).toLowerCase();
498
- const matched = projects.find(p => {
499
- const title = (p.data?.title || p.data?.displayName || "").toLowerCase();
500
- // Bidirectional: "beehive" matches "Beehive Project" and vice versa
501
- return title === lower || title.includes(lower) || lower.includes(title) || p.id === hint || p.projectId === hint;
502
- });
503
-
504
- return { projects, matched };
505
- }
506
-
507
- /**
508
- * List screens for a project — bypasses SDK's project.screens() to handle
509
- * the raw MCP response directly and avoid crashes on unexpected formats.
510
- */
511
- async function listScreensDirect(client, projectId) {
512
- try {
513
- const raw = await client.callTool("list_screens", { projectId });
514
- console.log(" Stitch list_screens raw:", JSON.stringify(raw)?.slice(0, 300));
515
- if (!raw) return [];
516
- const arr = raw.screens || raw.items || (Array.isArray(raw) ? raw : []);
517
- return arr;
518
- } catch (err) {
519
- console.error(" Stitch: list_screens failed:", err.message);
520
- return [];
521
- }
522
- }
523
-
524
- async function findScreen(client, projectId, hint) {
525
- const screenDataList = await listScreensDirect(client, projectId);
526
-
527
- // Wrap raw screen data into objects with a consistent shape
528
- const screens = screenDataList.map(s => ({
529
- data: s,
530
- id: s.id || (s.name ? s.name.split("/screens/").pop() : undefined),
531
- screenId: s.id || (s.name ? s.name.split("/screens/").pop() : undefined),
532
- projectId,
533
- // Keep raw data for getHtml/getImage later
534
- _raw: s,
535
- }));
536
-
537
- if (!hint) return { screens, matched: null };
538
-
539
- const lower = stripArticles(hint).toLowerCase();
540
- const matched = screens.find(s => {
541
- const name = (s.data?.displayName || s.data?.name || s.data?.title || "").toLowerCase();
542
- return name === lower || name.includes(lower) || lower.includes(name) || s.id === hint || s.screenId === hint;
543
- });
544
-
545
- return { screens, matched };
546
- }
547
-
548
- // ── Query handlers ──────────────────────────────────────────────────────────────
549
-
550
- async function handleListProjects(stitchApi, requestId, onEvent) {
551
- onEvent({ type: "phase_start", id: requestId, phase: "Listing Stitch projects..." });
552
- onEvent({ type: "tool_start", id: requestId, tool: "list_projects" });
553
-
554
- const projects = await stitchApi.projects();
555
-
556
- onEvent({ type: "tool_done", id: requestId, tool: "list_projects", isError: false });
557
-
558
- if (projects.length === 0) {
559
- const msg = "You don't have any Stitch projects yet. Ask me to create a screen and I'll set one up!\n";
560
- onEvent({ type: "text_delta", id: requestId, delta: msg });
561
- onEvent({ type: "done", id: requestId, fullText: msg });
562
- return;
563
- }
564
-
565
- const lines = [`Your Stitch Projects (${projects.length}):\n\n`];
566
- for (let i = 0; i < projects.length; i++) {
567
- const p = projects[i];
568
- const title = p.data?.title || p.data?.displayName || `Project ${i + 1}`;
569
- const id = p.id || p.projectId || "unknown";
570
- const created = p.data?.createTime ? new Date(p.data.createTime).toLocaleDateString() : "";
571
- lines.push(`${i + 1}. **${title}** (ID: ${id})${created ? `\n Created: ${created}` : ""}\n`);
572
- }
573
- lines.push(`\nSay "list screens in [project name]" to see screens in a project.\n`);
574
-
575
- const msg = lines.join("");
576
- onEvent({ type: "text_delta", id: requestId, delta: msg });
577
- onEvent({ type: "done", id: requestId, fullText: msg });
578
- }
579
-
580
- async function handleListScreens(stitchApi, client, projectHint, requestId, onEvent) {
581
- onEvent({ type: "phase_start", id: requestId, phase: "Finding project..." });
582
-
583
- const { projects, matched } = await findProject(stitchApi, projectHint);
584
-
585
- if (!matched) {
586
- if (!projectHint) {
587
- const names = projects.map(p => p.data?.title || p.data?.displayName || p.id).join(", ");
588
- const msg = `Which project? Your projects: ${names}\n\nSay "list screens in [project name]"\n`;
589
- onEvent({ type: "text_delta", id: requestId, delta: msg });
590
- onEvent({ type: "done", id: requestId, fullText: msg });
591
- return;
592
- }
593
- const msg = `Could not find project "${projectHint}". Your projects:\n` +
594
- projects.map(p => `- ${p.data?.title || p.data?.displayName || p.id}`).join("\n") + "\n";
595
- onEvent({ type: "text_delta", id: requestId, delta: msg });
596
- onEvent({ type: "done", id: requestId, fullText: msg });
597
- return;
598
- }
599
-
600
- const projectTitle = matched.data?.title || matched.data?.displayName || matched.id;
601
- onEvent({ type: "phase_start", id: requestId, phase: `Listing screens in "${projectTitle}"...` });
602
- onEvent({ type: "tool_start", id: requestId, tool: "list_screens" });
603
-
604
- const { screens } = await findScreen(client, matched.projectId || matched.id, null);
605
-
606
- onEvent({ type: "tool_done", id: requestId, tool: "list_screens", isError: false });
607
-
608
- if (screens.length === 0) {
609
- const msg = `No screens in "${projectTitle}" yet. Ask me to generate one!\n`;
610
- onEvent({ type: "text_delta", id: requestId, delta: msg });
611
- onEvent({ type: "done", id: requestId, fullText: msg });
612
- return;
613
- }
614
-
615
- const lines = [`Screens in "${projectTitle}" (${screens.length}):\n\n`];
616
- for (let i = 0; i < screens.length; i++) {
617
- const s = screens[i];
618
- const name = getScreenLabel(s, i + 1);
619
- const id = s.id || s.screenId || "unknown";
620
- lines.push(`${i + 1}. **${name}** (ID: ${id.slice(0, 12)})\n`);
621
- }
622
- lines.push(`\nSay "use screen [name] from ${projectTitle}" to create a Figma frame from it.\n`);
623
- lines.push(`Or "get HTML from screen [name] in ${projectTitle}" to get the raw code.\n`);
624
-
625
- const msg = lines.join("");
626
- onEvent({ type: "text_delta", id: requestId, delta: msg });
627
- onEvent({ type: "done", id: requestId, fullText: msg });
628
- }
629
-
630
- async function handleGetScreen(stitchApi, client, projectHint, screenHint, requestId, onEvent, deviceType, wantsCode) {
631
- onEvent({ type: "phase_start", id: requestId, phase: "Finding project and screen..." });
632
-
633
- const { projects, matched: matchedProject } = await findProject(stitchApi, projectHint);
634
-
635
- if (projects.length === 0) {
636
- const msg = "No Stitch projects found. Create a screen first!\n";
637
- onEvent({ type: "text_delta", id: requestId, delta: msg });
638
- onEvent({ type: "done", id: requestId, fullText: msg });
639
- return;
640
- }
641
-
642
- if (matchedProject) {
643
- return handleGetScreenFromProject(matchedProject, stitchApi, client, screenHint, requestId, onEvent, deviceType, wantsCode);
644
- }
645
-
646
- // No exact project match — search ALL projects for the screen
647
- // The user might have said the screen name (e.g. "Beehive Management Dashboard")
648
- // which could be in any project, or the project hint itself might be the screen name
649
- const combinedHint = screenHint || projectHint;
650
- onEvent({ type: "phase_start", id: requestId, phase: "Searching all projects for screen..." });
651
-
652
- for (const proj of projects) {
653
- const projId = proj.projectId || proj.id;
654
- try {
655
- const { screens, matched: matchedScreen } = await findScreen(client, projId, combinedHint);
656
- if (matchedScreen) {
657
- const projectTitle = proj.data?.title || proj.data?.displayName || proj.id;
658
- onEvent({ type: "text_delta", id: requestId, delta: `Found screen in "${projectTitle}"\n\n` });
659
- return fetchAndProcessScreen(client, matchedScreen, projectTitle, requestId, onEvent, deviceType, wantsCode);
660
- }
661
- // If no hint but project has screens, use the latest
662
- if (!combinedHint && screens.length > 0) {
663
- const latest = screens[screens.length - 1];
664
- const projectTitle = proj.data?.title || proj.data?.displayName || proj.id;
665
- const latestName = getScreenLabel(latest, screens.length);
666
- onEvent({ type: "text_delta", id: requestId, delta: `Auto-selecting latest screen: "${latestName}" from "${projectTitle}"\n\n` });
667
- return fetchAndProcessScreen(client, latest, projectTitle, requestId, onEvent, deviceType, wantsCode);
668
- }
669
- } catch {}
670
- }
671
-
672
- // Nothing found — try the first project with any screens as fallback
673
- for (const proj of projects) {
674
- const projId = proj.projectId || proj.id;
675
- try {
676
- const { screens } = await findScreen(client, projId, null);
677
- if (screens.length > 0) {
678
- return handleGetScreenFromProject(proj, stitchApi, client, screenHint, requestId, onEvent, deviceType, wantsCode);
679
- }
680
- } catch {}
681
- }
682
-
683
- const names = projects.map(p => p.data?.title || p.data?.displayName || p.id).join(", ");
684
- const msg = `No screens found in any project. Your projects: ${names}\nGenerate a screen first!\n`;
685
- onEvent({ type: "text_delta", id: requestId, delta: msg });
686
- onEvent({ type: "done", id: requestId, fullText: msg });
687
- }
688
-
689
- async function handleGetScreenFromProject(project, stitchApi, client, screenHint, requestId, onEvent, deviceType, wantsCode) {
690
- const projectTitle = project.data?.title || project.data?.displayName || project.id;
691
- const projId = project.projectId || project.id;
692
-
693
- onEvent({ type: "phase_start", id: requestId, phase: `Looking up screens in "${projectTitle}"...` });
694
- onEvent({ type: "tool_start", id: requestId, tool: "list_screens" });
695
-
696
- const { screens, matched: matchedScreen } = await findScreen(client, projId, screenHint);
697
-
698
- onEvent({ type: "tool_done", id: requestId, tool: "list_screens", isError: false });
699
-
700
- if (!matchedScreen) {
701
- if (screens.length === 0) {
702
- const msg = `No screens in "${projectTitle}". Generate one first!\n`;
703
- onEvent({ type: "text_delta", id: requestId, delta: msg });
704
- onEvent({ type: "done", id: requestId, fullText: msg });
705
- return;
706
- }
707
- // Auto-pick the latest (last) screen — user said "selected/the/my screen"
708
- // or hint didn't match any screen name, so just use the most recent one
709
- const latest = screens[screens.length - 1];
710
- const latestName = getScreenLabel(latest, screens.length);
711
- onEvent({ type: "text_delta", id: requestId, delta: `Auto-selecting latest screen: "${latestName}"\n\n` });
712
- return fetchAndProcessScreen(client, latest, projectTitle, requestId, onEvent, deviceType, wantsCode);
713
- }
714
-
715
- return fetchAndProcessScreen(client, matchedScreen, projectTitle, requestId, onEvent, deviceType, wantsCode);
716
- }
717
-
718
- async function fetchAndProcessScreen(client, screen, projectTitle, requestId, onEvent, deviceType, wantsCode) {
719
- const screenName = getScreenLabel(screen, 1);
720
- const projId = screen.projectId;
721
- const scrId = screen.screenId || screen.id;
722
-
723
- onEvent({ type: "phase_start", id: requestId, phase: `Fetching HTML for "${screenName}"...` });
724
- onEvent({ type: "tool_start", id: requestId, tool: "get_screen" });
725
-
726
- // Fetch screen details directly via MCP tool (bypass SDK wrapper)
727
- let htmlUrl = null;
728
- let imageUrl = null;
729
-
730
- // Check if cached data already has URLs
731
- if (screen.data?.htmlCode?.downloadUrl) {
732
- htmlUrl = screen.data.htmlCode.downloadUrl;
733
- }
734
- if (screen.data?.screenshot?.downloadUrl) {
735
- imageUrl = screen.data.screenshot.downloadUrl;
736
- }
737
-
738
- // If no cached URLs, fetch via get_screen tool
739
- if (!htmlUrl) {
740
- try {
741
- const raw = await client.callTool("get_screen", {
742
- projectId: projId,
743
- screenId: scrId,
744
- name: `projects/${projId}/screens/${scrId}`,
745
- });
746
- console.log(" Stitch get_screen raw:", JSON.stringify(raw)?.slice(0, 400));
747
- if (raw) {
748
- htmlUrl = raw.htmlCode?.downloadUrl || raw.html?.downloadUrl || null;
749
- imageUrl = imageUrl || raw.screenshot?.downloadUrl || null;
750
- }
751
- } catch (err) {
752
- console.error(" Stitch: get_screen failed:", err.message);
753
- }
754
- }
755
-
756
- onEvent({ type: "tool_done", id: requestId, tool: "get_screen", isError: false });
757
-
758
- if (!htmlUrl) {
759
- const msg = `Screen "${screenName}" has no HTML available. Raw screen data logged to console.\n`;
760
- onEvent({ type: "text_delta", id: requestId, delta: msg });
761
- onEvent({ type: "done", id: requestId, fullText: msg });
762
- return;
763
- }
764
-
765
- const html = await fetchUrl(htmlUrl);
766
-
767
- if (!html || html.length < 20) {
768
- const msg = `Screen "${screenName}" returned empty HTML.\n`;
769
- onEvent({ type: "text_delta", id: requestId, delta: msg });
770
- onEvent({ type: "done", id: requestId, fullText: msg });
771
- return;
772
- }
773
-
774
- if (wantsCode === "react") {
775
- // ── Code + live preview mode ──
776
- onEvent({
777
- type: "phase_start",
778
- id: requestId,
779
- phase: "Starting live preview...",
780
- });
781
-
782
- try {
783
- const port = await ensurePreviewServer();
784
- const slug = addPreview(html, screenName);
785
- const previewUrl = `http://localhost:${port}/${slug}`;
786
-
787
- const reactCode = htmlToReactComponent(html, screenName);
788
- const componentName = pascalCase(screenName) || "StitchScreen";
789
-
790
- onEvent({
791
- type: "text_delta",
792
- id: requestId,
793
- delta: `Fetched "${screenName}" from "${projectTitle}"\n` +
794
- (imageUrl ? `Screenshot: ${imageUrl}\n\n` : "\n") +
795
- `**Live Preview:** [${previewUrl}](${previewUrl})\n` +
796
- `Open in your browser to see the interactive page.\n\n` +
797
- `React component available for download:\n\n` +
798
- "```download:" + componentName + ".tsx\n" + reactCode + "\n```\n\n",
799
- });
800
- onEvent({ type: "done", id: requestId, fullText: `Live preview at ${previewUrl}` });
801
- } catch (err) {
802
- const reactCode = htmlToReactComponent(html, screenName);
803
- const componentName = pascalCase(screenName) || "StitchScreen";
804
- onEvent({
805
- type: "text_delta",
806
- id: requestId,
807
- delta: `Fetched "${screenName}" from "${projectTitle}"\n\n` +
808
- "```download:" + componentName + ".tsx\n" + reactCode + "\n```\n",
809
- });
810
- onEvent({ type: "done", id: requestId, fullText: `React component — ${reactCode.length} chars` });
811
- }
812
- } else {
813
- // ── Figma output mode: convert to native frames ──
814
- onEvent({
815
- type: "text_delta",
816
- id: requestId,
817
- delta: `Fetched "${screenName}" from "${projectTitle}" (${html.length} chars)\n` +
818
- (imageUrl ? `Screenshot: ${imageUrl}\n\n` : "\n") +
819
- `Converting to Figma frame...\n\n`,
820
- });
821
- processStitchHtml(html, "", requestId, screenName, deviceType, onEvent);
822
- }
823
- }
824
-
825
- async function handleGetScreenImage(stitchApi, client, projectHint, screenHint, requestId, onEvent) {
826
- onEvent({ type: "phase_start", id: requestId, phase: "Finding screen..." });
827
-
828
- const { projects, matched: matchedProject } = await findProject(stitchApi, projectHint);
829
- const project = matchedProject || (projects.length === 1 ? projects[0] : null);
830
-
831
- if (!project) {
832
- const names = projects.map(p => p.data?.title || p.data?.displayName || p.id).join(", ");
833
- const msg = `Which project? Your projects: ${names}\n`;
834
- onEvent({ type: "text_delta", id: requestId, delta: msg });
835
- onEvent({ type: "done", id: requestId, fullText: msg });
836
- return;
837
- }
838
-
839
- const projId = project.projectId || project.id;
840
- const { screens, matched: matchedScreen } = await findScreen(client, projId, screenHint);
841
- const screen = matchedScreen || (screens.length === 1 ? screens[0] : null);
842
-
843
- if (!screen) {
844
- const names = screens.map((s, i) => getScreenLabel(s, i + 1)).join(", ");
845
- const msg = `Which screen? Available: ${names}\n`;
846
- onEvent({ type: "text_delta", id: requestId, delta: msg });
847
- onEvent({ type: "done", id: requestId, fullText: msg });
848
- return;
849
- }
850
-
851
- const scrId = screen.screenId || screen.id;
852
- onEvent({ type: "tool_start", id: requestId, tool: "get_screen" });
853
-
854
- let imageUrl = screen.data?.screenshot?.downloadUrl || null;
855
- if (!imageUrl) {
856
- try {
857
- const raw = await client.callTool("get_screen", { projectId: projId, screenId: scrId, name: `projects/${projId}/screens/${scrId}` });
858
- imageUrl = raw?.screenshot?.downloadUrl || null;
859
- } catch {}
860
- }
861
-
862
- onEvent({ type: "tool_done", id: requestId, tool: "get_screen", isError: false });
863
-
864
- const screenName = getScreenLabel(screen, 1);
865
- const msg = imageUrl
866
- ? `Screenshot of "${screenName}":\n${imageUrl}\n`
867
- : `No screenshot available for "${screenName}".\n`;
868
-
869
- onEvent({ type: "text_delta", id: requestId, delta: msg });
870
- onEvent({ type: "done", id: requestId, fullText: msg });
871
- }
872
-
873
- // ── HTML → React conversion ─────────────────────────────────────────────────────
874
-
875
- function pascalCase(str) {
876
- return (str || "Screen")
877
- .replace(/[^a-zA-Z0-9\s]/g, " ")
878
- .split(/\s+/)
879
- .filter(Boolean)
880
- .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
881
- .join("");
882
- }
883
-
884
- /**
885
- * Convert Stitch-generated Tailwind HTML into a React functional component.
886
- * Handles: class→className, for→htmlFor, style strings→objects,
887
- * self-closing tags, inline event handlers, etc.
888
- */
889
- function htmlToReactComponent(html, screenName) {
890
- const componentName = pascalCase(screenName) || "StitchScreen";
891
-
892
- // Extract just the <body> content if it's a full HTML document
893
- let body = html;
894
- const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
895
- if (bodyMatch) body = bodyMatch[1].trim();
896
-
897
- // Extract <style> or <link> tags for reference
898
- const styles = [];
899
- html.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_, css) => { styles.push(css.trim()); });
900
-
901
- // Convert HTML attributes to JSX
902
- let jsx = body
903
- // class → className
904
- .replace(/\bclass="/g, 'className="')
905
- .replace(/\bclass='/g, "className='")
906
- // for → htmlFor
907
- .replace(/\bfor="/g, 'htmlFor="')
908
- // Self-close void elements: <img ...>, <input ...>, <br>, <hr>, etc.
909
- .replace(/<(img|input|br|hr|meta|link|source|area|embed|col|wbr)(\s[^>]*?)?\s*\/?>/gi,
910
- (_, tag, attrs) => `<${tag}${attrs || ""} />`)
911
- // tabindex → tabIndex
912
- .replace(/\btabindex=/g, "tabIndex=")
913
- // autocomplete → autoComplete
914
- .replace(/\bautocomplete=/g, "autoComplete=")
915
- // maxlength → maxLength
916
- .replace(/\bmaxlength=/g, "maxLength=")
917
- // Convert inline style="..." strings to style objects
918
- .replace(/\bstyle="([^"]*)"/g, (_, styleStr) => {
919
- const obj = styleStr.split(";").filter(Boolean).map(s => {
920
- const [prop, ...valParts] = s.split(":");
921
- if (!prop || !valParts.length) return null;
922
- const camelProp = prop.trim().replace(/-([a-z])/g, (__, c) => c.toUpperCase());
923
- const val = valParts.join(":").trim();
924
- // Numbers without units
925
- const numVal = /^-?\d+(\.\d+)?$/.test(val) ? val : `"${val}"`;
926
- return `${camelProp}: ${numVal}`;
927
- }).filter(Boolean).join(", ");
928
- return `style={{${obj}}}`;
929
- })
930
- // Remove onclick/onchange etc. (can't convert inline JS reliably)
931
- .replace(/\bon\w+="[^"]*"/g, "");
932
-
933
- // Build the component
934
- const lines = [];
935
- lines.push(`import React from "react";`);
936
- lines.push(``);
937
-
938
- // If there were inline styles, include them as a <style> in the component
939
- if (styles.length > 0) {
940
- lines.push(`const inlineStyles = \`${styles.join("\n")}\`;`);
941
- lines.push(``);
942
- }
943
-
944
- lines.push(`export default function ${componentName}() {`);
945
- lines.push(` return (`);
946
-
947
- if (styles.length > 0) {
948
- lines.push(` <>`);
949
- lines.push(` <style dangerouslySetInnerHTML={{ __html: inlineStyles }} />`);
950
- lines.push(` ${jsx}`);
951
- lines.push(` </>`);
952
- } else {
953
- // Wrap in a fragment if multiple root elements
954
- const rootTags = (jsx.match(/^<[a-zA-Z]/gm) || []).length;
955
- if (rootTags > 1) {
956
- lines.push(` <>`);
957
- lines.push(` ${jsx}`);
958
- lines.push(` </>`);
959
- } else {
960
- lines.push(` ${jsx}`);
961
- }
962
- }
963
-
964
- lines.push(` );`);
965
- lines.push(`}`);
966
-
967
- return lines.join("\n");
968
- }
969
-
970
- // ── Main runner ────────────────────────────────────────────────────────────────
971
-
972
- function runStitch({ message, requestId, apiKey, projectId, model, designContext, onEvent }) {
973
- const emitter = new EventEmitter();
974
- let aborted = false;
975
-
976
- // Auto-get access token from OAuth if not provided directly
977
- if (!apiKey && hasStitchAuth()) {
978
- // Will be fetched async inside the main flow
979
- } else if (!apiKey) {
980
- onEvent({
981
- type: "error",
982
- id: requestId,
983
- error: 'Not signed in to Stitch. Click "Sign in with Google" on the login screen to connect your Google account.',
984
- });
985
- onEvent({ type: "done", id: requestId, fullText: "" });
986
- setTimeout(() => emitter.emit("close", 1), 0);
987
- emitter.kill = () => {};
988
- return emitter;
989
- }
990
-
991
- const prompt = (message || "").trim() || "Create a simple landing page";
992
- const workspaceName = projectId || "Figma Intelligence";
993
-
994
- const deviceType = /mobile|ios|android|phone|app\s+screen/i.test(prompt)
995
- ? "MOBILE"
996
- : "DESKTOP";
997
-
998
- function abort() {
999
- aborted = true;
1000
- emitter.emit("close", null);
1001
- }
1002
-
1003
- (async () => {
1004
- let client = null;
1005
-
1006
- try {
1007
- // ── Step 1: Load SDK & connect ─────────────────────────────────────
1008
- onEvent({
1009
- type: "phase_start",
1010
- id: requestId,
1011
- phase: "Connecting to Stitch...",
1012
- });
1013
-
1014
- const { Stitch, StitchToolClient } = await loadSdk();
1015
-
1016
- // Get access token: from direct input or OAuth refresh
1017
- let accessToken = apiKey;
1018
- if (!accessToken) {
1019
- accessToken = await getStitchAccessToken();
1020
- }
1021
- if (!accessToken) {
1022
- onEvent({ type: "error", id: requestId, error: 'Stitch session expired. Click "Sign in with Google" to reconnect.' });
1023
- onEvent({ type: "done", id: requestId, fullText: "" });
1024
- return;
1025
- }
1026
-
1027
- // SDK requires accessToken + projectId for OAuth auth.
1028
- // projectId is Google Cloud project ID (for X-Goog-User-Project header).
1029
- // Auto-detect from user's GCP projects if not provided.
1030
- let gcpProjectId = projectId || process.env.GOOGLE_CLOUD_PROJECT;
1031
- if (!gcpProjectId) {
1032
- gcpProjectId = await detectGcpProject(accessToken);
1033
- }
1034
- client = new StitchToolClient({ accessToken, projectId: gcpProjectId });
1035
- await client.connect();
1036
-
1037
- if (aborted) return;
1038
-
1039
- const stitchApi = new Stitch(client);
1040
-
1041
- // ── Step 2: Detect intent ──────────────────────────────────────────
1042
- const intent = detectStitchIntent(prompt);
1043
-
1044
- switch (intent.type) {
1045
- case "list_projects":
1046
- await handleListProjects(stitchApi, requestId, onEvent);
1047
- return;
1048
-
1049
- case "list_screens":
1050
- await handleListScreens(stitchApi, client, intent.projectHint, requestId, onEvent);
1051
- return;
1052
-
1053
- case "get_screen":
1054
- await handleGetScreen(stitchApi, client, intent.projectHint, intent.screenHint, requestId, onEvent, deviceType, intent.wantsCode);
1055
- return;
1056
-
1057
- case "get_screen_image":
1058
- await handleGetScreenImage(stitchApi, client, intent.projectHint, intent.screenHint, requestId, onEvent);
1059
- return;
1060
-
1061
- // default: fall through to generation pipeline below
1062
- }
1063
-
1064
- if (aborted) return;
1065
-
1066
- // ── Generation pipeline (existing flow) ────────────────────────────
1067
-
1068
- // Find or create project
1069
- onEvent({
1070
- type: "phase_start",
1071
- id: requestId,
1072
- phase: "Setting up project...",
1073
- });
1074
-
1075
- let project;
1076
- try {
1077
- const projects = await stitchApi.projects();
1078
- const existing = projects.find(p =>
1079
- p.data?.title === workspaceName || p.data?.displayName === workspaceName
1080
- );
1081
- project = existing || await stitchApi.createProject(workspaceName);
1082
- } catch {
1083
- project = await stitchApi.createProject(workspaceName);
1084
- }
1085
-
1086
- if (aborted) return;
1087
-
1088
- // Check for design context: attached .md file takes priority over saved one
1089
- const existingDesignMd = designContext || loadDesignMd(workspaceName);
1090
- let generationPrompt = prompt;
1091
-
1092
- if (existingDesignMd) {
1093
- onEvent({
1094
- type: "phase_start",
1095
- id: requestId,
1096
- phase: designContext ? "Loading attached design context..." : "Loading design context (design.md)...",
1097
- });
1098
- generationPrompt =
1099
- `Use the following design system for visual consistency:\n\n` +
1100
- `${existingDesignMd}\n\n---\n\n` +
1101
- `Now generate: ${prompt}`;
1102
-
1103
- onEvent({
1104
- type: "text_delta",
1105
- id: requestId,
1106
- delta: designContext ? "Using attached .md design context.\n\n" : "Loaded existing design.md for visual consistency.\n\n",
1107
- });
1108
- }
1109
-
1110
- // Generate the screen
1111
- onEvent({
1112
- type: "phase_start",
1113
- id: requestId,
1114
- phase: `Generating UI with Stitch (${deviceType.toLowerCase()})...`,
1115
- });
1116
- onEvent({ type: "tool_start", id: requestId, tool: "generate_screen_from_text" });
1117
-
1118
- // Bypass SDK's project.generate() — it crashes when the API response
1119
- // structure doesn't match: raw.outputComponents[0].design.screens[0]
1120
- // throws "Cannot read properties of undefined (reading 'screens')".
1121
- // Instead, call the tool directly and handle the raw response safely.
1122
- const genRaw = await client.callTool("generate_screen_from_text", {
1123
- projectId: project.id,
1124
- prompt: generationPrompt,
1125
- deviceType,
1126
- });
1127
- stitchLog("generate raw", genRaw);
1128
-
1129
- // Extract screen data from whichever response structure the API returns.
1130
- // outputComponents is an array where some entries are design systems
1131
- // (have `designSystem` key) and others contain actual screens
1132
- // (have `design.screens` key). We need the screen, not the design system.
1133
- let screenData = null;
1134
- if (genRaw?.outputComponents) {
1135
- for (const comp of genRaw.outputComponents) {
1136
- if (comp?.design?.screens?.[0]) {
1137
- screenData = comp.design.screens[0];
1138
- break;
1139
- }
1140
- }
1141
- }
1142
- if (!screenData && genRaw?.screens?.[0]) {
1143
- screenData = genRaw.screens[0];
1144
- }
1145
- if (!screenData && (genRaw?.id || genRaw?.htmlCode)) {
1146
- screenData = genRaw;
1147
- }
1148
- stitchLog("screenData extracted", screenData);
1149
-
1150
- if (!screenData) {
1151
- onEvent({ type: "tool_done", id: requestId, tool: "generate_screen_from_text", isError: true });
1152
- const msg = "Stitch generation returned an unexpected response. Try again or use a different prompt.\n";
1153
- onEvent({ type: "text_delta", id: requestId, delta: msg });
1154
- onEvent({ type: "done", id: requestId, fullText: msg });
1155
- return;
1156
- }
1157
-
1158
- // Build a screen-like object with the same interface we use downstream
1159
- const screen = {
1160
- id: screenData.id || (screenData.name ? screenData.name.split("/screens/").pop() : null),
1161
- screenId: screenData.id || (screenData.name ? screenData.name.split("/screens/").pop() : null),
1162
- data: screenData,
1163
- getHtml: async () => {
1164
- if (screenData.htmlCode?.downloadUrl) return screenData.htmlCode.downloadUrl;
1165
- const scrId = screenData.id || (screenData.name ? screenData.name.split("/screens/").pop() : null);
1166
- stitchLog("getHtml: no cached URL, fetching screen " + scrId);
1167
-
1168
- // Try fetching, with one retry after a short delay (HTML may not be ready immediately)
1169
- for (let attempt = 0; attempt < 2; attempt++) {
1170
- try {
1171
- if (attempt > 0) await new Promise(r => setTimeout(r, 3000));
1172
- const raw = await client.callTool("get_screen", {
1173
- projectId: project.id,
1174
- screenId: scrId,
1175
- name: `projects/${project.id}/screens/${scrId}`,
1176
- });
1177
- stitchLog("get_screen raw (attempt " + attempt + ")", raw);
1178
- const url = raw?.htmlCode?.downloadUrl || raw?.html?.downloadUrl || null;
1179
- if (url) return url;
1180
- } catch (err) {
1181
- stitchLog("getHtml attempt " + attempt + " failed: " + err.message);
1182
- }
1183
- }
1184
-
1185
- // Last resort: list screens and find ours
1186
- try {
1187
- const listRaw = await client.callTool("list_screens", { projectId: project.id });
1188
- const allScreens = listRaw?.screens || listRaw?.items || (Array.isArray(listRaw) ? listRaw : []);
1189
- const ours = allScreens.find(s => s.id === scrId || (s.name && s.name.includes(scrId)));
1190
- if (ours?.htmlCode?.downloadUrl) return ours.htmlCode.downloadUrl;
1191
- stitchLog("screen found in list but no htmlCode", ours);
1192
- } catch {}
1193
-
1194
- return null;
1195
- },
1196
- };
1197
-
1198
- onEvent({ type: "tool_done", id: requestId, tool: "generate_screen_from_text", isError: false });
1199
-
1200
- if (aborted) return;
1201
-
1202
- // Extract Design DNA & save design.md (first generation only)
1203
- if (!existingDesignMd) {
1204
- onEvent({
1205
- type: "phase_start",
1206
- id: requestId,
1207
- phase: "Extracting design context (Design DNA)...",
1208
- });
1209
- onEvent({ type: "tool_start", id: requestId, tool: "extract_design_context" });
1210
-
1211
- try {
1212
- const designContext = await client.callTool("extract_design_context", {
1213
- projectId: project.id,
1214
- screenId: screen.id,
1215
- });
1216
-
1217
- onEvent({ type: "tool_done", id: requestId, tool: "extract_design_context", isError: false });
1218
-
1219
- const designMdContent = buildDesignMd(designContext, workspaceName, prompt, screen.data?.theme);
1220
- const savedPath = saveDesignMd(workspaceName, designMdContent);
1221
-
1222
- if (savedPath) {
1223
- onEvent({
1224
- type: "text_delta",
1225
- id: requestId,
1226
- delta: `Design context saved to design.md\n` +
1227
- ` Path: ${savedPath}\n` +
1228
- ` Future screens in "${workspaceName}" will use this design system.\n\n`,
1229
- });
1230
- }
1231
- } catch (err) {
1232
- onEvent({ type: "tool_done", id: requestId, tool: "extract_design_context", isError: true });
1233
- console.log(` Warning: extract_design_context failed: ${err.message}`);
1234
- onEvent({
1235
- type: "text_delta",
1236
- id: requestId,
1237
- delta: `Could not extract design context: ${err.message}\n\n`,
1238
- });
1239
- }
1240
- }
1241
-
1242
- if (aborted) return;
1243
-
1244
- // Get HTML download URL & fetch it
1245
- onEvent({
1246
- type: "phase_start",
1247
- id: requestId,
1248
- phase: "Fetching generated code...",
1249
- });
1250
- onEvent({ type: "tool_start", id: requestId, tool: "fetch_screen_code" });
1251
-
1252
- const htmlUrl = await screen.getHtml();
1253
-
1254
- onEvent({ type: "tool_done", id: requestId, tool: "fetch_screen_code", isError: false });
1255
-
1256
- if (!htmlUrl) {
1257
- onEvent({
1258
- type: "text_delta",
1259
- id: requestId,
1260
- delta: "Stitch generated a screen but returned no HTML URL. Try a different prompt.",
1261
- });
1262
- onEvent({ type: "done", id: requestId, fullText: "" });
1263
- return;
1264
- }
1265
-
1266
- const html = await fetchUrl(htmlUrl);
1267
-
1268
- if (aborted) return;
1269
-
1270
- if (!html || html.length < 20) {
1271
- onEvent({
1272
- type: "text_delta",
1273
- id: requestId,
1274
- delta: "Stitch HTML download returned empty content.",
1275
- });
1276
- onEvent({ type: "done", id: requestId, fullText: "" });
1277
- return;
1278
- }
1279
-
1280
- // Check if user wants code output (React/HTML/web) vs Figma frames
1281
- const codeIntent = detectCodeIntent(prompt.toLowerCase());
1282
-
1283
- if (codeIntent === "react") {
1284
- // ── Code + live preview mode ──
1285
- const screenName = screen.data?.title || screen.data?.displayName || "Generated Screen";
1286
-
1287
- // Start preview server and serve the HTML
1288
- onEvent({
1289
- type: "phase_start",
1290
- id: requestId,
1291
- phase: "Starting live preview...",
1292
- });
1293
-
1294
- try {
1295
- const port = await ensurePreviewServer();
1296
- const slug = addPreview(html, screenName);
1297
- const previewUrl = `http://localhost:${port}/${slug}`;
1298
-
1299
- // Convert to React component for download
1300
- const reactCode = htmlToReactComponent(html, screenName);
1301
- const componentName = pascalCase(screenName) || "StitchScreen";
1302
-
1303
- onEvent({
1304
- type: "text_delta",
1305
- id: requestId,
1306
- delta: `**Live Preview:** [${previewUrl}](${previewUrl})\n` +
1307
- `Open in your browser to see the interactive page.\n\n` +
1308
- `React component available for download:\n\n` +
1309
- "```download:" + componentName + ".tsx\n" + reactCode + "\n```\n\n" +
1310
- `**To run locally as a React app:**\n` +
1311
- `1. Install Tailwind CSS: \`npm install tailwindcss @tailwindcss/vite\`\n` +
1312
- `2. Save as \`${componentName}.tsx\`\n` +
1313
- `3. Import: \`import ${componentName} from './${componentName}'\`\n`,
1314
- });
1315
- onEvent({ type: "done", id: requestId, fullText: `Live preview at ${previewUrl}` });
1316
- } catch (err) {
1317
- // Fallback: just show the code if preview server fails
1318
- const reactCode = htmlToReactComponent(html, screenName);
1319
- const componentName = pascalCase(screenName) || "StitchScreen";
1320
- onEvent({
1321
- type: "text_delta",
1322
- id: requestId,
1323
- delta: `React component (preview server unavailable):\n\n` +
1324
- "```download:" + componentName + ".tsx\n" + reactCode + "\n```\n",
1325
- });
1326
- onEvent({ type: "done", id: requestId, fullText: `React component — ${reactCode.length} chars` });
1327
- }
1328
- } else {
1329
- // ── Figma output mode: convert to native frames ──
1330
- processStitchHtml(html, "", requestId, prompt, deviceType, onEvent);
1331
- }
1332
-
1333
- } catch (err) {
1334
- if (!aborted) {
1335
- let errMsg = err.message || String(err);
1336
-
1337
- if (errMsg.includes("401") || errMsg.includes("403") || errMsg.includes("PERMISSION") || errMsg.includes("API keys are not supported")) {
1338
- errMsg = `Stitch auth failed. This API requires an OAuth2 access token, not an API key. Get one at stitch.withgoogle.com → Settings, or run: gcloud auth print-access-token`;
1339
- } else if (errMsg.includes("429") || errMsg.includes("RATE_LIMITED")) {
1340
- errMsg = "Stitch rate limit exceeded. Free tier: 350 generations/month. Please wait and try again.";
1341
- } else if (errMsg.includes("ENOTFOUND") || errMsg.includes("ECONNREFUSED")) {
1342
- errMsg = "Cannot reach Stitch servers. Check your internet connection.";
1343
- }
1344
-
1345
- onEvent({ type: "error", id: requestId, error: `Stitch: ${errMsg}` });
1346
- onEvent({ type: "done", id: requestId, fullText: "" });
1347
- }
1348
- } finally {
1349
- if (client) {
1350
- try { await client.close(); } catch {}
1351
- }
1352
- emitter.emit("close", 0);
1353
- }
1354
- })();
1355
-
1356
- emitter.kill = abort;
1357
- return emitter;
1358
- }
1359
-
1360
- // ── Helpers ────────────────────────────────────────────────────────────────────
1361
-
1362
- function fetchUrl(url) {
1363
- return new Promise((resolve, reject) => {
1364
- const mod = url.startsWith("https") ? https : require("http");
1365
- mod.get(url, (res) => {
1366
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
1367
- fetchUrl(res.headers.location).then(resolve).catch(reject);
1368
- return;
1369
- }
1370
- if (res.statusCode !== 200) {
1371
- reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
1372
- return;
1373
- }
1374
- let data = "";
1375
- res.on("data", (chunk) => { data += chunk.toString(); });
1376
- res.on("end", () => resolve(data));
1377
- res.on("error", reject);
1378
- }).on("error", reject);
1379
- });
1380
- }
1381
-
1382
- function processStitchHtml(html, css, requestId, prompt, deviceType, onEvent) {
1383
- onEvent({
1384
- type: "phase_start",
1385
- id: requestId,
1386
- phase: "Converting to Figma frames…",
1387
- });
1388
-
1389
- let result;
1390
- try {
1391
- result = htmlToFigmaExecuteCode(html, css, prompt, deviceType || "DESKTOP");
1392
- } catch (err) {
1393
- onEvent({
1394
- type: "error",
1395
- id: requestId,
1396
- error: `HTML-to-Figma conversion failed: ${err.message}`,
1397
- });
1398
- onEvent({ type: "done", id: requestId, fullText: "" });
1399
- return;
1400
- }
1401
-
1402
- if (!result || !result.code) {
1403
- onEvent({
1404
- type: "text_delta",
1405
- id: requestId,
1406
- delta: "Could not convert Stitch output to Figma frames. The generated HTML may be too complex.",
1407
- });
1408
- onEvent({ type: "done", id: requestId, fullText: "" });
1409
- return;
1410
- }
1411
-
1412
- // Emit a single execute command with the full Figma Plugin API code
1413
- onEvent({ type: "tool_start", id: requestId, tool: "figma_execute" });
1414
- onEvent({
1415
- type: "figma_command",
1416
- id: requestId,
1417
- method: "execute",
1418
- params: { code: result.code },
1419
- });
1420
- onEvent({ type: "tool_done", id: requestId, tool: "figma_execute", isError: false });
1421
-
1422
- const summary = `Generated ${result.commandCount} Figma element(s) from Stitch for: "${prompt}"`;
1423
- onEvent({ type: "text_delta", id: requestId, delta: summary });
1424
- onEvent({ type: "done", id: requestId, fullText: summary });
1425
- }
1426
-
1427
- module.exports = { runStitch };