@agent-native/core 0.141.7 → 0.144.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 (328) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/corpus/README.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +19 -0
  4. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +296 -0
  5. package/corpus/templates/analytics/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  6. package/corpus/templates/analytics/.agents/skills/turn-into-skill/SKILL.md +112 -0
  7. package/corpus/templates/analytics/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  8. package/corpus/templates/analytics/AGENTS.md +35 -38
  9. package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +1 -1
  10. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +17 -22
  11. package/corpus/templates/analytics/actions/navigate.ts +4 -5
  12. package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
  13. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +44 -11
  14. package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
  15. package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
  16. package/corpus/templates/analytics/app/i18n-data.ts +680 -0
  17. package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +6 -0
  18. package/corpus/templates/analytics/app/lib/sql-query.ts +33 -6
  19. package/corpus/templates/analytics/app/pages/DataSources.tsx +4 -0
  20. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-load-reliably-during-background-data-sw.md +6 -0
  21. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-remain-responsive-while-historical-data.md +6 -0
  22. package/corpus/templates/analytics/changelog/2026-08-06-analytics-query-results-now-render-as-a-table-with-csv-downl.md +6 -0
  23. package/corpus/templates/analytics/changelog/2026-08-06-connect-public-https-custom-apis-and-hand-tested-endpoints-t.md +6 -0
  24. package/corpus/templates/analytics/changelog/2026-08-06-durable-agent-worker-memory.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-08-06-live-dashboard-panels-use-the-shared-query-timeout.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-08-06-on-demand-billing-is-available-as-a-native-dashboard-with-ta.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-08-06-visible-dashboard-names-are-unique.md +6 -0
  28. package/corpus/templates/analytics/netlify.toml +7 -0
  29. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -0
  30. package/corpus/templates/analytics/server/db/index.ts +27 -0
  31. package/corpus/templates/analytics/server/db/schema.ts +10 -0
  32. package/corpus/templates/analytics/server/lib/dashboards-store.ts +227 -60
  33. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +133 -44
  34. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
  35. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  36. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +1 -0
  37. package/corpus/templates/analytics/server/plugins/db.ts +52 -18
  38. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +28 -2
  39. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +16 -1
  40. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +296 -0
  41. package/corpus/templates/assets/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  42. package/corpus/templates/assets/.agents/skills/turn-into-skill/SKILL.md +112 -0
  43. package/corpus/templates/assets/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  44. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +296 -0
  45. package/corpus/templates/brain/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  46. package/corpus/templates/brain/.agents/skills/turn-into-skill/SKILL.md +112 -0
  47. package/corpus/templates/brain/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  48. package/corpus/templates/brain/actions/list-connection-providers.ts +24 -3
  49. package/corpus/templates/brain/app/lib/dispatch-links.ts +44 -0
  50. package/corpus/templates/brain/app/routes/sources.tsx +15 -7
  51. package/corpus/templates/brain/changelog/2026-08-06-brain-provider-setup-links-open-dispatch-correctly-in-standa.md +6 -0
  52. package/corpus/templates/brain/server/plugins/agent-chat.ts +1 -0
  53. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +26 -0
  54. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +296 -0
  55. package/corpus/templates/calendar/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  56. package/corpus/templates/calendar/.agents/skills/turn-into-skill/SKILL.md +112 -0
  57. package/corpus/templates/calendar/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  58. package/corpus/templates/calendar/AGENTS.md +11 -32
  59. package/corpus/templates/calendar/actions/update-event.ts +107 -1
  60. package/corpus/templates/calendar/app/components/calendar/EventCalendarSelect.tsx +83 -0
  61. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +57 -0
  62. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +59 -73
  63. package/corpus/templates/calendar/app/hooks/use-events.ts +4 -1
  64. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  65. package/corpus/templates/calendar/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  66. package/corpus/templates/calendar/changelog/2026-08-06-existing-events-can-move-between-connected-google-account-ca.md +6 -0
  67. package/corpus/templates/calendar/server/lib/google-calendar.ts +101 -0
  68. package/corpus/templates/calendar/server/plugins/agent-chat.ts +7 -1
  69. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  70. package/corpus/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  71. package/corpus/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  72. package/corpus/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  73. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +296 -0
  74. package/corpus/templates/clips/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  75. package/corpus/templates/clips/.agents/skills/turn-into-skill/SKILL.md +112 -0
  76. package/corpus/templates/clips/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  77. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +9 -24
  78. package/corpus/templates/clips/app/components/editor/timeline-geometry.ts +15 -0
  79. package/corpus/templates/clips/app/components/editor/waveform.tsx +4 -17
  80. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +14 -3
  81. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +28 -38
  82. package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -27
  83. package/corpus/templates/clips/changelog/2026-08-06-clips-recovers-from-stalled-recording-setup-without-requirin.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-08-06-clips-starts-faster-after-a-deploy-or-idle-period-startup-no.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-08-06-deleting-a-recording-returns-cleanly-to-the-library-and-fit-.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-08-06-hide-clip-insights-from-viewers-without-analytics-access.md +6 -0
  87. package/corpus/templates/clips/desktop/src/app.tsx +48 -20
  88. package/corpus/templates/clips/desktop/src/lib/recorder.ts +275 -74
  89. package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +84 -0
  90. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +14 -4
  91. package/corpus/templates/clips/server/plugins/db.ts +214 -26
  92. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +296 -0
  93. package/corpus/templates/content/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  94. package/corpus/templates/content/.agents/skills/turn-into-skill/SKILL.md +112 -0
  95. package/corpus/templates/content/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  96. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +296 -0
  97. package/corpus/templates/crm/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  98. package/corpus/templates/crm/.agents/skills/turn-into-skill/SKILL.md +112 -0
  99. package/corpus/templates/crm/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  100. package/corpus/templates/crm/changelog/2026-08-06-crm-starts-faster-after-a-deploy-or-idle-period-one-time-dat.md +6 -0
  101. package/corpus/templates/crm/server/plugins/db.ts +11 -8
  102. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +36 -1
  103. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +296 -0
  104. package/corpus/templates/design/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  105. package/corpus/templates/design/.agents/skills/turn-into-skill/SKILL.md +112 -0
  106. package/corpus/templates/design/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  107. package/corpus/templates/design/actions/import-from-url.ts +1 -1
  108. package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
  109. package/corpus/templates/design/app/pages/Index.tsx +6 -2
  110. package/corpus/templates/design/changelog/2026-08-05-deleting-a-design-from-the-home-page-no-longer-leaves-the-ap.md +6 -0
  111. package/corpus/templates/design/changelog/2026-08-05-renaming-a-layer-now-focuses-its-name-field-automatically.md +6 -0
  112. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  113. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +296 -0
  114. package/corpus/templates/dispatch/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  115. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/SKILL.md +112 -0
  116. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  117. package/corpus/templates/dispatch/changelog/2026-08-06-ready-workspace-apps-stay-one-click-away-in-a-compact-dispat.md +6 -0
  118. package/corpus/templates/dispatch/changelog/2026-08-06-shared-vault-values-and-management-are-now-restricted-to-wor.md +6 -0
  119. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +296 -0
  120. package/corpus/templates/factory/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  121. package/corpus/templates/factory/.agents/skills/turn-into-skill/SKILL.md +112 -0
  122. package/corpus/templates/factory/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  123. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +296 -0
  124. package/corpus/templates/forms/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  125. package/corpus/templates/forms/.agents/skills/turn-into-skill/SKILL.md +112 -0
  126. package/corpus/templates/forms/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  127. package/corpus/templates/forms/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  128. package/corpus/templates/forms/server/plugins/agent-chat.ts +4 -0
  129. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +296 -0
  130. package/corpus/templates/macros/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  131. package/corpus/templates/macros/.agents/skills/turn-into-skill/SKILL.md +112 -0
  132. package/corpus/templates/macros/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  133. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +296 -0
  134. package/corpus/templates/mail/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  135. package/corpus/templates/mail/.agents/skills/turn-into-skill/SKILL.md +112 -0
  136. package/corpus/templates/mail/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  137. package/corpus/templates/mail/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  138. package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
  139. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +296 -0
  140. package/corpus/templates/plan/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  141. package/corpus/templates/plan/.agents/skills/turn-into-skill/SKILL.md +112 -0
  142. package/corpus/templates/plan/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  143. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +48 -5
  144. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +37 -1
  145. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +6 -0
  146. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +15 -2
  147. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +29 -24
  148. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +296 -0
  149. package/corpus/templates/slides/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  150. package/corpus/templates/slides/.agents/skills/turn-into-skill/SKILL.md +112 -0
  151. package/corpus/templates/slides/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  152. package/corpus/templates/slides/AGENTS.md +7 -9
  153. package/corpus/templates/slides/_gitignore +1 -0
  154. package/corpus/templates/slides/actions/export-pptx.ts +402 -36
  155. package/corpus/templates/slides/actions/import-file.ts +46 -85
  156. package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
  157. package/corpus/templates/slides/actions/import-pptx.ts +85 -60
  158. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
  159. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +5 -1
  160. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +25 -12
  161. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +112 -57
  162. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +202 -0
  163. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +26 -14
  164. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +173 -0
  165. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +182 -141
  166. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  167. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +18 -0
  168. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +85 -0
  169. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -0
  170. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +4 -0
  171. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
  172. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  173. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
  174. package/corpus/templates/slides/app/lib/deck-preview-frame.ts +15 -0
  175. package/corpus/templates/slides/app/lib/deck-share-links.ts +9 -0
  176. package/corpus/templates/slides/app/lib/export-pptx-client.ts +89 -0
  177. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
  178. package/corpus/templates/slides/app/pages/Index.tsx +91 -1
  179. package/corpus/templates/slides/changelog/2026-08-06-clicking-the-gray-canvas-around-a-slide-clears-its-selection.md +6 -0
  180. package/corpus/templates/slides/changelog/2026-08-06-deck-thumbnails-keep-a-consistent-listing-frame-across-aspec.md +6 -0
  181. package/corpus/templates/slides/changelog/2026-08-06-deep-linked-decks-open-directly-instead-of-being-interrupted.md +6 -0
  182. package/corpus/templates/slides/changelog/2026-08-06-google-slides-exports-now-show-clear-import-steps-when-direc.md +6 -0
  183. package/corpus/templates/slides/changelog/2026-08-06-import-pdf-google-slides-and-powerpoint-decks-directly-from-.md +6 -0
  184. package/corpus/templates/slides/changelog/2026-08-06-imported-powerpoint-exports-keep-text-and-images-editable-an.md +6 -0
  185. package/corpus/templates/slides/changelog/2026-08-06-presentation-prompts-continue-adding-rendered-slides-until-t.md +6 -0
  186. package/corpus/templates/slides/changelog/2026-08-06-public-decks-now-share-read-only-presentation-links-by-defau.md +6 -0
  187. package/corpus/templates/slides/changelog/2026-08-06-slides-offers-a-direct-connect-google-button-when-a-drive-co.md +6 -0
  188. package/corpus/templates/slides/changelog/2026-08-06-slides-preserve-uploaded-pdf-layouts-accept-larger-reference.md +6 -0
  189. package/corpus/templates/slides/changelog/2026-08-06-styled-words-stay-part-of-their-text-box-instead-of-becoming.md +6 -0
  190. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +7 -20
  191. package/corpus/templates/slides/server/handlers/import/html-converter.ts +236 -13
  192. package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +67 -0
  193. package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +2 -0
  194. package/corpus/templates/slides/server/handlers/uploads.ts +7 -3
  195. package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -3
  196. package/corpus/templates/slides/server/lib/emails.ts +7 -2
  197. package/corpus/templates/slides/server/lib/google-docs-error.ts +50 -0
  198. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +1 -0
  199. package/corpus/templates/slides/server/lib/import-asset-storage.ts +127 -0
  200. package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -3
  201. package/corpus/templates/slides/server/routes/api/import-assets/[token].get.ts +70 -0
  202. package/corpus/templates/slides/shared/upload-types.ts +3 -0
  203. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +296 -0
  204. package/corpus/templates/tasks/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  205. package/corpus/templates/tasks/.agents/skills/turn-into-skill/SKILL.md +112 -0
  206. package/corpus/templates/tasks/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  207. package/dist/agent/production-agent.d.ts +17 -0
  208. package/dist/agent/production-agent.js +72 -1
  209. package/dist/cli/agent-plugin.d.ts +112 -0
  210. package/dist/cli/agent-plugin.js +1081 -0
  211. package/dist/cli/app-skill.js +39 -1
  212. package/dist/cli/code-agent-executor.d.ts +3 -0
  213. package/dist/cli/code-agent-executor.js +51 -13
  214. package/dist/cli/index.js +13 -0
  215. package/dist/cli/templates-meta.js +1 -1
  216. package/dist/client/AgentPanel.d.ts +3 -1
  217. package/dist/client/AgentPanel.js +9 -2
  218. package/dist/client/AssistantChat.d.ts +8 -0
  219. package/dist/client/AssistantChat.js +23 -8
  220. package/dist/client/NewWorkspaceAppFlow.js +1 -0
  221. package/dist/client/agent-chat-adapter.js +123 -4
  222. package/dist/client/assistant-ui-recovery.d.ts +2 -1
  223. package/dist/client/assistant-ui-recovery.js +7 -3
  224. package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
  225. package/dist/client/org/TeamPage.js +1 -1
  226. package/dist/client/require-session.js +1 -1
  227. package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
  228. package/dist/client/resources/McpConnectionSuggestion.js +27 -10
  229. package/dist/client/resources/McpIntegrationDialog.js +77 -45
  230. package/dist/client/resources/mcp-integration-catalog.d.ts +13 -1
  231. package/dist/client/resources/mcp-integration-catalog.js +255 -2
  232. package/dist/client/resources/mcp-integration-logos.js +18 -2
  233. package/dist/client/settings/AccountSettingsCard.js +2 -1
  234. package/dist/collab/routes.d.ts +1 -1
  235. package/dist/collab/struct-routes.d.ts +1 -1
  236. package/dist/db/client.d.ts +9 -0
  237. package/dist/db/client.js +51 -16
  238. package/dist/db/create-get-db.js +2 -2
  239. package/dist/db/ddl-guard.d.ts +11 -10
  240. package/dist/db/ddl-guard.js +40 -12
  241. package/dist/deploy/build.d.ts +2 -0
  242. package/dist/deploy/build.js +97 -4
  243. package/dist/deploy/workspace-deploy.js +0 -2
  244. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  245. package/dist/guards/no-env-credentials.js +9 -0
  246. package/dist/ingestion/index.d.ts +1 -1
  247. package/dist/ingestion/pptx.d.ts +68 -0
  248. package/dist/ingestion/pptx.js +660 -213
  249. package/dist/integrations/plugin.js +9 -6
  250. package/dist/localization/default-messages.d.ts +54 -0
  251. package/dist/localization/default-messages.js +57 -2
  252. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  253. package/dist/mcp-client/oauth-routes.d.ts +2 -0
  254. package/dist/mcp-client/oauth-routes.js +73 -6
  255. package/dist/notifications/routes.d.ts +1 -1
  256. package/dist/observability/routes.d.ts +3 -3
  257. package/dist/onboarding/app-profile.js +11 -0
  258. package/dist/resources/handlers.d.ts +1 -1
  259. package/dist/resources/store.js +15 -1
  260. package/dist/scripts/call-agent.js +34 -4
  261. package/dist/secrets/routes.d.ts +9 -9
  262. package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
  263. package/dist/server/agent-chat/action-filters-a2a.js +36 -1
  264. package/dist/server/agent-chat-plugin.js +29 -8
  265. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  266. package/dist/server/auth.js +47 -11
  267. package/dist/server/better-auth-instance.js +5 -3
  268. package/dist/server/core-routes-plugin.d.ts +8 -1
  269. package/dist/server/core-routes-plugin.js +28 -3
  270. package/dist/server/credential-provider.d.ts +3 -3
  271. package/dist/server/credential-provider.js +90 -4
  272. package/dist/server/onboarding-html.d.ts +2 -0
  273. package/dist/server/onboarding-html.js +115 -29
  274. package/dist/server/self-dispatch.d.ts +5 -4
  275. package/dist/server/self-dispatch.js +25 -14
  276. package/dist/server/ssr-handler.d.ts +1 -1
  277. package/dist/server/ssr-handler.js +1 -1
  278. package/dist/server/transcribe-voice.d.ts +1 -1
  279. package/dist/shared/cache-control.d.ts +26 -2
  280. package/dist/shared/cache-control.js +43 -2
  281. package/dist/shared/password-policy.d.ts +3 -0
  282. package/dist/shared/password-policy.js +3 -0
  283. package/dist/sharing/actions/set-resource-visibility.js +16 -4
  284. package/dist/sharing/registry.d.ts +14 -0
  285. package/dist/styles/agent-native.css +6 -0
  286. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  287. package/dist/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  288. package/dist/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  289. package/dist/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  290. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  291. package/dist/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  292. package/dist/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  293. package/dist/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  294. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  295. package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  296. package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  297. package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  298. package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  299. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  300. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  301. package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  302. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  303. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  304. package/dist/templates/workspace-root/AGENTS.md +7 -0
  305. package/dist/user-profile/actions/change-password.js +8 -3
  306. package/dist/user-profile/actions/set-password.js +5 -1
  307. package/dist/vite/action-types-plugin.js +59 -0
  308. package/docs/content/skills-guide.mdx +13 -0
  309. package/package.json +6 -2
  310. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  311. package/src/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  312. package/src/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  313. package/src/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  314. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  315. package/src/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  316. package/src/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  317. package/src/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  318. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  319. package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  320. package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  321. package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  322. package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  323. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  324. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  325. package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  326. package/src/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  327. package/src/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  328. package/src/templates/workspace-root/AGENTS.md +7 -0
