@agent-native/core 0.80.4 → 0.80.5

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 (292) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +21 -2
  3. package/corpus/core/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
  4. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -1
  5. package/corpus/core/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
  6. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -1
  7. package/corpus/core/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
  8. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -1
  9. package/corpus/core/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
  10. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -1
  11. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -1
  12. package/corpus/core/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
  13. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -1
  14. package/corpus/core/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
  15. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -1
  16. package/corpus/core/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
  17. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -1
  18. package/corpus/core/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
  19. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -1
  20. package/corpus/core/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
  21. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -1
  22. package/corpus/core/docs/content/real-time-collaboration.mdx +2 -2
  23. package/corpus/core/docs/content/template-design.mdx +1 -1
  24. package/corpus/core/package.json +3 -7
  25. package/corpus/core/src/action.ts +21 -0
  26. package/corpus/core/src/agent/durable-background.ts +32 -11
  27. package/corpus/core/src/agent/production-agent.ts +11 -1
  28. package/corpus/core/src/agent/run-loop-with-resume.ts +3 -1
  29. package/corpus/core/src/brand-kit/fig/index.ts +17 -37
  30. package/corpus/core/src/cli/index.ts +1 -1
  31. package/corpus/core/src/cli/mcp-config-writers.ts +187 -51
  32. package/corpus/core/src/cli/skills.ts +18 -3
  33. package/corpus/core/src/client/AssistantChat.tsx +11 -1
  34. package/corpus/core/src/client/agent-chat.ts +12 -1
  35. package/corpus/core/src/client/composer/PromptComposer.tsx +100 -41
  36. package/corpus/core/src/client/org/OrgSwitcher.tsx +1 -1
  37. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -27
  38. package/corpus/core/src/collab/presence.ts +1 -1
  39. package/corpus/core/src/mcp/connect-route.ts +34 -5
  40. package/corpus/core/src/server/action-discovery.ts +4 -0
  41. package/corpus/core/src/server/agent-chat-plugin.ts +20 -0
  42. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -0
  43. package/corpus/templates/analytics/actions/update-dashboard.ts +8 -0
  44. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
  45. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +117 -33
  46. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
  47. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -0
  48. package/corpus/templates/analytics/changelog/2026-06-27-dashboards-can-now-nest-under-a-parent-in-the-sidebar-via-a-.md +6 -0
  49. package/corpus/templates/analytics/changelog/2026-06-29-analytics-pages-now-sit-closer-under-the-header-with-tighter.md +6 -0
  50. package/corpus/templates/assets/actions/generate-image-batch.ts +3 -0
  51. package/corpus/templates/assets/actions/generate-image.ts +3 -0
  52. package/corpus/templates/assets/changelog/2026-06-29-longer-agent-generation-window.md +6 -0
  53. package/corpus/templates/assets/netlify.toml +3 -3
  54. package/corpus/templates/assets/server/plugins/agent-chat.ts +4 -1
  55. package/corpus/templates/clips/app/components/capture-install-options.tsx +7 -8
  56. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +1 -1
  57. package/corpus/templates/content/AGENTS.md +12 -0
  58. package/corpus/templates/content/app/blocks/BuilderDocsBlocks.tsx +10 -0
  59. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +1 -0
  60. package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +185 -0
  61. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -0
  62. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +19 -0
  63. package/corpus/templates/content/app/components/editor/extensions/ContentReferenceNode.tsx +82 -0
  64. package/corpus/templates/content/app/global.css +9 -0
  65. package/corpus/templates/content/app/i18n/zh-TW.ts +12 -0
  66. package/corpus/templates/content/app/i18n-data.ts +140 -0
  67. package/corpus/templates/content/changelog/2026-06-29-reusable-mdx-references-now-preview-linked-local-documents-i.md +6 -0
  68. package/corpus/templates/content/shared/builder-docs-blocks.ts +8 -0
  69. package/corpus/templates/content/shared/builder-mdx.ts +141 -4
  70. package/corpus/templates/content/shared/nfm.ts +62 -0
  71. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +10 -11
  72. package/corpus/templates/design/AGENTS.md +5 -0
  73. package/corpus/templates/design/_gitignore +7 -0
  74. package/corpus/templates/design/actions/apply-visual-edit.ts +22 -12
  75. package/corpus/templates/design/actions/create-design-system.ts +15 -4
  76. package/corpus/templates/design/actions/create-file.ts +20 -1
  77. package/corpus/templates/design/actions/delete-design-system.ts +2 -2
  78. package/corpus/templates/design/actions/duplicate-design.ts +35 -9
  79. package/corpus/templates/design/actions/generate-design.ts +146 -69
  80. package/corpus/templates/design/actions/generate-screens.ts +51 -19
  81. package/corpus/templates/design/actions/get-design-snapshot.ts +5 -1
  82. package/corpus/templates/design/actions/get-design.ts +2 -0
  83. package/corpus/templates/design/actions/import-design-project.ts +28 -9
  84. package/corpus/templates/design/actions/import-document.ts +22 -8
  85. package/corpus/templates/design/actions/index-design-system-with-builder.ts +6 -153
  86. package/corpus/templates/design/actions/navigate.ts +105 -54
  87. package/corpus/templates/design/actions/set-default-design-system.ts +17 -3
  88. package/corpus/templates/design/actions/show-design-questions.ts +2 -0
  89. package/corpus/templates/design/actions/update-design.ts +2 -1
  90. package/corpus/templates/design/actions/view-screen.ts +12 -6
  91. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +4 -4
  92. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +159 -41
  93. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +8 -4
  94. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +37 -42
  95. package/corpus/templates/design/app/components/design/EditPanel.tsx +336 -183
  96. package/corpus/templates/design/app/components/design/LayersPanel.tsx +153 -34
  97. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +135 -33
  98. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +6 -0
  99. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
  100. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +4 -10
  101. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +84 -43
  102. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +4 -4
  103. package/corpus/templates/design/app/components/design/inspector/{FigmaColorPicker.tsx → DesignColorPicker.tsx} +89 -62
  104. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +14 -26
  105. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +40 -12
  106. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -4
  107. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +13 -13
  108. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -9
  109. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +13 -4
  110. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +6 -6
  111. package/corpus/templates/design/app/components/design/inspector/{figma-icons.tsx → design-icons.tsx} +2 -2
  112. package/corpus/templates/design/app/components/design/inspector/index.ts +12 -12
  113. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +5 -2
  114. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -3
  115. package/corpus/templates/design/app/components/layout/Layout.tsx +5 -2
  116. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  117. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +167 -91
  118. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +16 -3
  119. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +15 -0
  120. package/corpus/templates/design/app/global.css +4 -0
  121. package/corpus/templates/design/app/hooks/use-design-system.ts +4 -1
  122. package/corpus/templates/design/app/hooks/use-navigation-state.ts +38 -3
  123. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +9 -2
  124. package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
  125. package/corpus/templates/design/app/i18n-data.ts +527 -0
  126. package/corpus/templates/design/app/lib/design-systems.ts +6 -4
  127. package/corpus/templates/design/app/lib/design-templates.ts +15 -3
  128. package/corpus/templates/design/app/pages/DesignEditor.tsx +641 -158
  129. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +42 -34
  130. package/corpus/templates/design/app/pages/VisualEdit.tsx +100 -0
  131. package/corpus/templates/design/app/root.tsx +42 -9
  132. package/corpus/templates/design/app/routes/visual-edit.tsx +7 -0
  133. package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +1 -1
  134. package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +1 -1
  135. package/corpus/templates/design/changelog/2026-06-28-visual-code-design-mode.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-29-design-generation-prompts-now-show-connect-ai-before-sending.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +1 -1
  138. package/corpus/templates/design/changelog/2026-06-29-fixed-design-editor-exports-and-gradient-edits-so-multi-scre.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-29-png-export-handles-modern-css-color-functions-more-reliably.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-29-png-export-now-ignores-duplicate-clicks-while-a-download-is-.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-29-public-visual-edit-and-shared-design-links-now-invite-visito.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-29-visual-edit-actions-now-reject-missing-design-targets-before.md +6 -0
  144. package/corpus/templates/design/e2e/README.md +71 -0
  145. package/corpus/templates/design/e2e/global-setup.ts +157 -0
  146. package/corpus/templates/design/e2e/helpers.ts +148 -0
  147. package/corpus/templates/design/netlify.toml +3 -4
  148. package/corpus/templates/design/package.json +5 -1
  149. package/corpus/templates/design/playwright.config.ts +51 -0
  150. package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +121 -0
  151. package/corpus/templates/design/server/handlers/uploads.ts +89 -38
  152. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +181 -0
  153. package/corpus/templates/design/server/lib/coding-handoff.ts +33 -4
  154. package/corpus/templates/design/server/lib/design-export.ts +35 -8
  155. package/corpus/templates/design/server/plugins/agent-chat.ts +4 -0
  156. package/corpus/templates/design/server/plugins/auth.ts +10 -1
  157. package/corpus/templates/design/server/routes/api/index-design-system-with-builder.post.ts +1 -0
  158. package/corpus/templates/design/shared/api.ts +2 -0
  159. package/corpus/templates/design/shared/apply-edits.ts +12 -8
  160. package/corpus/templates/design/shared/canvas-math.ts +19 -1
  161. package/corpus/templates/design/shared/code-layer.ts +87 -28
  162. package/corpus/templates/design/shared/generation-session.ts +1 -0
  163. package/corpus/templates/design/shared/resolve-tweaks.ts +4 -3
  164. package/corpus/templates/design/shared/shader-presets.ts +10 -0
  165. package/corpus/templates/design/shared/source-mode.ts +9 -0
  166. package/corpus/templates/design/vitest.config.ts +2 -1
  167. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
  168. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +1 -1
  169. package/corpus/templates/plan/AGENTS.md +1 -1
  170. package/corpus/templates/plan/actions/create-plan-design.ts +1 -1
  171. package/corpus/templates/plan/actions/validate-local-plan-source.ts +2 -10
  172. package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -0
  173. package/corpus/templates/plan/docs/commenting-ux-plan.md +2 -2
  174. package/corpus/templates/plan/server/plan-content.ts +1 -1
  175. package/corpus/templates/plan/server/ui-plan-html.ts +2 -2
  176. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +6 -153
  177. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +53 -38
  178. package/corpus/templates/slides/app/components/design-system/{fig-import-response.ts → builder-index-response.ts} +9 -6
  179. package/corpus/templates/slides/app/components/visual-editor/MultiSelectChip.tsx +1 -1
  180. package/corpus/templates/slides/changelog/2026-06-29-design-system-setup-now-indexes-uploaded-design-files-throug.md +6 -0
  181. package/corpus/templates/slides/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
  182. package/corpus/templates/slides/netlify.toml +3 -6
  183. package/corpus/templates/slides/server/handlers/index-design-system-with-builder.ts +116 -0
  184. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +179 -0
  185. package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -1
  186. package/corpus/templates/slides/server/routes/api/index-design-system-with-builder.post.ts +1 -0
  187. package/dist/action.d.ts +15 -0
  188. package/dist/action.d.ts.map +1 -1
  189. package/dist/action.js +6 -0
  190. package/dist/action.js.map +1 -1
  191. package/dist/agent/durable-background.d.ts +9 -7
  192. package/dist/agent/durable-background.d.ts.map +1 -1
  193. package/dist/agent/durable-background.js +27 -11
  194. package/dist/agent/durable-background.js.map +1 -1
  195. package/dist/agent/production-agent.d.ts +7 -0
  196. package/dist/agent/production-agent.d.ts.map +1 -1
  197. package/dist/agent/production-agent.js +4 -1
  198. package/dist/agent/production-agent.js.map +1 -1
  199. package/dist/agent/run-loop-with-resume.d.ts +2 -1
  200. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  201. package/dist/agent/run-loop-with-resume.js +2 -2
  202. package/dist/agent/run-loop-with-resume.js.map +1 -1
  203. package/dist/brand-kit/fig/index.d.ts +4 -4
  204. package/dist/brand-kit/fig/index.d.ts.map +1 -1
  205. package/dist/brand-kit/fig/index.js +14 -27
  206. package/dist/brand-kit/fig/index.js.map +1 -1
  207. package/dist/cli/index.js +1 -1
  208. package/dist/cli/index.js.map +1 -1
  209. package/dist/cli/mcp-config-writers.d.ts +15 -7
  210. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  211. package/dist/cli/mcp-config-writers.js +198 -45
  212. package/dist/cli/mcp-config-writers.js.map +1 -1
  213. package/dist/cli/skills.d.ts +1 -1
  214. package/dist/cli/skills.d.ts.map +1 -1
  215. package/dist/cli/skills.js +18 -3
  216. package/dist/cli/skills.js.map +1 -1
  217. package/dist/client/AssistantChat.d.ts.map +1 -1
  218. package/dist/client/AssistantChat.js +11 -1
  219. package/dist/client/AssistantChat.js.map +1 -1
  220. package/dist/client/agent-chat.d.ts.map +1 -1
  221. package/dist/client/agent-chat.js +15 -2
  222. package/dist/client/agent-chat.js.map +1 -1
  223. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  224. package/dist/client/composer/PromptComposer.js +35 -1
  225. package/dist/client/composer/PromptComposer.js.map +1 -1
  226. package/dist/client/org/OrgSwitcher.js +1 -1
  227. package/dist/client/org/OrgSwitcher.js.map +1 -1
  228. package/dist/client/sharing/ShareButton.d.ts +1 -1
  229. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  230. package/dist/client/sharing/ShareButton.js +2 -15
  231. package/dist/client/sharing/ShareButton.js.map +1 -1
  232. package/dist/collab/presence.d.ts +1 -1
  233. package/dist/collab/presence.js +1 -1
  234. package/dist/collab/presence.js.map +1 -1
  235. package/dist/collab/routes.d.ts +1 -1
  236. package/dist/mcp/connect-route.d.ts.map +1 -1
  237. package/dist/mcp/connect-route.js +34 -5
  238. package/dist/mcp/connect-route.js.map +1 -1
  239. package/dist/notifications/routes.d.ts +3 -3
  240. package/dist/observability/routes.d.ts +3 -3
  241. package/dist/resources/handlers.d.ts +1 -1
  242. package/dist/server/action-discovery.d.ts.map +1 -1
  243. package/dist/server/action-discovery.js +5 -0
  244. package/dist/server/action-discovery.js.map +1 -1
  245. package/dist/server/agent-chat-plugin.d.ts +6 -0
  246. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  247. package/dist/server/agent-chat-plugin.js +12 -3
  248. package/dist/server/agent-chat-plugin.js.map +1 -1
  249. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  250. package/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
  251. package/docs/content/locales/ar-SA/template-design.mdx +1 -1
  252. package/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
  253. package/docs/content/locales/de-DE/template-design.mdx +1 -1
  254. package/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
  255. package/docs/content/locales/es-ES/template-design.mdx +1 -1
  256. package/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
  257. package/docs/content/locales/fr-FR/template-design.mdx +1 -1
  258. package/docs/content/locales/hi-IN/template-design.mdx +1 -1
  259. package/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
  260. package/docs/content/locales/ja-JP/template-design.mdx +1 -1
  261. package/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
  262. package/docs/content/locales/ko-KR/template-design.mdx +1 -1
  263. package/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
  264. package/docs/content/locales/pt-BR/template-design.mdx +1 -1
  265. package/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
  266. package/docs/content/locales/zh-CN/template-design.mdx +1 -1
  267. package/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
  268. package/docs/content/locales/zh-TW/template-design.mdx +1 -1
  269. package/docs/content/real-time-collaboration.mdx +2 -2
  270. package/docs/content/template-design.mdx +1 -1
  271. package/package.json +3 -7
  272. package/corpus/core/src/brand-kit/fig/decode.ts +0 -410
  273. package/corpus/core/src/brand-kit/fig/extract-design-system.ts +0 -947
  274. package/corpus/core/src/brand-kit/fig/fig-to-html.ts +0 -1906
  275. package/corpus/templates/design/actions/import-figma.ts +0 -55
  276. package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +0 -6
  277. package/corpus/templates/design/server/handlers/import-figma-system.ts +0 -86
  278. package/corpus/templates/design/server/routes/api/import-figma-system.post.ts +0 -1
  279. package/corpus/templates/slides/server/handlers/import-figma-system.ts +0 -101
  280. package/corpus/templates/slides/server/routes/api/import-figma-system.post.ts +0 -1
  281. package/dist/brand-kit/fig/decode.d.ts +0 -33
  282. package/dist/brand-kit/fig/decode.d.ts.map +0 -1
  283. package/dist/brand-kit/fig/decode.js +0 -358
  284. package/dist/brand-kit/fig/decode.js.map +0 -1
  285. package/dist/brand-kit/fig/extract-design-system.d.ts +0 -44
  286. package/dist/brand-kit/fig/extract-design-system.d.ts.map +0 -1
  287. package/dist/brand-kit/fig/extract-design-system.js +0 -752
  288. package/dist/brand-kit/fig/extract-design-system.js.map +0 -1
  289. package/dist/brand-kit/fig/fig-to-html.d.ts +0 -246
  290. package/dist/brand-kit/fig/fig-to-html.d.ts.map +0 -1
  291. package/dist/brand-kit/fig/fig-to-html.js +0 -1506
  292. package/dist/brand-kit/fig/fig-to-html.js.map +0 -1
