@agent-native/core 0.141.7 → 0.144.1

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 (371) 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/migrate-first-party-analytics-to-bigquery.ts +1 -1
  12. package/corpus/templates/analytics/actions/navigate.ts +4 -5
  13. package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
  14. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +44 -11
  15. package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
  16. package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
  17. package/corpus/templates/analytics/app/i18n-data.ts +680 -0
  18. package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +6 -0
  19. package/corpus/templates/analytics/app/lib/sql-query.ts +33 -6
  20. package/corpus/templates/analytics/app/pages/DataSources.tsx +4 -0
  21. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-load-reliably-during-background-data-sw.md +6 -0
  22. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-remain-responsive-while-historical-data.md +6 -0
  23. package/corpus/templates/analytics/changelog/2026-08-06-analytics-query-results-now-render-as-a-table-with-csv-downl.md +6 -0
  24. package/corpus/templates/analytics/changelog/2026-08-06-connect-public-https-custom-apis-and-hand-tested-endpoints-t.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-08-06-durable-agent-worker-memory.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-08-06-live-dashboard-panels-use-the-shared-query-timeout.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-08-06-on-demand-billing-is-available-as-a-native-dashboard-with-ta.md +6 -0
  28. package/corpus/templates/analytics/changelog/2026-08-06-visible-dashboard-names-are-unique.md +6 -0
  29. package/corpus/templates/analytics/netlify.toml +7 -0
  30. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -0
  31. package/corpus/templates/analytics/server/db/index.ts +27 -0
  32. package/corpus/templates/analytics/server/db/schema.ts +10 -0
  33. package/corpus/templates/analytics/server/lib/dashboards-store.ts +227 -60
  34. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +156 -45
  35. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
  36. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  37. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +1 -0
  38. package/corpus/templates/analytics/server/plugins/db.ts +52 -18
  39. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +28 -2
  40. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +16 -1
  41. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +296 -0
  42. package/corpus/templates/assets/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  43. package/corpus/templates/assets/.agents/skills/turn-into-skill/SKILL.md +112 -0
  44. package/corpus/templates/assets/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  45. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +296 -0
  46. package/corpus/templates/brain/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  47. package/corpus/templates/brain/.agents/skills/turn-into-skill/SKILL.md +112 -0
  48. package/corpus/templates/brain/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  49. package/corpus/templates/brain/actions/list-connection-providers.ts +24 -3
  50. package/corpus/templates/brain/app/lib/dispatch-links.ts +44 -0
  51. package/corpus/templates/brain/app/routes/sources.tsx +15 -7
  52. package/corpus/templates/brain/changelog/2026-08-06-brain-provider-setup-links-open-dispatch-correctly-in-standa.md +6 -0
  53. package/corpus/templates/brain/server/plugins/agent-chat.ts +1 -0
  54. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +26 -0
  55. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +296 -0
  56. package/corpus/templates/calendar/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  57. package/corpus/templates/calendar/.agents/skills/turn-into-skill/SKILL.md +112 -0
  58. package/corpus/templates/calendar/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  59. package/corpus/templates/calendar/AGENTS.md +11 -32
  60. package/corpus/templates/calendar/actions/update-event.ts +107 -1
  61. package/corpus/templates/calendar/app/components/calendar/EventCalendarSelect.tsx +83 -0
  62. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +57 -0
  63. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +59 -73
  64. package/corpus/templates/calendar/app/hooks/use-events.ts +4 -1
  65. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  66. package/corpus/templates/calendar/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  67. package/corpus/templates/calendar/changelog/2026-08-06-existing-events-can-move-between-connected-google-account-ca.md +6 -0
  68. package/corpus/templates/calendar/server/lib/google-calendar.ts +101 -0
  69. package/corpus/templates/calendar/server/plugins/agent-chat.ts +7 -1
  70. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  71. package/corpus/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  72. package/corpus/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  73. package/corpus/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  74. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +296 -0
  75. package/corpus/templates/clips/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  76. package/corpus/templates/clips/.agents/skills/turn-into-skill/SKILL.md +112 -0
  77. package/corpus/templates/clips/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  78. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +9 -24
  79. package/corpus/templates/clips/app/components/editor/timeline-geometry.ts +15 -0
  80. package/corpus/templates/clips/app/components/editor/waveform.tsx +4 -17
  81. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +14 -3
  82. package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
  83. package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
  84. package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
  85. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +28 -38
  86. package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -27
  87. package/corpus/templates/clips/changelog/2026-08-06-clips-recovers-from-stalled-recording-setup-without-requirin.md +6 -0
  88. package/corpus/templates/clips/changelog/2026-08-06-clips-starts-faster-after-a-deploy-or-idle-period-startup-no.md +6 -0
  89. package/corpus/templates/clips/changelog/2026-08-06-deleting-a-recording-returns-cleanly-to-the-library-and-fit-.md +6 -0
  90. package/corpus/templates/clips/changelog/2026-08-06-hide-clip-insights-from-viewers-without-analytics-access.md +6 -0
  91. package/corpus/templates/clips/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.md +6 -0
  92. package/corpus/templates/clips/desktop/src/app.tsx +48 -20
  93. package/corpus/templates/clips/desktop/src/lib/recorder.ts +275 -74
  94. package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +84 -0
  95. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +14 -4
  96. package/corpus/templates/clips/server/plugins/db.ts +214 -26
  97. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +296 -0
  98. package/corpus/templates/content/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  99. package/corpus/templates/content/.agents/skills/turn-into-skill/SKILL.md +112 -0
  100. package/corpus/templates/content/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  101. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +296 -0
  102. package/corpus/templates/crm/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  103. package/corpus/templates/crm/.agents/skills/turn-into-skill/SKILL.md +112 -0
  104. package/corpus/templates/crm/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  105. package/corpus/templates/crm/changelog/2026-08-06-crm-starts-faster-after-a-deploy-or-idle-period-one-time-dat.md +6 -0
  106. package/corpus/templates/crm/server/plugins/db.ts +11 -8
  107. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +36 -1
  108. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +296 -0
  109. package/corpus/templates/design/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  110. package/corpus/templates/design/.agents/skills/turn-into-skill/SKILL.md +112 -0
  111. package/corpus/templates/design/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  112. package/corpus/templates/design/actions/import-from-url.ts +1 -1
  113. package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
  114. package/corpus/templates/design/app/pages/Index.tsx +6 -2
  115. package/corpus/templates/design/changelog/2026-08-05-deleting-a-design-from-the-home-page-no-longer-leaves-the-ap.md +6 -0
  116. package/corpus/templates/design/changelog/2026-08-05-renaming-a-layer-now-focuses-its-name-field-automatically.md +6 -0
  117. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  118. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +296 -0
  119. package/corpus/templates/dispatch/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  120. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/SKILL.md +112 -0
  121. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  122. package/corpus/templates/dispatch/AGENTS.md +8 -1
  123. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
  124. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
  125. package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
  126. package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
  127. package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
  128. package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
  129. package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
  130. package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
  131. package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
  132. package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
  133. package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
  134. package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
  135. package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
  136. package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
  137. package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
  138. package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
  139. package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
  140. package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
  141. package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
  142. package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
  143. package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
  144. package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
  145. package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
  146. package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
  147. package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
  148. package/corpus/templates/dispatch/changelog/2026-08-06-ready-workspace-apps-stay-one-click-away-in-a-compact-dispat.md +6 -0
  149. package/corpus/templates/dispatch/changelog/2026-08-06-shared-vault-values-and-management-are-now-restricted-to-wor.md +6 -0
  150. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +296 -0
  151. package/corpus/templates/factory/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  152. package/corpus/templates/factory/.agents/skills/turn-into-skill/SKILL.md +112 -0
  153. package/corpus/templates/factory/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  154. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +296 -0
  155. package/corpus/templates/forms/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  156. package/corpus/templates/forms/.agents/skills/turn-into-skill/SKILL.md +112 -0
  157. package/corpus/templates/forms/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  158. package/corpus/templates/forms/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  159. package/corpus/templates/forms/server/plugins/agent-chat.ts +4 -0
  160. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +296 -0
  161. package/corpus/templates/macros/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  162. package/corpus/templates/macros/.agents/skills/turn-into-skill/SKILL.md +112 -0
  163. package/corpus/templates/macros/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  164. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +296 -0
  165. package/corpus/templates/mail/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  166. package/corpus/templates/mail/.agents/skills/turn-into-skill/SKILL.md +112 -0
  167. package/corpus/templates/mail/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  168. package/corpus/templates/mail/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  169. package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
  170. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +296 -0
  171. package/corpus/templates/plan/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  172. package/corpus/templates/plan/.agents/skills/turn-into-skill/SKILL.md +112 -0
  173. package/corpus/templates/plan/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  174. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +48 -5
  175. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +37 -1
  176. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +6 -0
  177. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +15 -2
  178. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +29 -24
  179. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +296 -0
  180. package/corpus/templates/slides/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  181. package/corpus/templates/slides/.agents/skills/turn-into-skill/SKILL.md +112 -0
  182. package/corpus/templates/slides/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  183. package/corpus/templates/slides/AGENTS.md +17 -21
  184. package/corpus/templates/slides/_gitignore +1 -0
  185. package/corpus/templates/slides/actions/export-pptx.ts +470 -36
  186. package/corpus/templates/slides/actions/get-deck.ts +12 -0
  187. package/corpus/templates/slides/actions/import-file.ts +175 -139
  188. package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
  189. package/corpus/templates/slides/actions/import-pptx.ts +193 -68
  190. package/corpus/templates/slides/actions/patch-deck.ts +51 -0
  191. package/corpus/templates/slides/actions/update-slide.ts +29 -3
  192. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
  193. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +5 -1
  194. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +25 -12
  195. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +112 -57
  196. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +202 -0
  197. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +26 -14
  198. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +173 -0
  199. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +182 -141
  200. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  201. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +18 -0
  202. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +85 -0
  203. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -0
  204. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +15 -0
  205. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
  206. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  207. package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
  208. package/corpus/templates/slides/app/lib/deck-preview-frame.ts +15 -0
  209. package/corpus/templates/slides/app/lib/deck-share-links.ts +9 -0
  210. package/corpus/templates/slides/app/lib/export-pptx-client.ts +89 -0
  211. package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +89 -0
  212. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
  213. package/corpus/templates/slides/app/pages/Index.tsx +172 -15
  214. package/corpus/templates/slides/changelog/2026-08-06-clicking-the-gray-canvas-around-a-slide-clears-its-selection.md +6 -0
  215. package/corpus/templates/slides/changelog/2026-08-06-deck-thumbnails-keep-a-consistent-listing-frame-across-aspec.md +6 -0
  216. package/corpus/templates/slides/changelog/2026-08-06-deep-linked-decks-open-directly-instead-of-being-interrupted.md +6 -0
  217. package/corpus/templates/slides/changelog/2026-08-06-google-slides-exports-now-show-clear-import-steps-when-direc.md +6 -0
  218. package/corpus/templates/slides/changelog/2026-08-06-import-pdf-google-slides-and-powerpoint-decks-directly-from-.md +6 -0
  219. package/corpus/templates/slides/changelog/2026-08-06-imported-powerpoint-exports-keep-text-and-images-editable-an.md +6 -0
  220. package/corpus/templates/slides/changelog/2026-08-06-presentation-prompts-continue-adding-rendered-slides-until-t.md +6 -0
  221. package/corpus/templates/slides/changelog/2026-08-06-public-decks-now-share-read-only-presentation-links-by-defau.md +6 -0
  222. package/corpus/templates/slides/changelog/2026-08-06-slides-offers-a-direct-connect-google-button-when-a-drive-co.md +6 -0
  223. package/corpus/templates/slides/changelog/2026-08-06-slides-preserve-uploaded-pdf-layouts-accept-larger-reference.md +6 -0
  224. package/corpus/templates/slides/changelog/2026-08-06-styled-words-stay-part-of-their-text-box-instead-of-becoming.md +6 -0
  225. package/corpus/templates/slides/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.md +6 -0
  226. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +7 -20
  227. package/corpus/templates/slides/server/handlers/import/html-converter.ts +247 -13
  228. package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +67 -0
  229. package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +2 -0
  230. package/corpus/templates/slides/server/handlers/uploads.ts +7 -3
  231. package/corpus/templates/slides/server/lib/chat-attachments.ts +7 -4
  232. package/corpus/templates/slides/server/lib/emails.ts +7 -2
  233. package/corpus/templates/slides/server/lib/google-docs-error.ts +50 -0
  234. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +1 -0
  235. package/corpus/templates/slides/server/lib/import-asset-storage.ts +127 -0
  236. package/corpus/templates/slides/server/lib/source-import.ts +167 -0
  237. package/corpus/templates/slides/server/plugins/agent-chat.ts +12 -3
  238. package/corpus/templates/slides/server/routes/api/import-assets/[token].get.ts +70 -0
  239. package/corpus/templates/slides/shared/upload-types.ts +3 -0
  240. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +296 -0
  241. package/corpus/templates/tasks/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  242. package/corpus/templates/tasks/.agents/skills/turn-into-skill/SKILL.md +112 -0
  243. package/corpus/templates/tasks/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  244. package/dist/agent/production-agent.d.ts +17 -0
  245. package/dist/agent/production-agent.js +72 -1
  246. package/dist/cli/agent-plugin.d.ts +112 -0
  247. package/dist/cli/agent-plugin.js +1081 -0
  248. package/dist/cli/app-skill.js +39 -1
  249. package/dist/cli/code-agent-executor.d.ts +3 -0
  250. package/dist/cli/code-agent-executor.js +51 -13
  251. package/dist/cli/index.js +13 -0
  252. package/dist/cli/templates-meta.js +1 -1
  253. package/dist/client/AgentPanel.d.ts +3 -1
  254. package/dist/client/AgentPanel.js +9 -2
  255. package/dist/client/AssistantChat.d.ts +8 -0
  256. package/dist/client/AssistantChat.js +23 -8
  257. package/dist/client/NewWorkspaceAppFlow.js +1 -0
  258. package/dist/client/agent-chat-adapter.js +123 -4
  259. package/dist/client/assistant-ui-recovery.d.ts +2 -1
  260. package/dist/client/assistant-ui-recovery.js +7 -3
  261. package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
  262. package/dist/client/org/TeamPage.js +1 -1
  263. package/dist/client/require-session.js +1 -1
  264. package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
  265. package/dist/client/resources/McpConnectionSuggestion.js +27 -10
  266. package/dist/client/resources/McpIntegrationDialog.js +77 -45
  267. package/dist/client/resources/mcp-integration-catalog.d.ts +13 -1
  268. package/dist/client/resources/mcp-integration-catalog.js +255 -2
  269. package/dist/client/resources/mcp-integration-logos.js +18 -2
  270. package/dist/client/settings/AccountSettingsCard.js +2 -1
  271. package/dist/collab/struct-routes.d.ts +1 -1
  272. package/dist/db/client.d.ts +9 -0
  273. package/dist/db/client.js +51 -16
  274. package/dist/db/create-get-db.js +2 -2
  275. package/dist/db/ddl-guard.d.ts +11 -10
  276. package/dist/db/ddl-guard.js +40 -12
  277. package/dist/deploy/build.d.ts +2 -0
  278. package/dist/deploy/build.js +97 -4
  279. package/dist/deploy/workspace-deploy.js +0 -2
  280. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  281. package/dist/guards/no-env-credentials.js +9 -0
  282. package/dist/ingestion/index.d.ts +1 -1
  283. package/dist/ingestion/pptx.d.ts +68 -0
  284. package/dist/ingestion/pptx.js +660 -213
  285. package/dist/integrations/plugin.js +9 -6
  286. package/dist/jobs/scheduler.js +79 -23
  287. package/dist/localization/default-messages.d.ts +54 -0
  288. package/dist/localization/default-messages.js +57 -2
  289. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  290. package/dist/mcp-client/oauth-routes.d.ts +2 -0
  291. package/dist/mcp-client/oauth-routes.js +73 -6
  292. package/dist/observability/routes.d.ts +5 -5
  293. package/dist/onboarding/app-profile.js +11 -0
  294. package/dist/progress/routes.d.ts +1 -1
  295. package/dist/resources/handlers.d.ts +1 -1
  296. package/dist/resources/store.js +15 -1
  297. package/dist/scripts/call-agent.js +34 -4
  298. package/dist/secrets/routes.d.ts +3 -3
  299. package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
  300. package/dist/server/agent-chat/action-filters-a2a.js +36 -1
  301. package/dist/server/agent-chat-plugin.js +29 -8
  302. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  303. package/dist/server/auth.js +47 -11
  304. package/dist/server/better-auth-instance.js +5 -3
  305. package/dist/server/core-routes-plugin.d.ts +8 -1
  306. package/dist/server/core-routes-plugin.js +28 -3
  307. package/dist/server/credential-provider.d.ts +3 -3
  308. package/dist/server/credential-provider.js +90 -4
  309. package/dist/server/onboarding-html.d.ts +2 -0
  310. package/dist/server/onboarding-html.js +115 -29
  311. package/dist/server/realtime-token.d.ts +1 -1
  312. package/dist/server/self-dispatch.d.ts +5 -4
  313. package/dist/server/self-dispatch.js +25 -14
  314. package/dist/server/ssr-handler.d.ts +1 -1
  315. package/dist/server/ssr-handler.js +1 -1
  316. package/dist/server/transcribe-voice.d.ts +1 -1
  317. package/dist/shared/cache-control.d.ts +26 -2
  318. package/dist/shared/cache-control.js +43 -2
  319. package/dist/shared/password-policy.d.ts +3 -0
  320. package/dist/shared/password-policy.js +3 -0
  321. package/dist/sharing/actions/set-resource-visibility.js +16 -4
  322. package/dist/sharing/registry.d.ts +14 -0
  323. package/dist/styles/agent-native.css +6 -0
  324. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  325. package/dist/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  326. package/dist/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  327. package/dist/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  328. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  329. package/dist/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  330. package/dist/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  331. package/dist/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  332. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  333. package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  334. package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  335. package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  336. package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  337. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  338. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  339. package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  340. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  341. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  342. package/dist/templates/workspace-root/AGENTS.md +7 -0
  343. package/dist/user-profile/actions/change-password.js +8 -3
  344. package/dist/user-profile/actions/set-password.js +5 -1
  345. package/dist/vite/action-types-plugin.js +59 -0
  346. package/docs/content/creating-templates.mdx +1 -1
  347. package/docs/content/getting-started-actions.mdx +38 -189
  348. package/docs/content/getting-started-database.mdx +80 -164
  349. package/docs/content/getting-started-pages.mdx +44 -155
  350. package/docs/content/getting-started.mdx +26 -106
  351. package/docs/content/skills-guide.mdx +13 -0
  352. package/package.json +6 -2
  353. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  354. package/src/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  355. package/src/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  356. package/src/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  357. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  358. package/src/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  359. package/src/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  360. package/src/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  361. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  362. package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  363. package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  364. package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  365. package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  366. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  367. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  368. package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  369. package/src/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  370. package/src/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  371. package/src/templates/workspace-root/AGENTS.md +7 -0