@@ -18,6 +18,13 @@ export async function parsePptxPresentation(fileBuffer) {
18
18
  const relationships = relationshipsXml
19
19
  ? parseRelationships(parseXml(relationshipsXml))
20
20
  : new Map();
21
+ const slideMasterRelationship = [...relationships.values()].find((value) => value.type.endsWith("/slideMaster"));
22
+ const backgroundGrid = slideMasterRelationship
23
+ ? await parseMasterGrid({
24
+ zip,
25
+ target: slideMasterRelationship.target,
26
+ })
27
+ : undefined;
21
28
  const slidePaths = slideIds.flatMap((id) => {
22
29
  const relationship = relationships.get(id);
23
30
  if (!relationship)
@@ -46,61 +53,27 @@ export async function parsePptxPresentation(fileBuffer) {
46
53
  catch {
47
54
  continue;
48
55
  }
49
- const texts = [];
50
- collectTextRuns(slide, texts);
56
+ const metadata = parsePptxSlideMetadata(slide);
57
+ let elements = [];
51
58
  const images = [];
52
59
  const relationshipPath = slidePath.replace(/slides\/(slide\d+\.xml)/, "slides/_rels/$1.rels");
53
60
  const slideRelationshipsXml = await zip
54
61
  .file(relationshipPath)
55
62
  ?.async("string");
56
- if (slideRelationshipsXml) {
57
- const slideRelationships = parseRelationships(parseXml(slideRelationshipsXml));
58
- // Walk the slide's own picture shapes (in document order) rather than
59
- // every image relationship, so each image carries the placed size the
60
- // author gave it on the slide — that size is what tells a full-bleed
61
- // cover photo apart from a small inset card photo, which a flat
62
- // relationship scan has no way to know.
63
- const pictureShapes = [];
64
- collectPictureShapes(slide, pictureShapes);
65
- reorderByDocumentPosition(pictureShapes, xml);
66
- addBackgroundFillShape(slide, pictureShapes, slideWidthEmu, slideHeightEmu);
67
- for (const shape of pictureShapes) {
68
- if (!shape.embedId)
69
- continue;
70
- const relationship = slideRelationships.get(shape.embedId);
71
- if (!relationship)
72
- continue;
73
- if (!relationship.type.includes("/image") &&
74
- !/\.(png|jpe?g|gif|svg|webp|bmp|tiff?|emf|wmf)$/i.test(relationship.target)) {
75
- continue;
76
- }
77
- const imagePath = relationship.target.startsWith("/")
78
- ? relationship.target.slice(1)
79
- : relationship.target.startsWith("../")
80
- ? `ppt/${relationship.target.replace(/^\.\.\//, "")}`
81
- : `ppt/slides/${relationship.target}`;
82
- const image = zip.file(imagePath);
83
- if (!image)
84
- continue;
85
- const name = imagePath.split("/").at(-1) ?? "image";
86
- const aspectRatio = shape.widthEmu && shape.heightEmu
87
- ? shape.widthEmu / shape.heightEmu
88
- : undefined;
89
- const fullBleed = Boolean(shape.widthEmu &&
90
- shape.heightEmu &&
91
- slideWidthEmu &&
92
- slideHeightEmu &&
93
- shape.widthEmu / slideWidthEmu >= 0.85 &&
94
- shape.heightEmu / slideHeightEmu >= 0.85);
95
- images.push({
96
- data: new Uint8Array(await image.async("nodebuffer")),
97
- mimeType: imageMimeType(name),
98
- name,
99
- aspectRatio,
100
- fullBleed,
101
- });
102
- }
103
- }
63
+ const slideRelationships = slideRelationshipsXml
64
+ ? parseRelationships(parseXml(slideRelationshipsXml))
65
+ : new Map();
66
+ elements = await parseSlideElements({
67
+ xml,
68
+ parseXml,
69
+ slide,
70
+ zip,
71
+ slideRelationships,
72
+ slideWidthEmu,
73
+ slideHeightEmu,
74
+ images,
75
+ });
76
+ const texts = flattenElementText(elements);
104
77
  const number = slideNumber(slidePath);
105
78
  const notesXml = await zip
106
79
  .file(`ppt/notesSlides/notesSlide${number}.xml`)
@@ -119,8 +92,14 @@ export async function parsePptxPresentation(fileBuffer) {
119
92
  slides.push({
120
93
  texts,
121
94
  images,
95
+ elements,
96
+ widthEmu: slideWidthEmu,
97
+ heightEmu: slideHeightEmu,
98
+ backgroundColor: extractSlideBackgroundColor(slide),
99
+ ...(backgroundGrid ? { backgroundGrid } : {}),
122
100
  notes,
123
101
  layoutHint: guessLayoutHint(texts, images.length > 0),
102
+ ...metadata,
124
103
  });
125
104
  }
126
105
  const firstSlide = slides[0]?.texts ?? [];
@@ -130,6 +109,573 @@ export async function parsePptxPresentation(fileBuffer) {
130
109
  .slice(0, 200) || "Imported Presentation";
131
110
  return { title, slides, theme };
132
111
  }
112
+ /**
113
+ * Google Slides exports decorative grids as connector shapes on the slide
114
+ * master instead of as a slide background. Preserve the repeated geometry as
115
+ * metadata so the HTML renderer can reproduce it without making the lines
116
+ * editable slide objects.
117
+ */
118
+ async function parseMasterGrid(args) {
119
+ const path = args.target.startsWith("/")
120
+ ? args.target.slice(1)
121
+ : `ppt/${args.target.replace(/^\.\.\//, "")}`;
122
+ const xml = await args.zip.file(path)?.async("string");
123
+ if (!xml)
124
+ return undefined;
125
+ const connectors = xml.match(/<p:cxnSp\b[\s\S]*?<\/p:cxnSp>/gi) ?? [];
126
+ const candidates = connectors.flatMap((fragment) => {
127
+ const color = fragment.match(/<a:solidFill>\s*<a:srgbClr\s+val="([0-9a-f]{6})"/i)?.[1];
128
+ const transform = fragment.match(/<a:xfrm[^>]*>\s*<a:off\s+x="(-?\d+)"\s+y="(-?\d+)"\s*\/>\s*<a:ext\s+cx="(-?\d+)"\s+cy="(-?\d+)"/i);
129
+ const lineWidth = fragment.match(/<a:ln[^>]*\bw="(\d+)"/i)?.[1];
130
+ if (!color || !transform || !lineWidth)
131
+ return [];
132
+ return [
133
+ {
134
+ color: color.toLowerCase(),
135
+ x: Number(transform[1]),
136
+ y: Number(transform[2]),
137
+ width: Number(transform[3]),
138
+ height: Number(transform[4]),
139
+ lineWidth: Number(lineWidth),
140
+ },
141
+ ];
142
+ });
143
+ if (candidates.length < 20)
144
+ return undefined;
145
+ const colorCounts = new Map();
146
+ for (const candidate of candidates) {
147
+ colorCounts.set(candidate.color, (colorCounts.get(candidate.color) ?? 0) + 1);
148
+ }
149
+ const [color] = [...colorCounts.entries()].sort((a, b) => b[1] - a[1])[0] ?? [];
150
+ if (!color)
151
+ return undefined;
152
+ const xPositions = [
153
+ ...new Set(candidates
154
+ .filter((candidate) => candidate.color === color)
155
+ .map((candidate) => candidate.x)),
156
+ ].sort((a, b) => a - b);
157
+ const gaps = xPositions
158
+ .slice(1)
159
+ .map((value, index) => value - xPositions[index])
160
+ .filter((value) => value > 100_000);
161
+ if (gaps.length < 3)
162
+ return undefined;
163
+ gaps.sort((a, b) => a - b);
164
+ const stepXEmu = gaps[Math.floor(gaps.length / 2)];
165
+ if (!stepXEmu)
166
+ return undefined;
167
+ const offsetXEmu = xPositions.find((value) => value >= 0) ?? xPositions[0];
168
+ const lineWidthEmu = Math.max(1, Math.round(candidates
169
+ .filter((candidate) => candidate.color === color)
170
+ .reduce((sum, candidate) => sum + candidate.lineWidth, 0) /
171
+ candidates.filter((candidate) => candidate.color === color).length));
172
+ // The same repeated connector lattice is used for both axes in the Google
173
+ // export. Its horizontal phase is the master group's first repeated offset.
174
+ // Keeping the phase relative to the detected step also works for custom
175
+ // slide sizes that preserve the source grid's square-cell geometry.
176
+ const stepYEmu = stepXEmu;
177
+ const offsetYEmu = Math.round(stepYEmu * 0.9);
178
+ return {
179
+ color: `#${color}`,
180
+ stepXEmu,
181
+ stepYEmu,
182
+ offsetXEmu,
183
+ offsetYEmu,
184
+ lineWidthEmu,
185
+ };
186
+ }
187
+ const SHAPE_ELEMENT_NAMES = new Set([
188
+ "sp",
189
+ "pic",
190
+ "grpSp",
191
+ "cxnSp",
192
+ "graphicFrame",
193
+ ]);
194
+ async function parseSlideElements(args) {
195
+ const fragments = extractDirectShapeFragments(args.xml, "spTree");
196
+ const elements = [];
197
+ const context = {
198
+ originX: 0,
199
+ originY: 0,
200
+ scaleX: 1,
201
+ scaleY: 1,
202
+ rotation: 0,
203
+ };
204
+ for (const fragment of fragments) {
205
+ const parsed = await parseShapeFragment(fragment, {
206
+ ...args,
207
+ context,
208
+ });
209
+ if (parsed.length > 0)
210
+ elements.push(...parsed);
211
+ }
212
+ // Some authors use a picture fill on the slide background instead of a
213
+ // picture shape. Keep it in the same ordered scene graph at the back.
214
+ const backgroundEmbedId = extractBackgroundFillEmbedId(args.slide);
215
+ if (backgroundEmbedId) {
216
+ const backgroundRelationship = args.slideRelationships.get(backgroundEmbedId);
217
+ if (backgroundRelationship) {
218
+ const image = await loadPptxImage({
219
+ relationship: backgroundRelationship,
220
+ zip: args.zip,
221
+ slideWidthEmu: args.slideWidthEmu,
222
+ slideHeightEmu: args.slideHeightEmu,
223
+ x: 0,
224
+ y: 0,
225
+ width: args.slideWidthEmu ?? 0,
226
+ height: args.slideHeightEmu ?? 0,
227
+ });
228
+ if (image) {
229
+ args.images.unshift(image.image);
230
+ elements.unshift(image.element);
231
+ }
232
+ }
233
+ }
234
+ return elements;
235
+ }
236
+ async function parseShapeFragment(fragment, args) {
237
+ const parsed = record(args.parseXml(fragment));
238
+ if (!parsed)
239
+ return [];
240
+ const entry = [...SHAPE_ELEMENT_NAMES].find((name) => parsed[`p:${name}`] != null);
241
+ if (!entry)
242
+ return [];
243
+ const node = record(parsed[`p:${entry}`]);
244
+ if (!node)
245
+ return [];
246
+ if (entry === "grpSp") {
247
+ const groupTransform = readTransform(node, "p:grpSpPr");
248
+ const groupXfrm = record(record(node["p:grpSpPr"])?.["a:xfrm"]);
249
+ const childOffset = readPoint(groupXfrm?.["a:chOff"]);
250
+ const childExtent = readPoint(groupXfrm?.["a:chExt"]);
251
+ const groupScaleX = childExtent.x > 0 ? groupTransform.width / childExtent.x : 1;
252
+ const groupScaleY = childExtent.y > 0 ? groupTransform.height / childExtent.y : 1;
253
+ const nextContext = {
254
+ originX: args.context.originX +
255
+ args.context.scaleX * (groupTransform.x - childOffset.x * groupScaleX),
256
+ originY: args.context.originY +
257
+ args.context.scaleY * (groupTransform.y - childOffset.y * groupScaleY),
258
+ scaleX: args.context.scaleX * groupScaleX,
259
+ scaleY: args.context.scaleY * groupScaleY,
260
+ rotation: args.context.rotation + (groupTransform.rotation ?? 0),
261
+ };
262
+ const output = [];
263
+ for (const child of extractDirectShapeFragments(fragment, "grpSp")) {
264
+ output.push(...(await parseShapeFragment(child, {
265
+ ...args,
266
+ context: nextContext,
267
+ })));
268
+ }
269
+ return output;
270
+ }
271
+ const transform = applyTransform(readTransform(node, "p:spPr"), args.context);
272
+ const id = readShapeId(node);
273
+ const name = readShapeName(node);
274
+ const placeholderType = stringValue(record(record(record(node["p:nvSpPr"])?.["p:nvPr"])?.["p:ph"])?.["@_type"]);
275
+ const shapeProperties = record(node["p:spPr"]);
276
+ const text = parseTextBody(node);
277
+ const fill = parseShapeFill(shapeProperties);
278
+ const line = parseShapeLine(shapeProperties);
279
+ const shapeType = stringValue(record(shapeProperties?.["a:prstGeom"])?.["@_prst"]);
280
+ if (entry === "pic") {
281
+ const embedId = stringValue(record(record(node["p:blipFill"])?.["a:blip"])?.["@_r:embed"]);
282
+ if (!embedId)
283
+ return [];
284
+ const relationship = args.slideRelationships.get(embedId);
285
+ if (!relationship)
286
+ return [];
287
+ const image = await loadPptxImage({
288
+ relationship,
289
+ zip: args.zip,
290
+ slideWidthEmu: args.slideWidthEmu,
291
+ slideHeightEmu: args.slideHeightEmu,
292
+ x: transform.x,
293
+ y: transform.y,
294
+ width: transform.width,
295
+ height: transform.height,
296
+ crop: parseImageCrop(node),
297
+ });
298
+ if (!image)
299
+ return [];
300
+ args.images.push(image.image);
301
+ return [
302
+ {
303
+ id,
304
+ name,
305
+ kind: "image",
306
+ ...transform,
307
+ image: image.image,
308
+ },
309
+ ];
310
+ }
311
+ const hasText = text.some((paragraph) => paragraph.runs.length > 0);
312
+ if (hasText) {
313
+ return [
314
+ {
315
+ id,
316
+ name,
317
+ ...(placeholderType ? { placeholderType } : {}),
318
+ kind: "text",
319
+ ...transform,
320
+ shapeType,
321
+ ...(fill ? { fill } : {}),
322
+ ...(line ? { lineColor: line.color, lineWidth: line.width } : {}),
323
+ ...parseTextBoxProperties(node),
324
+ paragraphs: text,
325
+ },
326
+ ];
327
+ }
328
+ if (fill || line) {
329
+ return [
330
+ {
331
+ id,
332
+ name,
333
+ kind: "shape",
334
+ ...transform,
335
+ shapeType,
336
+ ...(fill ? { fill } : {}),
337
+ ...(line ? { lineColor: line.color, lineWidth: line.width } : {}),
338
+ },
339
+ ];
340
+ }
341
+ return [];
342
+ }
343
+ function extractDirectShapeFragments(xml, container) {
344
+ const containerMatch = new RegExp(`<p:${container}\\b[^>]*>`, "i").exec(xml);
345
+ if (!containerMatch)
346
+ return [];
347
+ const containerEnd = findMatchingXmlTag(xml, containerMatch.index, container);
348
+ if (containerEnd < 0)
349
+ return [];
350
+ const start = containerMatch.index + containerMatch[0].length;
351
+ const end = containerEnd;
352
+ const tagPattern = /<\/?(?:[A-Za-z_][\w.-]*:)?([A-Za-z_][\w.-]*)\b[^>]*>/g;
353
+ tagPattern.lastIndex = start;
354
+ const stack = [];
355
+ const fragments = [];
356
+ let shapeStart = -1;
357
+ let match;
358
+ while ((match = tagPattern.exec(xml)) && match.index < end) {
359
+ const token = match[0];
360
+ const localName = match[1];
361
+ const isClosing = token.startsWith("</");
362
+ const isSelfClosing = /\/\s*>$/.test(token);
363
+ if (isClosing) {
364
+ if (stack.length > 0)
365
+ stack.pop();
366
+ if (stack.length === 0 && shapeStart >= 0) {
367
+ fragments.push(xml.slice(shapeStart, match.index + token.length));
368
+ shapeStart = -1;
369
+ }
370
+ continue;
371
+ }
372
+ if (stack.length === 0 && SHAPE_ELEMENT_NAMES.has(localName)) {
373
+ shapeStart = match.index;
374
+ }
375
+ if (!isSelfClosing)
376
+ stack.push(localName);
377
+ }
378
+ return fragments;
379
+ }
380
+ function findMatchingXmlTag(xml, start, localName) {
381
+ const tagPattern = /<\/?(?:[A-Za-z_][\w.-]*:)?([A-Za-z_][\w.-]*)\b[^>]*>/g;
382
+ tagPattern.lastIndex = start;
383
+ let depth = 0;
384
+ let match;
385
+ while ((match = tagPattern.exec(xml))) {
386
+ const token = match[0];
387
+ if (match[1] !== localName || /\/\s*>$/.test(token))
388
+ continue;
389
+ if (token.startsWith("</")) {
390
+ depth -= 1;
391
+ if (depth === 0)
392
+ return match.index;
393
+ }
394
+ else {
395
+ depth += 1;
396
+ }
397
+ }
398
+ return -1;
399
+ }
400
+ function readShapeId(node) {
401
+ const cNvPr = record(record(node["p:nvSpPr"])?.["p:cNvPr"]) ??
402
+ record(record(node["p:nvPicPr"])?.["p:cNvPr"]);
403
+ return (stringValue(cNvPr?.["@_id"]) ??
404
+ `shape-${Math.random().toString(36).slice(2)}`);
405
+ }
406
+ function readShapeName(node) {
407
+ return stringValue(record(record(node["p:nvSpPr"])?.["p:cNvPr"])?.["@_name"]);
408
+ }
409
+ function readPoint(value) {
410
+ const point = record(value);
411
+ return {
412
+ x: Number(point?.["@_x"]) || 0,
413
+ y: Number(point?.["@_y"]) || 0,
414
+ };
415
+ }
416
+ function readExtent(value) {
417
+ const extent = record(value);
418
+ return {
419
+ x: Number(extent?.["@_cx"]) || 0,
420
+ y: Number(extent?.["@_cy"]) || 0,
421
+ };
422
+ }
423
+ function readTransform(node, key) {
424
+ const xfrm = record(record(node[key])?.["a:xfrm"]);
425
+ const off = readPoint(xfrm?.["a:off"]);
426
+ const ext = readExtent(xfrm?.["a:ext"]);
427
+ const rawRotation = Number(xfrm?.["@_rot"]);
428
+ return {
429
+ x: off.x,
430
+ y: off.y,
431
+ width: ext.x,
432
+ height: ext.y,
433
+ ...(Number.isFinite(rawRotation) && rawRotation !== 0
434
+ ? { rotation: rawRotation / 60000 }
435
+ : {}),
436
+ };
437
+ }
438
+ function applyTransform(transform, context) {
439
+ return {
440
+ x: context.originX + transform.x * context.scaleX,
441
+ y: context.originY + transform.y * context.scaleY,
442
+ width: transform.width * context.scaleX,
443
+ height: transform.height * context.scaleY,
444
+ ...(transform.rotation || context.rotation
445
+ ? { rotation: (transform.rotation ?? 0) + context.rotation }
446
+ : {}),
447
+ };
448
+ }
449
+ function parseTextBody(node) {
450
+ const txBody = record(node["p:txBody"]);
451
+ if (!txBody)
452
+ return [];
453
+ return asArray(txBody["a:p"]).map((rawParagraph) => {
454
+ const paragraph = record(rawParagraph);
455
+ const pPr = record(paragraph?.["a:pPr"]);
456
+ const runs = [];
457
+ for (const rawRun of asArray(paragraph?.["a:r"])) {
458
+ const run = record(rawRun);
459
+ const content = innerText(run?.["a:t"]);
460
+ if (content) {
461
+ runs.push({
462
+ content,
463
+ ...runProperties(record(run?.["a:rPr"]), {}),
464
+ });
465
+ }
466
+ }
467
+ for (const rawField of asArray(paragraph?.["a:fld"])) {
468
+ const field = record(rawField);
469
+ const content = innerText(field?.["a:t"]);
470
+ if (content) {
471
+ runs.push({
472
+ content,
473
+ ...runProperties(record(field?.["a:rPr"]), {}),
474
+ });
475
+ }
476
+ }
477
+ const bullet = record(pPr?.["a:buChar"]);
478
+ const bulletColor = parseColor(record(pPr?.["a:buClr"]));
479
+ const bulletFont = stringValue(record(pPr?.["a:buFont"])?.["@_typeface"]);
480
+ const bulletSize = Number(record(pPr?.["a:buSzPts"])?.["@_val"]);
481
+ const lineSpacing = parseParagraphSpacing(pPr?.["a:lnSpc"]);
482
+ const spaceBeforePt = parsePoints(pPr?.["a:spcBef"]);
483
+ const spaceAfterPt = parsePoints(pPr?.["a:spcAft"]);
484
+ const alignment = mapAlignment(stringValue(pPr?.["@_algn"]));
485
+ return {
486
+ runs,
487
+ ...(alignment ? { alignment } : {}),
488
+ ...(bullet?.["@_char"] && !pPr?.["a:buNone"]
489
+ ? { bulletChar: String(bullet["@_char"]) }
490
+ : {}),
491
+ ...(bulletColor ? { bulletColor } : {}),
492
+ ...(bulletFont ? { bulletFontFamily: bulletFont } : {}),
493
+ ...(Number.isFinite(bulletSize) && bulletSize > 0
494
+ ? { bulletSize: bulletSize / 100 }
495
+ : {}),
496
+ ...(Number.isFinite(Number(pPr?.["@_lvl"]))
497
+ ? { level: Number(pPr?.["@_lvl"]) }
498
+ : {}),
499
+ ...(Number.isFinite(Number(pPr?.["@_marL"]))
500
+ ? { marginLeftEmu: Number(pPr?.["@_marL"]) }
501
+ : {}),
502
+ ...(Number.isFinite(Number(pPr?.["@_indent"]))
503
+ ? { indentEmu: Number(pPr?.["@_indent"]) }
504
+ : {}),
505
+ ...(lineSpacing !== undefined ? { lineSpacing } : {}),
506
+ ...(spaceBeforePt !== undefined ? { spaceBeforePt } : {}),
507
+ ...(spaceAfterPt !== undefined ? { spaceAfterPt } : {}),
508
+ };
509
+ });
510
+ }
511
+ function parseTextBoxProperties(node) {
512
+ const bodyPr = record(record(node["p:txBody"])?.["a:bodyPr"]);
513
+ if (!bodyPr)
514
+ return {};
515
+ const anchor = stringValue(bodyPr["@_anchor"]);
516
+ return {
517
+ padding: {
518
+ left: Number(bodyPr["@_lIns"]) || 0,
519
+ right: Number(bodyPr["@_rIns"]) || 0,
520
+ top: Number(bodyPr["@_tIns"]) || 0,
521
+ bottom: Number(bodyPr["@_bIns"]) || 0,
522
+ },
523
+ ...(anchor === "ctr"
524
+ ? { verticalAlign: "middle" }
525
+ : anchor === "b"
526
+ ? { verticalAlign: "bottom" }
527
+ : { verticalAlign: "top" }),
528
+ };
529
+ }
530
+ function parseShapeFill(shapeProperties) {
531
+ if (!shapeProperties)
532
+ return undefined;
533
+ if (shapeProperties["a:noFill"] !== undefined)
534
+ return undefined;
535
+ return parseColor(record(shapeProperties["a:solidFill"]));
536
+ }
537
+ function parseShapeLine(shapeProperties) {
538
+ const line = record(shapeProperties?.["a:ln"]);
539
+ if (!line || line["a:noFill"] !== undefined)
540
+ return undefined;
541
+ const color = parseColor(record(line["a:solidFill"]));
542
+ if (!color)
543
+ return undefined;
544
+ const width = Number(line["@_w"]);
545
+ return {
546
+ color,
547
+ ...(Number.isFinite(width) && width > 0 ? { width } : {}),
548
+ };
549
+ }
550
+ function parseColor(value) {
551
+ if (!value)
552
+ return undefined;
553
+ const rgb = stringValue(record(value["a:srgbClr"])?.["@_val"]);
554
+ if (rgb)
555
+ return `#${rgb}`;
556
+ const scheme = stringValue(record(value["a:schemeClr"])?.["@_val"]);
557
+ if (!scheme)
558
+ return undefined;
559
+ const pptxDarkColor = "#000000"; // guard:allow-raw-color - PPTX dark scheme fallback
560
+ const pptxLightColor = "#ffffff"; // guard:allow-raw-color - PPTX light scheme fallback
561
+ const fallback = {
562
+ dk1: pptxDarkColor,
563
+ dk2: pptxDarkColor,
564
+ lt1: pptxLightColor,
565
+ lt2: pptxLightColor,
566
+ };
567
+ return fallback[scheme];
568
+ }
569
+ function parseParagraphSpacing(value) {
570
+ const node = record(value);
571
+ const percent = Number(record(node?.["a:spcPct"])?.["@_val"]);
572
+ if (Number.isFinite(percent) && percent > 0)
573
+ return percent / 100000;
574
+ return parsePoints(node?.["a:spcPts"]);
575
+ }
576
+ function parsePoints(value) {
577
+ const node = record(value);
578
+ const points = Number(node?.["@_val"]);
579
+ return Number.isFinite(points) && points >= 0 ? points / 100 : undefined;
580
+ }
581
+ function mapAlignment(value) {
582
+ if (value === "ctr")
583
+ return "center";
584
+ if (value === "r")
585
+ return "right";
586
+ if (value === "just")
587
+ return "justify";
588
+ if (value === "l")
589
+ return "left";
590
+ return undefined;
591
+ }
592
+ function parseImageCrop(node) {
593
+ const srcRect = record(record(node["p:blipFill"])?.["a:srcRect"]);
594
+ if (!srcRect)
595
+ return undefined;
596
+ const left = Number(srcRect["@_l"]) || 0;
597
+ const top = Number(srcRect["@_t"]) || 0;
598
+ const right = Number(srcRect["@_r"]) || 0;
599
+ const bottom = Number(srcRect["@_b"]) || 0;
600
+ return left || top || right || bottom
601
+ ? {
602
+ left: left / 100000,
603
+ top: top / 100000,
604
+ right: right / 100000,
605
+ bottom: bottom / 100000,
606
+ }
607
+ : undefined;
608
+ }
609
+ async function loadPptxImage(args) {
610
+ if (!args.relationship.type.includes("/image") &&
611
+ !/\.(png|jpe?g|gif|svg|webp|bmp|tiff?|emf|wmf)$/i.test(args.relationship.target)) {
612
+ return null;
613
+ }
614
+ const imagePath = args.relationship.target.startsWith("/")
615
+ ? args.relationship.target.slice(1)
616
+ : args.relationship.target.startsWith("../")
617
+ ? `ppt/${args.relationship.target.replace(/^\.\.\//, "")}`
618
+ : `ppt/slides/${args.relationship.target}`;
619
+ const imageFile = args.zip.file(imagePath);
620
+ if (!imageFile)
621
+ return null;
622
+ const name = imagePath.split("/").at(-1) ?? "image";
623
+ const image = {
624
+ data: new Uint8Array(await imageFile.async("nodebuffer")),
625
+ mimeType: imageMimeType(name),
626
+ name,
627
+ aspectRatio: args.width && args.height ? args.width / args.height : undefined,
628
+ fullBleed: Boolean(args.width &&
629
+ args.height &&
630
+ args.slideWidthEmu &&
631
+ args.slideHeightEmu &&
632
+ args.width / args.slideWidthEmu >= 0.85 &&
633
+ args.height / args.slideHeightEmu >= 0.85),
634
+ ...(args.crop ? { crop: args.crop } : {}),
635
+ };
636
+ return {
637
+ image,
638
+ element: {
639
+ id: `image-${name}-${args.x}-${args.y}`,
640
+ name,
641
+ kind: "image",
642
+ x: args.x,
643
+ y: args.y,
644
+ width: args.width,
645
+ height: args.height,
646
+ image,
647
+ },
648
+ };
649
+ }
650
+ function flattenElementText(elements) {
651
+ const output = [];
652
+ const textElements = elements.filter((element) => element.kind === "text" && element.paragraphs);
653
+ for (const [elementIndex, element] of textElements.entries()) {
654
+ const paragraphs = element.paragraphs ?? [];
655
+ for (const [paragraphIndex, paragraph] of paragraphs.entries()) {
656
+ output.push(...paragraph.runs);
657
+ if (paragraphIndex < paragraphs.length - 1)
658
+ output.push({ content: "\n" });
659
+ }
660
+ if (elementIndex < textElements.length - 1)
661
+ output.push({ content: "\n" });
662
+ }
663
+ return output;
664
+ }
665
+ function extractSlideBackgroundColor(value) {
666
+ const root = record(value);
667
+ const cSld = record(record(root?.["p:sld"])?.["p:cSld"] ?? root?.["p:cSld"]);
668
+ const bgPr = record(record(cSld?.["p:bg"])?.["p:bgPr"]);
669
+ return parseColor(record(bgPr?.["a:solidFill"]));
670
+ }
671
+ export function parsePptxSlideMetadata(value) {
672
+ const slide = record(value)?.["p:sld"] ?? value;
673
+ const transition = parsePptxTransition(slide);
674
+ return {
675
+ ...(transition ? { transition } : {}),
676
+ ...(detectSplitByParagraph(slide) ? { splitByParagraph: true } : {}),
677
+ };
678
+ }
133
679
  async function parseTheme(zip, parseXml) {
134
680
  const xml = await zip.file("ppt/theme/theme1.xml")?.async("string");
135
681
  if (!xml)
@@ -158,6 +704,17 @@ function collectTextRuns(value, runs, inherited = {}) {
158
704
  const node = record(value);
159
705
  if (!node)
160
706
  return;
707
+ const paragraphs = asArray(node["a:p"]);
708
+ if (paragraphs.length > 0) {
709
+ paragraphs.forEach((paragraph, index) => {
710
+ const before = runs.length;
711
+ collectTextRuns(paragraph, runs, inherited);
712
+ if (runs.length > before && index < paragraphs.length - 1) {
713
+ runs.push({ content: "\n" });
714
+ }
715
+ });
716
+ return;
717
+ }
161
718
  for (const raw of asArray(node["a:r"])) {
162
719
  const run = record(raw);
163
720
  const content = innerText(run?.["a:t"]);
@@ -175,158 +732,60 @@ function collectTextRuns(value, runs, inherited = {}) {
175
732
  for (const [key, child] of Object.entries(node)) {
176
733
  if (key.startsWith("@_") || key === "a:r" || key === "a:t")
177
734
  continue;
178
- for (const item of asArray(child))
735
+ const items = asArray(child);
736
+ items.forEach((item, index) => {
737
+ const before = runs.length;
179
738
  collectTextRuns(item, runs, inherited);
180
- }
181
- }
182
- /**
183
- * Recursively find every embedded picture in a slide, in document order:
184
- * plain `p:pic` shapes, and `p:sp` autoshapes whose fill is a picture
185
- * (common for "photo cutout" shapes and full-bleed decorative rectangles).
186
- * A group shape (`p:grpSp`) defines its own local coordinate space for its
187
- * children (`chOff`/`chExt`) that can be scaled arbitrarily relative to the
188
- * group's own placed size on the slide, so each level of nesting multiplies
189
- * a running scale factor into the child sizes below it — without that, a
190
- * picture inside a resized group would report its unscaled design-time
191
- * size instead of how large it actually appears on the slide.
192
- */
193
- function collectPictureShapes(value, out, scaleX = 1, scaleY = 1) {
194
- const node = record(value);
195
- if (!node)
196
- return;
197
- for (const [key, child] of Object.entries(node)) {
198
- if (key.startsWith("@_"))
199
- continue;
200
- if (key === "p:pic") {
201
- for (const picNode of asArray(child)) {
202
- const pic = record(picNode);
203
- const blip = record(record(pic?.["p:blipFill"])?.["a:blip"]);
204
- out.push(scaledShape(stringValue(blip?.["@_r:embed"]), extFromSpPr(pic), scaleX, scaleY));
205
- }
206
- continue;
207
- }
208
- if (key === "p:sp") {
209
- for (const spNode of asArray(child)) {
210
- const sp = record(spNode);
211
- const blip = record(record(record(sp?.["p:spPr"])?.["a:blipFill"])?.["a:blip"]);
212
- const embedId = stringValue(blip?.["@_r:embed"]);
213
- if (embedId) {
214
- out.push(scaledShape(embedId, extFromSpPr(sp), scaleX, scaleY));
215
- }
216
- }
217
- continue;
218
- }
219
- if (key === "p:grpSp") {
220
- for (const groupNode of asArray(child)) {
221
- const scale = groupChildScale(record(groupNode), scaleX, scaleY);
222
- collectPictureShapes(groupNode, out, scale.x, scale.y);
739
+ if (key === "p:sp" && index < items.length - 1 && runs.length > before) {
740
+ runs.push({ content: "\n" });
223
741
  }
224
- continue;
225
- }
226
- for (const item of asArray(child)) {
227
- collectPictureShapes(item, out, scaleX, scaleY);
228
- }
742
+ });
229
743
  }
230
744
  }
231
- /** Whether an `a:xfrm` `rot` value (60,000ths of a degree) is an odd multiple of 90° — a 90/270 turn that swaps effective width and height. */
232
- function isOddQuarterTurn(rot) {
233
- if (!Number.isFinite(rot))
234
- return false;
235
- const quarterTurns = Math.round(rot / 60000 / 90);
236
- return ((quarterTurns % 2) + 2) % 2 === 1;
237
- }
238
- /**
239
- * A group's `chExt` is the coordinate space its children are authored in;
240
- * `ext` is how large the group actually renders — the ratio between them is
241
- * the extra scale nested children need on top of their own declared size.
242
- * When the group itself is rotated a 90/270-degree turn, that ratio applies
243
- * to the perpendicular axis on the slide, so the X/Y scale factors need
244
- * swapping the same way a rotated picture's own width/height do.
245
- */
246
- function groupChildScale(groupNode, parentScaleX, parentScaleY) {
247
- const xfrm = record(record(groupNode?.["p:grpSpPr"])?.["a:xfrm"]);
248
- const ext = record(xfrm?.["a:ext"]);
249
- const chExt = record(xfrm?.["a:chExt"]);
250
- const extCx = Number(ext?.["@_cx"]);
251
- const extCy = Number(ext?.["@_cy"]);
252
- const chExtCx = Number(chExt?.["@_cx"]);
253
- const chExtCy = Number(chExt?.["@_cy"]);
254
- let groupScaleX = Number.isFinite(extCx) && Number.isFinite(chExtCx) && chExtCx > 0
255
- ? extCx / chExtCx
256
- : 1;
257
- let groupScaleY = Number.isFinite(extCy) && Number.isFinite(chExtCy) && chExtCy > 0
258
- ? extCy / chExtCy
259
- : 1;
260
- if (isOddQuarterTurn(Number(xfrm?.["@_rot"]))) {
261
- [groupScaleX, groupScaleY] = [groupScaleY, groupScaleX];
745
+ const PPTX_TRANSITION_MAP = {
746
+ "p:fade": "fade",
747
+ "p:zoom": "zoom",
748
+ "p:push": "slide",
749
+ "p:wipe": "slide",
750
+ "p:split": "slide",
751
+ "p:cut": "instant",
752
+ };
753
+ function parsePptxTransition(value) {
754
+ const node = record(value);
755
+ const transition = record(node?.["p:transition"]);
756
+ if (!transition)
757
+ return undefined;
758
+ for (const key of Object.keys(transition)) {
759
+ const mapped = PPTX_TRANSITION_MAP[key];
760
+ if (mapped)
761
+ return mapped;
262
762
  }
263
- return { x: parentScaleX * groupScaleX, y: parentScaleY * groupScaleY };
763
+ return undefined;
264
764
  }
265
- /**
266
- * `a:xfrm/a:ext` always stores the shape's *unrotated* design-time width and
267
- * height — `a:xfrm/@rot` (in 60,000ths of a degree) rotates it around its
268
- * own center afterward. For a 90/270-degree turn, the shape's effective
269
- * on-slide footprint has its width and height swapped relative to that
270
- * declared size, so a rotated full-bleed photo can otherwise get treated as
271
- * a small inset, or a portrait image get an inverted aspect ratio.
272
- */
273
- function extFromSpPr(shapeNode) {
274
- const xfrm = record(record(shapeNode?.["p:spPr"])?.["a:xfrm"]);
275
- const ext = record(xfrm?.["a:ext"]);
276
- const cx = Number(ext?.["@_cx"]);
277
- const cy = Number(ext?.["@_cy"]);
278
- if (!Number.isFinite(cx) || cx <= 0 || !Number.isFinite(cy) || cy <= 0) {
279
- return { cx: undefined, cy: undefined };
765
+ function detectSplitByParagraph(value) {
766
+ let clickParagraphRanges = 0;
767
+ walk(value, false);
768
+ return clickParagraphRanges > 1;
769
+ function walk(nodeValue, clickContext) {
770
+ const node = record(nodeValue);
771
+ if (!node)
772
+ return;
773
+ const nodeType = stringValue(node["@_nodeType"]);
774
+ const event = stringValue(node["@_evt"]);
775
+ const nextClickContext = clickContext ||
776
+ nodeType === "clickEffect" ||
777
+ nodeType === "clickPar" ||
778
+ event === "onClick";
779
+ if (nextClickContext) {
780
+ clickParagraphRanges += asArray(node["p:pRg"]).length;
781
+ }
782
+ for (const [key, child] of Object.entries(node)) {
783
+ if (key.startsWith("@_") || key === "p:pRg")
784
+ continue;
785
+ for (const item of asArray(child))
786
+ walk(item, nextClickContext);
787
+ }
280
788
  }
281
- return isOddQuarterTurn(Number(xfrm?.["@_rot"]))
282
- ? { cx: cy, cy: cx }
283
- : { cx, cy };
284
- }
285
- function scaledShape(embedId, size, scaleX, scaleY) {
286
- return {
287
- embedId,
288
- widthEmu: size.cx !== undefined ? size.cx * scaleX : undefined,
289
- heightEmu: size.cy !== undefined ? size.cy * scaleY : undefined,
290
- };
291
- }
292
- /**
293
- * fast-xml-parser groups sibling elements by tag name, so a slide whose
294
- * picture-bearing shapes alternate types on the page (e.g. `p:sp`, `p:pic`,
295
- * `p:sp`) comes out of `collectPictureShapes` re-sorted by tag rather than
296
- * by the order they actually appear — every `p:sp` gets visited before any
297
- * `p:pic`, even if a `p:pic` came first in the file. Re-derive the true
298
- * order from the raw XML text instead: `<a:blip r:embed="...">` occurrences
299
- * appear in exactly document order regardless of nesting, so they can be
300
- * used to restore the author's original front-to-back ordering (which
301
- * matters because downstream rendering treats the first image as primary).
302
- */
303
- function reorderByDocumentPosition(shapes, xml) {
304
- const order = [];
305
- const blipPattern = /<a:blip\b[^>]*\br:embed=(?:"([^"]+)"|'([^']+)')/g;
306
- let match;
307
- while ((match = blipPattern.exec(xml)))
308
- order.push(match[1] ?? match[2]);
309
- // The same relationship id can legitimately be reused across multiple
310
- // placements (e.g. a repeated icon), so looking up a single fixed index
311
- // per id would assign every reused shape the position of its first XML
312
- // occurrence. Instead give each id a queue of its occurrence positions
313
- // and consume one per shape, in the order shapes were collected.
314
- const occurrences = new Map();
315
- order.forEach((embedId, index) => {
316
- const queue = occurrences.get(embedId);
317
- if (queue)
318
- queue.push(index);
319
- else
320
- occurrences.set(embedId, [index]);
321
- });
322
- const positions = shapes.map((shape) => {
323
- const queue = shape.embedId ? occurrences.get(shape.embedId) : undefined;
324
- return queue && queue.length > 0 ? queue.shift() : -1;
325
- });
326
- const sortedIndices = shapes.map((_, i) => i);
327
- sortedIndices.sort((a, b) => positions[a] - positions[b]);
328
- const sorted = sortedIndices.map((i) => shapes[i]);
329
- shapes.splice(0, shapes.length, ...sorted);
330
789
  }
331
790
  /** Read the embed relationship id of a slide's background picture fill (`p:cSld/p:bg/p:bgPr/a:blipFill/a:blip`), if any. */
332
791
  function extractBackgroundFillEmbedId(slide) {
@@ -336,29 +795,14 @@ function extractBackgroundFillEmbedId(slide) {
336
795
  const blip = record(record(bgPr?.["a:blipFill"])?.["a:blip"]);
337
796
  return stringValue(blip?.["@_r:embed"]);
338
797
  }
339
- /**
340
- * Add the slide's background picture fill, if any, as a synthetic
341
- * full-slide-sized shape. It's unshifted to the front rather than appended:
342
- * downstream rendering only ever uses the first image in the list, and a
343
- * background fill is the slide's base layer — the primary visual — so a
344
- * smaller foreground picture (a logo, an icon) must not bump it out of that
345
- * slot.
346
- */
347
- function addBackgroundFillShape(slide, pictureShapes, slideWidthEmu, slideHeightEmu) {
348
- const embedId = extractBackgroundFillEmbedId(slide);
349
- if (!embedId)
350
- return;
351
- pictureShapes.unshift({
352
- embedId,
353
- widthEmu: slideWidthEmu,
354
- heightEmu: slideHeightEmu,
355
- });
356
- }
357
798
  function runProperties(value, inherited) {
358
799
  if (!value)
359
800
  return inherited;
360
801
  const size = Number(value["@_sz"]);
361
802
  const rgb = stringValue(record(record(value["a:solidFill"])?.["a:srgbClr"])?.["@_val"]);
803
+ const fontFamily = stringValue(record(value["a:latin"])?.["@_typeface"]) ??
804
+ stringValue(record(value["a:ea"])?.["@_typeface"]) ??
805
+ stringValue(record(value["a:cs"])?.["@_typeface"]);
362
806
  return {
363
807
  ...inherited,
364
808
  ...(value["@_b"] === "1" || value["@_b"] === 1 || value["@_b"] === true
@@ -369,6 +813,8 @@ function runProperties(value, inherited) {
369
813
  : {}),
370
814
  ...(Number.isFinite(size) && size > 0 ? { fontSize: size / 100 } : {}),
371
815
  ...(rgb ? { color: `#${rgb}` } : {}),
816
+ ...(fontFamily ? { fontFamily } : {}),
817
+ ...(value["@_u"] && value["@_u"] !== "none" ? { underline: true } : {}),
372
818
  };
373
819
  }
374
820
  function parseRelationships(value) {
@@ -422,6 +868,7 @@ async function loadPptxDependencies() {
422
868
  const parser = new xmlModule.XMLParser({
423
869
  ignoreAttributes: false,
424
870
  attributeNamePrefix: "@_",
871
+ trimValues: false,
425
872
  });
426
873
  return {
427
874
  loadZip: (data) => zipModule.default.loadAsync(data),