@@ -1,410 +0,0 @@
1
- /**
2
- * Binary decoder for Figma `.fig` files. Handles both modern fig-kiwi files
3
- * (a custom container of kiwi-schema chunks, optionally zstd/zlib compressed)
4
- * and the legacy zip-format archives. Returns the decoded document node tree,
5
- * extracted image blobs (SHA1-keyed, with magic-byte extensions), and a
6
- * best-effort thumbnail.
7
- *
8
- * Ported faithfully from the ai-services design-systems pipeline. It is a pure
9
- * function on a Buffer — no storage, queue, or job coupling.
10
- */
11
-
12
- import * as crypto from "node:crypto";
13
- import * as zlib from "node:zlib";
14
-
15
- import { decompress as zstdDecompress } from "fzstd";
16
- import {
17
- ByteBuffer,
18
- compileSchema,
19
- decodeBinarySchema,
20
- type Schema,
21
- } from "kiwi-schema";
22
- import * as pako from "pako";
23
-
24
- const MAX_DECOMPRESSED_BYTES = 512 * 1024 * 1024; // 512 MB cap per chunk to prevent compression bombs
25
- const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
26
- const FIG_KIWI_MAGIC = Buffer.from("fig-kiwi", "utf8");
27
- const ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04]);
28
- const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
29
- const JPEG_MAGIC = Buffer.from([0xff, 0xd8, 0xff]);
30
-
31
- export interface DecodedFigKiwi {
32
- version: number;
33
- schema: Buffer;
34
- document: Buffer;
35
- blobs: Buffer[];
36
- }
37
-
38
- export interface DecodedFigImage {
39
- /** SHA1 of the blob bytes — matches what the document references. */
40
- hash: string;
41
- ext: string;
42
- bytes: Buffer;
43
- }
44
-
45
- export interface DecodedFig {
46
- format: "kiwi" | "zip";
47
- version?: number;
48
- document: unknown;
49
- images: DecodedFigImage[];
50
- thumbnail: Buffer | null;
51
- }
52
-
53
- function sha1(buf: Buffer): string {
54
- return crypto.createHash("sha1").update(buf).digest("hex");
55
- }
56
-
57
- function detectImageExt(buf: Buffer): string {
58
- if (buf.length >= 8 && buf.subarray(0, 8).equals(PNG_MAGIC)) return "png";
59
- if (buf.length >= 3 && buf.subarray(0, 3).equals(JPEG_MAGIC)) return "jpg";
60
- if (
61
- buf.length >= 12 &&
62
- buf.subarray(0, 4).toString("ascii") === "RIFF" &&
63
- buf.subarray(8, 12).toString("ascii") === "WEBP"
64
- ) {
65
- return "webp";
66
- }
67
- if (buf.length >= 4 && buf.subarray(0, 4).toString("ascii") === "GIF8") {
68
- return "gif";
69
- }
70
- return "bin";
71
- }
72
-
73
- function checkDecompressedSize(buf: Buffer): Buffer {
74
- if (buf.length > MAX_DECOMPRESSED_BYTES) {
75
- throw new Error(
76
- `Decompressed chunk exceeds size limit (${buf.length} > ${MAX_DECOMPRESSED_BYTES})`,
77
- );
78
- }
79
- return buf;
80
- }
81
-
82
- function decompressChunk(buf: Buffer): Buffer {
83
- if (buf.length >= 4 && buf.subarray(0, 4).equals(ZSTD_MAGIC)) {
84
- try {
85
- return checkDecompressedSize(Buffer.from(zstdDecompress(buf)));
86
- } catch {
87
- /* fall through */
88
- }
89
- }
90
- try {
91
- return zlib.inflateRawSync(buf, {
92
- maxOutputLength: MAX_DECOMPRESSED_BYTES,
93
- });
94
- } catch (e) {
95
- if (e instanceof RangeError) throw e; // output exceeded size cap; don't fall through
96
- /* fall through for format/data errors */
97
- }
98
- try {
99
- return zlib.inflateSync(buf, { maxOutputLength: MAX_DECOMPRESSED_BYTES });
100
- } catch (e) {
101
- if (e instanceof RangeError) throw e;
102
- /* fall through */
103
- }
104
- try {
105
- return checkDecompressedSize(Buffer.from(pako.inflateRaw(buf)));
106
- } catch {
107
- /* fall through */
108
- }
109
- try {
110
- return checkDecompressedSize(Buffer.from(pako.inflate(buf)));
111
- } catch {
112
- /* fall through */
113
- }
114
- return Buffer.from(buf);
115
- }
116
-
117
- export function decodeKiwiContainer(file: Buffer): DecodedFigKiwi {
118
- if (!file.subarray(0, 8).equals(FIG_KIWI_MAGIC)) {
119
- throw new Error("Not a fig-kiwi file (missing magic header)");
120
- }
121
- if (file.length < 12) {
122
- throw new Error(
123
- "Truncated kiwi header (file too short to contain version)",
124
- );
125
- }
126
- const version = file.readUInt32LE(8);
127
- let offset = 12;
128
- const chunks: Buffer[] = [];
129
- while (offset < file.length) {
130
- if (offset + 4 > file.length) {
131
- throw new Error(`Truncated chunk header at offset ${offset}`);
132
- }
133
- const length = file.readUInt32LE(offset);
134
- offset += 4;
135
- if (offset + length > file.length) {
136
- throw new Error(
137
- `Chunk extends past end of file (offset=${offset}, length=${length}, total=${file.length})`,
138
- );
139
- }
140
- const compressed = file.subarray(offset, offset + length);
141
- offset += length;
142
- chunks.push(decompressChunk(compressed));
143
- }
144
- if (chunks.length < 2) {
145
- throw new Error(
146
- `Expected at least 2 chunks (schema + document), got ${chunks.length}`,
147
- );
148
- }
149
- return {
150
- version,
151
- schema: chunks[0]!,
152
- document: chunks[1]!,
153
- blobs: chunks.slice(2),
154
- };
155
- }
156
-
157
- interface ZipEntry {
158
- name: string;
159
- data: Buffer;
160
- }
161
-
162
- /**
163
- * Minimal zip reader: supports stored (method 0) and deflate (method 8)
164
- * entries, no encryption, no zip64. Sufficient for legacy `.fig` archives.
165
- */
166
- function readZip(file: Buffer): ZipEntry[] {
167
- const EOCD_SIG = 0x06054b50;
168
- const maxScan = Math.min(file.length, 65557);
169
- let eocdOffset = -1;
170
- for (let i = file.length - 22; i >= file.length - maxScan && i >= 0; i--) {
171
- if (file.readUInt32LE(i) === EOCD_SIG) {
172
- eocdOffset = i;
173
- break;
174
- }
175
- }
176
- if (eocdOffset < 0) throw new Error("Zip EOCD record not found");
177
-
178
- const totalEntries = file.readUInt16LE(eocdOffset + 10);
179
- const cdOffset = file.readUInt32LE(eocdOffset + 16);
180
- if (cdOffset > file.length) {
181
- throw new Error(`Central directory offset ${cdOffset} exceeds file length`);
182
- }
183
-
184
- const entries: ZipEntry[] = [];
185
- let p = cdOffset;
186
- for (let i = 0; i < totalEntries; i++) {
187
- if (p + 46 > file.length) {
188
- throw new Error(`Truncated central directory entry at offset ${p}`);
189
- }
190
- if (file.readUInt32LE(p) !== 0x02014b50) {
191
- throw new Error(`Bad central directory entry signature at ${p}`);
192
- }
193
- const compressionMethod = file.readUInt16LE(p + 10);
194
- const compressedSize = file.readUInt32LE(p + 20);
195
- const uncompressedSize = file.readUInt32LE(p + 24);
196
- const nameLen = file.readUInt16LE(p + 28);
197
- const extraLen = file.readUInt16LE(p + 30);
198
- const commentLen = file.readUInt16LE(p + 32);
199
- const localHeaderOffset = file.readUInt32LE(p + 42);
200
- const name = file.subarray(p + 46, p + 46 + nameLen).toString("utf8");
201
- p += 46 + nameLen + extraLen + commentLen;
202
-
203
- const lh = localHeaderOffset;
204
- if (lh + 30 > file.length) {
205
- throw new Error(`Local header offset ${lh} out of bounds`);
206
- }
207
- if (file.readUInt32LE(lh) !== 0x04034b50) {
208
- throw new Error(`Bad local file header signature at ${lh}`);
209
- }
210
- const lhNameLen = file.readUInt16LE(lh + 26);
211
- const lhExtraLen = file.readUInt16LE(lh + 28);
212
- const dataStart = lh + 30 + lhNameLen + lhExtraLen;
213
- if (dataStart + compressedSize > file.length) {
214
- throw new Error(`Compressed data for "${name}" extends past end of file`);
215
- }
216
- const compressed = file.subarray(dataStart, dataStart + compressedSize);
217
-
218
- let data: Buffer;
219
- if (compressionMethod === 0) {
220
- data = Buffer.from(compressed);
221
- } else if (compressionMethod === 8) {
222
- data = zlib.inflateRawSync(compressed, {
223
- maxOutputLength: MAX_DECOMPRESSED_BYTES,
224
- });
225
- } else {
226
- throw new Error(
227
- `Unsupported zip compression method ${compressionMethod} for entry "${name}"`,
228
- );
229
- }
230
- if (uncompressedSize !== 0 && data.length !== uncompressedSize) {
231
- throw new Error(
232
- `Size mismatch for "${name}": expected ${uncompressedSize}, got ${data.length}`,
233
- );
234
- }
235
- if (name.endsWith("/")) continue;
236
- entries.push({ name, data });
237
- }
238
- return entries;
239
- }
240
-
241
- function isZip(file: Buffer): boolean {
242
- return file.length >= 4 && file.subarray(0, 4).equals(ZIP_MAGIC);
243
- }
244
-
245
- // Recursively normalize a decoded kiwi document into plain, JSON-safe values.
246
- // We walk the tree directly instead of `JSON.parse(JSON.stringify(...))`:
247
- // real Figma files decode to hundreds of thousands of nodes, and serializing
248
- // the whole tree to a single string blows V8's max-string-length limit
249
- // (RangeError: Invalid string length). A direct walk has no such ceiling.
250
- function normalizeDecoded(value: unknown): unknown {
251
- if (value instanceof Uint8Array) return Buffer.from(value).toString("hex");
252
- if (typeof value === "bigint") return value.toString();
253
- if (Array.isArray(value)) {
254
- const arr = new Array(value.length);
255
- for (let i = 0; i < value.length; i++) arr[i] = normalizeDecoded(value[i]);
256
- return arr;
257
- }
258
- if (value && typeof value === "object") {
259
- const out: Record<string, unknown> = {};
260
- for (const k of Object.keys(value as Record<string, unknown>)) {
261
- const v = (value as Record<string, unknown>)[k];
262
- if (v === undefined) continue;
263
- out[k] = normalizeDecoded(v);
264
- }
265
- return out;
266
- }
267
- return value;
268
- }
269
-
270
- // Returns null on any decode failure so callers can still surface the raw
271
- // document buffer.
272
- function decodeKiwiDocument(
273
- schemaBuf: Buffer,
274
- documentBuf: Buffer,
275
- ): unknown | null {
276
- let schema: Schema;
277
- try {
278
- schema = decodeBinarySchema(schemaBuf);
279
- } catch {
280
- return null;
281
- }
282
- const rootMessage =
283
- schema.definitions.find((d) => d.name === "Message")?.name ??
284
- schema.definitions.find((d) => d.kind === "MESSAGE")?.name ??
285
- null;
286
- if (!rootMessage) return null;
287
-
288
- let compiled: Record<string, (bb: ByteBuffer) => unknown>;
289
- try {
290
- compiled = compileSchema(schema) as Record<
291
- string,
292
- (bb: ByteBuffer) => unknown
293
- >;
294
- } catch {
295
- return null;
296
- }
297
- const decodeKey = `decode${rootMessage}`;
298
- const decoder = compiled[decodeKey];
299
- if (typeof decoder !== "function") return null;
300
-
301
- try {
302
- const view = new Uint8Array(
303
- documentBuf.buffer,
304
- documentBuf.byteOffset,
305
- documentBuf.byteLength,
306
- );
307
- const bb = new ByteBuffer(view);
308
- const document = decoder.call(compiled, bb);
309
- return normalizeDecoded(document);
310
- } catch {
311
- return null;
312
- }
313
- }
314
-
315
- function collectImagesFromBlobs(blobs: Buffer[]): DecodedFigImage[] {
316
- const seen = new Map<string, DecodedFigImage>();
317
- for (const blob of blobs) {
318
- if (blob.length === 0) continue;
319
- const ext = detectImageExt(blob);
320
- if (ext === "bin") continue;
321
- const hash = sha1(blob);
322
- if (seen.has(hash)) continue;
323
- seen.set(hash, { hash, ext, bytes: blob });
324
- }
325
- return Array.from(seen.values());
326
- }
327
-
328
- function findThumbnail(documentBuf: Buffer, blobs: Buffer[]): Buffer | null {
329
- const pngBlobs = blobs
330
- .filter((b) => b.length >= 8 && b.subarray(0, 8).equals(PNG_MAGIC))
331
- .sort((a, b) => a.length - b.length);
332
- if (pngBlobs.length > 0) return pngBlobs[0]!;
333
-
334
- const idx = documentBuf.indexOf(PNG_MAGIC);
335
- if (idx >= 0) {
336
- const iend = Buffer.from([0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]);
337
- const end = documentBuf.indexOf(iend, idx);
338
- if (end > idx) return documentBuf.subarray(idx, end + iend.length);
339
- }
340
- return null;
341
- }
342
-
343
- // Handles both modern fig-kiwi files and legacy zip-format archives.
344
- // `document` is null if kiwi decoding failed.
345
- export function decodeFig(file: Buffer): DecodedFig {
346
- if (isZip(file)) {
347
- const entries = readZip(file);
348
- const canvasEntry = entries.find((e) => e.name === "canvas.fig");
349
- const imageEntries = entries.filter((e) => e.name.startsWith("images/"));
350
-
351
- let document: unknown = null;
352
- let version: number | undefined;
353
- let extraBlobs: Buffer[] = [];
354
- if (canvasEntry) {
355
- try {
356
- const inner = decodeKiwiContainer(canvasEntry.data);
357
- version = inner.version;
358
- extraBlobs = inner.blobs;
359
- document = decodeKiwiDocument(inner.schema, inner.document);
360
- } catch {
361
- /* leave document null */
362
- }
363
- }
364
-
365
- const images: DecodedFigImage[] = [];
366
- const seen = new Set<string>();
367
- for (const e of imageEntries) {
368
- const ext = detectImageExt(e.data) || "bin";
369
- if (ext === "bin") continue;
370
- const hash = sha1(e.data);
371
- if (seen.has(hash)) continue;
372
- seen.add(hash);
373
- images.push({ hash, ext, bytes: e.data });
374
- }
375
- for (const img of collectImagesFromBlobs(extraBlobs)) {
376
- if (seen.has(img.hash)) continue;
377
- seen.add(img.hash);
378
- images.push(img);
379
- }
380
-
381
- const thumbnailEntry = entries.find((e) => e.name === "thumbnail.png");
382
- return {
383
- format: "zip",
384
- version,
385
- document,
386
- images,
387
- thumbnail: thumbnailEntry?.data ?? null,
388
- };
389
- }
390
-
391
- const decoded = decodeKiwiContainer(file);
392
- const document = decodeKiwiDocument(decoded.schema, decoded.document);
393
- const images = collectImagesFromBlobs(decoded.blobs);
394
- const thumbnail = findThumbnail(decoded.document, decoded.blobs);
395
- return {
396
- format: "kiwi",
397
- version: decoded.version,
398
- document,
399
- images,
400
- thumbnail,
401
- };
402
- }
403
-
404
- export function buildImageMap(images: DecodedFigImage[]): Map<string, string> {
405
- const map = new Map<string, string>();
406
- for (const img of images) {
407
- map.set(img.hash, `${img.hash}.${img.ext}`);
408
- }
409
- return map;
410
- }