@@ -1,4 +1,9 @@
1
- import type { ParsedSlide, ParsedTextRun } from "./pptx-parser.js";
1
+ import type {
2
+ ParsedElement,
3
+ ParsedParagraph,
4
+ ParsedSlide,
5
+ ParsedTextRun,
6
+ } from "./pptx-parser.js";
2
7
 
3
8
  /** Escape HTML special characters. */
4
9
  function esc(text: string): string {
@@ -47,6 +52,24 @@ export function buildFullBleedImageSlideHtml(
47
52
  </div>`;
48
53
  }
49
54
 
55
+ /** Render a rasterized source page without changing its layout or text. */
56
+ export function buildFullPageImageSlideHtml(
57
+ imageUrl: string,
58
+ sourceWidth?: number,
59
+ sourceHeight?: number,
60
+ ): string {
61
+ const sourceDimensions =
62
+ Number.isFinite(sourceWidth) &&
63
+ Number.isFinite(sourceHeight) &&
64
+ sourceWidth! > 0 &&
65
+ sourceHeight! > 0
66
+ ? ` data-source-width="${sourceWidth}" data-source-height="${sourceHeight}"`
67
+ : "";
68
+ return `<div class="fmd-slide fmd-imported-pdf" data-imported-pdf="true"${sourceDimensions} style="position: relative; width: 100%; height: 100%; overflow: hidden; background: hsl(var(--background));">
69
+ <img src="${esc(imageUrl)}" alt="" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;" />
70
+ </div>`;
71
+ }
72
+
50
73
  /** Wrap text in formatting tags based on run properties. */
51
74
  function formatRun(run: ParsedTextRun): string {
52
75
  let text = esc(run.content);
@@ -63,7 +86,11 @@ const DEFAULT_IMPORT_FONT = "'Poppins', sans-serif";
63
86
  function cssFontFamily(themeFont: string | undefined): string {
64
87
  if (!themeFont) return DEFAULT_IMPORT_FONT;
65
88
  const safeName = themeFont.replace(/["']/g, "").trim();
66
- return safeName ? `'${safeName}', sans-serif` : DEFAULT_IMPORT_FONT;
89
+ if (!safeName) return DEFAULT_IMPORT_FONT;
90
+ if (safeName.toLowerCase().startsWith("poppins")) {
91
+ return "'Poppins', sans-serif";
92
+ }
93
+ return `'${safeName}', sans-serif`;
67
94
  }
68
95
 
69
96
  /**
@@ -107,9 +134,13 @@ function groupIntoParagraphs(texts: ParsedTextRun[]): ParsedTextRun[][] {
107
134
  */
108
135
  export function convertToSlideHtml(
109
136
  slide: ParsedSlide,
110
- imageUrl?: string,
137
+ imageUrls?: string | Record<string, string>,
111
138
  themeFont?: string,
112
139
  ): string {
140
+ if (slide.elements?.length) {
141
+ return buildFidelitySlide(slide, imageUrls, themeFont);
142
+ }
143
+
113
144
  const paragraphs = groupIntoParagraphs(slide.texts);
114
145
  const fontFamily = cssFontFamily(themeFont);
115
146
 
@@ -117,7 +148,12 @@ export function convertToSlideHtml(
117
148
  // has no room to show it, which is how imports used to silently drop
118
149
  // photos from otherwise short/title-shaped slides.
119
150
  if (slide.images.length > 0) {
120
- return buildImageSlide(paragraphs, slide, imageUrl, fontFamily);
151
+ return buildImageSlide(
152
+ paragraphs,
153
+ slide,
154
+ typeof imageUrls === "string" ? imageUrls : undefined,
155
+ fontFamily,
156
+ );
121
157
  }
122
158
 
123
159
  if (slide.layoutHint === "title" || paragraphs.length <= 2) {
@@ -127,6 +163,194 @@ export function convertToSlideHtml(
127
163
  return buildContentSlide(paragraphs, slide, fontFamily);
128
164
  }
129
165
 
166
+ const DEFAULT_SLIDE_WIDTH_EMU = 9144000;
167
+ const DEFAULT_SLIDE_HEIGHT_EMU = 5143500;
168
+ const CSS_PX_PER_POINT = 96 / 72;
169
+ const DEFAULT_PPTX_BACKGROUND = "#000000"; // guard:allow-raw-color - preserve PPTX black when no background is declared
170
+ const DEFAULT_PPTX_FOREGROUND = "#ffffff"; // guard:allow-raw-color - preserve PPTX white when no run color is declared
171
+
172
+ function buildFidelitySlide(
173
+ slide: ParsedSlide,
174
+ imageUrls: string | Record<string, string> | undefined,
175
+ themeFont: string | undefined,
176
+ ): string {
177
+ const widthEmu = slide.widthEmu || DEFAULT_SLIDE_WIDTH_EMU;
178
+ const heightEmu = slide.heightEmu || DEFAULT_SLIDE_HEIGHT_EMU;
179
+ const background = slide.backgroundColor ?? DEFAULT_PPTX_BACKGROUND;
180
+ const gridStyle = slide.backgroundGrid
181
+ ? `background-image:linear-gradient(to right, ${esc(slide.backgroundGrid.color)} 0 ${Math.max(0.5, toSlidePxX(slide.backgroundGrid.lineWidthEmu, widthEmu))}px, transparent ${Math.max(0.5, toSlidePxX(slide.backgroundGrid.lineWidthEmu, widthEmu))}px),linear-gradient(to bottom, ${esc(slide.backgroundGrid.color)} 0 ${Math.max(0.5, toSlidePxY(slide.backgroundGrid.lineWidthEmu, heightEmu))}px, transparent ${Math.max(0.5, toSlidePxY(slide.backgroundGrid.lineWidthEmu, heightEmu))}px);background-size:${toSlidePxX(slide.backgroundGrid.stepXEmu, widthEmu)}px ${toSlidePxY(slide.backgroundGrid.stepYEmu, heightEmu)}px;background-position:${toSlidePxX(slide.backgroundGrid.offsetXEmu, widthEmu)}px ${toSlidePxY(slide.backgroundGrid.offsetYEmu, heightEmu)}px;background-repeat:repeat;`
182
+ : "";
183
+ const elements = slide.elements ?? [];
184
+ const html = elements
185
+ .map((element, index) =>
186
+ buildFidelityElement(
187
+ element,
188
+ index,
189
+ widthEmu,
190
+ heightEmu,
191
+ imageUrls,
192
+ themeFont,
193
+ ),
194
+ )
195
+ .join("\n");
196
+
197
+ return `<div class="fmd-slide fmd-imported-pptx" data-imported-pptx="true" data-slide-width-emu="${widthEmu}" data-slide-height-emu="${heightEmu}" style="position: relative; width: 100%; height: 100%; overflow: hidden; background: ${esc(background)};${gridStyle} font-family: ${cssFontFamily(themeFont)};">${html}
198
+ </div>`;
199
+ }
200
+
201
+ function buildFidelityElement(
202
+ element: ParsedElement,
203
+ index: number,
204
+ widthEmu: number,
205
+ heightEmu: number,
206
+ imageUrls: string | Record<string, string> | undefined,
207
+ themeFont: string | undefined,
208
+ ): string {
209
+ const position = `position: absolute; left: ${toSlidePxX(element.x, widthEmu)}px; top: ${toSlidePxY(element.y, heightEmu)}px; width: ${toSlidePxX(element.width, widthEmu)}px; height: ${toSlidePxY(element.height, heightEmu)}px; z-index: ${index}; box-sizing: border-box;`;
210
+ const rotation = element.rotation
211
+ ? ` transform: rotate(${element.rotation}deg); transform-origin: center center;`
212
+ : "";
213
+ const objectId = ` data-slide-object-id="${esc(element.id)}"`;
214
+
215
+ if (element.kind === "image") {
216
+ const url = imageUrlForElement(element, imageUrls);
217
+ const imageStyle = imageRenderStyle(element);
218
+ return `<div class="fmd-pptx-image" data-pptx-element-kind="image" data-pptx-image-name="${esc(element.image?.name ?? "image")}"${objectId} style="${position}${rotation} overflow: hidden;">${url ? `<img src="${esc(url)}" alt="" style="${imageStyle}" />` : `<div class="fmd-img-placeholder" style="width:100%;height:100%;">Imported image: ${esc(element.image?.name ?? "image")}</div>`}</div>`;
219
+ }
220
+
221
+ const decoration = shapeDecoration(element, widthEmu);
222
+ if (element.kind === "shape") {
223
+ return `<div class="fmd-pptx-shape" data-pptx-element-kind="shape"${objectId} style="${position}${rotation}${decoration}"></div>`;
224
+ }
225
+
226
+ const textStyle = textBoxStyle(element, widthEmu, heightEmu, themeFont);
227
+ const defaultFontWeight = element.placeholderType === "title" ? 700 : 400;
228
+ const paragraphs = (element.paragraphs ?? [])
229
+ .map((paragraph, paragraphIndex) =>
230
+ buildFidelityParagraph(
231
+ paragraph,
232
+ paragraphIndex,
233
+ widthEmu,
234
+ themeFont,
235
+ defaultFontWeight,
236
+ ),
237
+ )
238
+ .join("\n");
239
+ return `<div class="fmd-pptx-text" data-pptx-element-kind="text"${objectId} style="${position}${rotation}${decoration}${textStyle}">${paragraphs}</div>`;
240
+ }
241
+
242
+ function toSlidePxX(valueEmu: number, slideWidthEmu: number): number {
243
+ return Math.round((valueEmu / slideWidthEmu) * 960 * 1000) / 1000;
244
+ }
245
+
246
+ function toSlidePxY(valueEmu: number, slideHeightEmu: number): number {
247
+ return Math.round((valueEmu / slideHeightEmu) * 540 * 1000) / 1000;
248
+ }
249
+
250
+ function imageUrlForElement(
251
+ element: ParsedElement,
252
+ imageUrls: string | Record<string, string> | undefined,
253
+ ): string | undefined {
254
+ if (typeof imageUrls === "string") return imageUrls;
255
+ return imageUrls?.[element.id];
256
+ }
257
+
258
+ function imageRenderStyle(element: ParsedElement): string {
259
+ const crop = element.image?.crop;
260
+ if (!crop) return "display:block;width:100%;height:100%;object-fit:fill;";
261
+ const visibleWidth = Math.max(0.001, 1 - crop.left - crop.right);
262
+ const visibleHeight = Math.max(0.001, 1 - crop.top - crop.bottom);
263
+ return `display:block;position:absolute;left:${(-crop.left / visibleWidth) * 100}%;top:${(-crop.top / visibleHeight) * 100}%;width:${(1 / visibleWidth) * 100}%;height:${(1 / visibleHeight) * 100}%;object-fit:fill;`;
264
+ }
265
+
266
+ function shapeDecoration(element: ParsedElement, widthEmu: number): string {
267
+ const fill = element.fill ? `background: ${esc(element.fill)};` : "";
268
+ const line = element.lineColor
269
+ ? `border: ${Math.max(1, toSlidePxX(element.lineWidth ?? 12700, widthEmu))}px solid ${esc(element.lineColor)};`
270
+ : "";
271
+ const radius = element.shapeType === "roundRect" ? "border-radius: 6px;" : "";
272
+ return `${fill}${line}${radius}`;
273
+ }
274
+
275
+ function textBoxStyle(
276
+ element: ParsedElement,
277
+ widthEmu: number,
278
+ heightEmu: number,
279
+ themeFont: string | undefined,
280
+ ): string {
281
+ const padding = element.padding;
282
+ const left = padding ? toSlidePxX(padding.left, widthEmu) : 0;
283
+ const right = padding ? toSlidePxX(padding.right, widthEmu) : 0;
284
+ const top = padding ? toSlidePxY(padding.top, heightEmu) : 0;
285
+ const bottom = padding ? toSlidePxY(padding.bottom, heightEmu) : 0;
286
+ const align = element.paragraphs?.[0]?.alignment ?? "left";
287
+ const vertical =
288
+ element.verticalAlign === "middle"
289
+ ? "justify-content:center;"
290
+ : element.verticalAlign === "bottom"
291
+ ? "justify-content:flex-end;"
292
+ : "justify-content:flex-start;";
293
+ return `display:flex;flex-direction:column;${vertical}padding:${top}px ${right}px ${bottom}px ${left}px;font-family:${cssFontFamily(themeFont)};text-align:${align};overflow:visible;`;
294
+ }
295
+
296
+ function buildFidelityParagraph(
297
+ paragraph: ParsedParagraph,
298
+ paragraphIndex: number,
299
+ widthEmu: number,
300
+ themeFont: string | undefined,
301
+ defaultFontWeight: number,
302
+ ): string {
303
+ const firstRun = paragraph.runs[0];
304
+ const fontSize = (firstRun?.fontSize ?? 18) * CSS_PX_PER_POINT;
305
+ const lineHeight = paragraph.lineSpacing ?? 1.2;
306
+ const bullet = paragraph.bulletChar
307
+ ? `<span aria-hidden="true" style="display:inline-block;width:${fontSize * 0.75}px;min-width:${fontSize * 0.75}px;margin-right:${fontSize * 0.65}px;color:${esc(paragraph.bulletColor ?? firstRun?.color ?? DEFAULT_PPTX_FOREGROUND)};font-family:${cssFontFamily(paragraph.bulletFontFamily ?? themeFont)};font-size:${(paragraph.bulletSize ?? firstRun?.fontSize ?? 18) * CSS_PX_PER_POINT}px;">${esc(paragraph.bulletChar)}</span>`
308
+ : "";
309
+ const marginLeft = paragraph.marginLeftEmu
310
+ ? toSlidePxX(paragraph.marginLeftEmu, widthEmu)
311
+ : 0;
312
+ const indent = paragraph.indentEmu
313
+ ? toSlidePxX(paragraph.indentEmu, widthEmu)
314
+ : 0;
315
+ const spacingBefore = paragraph.spaceBeforePt ?? 0;
316
+ const spacingAfter = paragraph.spaceAfterPt ?? 0;
317
+ const bulletMargin = paragraph.bulletChar ? `margin-left:${indent}px;` : "";
318
+ const text = paragraph.runs
319
+ .map((run) => formatFidelityRun(run, themeFont, defaultFontWeight))
320
+ .join("");
321
+ return `<p data-pptx-paragraph="${paragraphIndex}" style="display:block;flex:0 0 auto;text-align:${paragraph.alignment ?? "left"};white-space:pre-wrap;margin:${spacingBefore * CSS_PX_PER_POINT}px 0 ${spacingAfter * CSS_PX_PER_POINT}px;line-height:${lineHeight};font-size:${fontSize}px;min-height:${fontSize * lineHeight}px;padding-left:${marginLeft}px;text-indent:${paragraph.bulletChar ? 0 : indent}px;">${bullet.replace("display:inline-block;", `display:inline-block;${bulletMargin}`)}${text}</p>`;
322
+ }
323
+
324
+ function formatFidelityRun(
325
+ run: ParsedTextRun,
326
+ themeFont: string | undefined,
327
+ defaultFontWeight = 400,
328
+ ): string {
329
+ const styles = [
330
+ `font-size:${(run.fontSize ?? 18) * CSS_PX_PER_POINT}px`,
331
+ `font-family:${cssFontFamily(run.fontFamily ?? themeFont)}`,
332
+ `color:${esc(run.color ?? DEFAULT_PPTX_FOREGROUND)}`,
333
+ `font-weight:${run.bold ? 700 : fontWeightForFamily(run.fontFamily, defaultFontWeight)}`,
334
+ `font-style:${run.italic ? "italic" : "normal"}`,
335
+ `text-decoration:${run.underline ? "underline" : "none"}`,
336
+ ].join(";");
337
+ return `<span style="${styles};">${esc(run.content)}</span>`;
338
+ }
339
+
340
+ function fontWeightForFamily(
341
+ fontFamily: string | undefined,
342
+ fallback: number,
343
+ ): number {
344
+ const normalized = fontFamily?.toLowerCase() ?? "";
345
+ if (!normalized) return fallback;
346
+ if (/(?:semi|demi)bold|semibold/.test(normalized)) return 600;
347
+ if (/extra[- ]?bold|heavy/.test(normalized)) return 800;
348
+ if (/bold/.test(normalized)) return 700;
349
+ if (/medium/.test(normalized)) return 500;
350
+ if (/light|thin/.test(normalized)) return 300;
351
+ return 400;
352
+ }
353
+
130
354
  function buildTitleSlide(
131
355
  paragraphs: ParsedTextRun[][],
132
356
  slide: ParsedSlide,
@@ -166,7 +390,7 @@ function buildContentSlide(
166
390
  })
167
391
  .join("\n");
168
392
 
169
- bulletsHtml = `\n <div style="display: flex; flex-direction: column; gap: 20px;">
393
+ bulletsHtml = `\n <div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 20px;">
170
394
  ${bulletItems}
171
395
  </div>`;
172
396
  }
@@ -232,15 +456,20 @@ function buildOverlayImageSlide(
232
456
  const headingHtml = headingPara.map(formatRun).join(" ") || "Slide";
233
457
 
234
458
  const captionParas = paragraphs.slice(1);
235
- const captionHtml = captionParas
236
- .map((para) => para.map(formatRun).join(" "))
237
- .join(" ");
459
+ const captionHtml = captionParas.length
460
+ ? `<div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 8px;">${captionParas
461
+ .map(
462
+ (para) =>
463
+ `<p style="font-size: 18px; color: rgba(255,255,255,0.75); /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.5; margin: 0;">${para.map(formatRun).join(" ")}</p>`,
464
+ )
465
+ .join("\n")}</div>`
466
+ : "";
238
467
 
239
468
  return `<div class="fmd-slide" style="position: relative; width: 100%; height: 100%; overflow: hidden;">
240
469
  <img src="${esc(imageUrl)}" alt="" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;" />
241
470
  <div style="position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.35) 55%, rgba(0,0,0,0) 80%);"></div>
242
471
  <div style="position: absolute; left: 0; right: 0; bottom: 0; padding: 56px 70px; font-family: ${fontFamily};">
243
- <h2 style="font-size: 40px; font-weight: 900; color: #fff; line-height: 1.15; letter-spacing: -1px; margin: 0 0 ${captionHtml ? "12px" : "0"} 0;">${headingHtml}</h2>${captionHtml ? `\n <p style="font-size: 18px; color: rgba(255,255,255,0.75); line-height: 1.5; margin: 0;">${captionHtml}</p>` : ""}
472
+ <h2 style="font-size: 40px; font-weight: 900; color: #fff; /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.15; letter-spacing: -1px; margin: 0 0 ${captionHtml ? "12px" : "0"} 0;">${headingHtml}</h2>${captionHtml ? `\n ${captionHtml}` : ""}
244
473
  </div>
245
474
  </div>`;
246
475
  }
@@ -256,9 +485,14 @@ function buildStackedImageSlide(
256
485
  const headingText = headingPara.map(formatRun).join(" ") || "Slide";
257
486
 
258
487
  const captionParas = paragraphs.slice(1);
259
- const captionText = captionParas
260
- .map((para) => para.map(formatRun).join(" "))
261
- .join(" ");
488
+ const captionText = captionParas.length
489
+ ? `<div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 8px;">${captionParas
490
+ .map(
491
+ (para) =>
492
+ `<p style="font-size: 16px; color: rgba(255,255,255,0.7); /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.5; margin: 0;">${para.map(formatRun).join(" ")}</p>`,
493
+ )
494
+ .join("\n")}</div>`
495
+ : "";
262
496
 
263
497
  const imageName = slide.images[0]?.name ?? "image";
264
498
  // Size the box to the image's own placed aspect ratio instead of a fixed
@@ -278,7 +512,7 @@ function buildStackedImageSlide(
278
512
 
279
513
  return `<div class="fmd-slide" style="padding: 64px 90px; display: flex; flex-direction: column; justify-content: flex-start; font-family: ${fontFamily};">
280
514
  ${imageHtml}
281
- <h2 style="font-size: 32px; font-weight: 900; color: #fff; line-height: 1.2; letter-spacing: -0.5px; margin: 0 0 12px 0;">${headingText}</h2>${captionText ? `\n <p style="font-size: 16px; color: rgba(255,255,255,0.7); line-height: 1.5; margin: 0;">${captionText}</p>` : ""}
515
+ <h2 style="font-size: 32px; font-weight: 900; color: #fff; /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.2; letter-spacing: -0.5px; margin: 0 0 12px 0;">${headingText}</h2>${captionText ? `\n ${captionText}` : ""}
282
516
  </div>`;
283
517
  }
284
518
 
@@ -0,0 +1,67 @@
1
+ import { uploadFile } from "@agent-native/core/file-upload";
2
+
3
+ import { storeLocalImportedAsset } from "../../lib/import-asset-storage.js";
4
+ import type { ParsedElement, ParsedSlide } from "./pptx-parser.js";
5
+
6
+ const BROWSER_RENDERABLE_IMAGE_MIME_TYPES = new Set([
7
+ "image/png",
8
+ "image/jpeg",
9
+ "image/gif",
10
+ "image/webp",
11
+ "image/svg+xml",
12
+ "image/bmp",
13
+ ]);
14
+
15
+ export async function uploadPptxSlideImages(args: {
16
+ slide: ParsedSlide;
17
+ slideIndex: number;
18
+ ownerEmail: string;
19
+ }): Promise<{ urls: Record<string, string>; imageSkippedCount: number }> {
20
+ const imageElements = (args.slide.elements ?? []).filter(
21
+ (
22
+ element,
23
+ ): element is ParsedElement & {
24
+ kind: "image";
25
+ image: NonNullable<ParsedElement["image"]>;
26
+ } => element.kind === "image" && Boolean(element.image),
27
+ );
28
+ const urls: Record<string, string> = {};
29
+
30
+ for (const [imageIndex, element] of imageElements.entries()) {
31
+ const image = element.image;
32
+ if (!BROWSER_RENDERABLE_IMAGE_MIME_TYPES.has(image.mimeType)) continue;
33
+ const filename = `pptx-import-${Date.now()}-s${args.slideIndex}-i${imageIndex}-${image.name}`;
34
+ let url: string | undefined;
35
+ try {
36
+ const result = await uploadFile({
37
+ data: Buffer.from(image.data),
38
+ filename,
39
+ mimeType: image.mimeType,
40
+ ownerEmail: args.ownerEmail,
41
+ recordAsset: false,
42
+ });
43
+ url = result?.url;
44
+ } catch {
45
+ url = undefined;
46
+ }
47
+
48
+ if (!url) {
49
+ url =
50
+ (await storeLocalImportedAsset({
51
+ email: args.ownerEmail,
52
+ filename,
53
+ mimeType: image.mimeType,
54
+ data: image.data,
55
+ })) ?? undefined;
56
+ }
57
+ if (url) urls[element.id] = url;
58
+ }
59
+
60
+ return {
61
+ urls,
62
+ imageSkippedCount: Math.max(
63
+ 0,
64
+ imageElements.length - Object.keys(urls).length,
65
+ ),
66
+ };
67
+ }
@@ -1,5 +1,7 @@
1
1
  export {
2
2
  parsePptxPresentation as parsePptx,
3
+ type ParsedPptxElement as ParsedElement,
4
+ type ParsedPptxParagraph as ParsedParagraph,
3
5
  type ParsedPptxImage as ParsedImage,
4
6
  type ParsedPptxPresentation as ParsedPresentation,
5
7
  type ParsedPptxSlide as ParsedSlide,
@@ -9,9 +9,11 @@ import {
9
9
  import { nanoid } from "nanoid";
10
10
 
11
11
  import {
12
+ MAX_FIG_REFERENCE_FILE_BYTES,
13
+ MAX_REFERENCE_FILE_BYTES,
12
14
  SLIDES_REFERENCE_FILE_ERROR_LABEL,
13
15
  isSlidesReferenceFileExtension,
14
- } from "../../shared/upload-types";
16
+ } from "../../shared/upload-types.js";
15
17
  import { tenantUploadDir } from "../lib/tenant-files.js";
16
18
  import {
17
19
  isHostedSlidesRuntime,
@@ -23,8 +25,10 @@ import {
23
25
  withSlidesRequestContext,
24
26
  } from "./request-auth-context.js";
25
27
 
26
- export const MAX_REFERENCE_FILE_BYTES = 50 * 1024 * 1024;
27
- export const MAX_FIG_REFERENCE_FILE_BYTES = 200 * 1024 * 1024;
28
+ export {
29
+ MAX_FIG_REFERENCE_FILE_BYTES,
30
+ MAX_REFERENCE_FILE_BYTES,
31
+ } from "../../shared/upload-types.js";
28
32
  const FIG_LOCAL_COPY_SIGNATURE = new Uint8Array([
29
33
  0x66, 0x69, 0x67, 0x2d, 0x6b, 0x69, 0x77, 0x69,
30
34
  ]);
@@ -2,10 +2,13 @@ import path from "path";
2
2
 
3
3
  import type { AgentChatAttachment } from "@agent-native/core/server";
4
4
 
5
- import { isSlidesReferenceFileExtension } from "../../shared/upload-types.js";
5
+ import {
6
+ isSlidesReferenceFileExtension,
7
+ MAX_REFERENCE_FILE_BYTES,
8
+ } from "../../shared/upload-types.js";
6
9
  import { saveUploadedReferenceFile } from "../handlers/uploads.js";
7
10
 
8
- const MAX_CHAT_UPLOAD_BYTES = 50 * 1024 * 1024;
11
+ const MAX_CHAT_UPLOAD_BYTES = MAX_REFERENCE_FILE_BYTES;
9
12
  const MAX_INLINE_IMAGE_BYTES = 10 * 1024 * 1024;
10
13
 
11
14
  function decodeDataUrl(data: string | undefined): {
@@ -116,8 +119,8 @@ export async function prepareSlidesChatAttachments(args: {
116
119
  "",
117
120
  "File handling rules:",
118
121
  "- If the request refers to the current or visible deck, call `view-screen` first to confirm the active deckId, then pass that deckId to import or slide-edit actions.",
119
- '- PPTX files: call `import-pptx --filePath "<path>" --deckId <deckId>` when updating the visible deck, or omit deckId only when the user explicitly wants a new deck.',
120
- '- PDF and DOCX files: call `import-file --filePath "<path>" --format auto --deckId <deckId>` and use the returned extracted text as source material before creating slides. The returned text is capped for reliability; re-run with maxChars only if more context is needed. If the user wants a direct replacement import, pass `--importIntoDeck true` as well.',
122
+ '- PPTX files: when the user wants the visible deck improved, call `import-pptx --filePath "<path>" --deckId <deckId>` first, then edit those imported slide IDs in place with update-slide. Do not rebuild the source deck with add-slide.',
123
+ '- PDF and DOCX files: call `import-file --filePath "<path>" --format auto --deckId <deckId>` and use the returned extracted text as source material before creating editable slides. For a visual PDF that the user wants preserved, beautified, or restyled from its original layout, pass `--importIntoDeck true` so each page is imported source-faithfully first; keep the full-page image and style around it, with source text persisted in slide notes for inspection.',
121
124
  '- Figma `.fig` files: call `import-file --filePath "<path>" --format fig` to start Builder design-system indexing. Do not create a local design system directly from the upload.',
122
125
  "- For deck-generation requests, start mutating promptly: create or update the first slide as soon as source material is extracted, then continue slide-by-slide with add-slide/update-slide.",
123
126
  '- Image files with an embeddable URL can be inserted directly into slide HTML as `<img src="...">` or used as visual references.',
@@ -5,7 +5,10 @@
5
5
  * and preview them without the app having sent anything yet.
6
6
  */
7
7
 
8
- import { defineTransactionalEmail } from "@agent-native/core/email-catalog";
8
+ import {
9
+ defineTransactionalEmail,
10
+ getTransactionalEmail,
11
+ } from "@agent-native/core/email-catalog";
9
12
 
10
13
  import { renderDeckCommentEmail } from "./comment-notifications.js";
11
14
 
@@ -14,7 +17,9 @@ export const SLIDES_DECK_COMMENT_EMAIL_ID = "slides.deck-comment";
14
17
  let registered = false;
15
18
 
16
19
  export function registerSlidesEmails(): void {
17
- if (registered) return;
20
+ // Nitro can re-evaluate this module during dev HMR while the core registry
21
+ // remains alive. Treat the existing Slides-owned entry as already registered.
22
+ if (registered || getTransactionalEmail(SLIDES_DECK_COMMENT_EMAIL_ID)) return;
18
23
  registered = true;
19
24
 
20
25
  defineTransactionalEmail({
@@ -0,0 +1,50 @@
1
+ export function formatGoogleOAuthError(error: unknown): string {
2
+ const rawMessage =
3
+ error instanceof Error ? error.message : String(error || "Unknown error");
4
+ const message = rawMessage.replace(/\s+/g, " ").trim();
5
+ const normalized = message.toLowerCase();
6
+
7
+ if (
8
+ normalized.includes("insufficient permission") ||
9
+ normalized.includes("insufficient_scope")
10
+ ) {
11
+ return "Google Docs access was denied. Connect again and approve the requested Google Drive file permission.";
12
+ }
13
+ if (
14
+ normalized.includes("redirect_uri_mismatch") ||
15
+ normalized.includes("redirect uri")
16
+ ) {
17
+ return "Google rejected the redirect URI. Add the current Slides callback URL to the Google OAuth client and try again.";
18
+ }
19
+ if (
20
+ normalized.includes("access_denied") ||
21
+ normalized.includes("user denied") ||
22
+ normalized === "denied"
23
+ ) {
24
+ return "Google access was canceled or denied. Connect Google again and approve access.";
25
+ }
26
+ if (
27
+ normalized.includes("invalid_grant") ||
28
+ normalized.includes("connection expired") ||
29
+ normalized.includes("token has been expired")
30
+ ) {
31
+ return "The Google connection expired. Connect Google again.";
32
+ }
33
+ if (
34
+ normalized.includes("invalid_client") ||
35
+ normalized.includes("unauthorized_client")
36
+ ) {
37
+ return "Google rejected the OAuth client configuration. Check the Slides Google OAuth credentials and authorized redirect URI.";
38
+ }
39
+ if (
40
+ normalized.includes("unverified") ||
41
+ normalized.includes("not verified") ||
42
+ normalized.includes("test user") ||
43
+ normalized.includes("sensitive app") ||
44
+ normalized.includes("access blocked")
45
+ ) {
46
+ return "Google blocked this app because its OAuth consent screen is unverified or this account is not an allowed test user.";
47
+ }
48
+
49
+ return message.slice(0, 240) || "Google OAuth failed. Try connecting again.";
50
+ }
@@ -17,6 +17,7 @@ const USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
17
17
  export const GOOGLE_DOCS_PROVIDER = "google-docs";
18
18
  export const GOOGLE_DOCS_SCOPES = [
19
19
  "https://www.googleapis.com/auth/drive.file",
20
+ "https://www.googleapis.com/auth/presentations.readonly",
20
21
  "https://www.googleapis.com/auth/userinfo.email",
21
22
  "https://www.googleapis.com/auth/userinfo.profile",
22
23
  ];
@@ -0,0 +1,127 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import {
6
+ decryptSecretValue,
7
+ encryptSecretValue,
8
+ } from "@agent-native/core/secrets/crypto";
9
+ import { getConfiguredAppBasePath } from "@agent-native/core/server";
10
+
11
+ import { isHostedSlidesRuntime, tenantFileKey } from "./tenant-files.js";
12
+
13
+ const IMPORT_ASSET_PREFIX = "slides-import-asset:v1:";
14
+
15
+ export interface LocalImportedAssetDescriptor {
16
+ kind: "slides-import-asset";
17
+ version: 1;
18
+ ownerKey: string;
19
+ id: string;
20
+ filename: string;
21
+ mimeType: string;
22
+ }
23
+
24
+ export function isLocalImportAssetFallbackAvailable(): boolean {
25
+ return !isHostedSlidesRuntime() && process.env.NODE_ENV !== "production";
26
+ }
27
+
28
+ export async function storeLocalImportedAsset(args: {
29
+ email: string;
30
+ filename: string;
31
+ mimeType: string;
32
+ data: Uint8Array;
33
+ }): Promise<string | null> {
34
+ if (!isLocalImportAssetFallbackAvailable()) return null;
35
+
36
+ const id = randomUUID();
37
+ const safeFilename = path
38
+ .basename(args.filename)
39
+ .replace(/[^a-zA-Z0-9_.-]/g, "-");
40
+ const directory = path.join(
41
+ process.cwd(),
42
+ "data",
43
+ "import-assets",
44
+ tenantFileKey(args.email),
45
+ );
46
+ await mkdir(directory, { recursive: true });
47
+ await writeFile(path.join(directory, `${id}-${safeFilename}`), args.data);
48
+
49
+ const descriptor: LocalImportedAssetDescriptor = {
50
+ kind: "slides-import-asset",
51
+ version: 1,
52
+ ownerKey: tenantFileKey(args.email),
53
+ id,
54
+ filename: safeFilename,
55
+ mimeType: args.mimeType,
56
+ };
57
+ const token = `${IMPORT_ASSET_PREFIX}${encryptSecretValue(
58
+ JSON.stringify(descriptor),
59
+ )}`;
60
+ return `${getConfiguredAppBasePath()}/api/import-assets/${encodeURIComponent(token)}`;
61
+ }
62
+
63
+ export function decodeLocalImportedAssetToken(
64
+ token: string,
65
+ ): LocalImportedAssetDescriptor {
66
+ if (!token.startsWith(IMPORT_ASSET_PREFIX)) {
67
+ throw new Error("Invalid imported asset token");
68
+ }
69
+ const descriptor = JSON.parse(
70
+ decryptSecretValue(token.slice(IMPORT_ASSET_PREFIX.length)),
71
+ ) as LocalImportedAssetDescriptor;
72
+ if (
73
+ descriptor?.kind !== "slides-import-asset" ||
74
+ descriptor.version !== 1 ||
75
+ typeof descriptor.ownerKey !== "string" ||
76
+ !/^[a-f0-9]{24}$/.test(descriptor.ownerKey) ||
77
+ typeof descriptor.id !== "string" ||
78
+ !/^[0-9a-f-]{36}$/.test(descriptor.id) ||
79
+ typeof descriptor.filename !== "string" ||
80
+ descriptor.filename !== path.basename(descriptor.filename) ||
81
+ typeof descriptor.mimeType !== "string"
82
+ ) {
83
+ throw new Error("Invalid imported asset descriptor");
84
+ }
85
+ return descriptor;
86
+ }
87
+
88
+ /** Read a local development import asset after rechecking its owner scope. */
89
+ export async function readLocalImportedAsset(args: {
90
+ token: string;
91
+ email: string;
92
+ }): Promise<{ data: Uint8Array; mimeType: string } | null> {
93
+ if (!isLocalImportAssetFallbackAvailable()) return null;
94
+
95
+ let descriptor: LocalImportedAssetDescriptor;
96
+ try {
97
+ descriptor = decodeLocalImportedAssetToken(decodeURIComponent(args.token));
98
+ } catch {
99
+ // coercion-ok: malformed tokens represent an absent local asset, not a successful read.
100
+ return null;
101
+ }
102
+ if (descriptor.ownerKey !== tenantFileKey(args.email)) return null;
103
+
104
+ const directory = path.resolve(
105
+ process.cwd(),
106
+ "data",
107
+ "import-assets",
108
+ descriptor.ownerKey,
109
+ );
110
+ const filepath = path.resolve(
111
+ directory,
112
+ `${descriptor.id}-${descriptor.filename}`,
113
+ );
114
+ if (!filepath.startsWith(directory + path.sep)) return null;
115
+
116
+ try {
117
+ return {
118
+ data: await readFile(filepath),
119
+ mimeType: descriptor.mimeType,
120
+ };
121
+ } catch {
122
+ // coercion-ok: a missing local asset is an absent export image and is skipped by the caller.
123
+ return null;
124
+ }
125
+ }
126
+
127
+ export { IMPORT_ASSET_PREFIX };