@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
@@ -15,6 +15,7 @@ import { z } from "zod";
15
15
 
16
16
  import { getDb, schema } from "../server/db/index.js";
17
17
  import { notifyClients } from "../server/handlers/decks.js";
18
+ import { uploadPptxSlideImages } from "../server/handlers/import/pptx-assets.js";
18
19
  import { upsertBuilderProxyDesignSystem } from "../server/lib/builder-design-system-proxy.js";
19
20
  import { setupPdfParse } from "../server/lib/pdf-parse-setup.js";
20
21
  import {
@@ -32,7 +33,7 @@ export default defineAction({
32
33
  "Import a file (PPTX, DOCX, PDF, FIG) and extract content for creating slides or slide design systems. " +
33
34
  "For PPTX files, returns parsed slides with text and layout info ready for conversion. " +
34
35
  "For DOCX files, returns structured sections extracted from the document. " +
35
- "For PDF files, returns extracted text organized by page. " +
36
+ "For PDF files, returns extracted text organized by page, or source-faithful page-image slides when importIntoDeck is true. " +
36
37
  "For Figma .fig files, requires Builder.io (free tier available) and starts Builder design-system indexing; the returned Builder job/design-system ids are the source of truth. " +
37
38
  "The agent can then use the extracted content to create a deck via create-deck or add-slide, or tell the user where Builder is indexing the design system.",
38
39
  schema: z.object({
@@ -53,7 +54,7 @@ export default defineAction({
53
54
  .optional()
54
55
  .default(false)
55
56
  .describe(
56
- "If true, append slides converted from the file to the end of deckId's existing slides.",
57
+ "If true, append slides to deckId. PDF pages are imported as source-faithful images that preserve their original layout and aspect ratio; use the default false when editable extracted source text is needed.",
57
58
  ),
58
59
  maxChars: z.coerce
59
60
  .number()
@@ -176,6 +177,8 @@ export default defineAction({
176
177
  imageNames: slide.images.map((img) => img.name),
177
178
  notes: slide.notes,
178
179
  layoutHint: slide.layoutHint,
180
+ transition: slide.transition,
181
+ splitByParagraph: slide.splitByParagraph,
179
182
  })),
180
183
  theme: presentation.theme,
181
184
  deckId,
@@ -339,7 +342,7 @@ async function importPdfPagesAsFullBleedSlides(args: {
339
342
  canvasFactory: object;
340
343
  }) {
341
344
  const { fileBuffer, title, deckId, PDFParse, canvasFactory } = args;
342
- const { buildFullBleedImageSlideHtml, convertSectionsToSlides } =
345
+ const { buildFullPageImageSlideHtml, convertSectionsToSlides } =
343
346
  await import("../server/handlers/import/html-converter.js");
344
347
 
345
348
  const pdf = new PDFParse({
@@ -347,31 +350,26 @@ async function importPdfPagesAsFullBleedSlides(args: {
347
350
  CanvasFactory: canvasFactory,
348
351
  });
349
352
  let pages: { num: number; text: string }[];
350
- let imagesByPage: Map<
353
+ let screenshotsByPage: Map<
351
354
  number,
352
355
  { data: Uint8Array; width: number; height: number }
353
356
  >;
354
357
  try {
355
358
  pages = normalizePdfPages(await pdf.getText());
356
- // Reuse the page's own embedded photo rather than rasterizing the whole
357
- // page: headless canvas text rendering depends on the PDF's embedded
358
- // fonts resolving in this runtime, which is unreliable, so real
359
- // extracted text is drawn as HTML below instead of trusting the render.
360
- const imageResult = await pdf.getImage({
359
+ // Rasterize the complete page so positioned text, vector shapes, logos,
360
+ // photos, and the source page proportions survive the import together.
361
+ const screenshotResult = await pdf.getScreenshot({
362
+ desiredWidth: 1600,
361
363
  imageBuffer: true,
362
364
  imageDataUrl: false,
363
365
  });
364
- imagesByPage = new Map();
365
- for (const page of imageResult.pages) {
366
- const largest = page.images.reduce<
367
- { data: Uint8Array; width: number; height: number } | undefined
368
- >((best, image) => {
369
- if (!best || image.width * image.height > best.width * best.height) {
370
- return { data: image.data, width: image.width, height: image.height };
371
- }
372
- return best;
373
- }, undefined);
374
- if (largest) imagesByPage.set(page.pageNumber, largest);
366
+ screenshotsByPage = new Map();
367
+ for (const page of screenshotResult.pages) {
368
+ screenshotsByPage.set(page.pageNumber, {
369
+ data: page.data,
370
+ width: page.width,
371
+ height: page.height,
372
+ });
375
373
  }
376
374
  } finally {
377
375
  await pdf.destroy();
@@ -379,12 +377,12 @@ async function importPdfPagesAsFullBleedSlides(args: {
379
377
 
380
378
  // Source decks (e.g. Instagram carousel exports) are commonly portrait or
381
379
  // square, not the deck editor's 16:9 default — match the canvas to the
382
- // first embedded photo's own proportions instead of stretching/cropping it.
383
- const firstImage = pages
384
- .map((page) => imagesByPage.get(page.num))
380
+ // first rendered page's own proportions instead of stretching/cropping it.
381
+ const firstScreenshot = pages
382
+ .map((page) => screenshotsByPage.get(page.num))
385
383
  .find((image): image is NonNullable<typeof image> => Boolean(image));
386
- const aspectRatio = firstImage
387
- ? nearestAspectRatio(firstImage.width, firstImage.height)
384
+ const aspectRatio = firstScreenshot
385
+ ? nearestAspectRatio(firstScreenshot.width, firstScreenshot.height)
388
386
  : undefined;
389
387
 
390
388
  const ownerEmail = getRequestUserEmail();
@@ -394,9 +392,9 @@ async function importPdfPagesAsFullBleedSlides(args: {
394
392
  .split(/\r?\n/)
395
393
  .map((line) => line.trim())
396
394
  .filter(Boolean);
397
- const backgroundImage = imagesByPage.get(page.num);
395
+ const screenshot = screenshotsByPage.get(page.num);
398
396
 
399
- if (!backgroundImage) {
397
+ if (!screenshot) {
400
398
  const [content] = convertSectionsToSlides([
401
399
  { heading: lines[0] ?? `Page ${page.num}`, content: page.text },
402
400
  ]);
@@ -409,7 +407,7 @@ async function importPdfPagesAsFullBleedSlides(args: {
409
407
  }
410
408
 
411
409
  const uploadResult = await uploadFile({
412
- data: Buffer.from(backgroundImage.data),
410
+ data: Buffer.from(screenshot.data),
413
411
  filename: `slide-import-${Date.now()}-p${page.num}.png`,
414
412
  mimeType: "image/png",
415
413
  ownerEmail: ownerEmail ?? undefined,
@@ -420,22 +418,9 @@ async function importPdfPagesAsFullBleedSlides(args: {
420
418
  "File storage is not configured. Connect Builder.io (free tier available) or another upload provider before importing PDF slides.",
421
419
  );
422
420
  }
423
- // pdf-parse breaks wrapped text across lines with no way to tell a
424
- // wrapped title from a heading followed by a body paragraph. Treat a
425
- // couple of short lines as one wrapped title (join them so the
426
- // sentence isn't cut off); three or more lines as a heading plus body
427
- // text, rendered with distinct sizes so they don't collapse into one
428
- // flat paragraph.
429
- const heading =
430
- lines.length > 2 ? lines[0] : lines.join(" ") || undefined;
431
- const subtitle = lines.length > 2 ? lines.slice(1).join(" ") : undefined;
432
421
  return {
433
422
  id: newSlideId(),
434
- content: buildFullBleedImageSlideHtml(
435
- uploadResult.url,
436
- heading,
437
- subtitle,
438
- ),
423
+ content: buildFullPageImageSlideHtml(uploadResult.url),
439
424
  layout: "full-image",
440
425
  notes: page.text,
441
426
  };
@@ -459,65 +444,39 @@ function newSlideId(): string {
459
444
  return `slide-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
460
445
  }
461
446
 
462
- // EMF/WMF (Windows metafiles) and TIFF are valid PPTX embed formats but
463
- // browsers can't render them in an <img> tag — uploading and linking one
464
- // would just produce a broken image icon.
465
- const PPTX_BROWSER_RENDERABLE_IMAGE_MIME_TYPES = new Set([
466
- "image/png",
467
- "image/jpeg",
468
- "image/gif",
469
- "image/webp",
470
- "image/svg+xml",
471
- "image/bmp",
472
- ]);
473
-
474
447
  async function buildPptxSlide(
475
448
  slide: import("../server/handlers/import/pptx-parser.js").ParsedSlide,
476
449
  slideIndex: number,
477
450
  ownerEmail: string,
478
451
  themeFont: string | undefined,
479
452
  ): Promise<{
480
- slide: { id: string; content: string; layout: string; notes?: string };
453
+ slide: {
454
+ id: string;
455
+ content: string;
456
+ layout: string;
457
+ notes?: string;
458
+ transition?: "instant" | "none" | "fade" | "slide" | "zoom";
459
+ splitByParagraph?: boolean;
460
+ };
481
461
  imageSkippedCount: number;
482
462
  }> {
483
463
  const { convertToSlideHtml } =
484
464
  await import("../server/handlers/import/html-converter.js");
485
- const image = slide.images[0];
486
- const uploadable =
487
- image && PPTX_BROWSER_RENDERABLE_IMAGE_MIME_TYPES.has(image.mimeType)
488
- ? image
489
- : undefined;
490
- const imageUrl = uploadable
491
- ? await uploadFile({
492
- data: Buffer.from(uploadable.data),
493
- filename:
494
- "pptx-import-" +
495
- Date.now() +
496
- "-s" +
497
- slideIndex +
498
- "-" +
499
- uploadable.name,
500
- mimeType: uploadable.mimeType,
501
- ownerEmail,
502
- recordAsset: false,
503
- })
504
- .then((result) => result?.url)
505
- // A single slide's upload failing (network/API/rate-limit)
506
- // shouldn't abort the whole deck replacement — fall back to a
507
- // placeholder like an unsupported format would.
508
- .catch(() => undefined)
509
- : undefined;
465
+ const uploadedImages = await uploadPptxSlideImages({
466
+ slide,
467
+ slideIndex,
468
+ ownerEmail,
469
+ });
510
470
  return {
511
471
  slide: {
512
472
  id: newSlideId(),
513
- content: convertToSlideHtml(slide, imageUrl, themeFont),
473
+ content: convertToSlideHtml(slide, uploadedImages.urls, themeFont),
514
474
  layout: slide.layoutHint ?? "content",
515
475
  notes: slide.notes,
476
+ ...(slide.transition ? { transition: slide.transition } : {}),
477
+ ...(slide.splitByParagraph ? { splitByParagraph: true } : {}),
516
478
  },
517
- // Only the first image on a slide is ever uploaded, so every other
518
- // image on that slide is unconditionally dropped too — not just the
519
- // first one when it's unsupported.
520
- imageSkippedCount: Math.max(0, slide.images.length - (imageUrl ? 1 : 0)),
479
+ imageSkippedCount: uploadedImages.imageSkippedCount,
521
480
  };
522
481
  }
523
482
 
@@ -604,6 +563,8 @@ async function appendDeckSlides(
604
563
  content: string;
605
564
  layout: string;
606
565
  notes?: string;
566
+ transition?: "instant" | "none" | "fade" | "slide" | "zoom";
567
+ splitByParagraph?: boolean;
607
568
  }>,
608
569
  source: string,
609
570
  aspectRatio?: AspectRatio,
@@ -1,10 +1,19 @@
1
1
  import { defineAction } from "@agent-native/core";
2
+ import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
2
3
  import { buildDeepLink } from "@agent-native/core/server";
3
4
  import { getRequestUserEmail } from "@agent-native/core/server/request-context";
5
+ import pLimit from "p-limit";
4
6
  import { z } from "zod";
5
7
 
8
+ import {
9
+ parsePptx,
10
+ type ParsedPresentation,
11
+ } from "../server/handlers/import/pptx-parser.js";
6
12
  import { getGoogleDocsAccessToken } from "../server/lib/google-docs-oauth.js";
7
- import { importPptxBufferToDeck } from "./import-pptx.js";
13
+ import {
14
+ importPptxBufferToDeck,
15
+ type ImportedImageFallback,
16
+ } from "./import-pptx.js";
8
17
 
9
18
  function deckDeepLink(deckId: string): string {
10
19
  return buildDeepLink({
@@ -14,6 +23,38 @@ function deckDeepLink(deckId: string): string {
14
23
  });
15
24
  }
16
25
 
26
+ export function extractGoogleSlidesPresentationId(value: string): string {
27
+ const candidate = value.trim();
28
+ if (!candidate)
29
+ throw new Error("A Google Slides file ID or URL is required.");
30
+
31
+ if (!/^https?:\/\//i.test(candidate)) {
32
+ if (!/^[a-zA-Z0-9_-]+$/.test(candidate)) {
33
+ throw new Error(
34
+ "Use a Google Slides file ID or a docs.google.com presentation URL.",
35
+ );
36
+ }
37
+ return candidate;
38
+ }
39
+
40
+ let url: URL;
41
+ try {
42
+ url = new URL(candidate);
43
+ } catch {
44
+ throw new Error("Use a valid Google Slides presentation URL.");
45
+ }
46
+ if (url.hostname !== "docs.google.com") {
47
+ throw new Error("Use a docs.google.com Google Slides presentation URL.");
48
+ }
49
+ const match = url.pathname.match(
50
+ /^\/presentation\/d\/([a-zA-Z0-9_-]+)(?:\/|$)/,
51
+ );
52
+ if (!match) {
53
+ throw new Error("That URL is not a Google Slides presentation link.");
54
+ }
55
+ return match[1];
56
+ }
57
+
17
58
  async function exportGoogleSlidesAsPptx(fileId: string, accessToken: string) {
18
59
  const response = await fetch(
19
60
  `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}/export?mimeType=application/vnd.openxmlformats-officedocument.presentationml.presentation`,
@@ -36,36 +77,254 @@ async function exportGoogleSlidesAsPptx(fileId: string, accessToken: string) {
36
77
  return Buffer.from(bytes);
37
78
  }
38
79
 
80
+ interface GoogleSlidesImageElement {
81
+ objectId?: string;
82
+ size?: {
83
+ width?: { magnitude?: number };
84
+ height?: { magnitude?: number };
85
+ };
86
+ transform?: {
87
+ scaleX?: number;
88
+ scaleY?: number;
89
+ translateX?: number;
90
+ translateY?: number;
91
+ };
92
+ image?: {
93
+ contentUrl?: string;
94
+ imageProperties?: {
95
+ cropProperties?: {
96
+ leftOffset?: number;
97
+ rightOffset?: number;
98
+ topOffset?: number;
99
+ bottomOffset?: number;
100
+ };
101
+ };
102
+ };
103
+ }
104
+
105
+ interface GoogleSlidesPresentationImages {
106
+ slides?: Array<{
107
+ pageElements?: GoogleSlidesImageElement[];
108
+ }>;
109
+ }
110
+
111
+ interface GoogleSlidesImageCandidate {
112
+ slideIndex: number;
113
+ imageIndex: number;
114
+ objectId?: string;
115
+ contentUrl: string;
116
+ x: number;
117
+ y: number;
118
+ width: number;
119
+ height: number;
120
+ crop?: ImportedImageFallback["crop"];
121
+ }
122
+
123
+ function finiteNumber(value: number | undefined): number | undefined {
124
+ return value != null && Number.isFinite(value) ? value : undefined;
125
+ }
126
+
127
+ /**
128
+ * Google Drive's PPTX export can omit image page elements that remain present
129
+ * in the native Slides document. Read those native objects as a fidelity
130
+ * fallback so a direct Google Slides import does not silently lose artwork.
131
+ */
132
+ async function fetchGoogleSlidesImageFallbacks(
133
+ fileId: string,
134
+ accessToken: string,
135
+ presentation: ParsedPresentation,
136
+ ): Promise<ImportedImageFallback[]> {
137
+ const fields =
138
+ "slides(pageElements(objectId,size,transform,image(contentUrl,imageProperties(cropProperties))))";
139
+ const response = await ssrfSafeFetch(
140
+ `https://slides.googleapis.com/v1/presentations/${encodeURIComponent(fileId)}?fields=${encodeURIComponent(fields)}`,
141
+ { headers: { Authorization: `Bearer ${accessToken}` } },
142
+ { httpsOnly: true, maxRedirects: 2 },
143
+ );
144
+ if (!response.ok) {
145
+ throw new Error(
146
+ `Google Slides returned HTTP ${response.status} while reading native image elements. Reconnect Google Drive and try the import again.`,
147
+ );
148
+ }
149
+ const googlePresentation =
150
+ (await response.json()) as GoogleSlidesPresentationImages;
151
+ const candidates: GoogleSlidesImageCandidate[] = [];
152
+ const candidateKeys = new Set<string>();
153
+
154
+ for (const [slideIndex, slide] of (
155
+ googlePresentation.slides ?? []
156
+ ).entries()) {
157
+ for (const [imageIndex, element] of (slide.pageElements ?? []).entries()) {
158
+ const contentUrl = element.image?.contentUrl;
159
+ const baseWidth = finiteNumber(element.size?.width?.magnitude);
160
+ const baseHeight = finiteNumber(element.size?.height?.magnitude);
161
+ const scaleX = finiteNumber(element.transform?.scaleX) ?? 1;
162
+ const scaleY = finiteNumber(element.transform?.scaleY) ?? 1;
163
+ const x = finiteNumber(element.transform?.translateX);
164
+ const y = finiteNumber(element.transform?.translateY);
165
+ const width =
166
+ baseWidth == null ? undefined : baseWidth * Math.abs(scaleX);
167
+ const height =
168
+ baseHeight == null ? undefined : baseHeight * Math.abs(scaleY);
169
+ if (
170
+ !contentUrl ||
171
+ x == null ||
172
+ y == null ||
173
+ width == null ||
174
+ height == null ||
175
+ width <= 0 ||
176
+ height <= 0
177
+ ) {
178
+ continue;
179
+ }
180
+
181
+ const existingImage = presentation.slides[slideIndex]?.elements.some(
182
+ (existing) =>
183
+ existing.kind === "image" &&
184
+ Math.abs(existing.x - x) < 1000 &&
185
+ Math.abs(existing.y - y) < 1000 &&
186
+ Math.abs(existing.width - width) < 1000 &&
187
+ Math.abs(existing.height - height) < 1000,
188
+ );
189
+ if (existingImage) continue;
190
+
191
+ const cropProperties = element.image?.imageProperties?.cropProperties;
192
+ const crop = cropProperties
193
+ ? {
194
+ left: cropProperties.leftOffset ?? 0,
195
+ top: cropProperties.topOffset ?? 0,
196
+ right: cropProperties.rightOffset ?? 0,
197
+ bottom: cropProperties.bottomOffset ?? 0,
198
+ }
199
+ : undefined;
200
+ const candidateKey = `${slideIndex}:${x}:${y}:${width}:${height}`;
201
+ if (candidateKeys.has(candidateKey)) continue;
202
+ candidateKeys.add(candidateKey);
203
+ candidates.push({
204
+ slideIndex,
205
+ imageIndex,
206
+ ...(element.objectId ? { objectId: element.objectId } : {}),
207
+ contentUrl,
208
+ x,
209
+ y,
210
+ width,
211
+ height,
212
+ ...(crop ? { crop } : {}),
213
+ });
214
+ }
215
+ }
216
+
217
+ const downloadLimit = pLimit(4);
218
+ const imageDownloads = new Map<
219
+ string,
220
+ Promise<{ data: Uint8Array; mimeType: string }>
221
+ >();
222
+ const downloadImage = (candidate: GoogleSlidesImageCandidate) => {
223
+ const existing = imageDownloads.get(candidate.contentUrl);
224
+ if (existing) return existing;
225
+
226
+ const download = downloadLimit(async () => {
227
+ const imageResponse = await ssrfSafeFetch(
228
+ candidate.contentUrl,
229
+ { headers: { Authorization: `Bearer ${accessToken}` } },
230
+ { httpsOnly: true, maxRedirects: 2 },
231
+ );
232
+ if (!imageResponse.ok) {
233
+ throw new Error(
234
+ `Google Slides returned HTTP ${imageResponse.status} while downloading image ${candidate.imageIndex + 1} on slide ${candidate.slideIndex + 1}.`,
235
+ );
236
+ }
237
+ const mimeType =
238
+ imageResponse.headers.get("content-type")?.split(";", 1)[0] ||
239
+ "image/png";
240
+ if (!mimeType.startsWith("image/")) {
241
+ throw new Error(
242
+ `Google Slides returned a non-image asset for image ${candidate.imageIndex + 1} on slide ${candidate.slideIndex + 1}.`,
243
+ );
244
+ }
245
+ return {
246
+ data: new Uint8Array(await imageResponse.arrayBuffer()),
247
+ mimeType,
248
+ };
249
+ });
250
+ imageDownloads.set(candidate.contentUrl, download);
251
+ return download;
252
+ };
253
+
254
+ return Promise.all(
255
+ candidates.map(async (candidate) => {
256
+ const { data, mimeType } = await downloadImage(candidate);
257
+ return {
258
+ slideIndex: candidate.slideIndex,
259
+ x: candidate.x,
260
+ y: candidate.y,
261
+ width: candidate.width,
262
+ height: candidate.height,
263
+ data,
264
+ mimeType,
265
+ name: `google-slides-${candidate.slideIndex + 1}-${candidate.objectId ?? candidate.imageIndex}.png`,
266
+ ...(candidate.crop ? { crop: candidate.crop } : {}),
267
+ };
268
+ }),
269
+ );
270
+ }
271
+
39
272
  export default defineAction({
40
273
  description:
41
- "Import a Google Slides deck selected from Google Picker and save it as a reusable Slides reference deck. " +
274
+ "Import a Google Slides deck selected from Google Picker or provided as a Google Slides URL and save it as a reusable Slides reference deck. " +
42
275
  "The selected presentation is exported from Drive as PPTX, parsed, and stored as a normal deck in the caller's Slides workspace.",
43
- schema: z.object({
44
- fileId: z.string().describe("Google Slides file ID from Google Picker"),
45
- title: z
46
- .string()
47
- .optional()
48
- .describe("Optional title for the imported reference deck"),
49
- }),
50
- run: async ({ fileId, title }) => {
276
+ schema: z
277
+ .object({
278
+ fileId: z
279
+ .string()
280
+ .optional()
281
+ .describe("Google Slides file ID from Google Picker"),
282
+ presentationUrl: z
283
+ .string()
284
+ .url()
285
+ .optional()
286
+ .describe("A docs.google.com Google Slides presentation URL"),
287
+ title: z
288
+ .string()
289
+ .optional()
290
+ .describe("Optional title for the imported reference deck"),
291
+ })
292
+ .refine(
293
+ ({ fileId, presentationUrl }) => Boolean(fileId || presentationUrl),
294
+ {
295
+ message: "Provide either fileId or presentationUrl.",
296
+ },
297
+ ),
298
+ run: async ({ fileId, presentationUrl, title }) => {
51
299
  const owner = getRequestUserEmail();
52
300
  if (!owner) throw new Error("no authenticated user");
53
301
 
54
302
  const connection = await getGoogleDocsAccessToken(owner);
55
303
  if (!connection) {
56
304
  throw new Error(
57
- "Connect Google Docs before importing a Google Slides deck.",
305
+ "Google Drive is not connected. Use the Connect Google button in Slides, then try again.",
58
306
  );
59
307
  }
60
308
 
309
+ const presentationId = extractGoogleSlidesPresentationId(
310
+ presentationUrl ?? fileId ?? "",
311
+ );
61
312
  const fileBuffer = await exportGoogleSlidesAsPptx(
62
- fileId,
313
+ presentationId,
314
+ connection.accessToken,
315
+ );
316
+ const parsedPresentation = await parsePptx(fileBuffer);
317
+ const imageFallbacks = await fetchGoogleSlidesImageFallbacks(
318
+ presentationId,
63
319
  connection.accessToken,
320
+ parsedPresentation,
64
321
  );
65
322
  return importPptxBufferToDeck({
66
323
  fileBuffer,
324
+ parsedPresentation,
67
325
  title,
68
326
  source: "import-google-slides-reference",
327
+ imageFallbacks,
69
328
  });
70
329
  },
71
330
  link: ({ result }) => {