@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.
- package/agent-native.eject.json +9 -0
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +19 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/analytics/AGENTS.md +35 -38
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +1 -1
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +17 -22
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +1 -1
- package/corpus/templates/analytics/actions/navigate.ts +4 -5
- package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +44 -11
- package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
- package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
- package/corpus/templates/analytics/app/i18n-data.ts +680 -0
- package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +6 -0
- package/corpus/templates/analytics/app/lib/sql-query.ts +33 -6
- package/corpus/templates/analytics/app/pages/DataSources.tsx +4 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-load-reliably-during-background-data-sw.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-remain-responsive-while-historical-data.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-query-results-now-render-as-a-table-with-csv-downl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-connect-public-https-custom-apis-and-hand-tested-endpoints-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-durable-agent-worker-memory.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-live-dashboard-panels-use-the-shared-query-timeout.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-on-demand-billing-is-available-as-a-native-dashboard-with-ta.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-visible-dashboard-names-are-unique.md +6 -0
- package/corpus/templates/analytics/netlify.toml +7 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -0
- package/corpus/templates/analytics/server/db/index.ts +27 -0
- package/corpus/templates/analytics/server/db/schema.ts +10 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +227 -60
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +156 -45
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +1 -0
- package/corpus/templates/analytics/server/plugins/db.ts +52 -18
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +28 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +16 -1
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/assets/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/assets/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/assets/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/brain/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/brain/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/brain/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/brain/actions/list-connection-providers.ts +24 -3
- package/corpus/templates/brain/app/lib/dispatch-links.ts +44 -0
- package/corpus/templates/brain/app/routes/sources.tsx +15 -7
- package/corpus/templates/brain/changelog/2026-08-06-brain-provider-setup-links-open-dispatch-correctly-in-standa.md +6 -0
- package/corpus/templates/brain/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +26 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/calendar/AGENTS.md +11 -32
- package/corpus/templates/calendar/actions/update-event.ts +107 -1
- package/corpus/templates/calendar/app/components/calendar/EventCalendarSelect.tsx +83 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +57 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +59 -73
- package/corpus/templates/calendar/app/hooks/use-events.ts +4 -1
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- package/corpus/templates/calendar/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-06-existing-events-can-move-between-connected-google-account-ca.md +6 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +101 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +7 -1
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/clips/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/clips/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/clips/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +9 -24
- package/corpus/templates/clips/app/components/editor/timeline-geometry.ts +15 -0
- package/corpus/templates/clips/app/components/editor/waveform.tsx +4 -17
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +14 -3
- package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
- package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +28 -38
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -27
- package/corpus/templates/clips/changelog/2026-08-06-clips-recovers-from-stalled-recording-setup-without-requirin.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-clips-starts-faster-after-a-deploy-or-idle-period-startup-no.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-deleting-a-recording-returns-cleanly-to-the-library-and-fit-.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-hide-clip-insights-from-viewers-without-analytics-access.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +48 -20
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +275 -74
- package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +84 -0
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +14 -4
- package/corpus/templates/clips/server/plugins/db.ts +214 -26
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/content/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/content/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/content/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/crm/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/crm/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/crm/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/crm/changelog/2026-08-06-crm-starts-faster-after-a-deploy-or-idle-period-one-time-dat.md +6 -0
- package/corpus/templates/crm/server/plugins/db.ts +11 -8
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +36 -1
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/design/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/design/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/design/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/design/actions/import-from-url.ts +1 -1
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
- package/corpus/templates/design/app/pages/Index.tsx +6 -2
- package/corpus/templates/design/changelog/2026-08-05-deleting-a-design-from-the-home-page-no-longer-leaves-the-ap.md +6 -0
- package/corpus/templates/design/changelog/2026-08-05-renaming-a-layer-now-focuses-its-name-field-automatically.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/dispatch/AGENTS.md +8 -1
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
- package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
- package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-ready-workspace-apps-stay-one-click-away-in-a-compact-dispat.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-shared-vault-values-and-management-are-now-restricted-to-wor.md +6 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/factory/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/factory/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/forms/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/forms/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/forms/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/forms/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/macros/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/macros/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/macros/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/mail/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/mail/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/mail/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/mail/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/plan/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/plan/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/plan/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +48 -5
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +37 -1
- package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +6 -0
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +15 -2
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +29 -24
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/slides/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/slides/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/slides/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/slides/AGENTS.md +17 -21
- package/corpus/templates/slides/_gitignore +1 -0
- package/corpus/templates/slides/actions/export-pptx.ts +470 -36
- package/corpus/templates/slides/actions/get-deck.ts +12 -0
- package/corpus/templates/slides/actions/import-file.ts +175 -139
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
- package/corpus/templates/slides/actions/import-pptx.ts +193 -68
- package/corpus/templates/slides/actions/patch-deck.ts +51 -0
- package/corpus/templates/slides/actions/update-slide.ts +29 -3
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +25 -12
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +112 -57
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +202 -0
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +26 -14
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +173 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +182 -141
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +18 -0
- package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +85 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +15 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
- package/corpus/templates/slides/app/lib/deck-preview-frame.ts +15 -0
- package/corpus/templates/slides/app/lib/deck-share-links.ts +9 -0
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +89 -0
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +89 -0
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
- package/corpus/templates/slides/app/pages/Index.tsx +172 -15
- package/corpus/templates/slides/changelog/2026-08-06-clicking-the-gray-canvas-around-a-slide-clears-its-selection.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-deck-thumbnails-keep-a-consistent-listing-frame-across-aspec.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-deep-linked-decks-open-directly-instead-of-being-interrupted.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-google-slides-exports-now-show-clear-import-steps-when-direc.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-import-pdf-google-slides-and-powerpoint-decks-directly-from-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-imported-powerpoint-exports-keep-text-and-images-editable-an.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-presentation-prompts-continue-adding-rendered-slides-until-t.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-public-decks-now-share-read-only-presentation-links-by-defau.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-slides-offers-a-direct-connect-google-button-when-a-drive-co.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-slides-preserve-uploaded-pdf-layouts-accept-larger-reference.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-styled-words-stay-part-of-their-text-box-instead-of-becoming.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +7 -20
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +247 -13
- package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +67 -0
- package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +2 -0
- package/corpus/templates/slides/server/handlers/uploads.ts +7 -3
- package/corpus/templates/slides/server/lib/chat-attachments.ts +7 -4
- package/corpus/templates/slides/server/lib/emails.ts +7 -2
- package/corpus/templates/slides/server/lib/google-docs-error.ts +50 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +1 -0
- package/corpus/templates/slides/server/lib/import-asset-storage.ts +127 -0
- package/corpus/templates/slides/server/lib/source-import.ts +167 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +12 -3
- package/corpus/templates/slides/server/routes/api/import-assets/[token].get.ts +70 -0
- package/corpus/templates/slides/shared/upload-types.ts +3 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/agent/production-agent.d.ts +17 -0
- package/dist/agent/production-agent.js +72 -1
- package/dist/cli/agent-plugin.d.ts +112 -0
- package/dist/cli/agent-plugin.js +1081 -0
- package/dist/cli/app-skill.js +39 -1
- package/dist/cli/code-agent-executor.d.ts +3 -0
- package/dist/cli/code-agent-executor.js +51 -13
- package/dist/cli/index.js +13 -0
- package/dist/cli/templates-meta.js +1 -1
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.js +9 -2
- package/dist/client/AssistantChat.d.ts +8 -0
- package/dist/client/AssistantChat.js +23 -8
- package/dist/client/NewWorkspaceAppFlow.js +1 -0
- package/dist/client/agent-chat-adapter.js +123 -4
- package/dist/client/assistant-ui-recovery.d.ts +2 -1
- package/dist/client/assistant-ui-recovery.js +7 -3
- package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
- package/dist/client/org/TeamPage.js +1 -1
- package/dist/client/require-session.js +1 -1
- package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
- package/dist/client/resources/McpConnectionSuggestion.js +27 -10
- package/dist/client/resources/McpIntegrationDialog.js +77 -45
- package/dist/client/resources/mcp-integration-catalog.d.ts +13 -1
- package/dist/client/resources/mcp-integration-catalog.js +255 -2
- package/dist/client/resources/mcp-integration-logos.js +18 -2
- package/dist/client/settings/AccountSettingsCard.js +2 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts +9 -0
- package/dist/db/client.js +51 -16
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/ddl-guard.d.ts +11 -10
- package/dist/db/ddl-guard.js +40 -12
- package/dist/deploy/build.d.ts +2 -0
- package/dist/deploy/build.js +97 -4
- package/dist/deploy/workspace-deploy.js +0 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/guards/no-env-credentials.js +9 -0
- package/dist/ingestion/index.d.ts +1 -1
- package/dist/ingestion/pptx.d.ts +68 -0
- package/dist/ingestion/pptx.js +660 -213
- package/dist/integrations/plugin.js +9 -6
- package/dist/jobs/scheduler.js +79 -23
- package/dist/localization/default-messages.d.ts +54 -0
- package/dist/localization/default-messages.js +57 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp-client/oauth-routes.d.ts +2 -0
- package/dist/mcp-client/oauth-routes.js +73 -6
- package/dist/observability/routes.d.ts +5 -5
- package/dist/onboarding/app-profile.js +11 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.js +15 -1
- package/dist/scripts/call-agent.js +34 -4
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +36 -1
- package/dist/server/agent-chat-plugin.js +29 -8
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +47 -11
- package/dist/server/better-auth-instance.js +5 -3
- package/dist/server/core-routes-plugin.d.ts +8 -1
- package/dist/server/core-routes-plugin.js +28 -3
- package/dist/server/credential-provider.d.ts +3 -3
- package/dist/server/credential-provider.js +90 -4
- package/dist/server/onboarding-html.d.ts +2 -0
- package/dist/server/onboarding-html.js +115 -29
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/self-dispatch.d.ts +5 -4
- package/dist/server/self-dispatch.js +25 -14
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.js +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/cache-control.d.ts +26 -2
- package/dist/shared/cache-control.js +43 -2
- package/dist/shared/password-policy.d.ts +3 -0
- package/dist/shared/password-policy.js +3 -0
- package/dist/sharing/actions/set-resource-visibility.js +16 -4
- package/dist/sharing/registry.d.ts +14 -0
- package/dist/styles/agent-native.css +6 -0
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/workspace-root/AGENTS.md +7 -0
- package/dist/user-profile/actions/change-password.js +8 -3
- package/dist/user-profile/actions/set-password.js +5 -1
- package/dist/vite/action-types-plugin.js +59 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/getting-started-actions.mdx +38 -189
- package/docs/content/getting-started-database.mdx +80 -164
- package/docs/content/getting-started-pages.mdx +44 -155
- package/docs/content/getting-started.mdx +26 -106
- package/docs/content/skills-guide.mdx +13 -0
- package/package.json +6 -2
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/workspace-root/AGENTS.md +7 -0
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
261
|
-
|
|
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
|
|
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
|
+
}
|
|
@@ -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
|
|
27
|
-
|
|
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 {
|
|
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 =
|
|
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>`
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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 };
|