@agent-native/core 0.14.8 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +30 -9
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +14 -4
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +71 -4
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +9 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/appearance/actions/change-appearance.d.ts +3 -0
- package/dist/appearance/actions/change-appearance.d.ts.map +1 -0
- package/dist/appearance/actions/change-appearance.js +29 -0
- package/dist/appearance/actions/change-appearance.js.map +1 -0
- package/dist/chat-threads/store.d.ts +53 -2
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +172 -12
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +114 -37
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/index.js +30 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/workspace-dev.d.ts +25 -1
- package/dist/cli/workspace-dev.d.ts.map +1 -1
- package/dist/cli/workspace-dev.js +275 -49
- package/dist/cli/workspace-dev.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +23 -4
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +276 -53
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AppearancePicker.d.ts +11 -0
- package/dist/client/AppearancePicker.d.ts.map +1 -0
- package/dist/client/AppearancePicker.js +16 -0
- package/dist/client/AppearancePicker.js.map +1 -0
- package/dist/client/AssistantChat.d.ts +35 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +315 -32
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +5 -2
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/ErrorBoundary.d.ts.map +1 -1
- package/dist/client/ErrorBoundary.js +8 -10
- package/dist/client/ErrorBoundary.js.map +1 -1
- package/dist/client/FeedbackButton.d.ts.map +1 -1
- package/dist/client/FeedbackButton.js +1 -1
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +13 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +217 -38
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.d.ts.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.js +37 -14
- package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts +5 -0
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +4 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-sidebar-state.d.ts +12 -0
- package/dist/client/agent-sidebar-state.d.ts.map +1 -1
- package/dist/client/agent-sidebar-state.js +8 -0
- package/dist/client/agent-sidebar-state.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +175 -3
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/appearance.d.ts +40 -0
- package/dist/client/appearance.d.ts.map +1 -0
- package/dist/client/appearance.js +114 -0
- package/dist/client/appearance.js.map +1 -0
- package/dist/client/builder-frame.d.ts +1 -0
- package/dist/client/builder-frame.d.ts.map +1 -1
- package/dist/client/builder-frame.js +19 -9
- package/dist/client/builder-frame.js.map +1 -1
- package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
- package/dist/client/components/CodeRequiredDialog.js +10 -2
- package/dist/client/components/CodeRequiredDialog.js.map +1 -1
- package/dist/client/components/ui/dropdown-menu.js +2 -2
- package/dist/client/components/ui/dropdown-menu.js.map +1 -1
- package/dist/client/components/ui/hover-card.js +1 -1
- package/dist/client/components/ui/hover-card.js.map +1 -1
- package/dist/client/components/ui/popover.js +1 -1
- package/dist/client/components/ui/popover.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +7 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +63 -32
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +5 -0
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +36 -6
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
- package/dist/client/composer/useVoiceDictation.js +13 -1
- package/dist/client/composer/useVoiceDictation.js.map +1 -1
- package/dist/client/error-format.d.ts +3 -2
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +9 -2
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +24 -2
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/index.d.ts +8 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +1 -0
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/org/InvitationBanner.d.ts.map +1 -1
- package/dist/client/org/InvitationBanner.js +23 -2
- package/dist/client/org/InvitationBanner.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +5 -4
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +57 -9
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/org/hooks.d.ts.map +1 -1
- package/dist/client/org/hooks.js +10 -6
- package/dist/client/org/hooks.js.map +1 -1
- package/dist/client/org/workspace-app-links.d.ts +31 -0
- package/dist/client/org/workspace-app-links.d.ts.map +1 -0
- package/dist/client/org/workspace-app-links.js +268 -0
- package/dist/client/org/workspace-app-links.js.map +1 -0
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +18 -5
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/resources/use-resources.d.ts +18 -13
- package/dist/client/resources/use-resources.d.ts.map +1 -1
- package/dist/client/resources/use-resources.js +24 -6
- package/dist/client/resources/use-resources.js.map +1 -1
- package/dist/client/settings/BackgroundAgentSection.d.ts.map +1 -1
- package/dist/client/settings/BackgroundAgentSection.js +9 -1
- package/dist/client/settings/BackgroundAgentSection.js.map +1 -1
- package/dist/client/settings/BrowserSection.d.ts.map +1 -1
- package/dist/client/settings/BrowserSection.js +16 -1
- package/dist/client/settings/BrowserSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +4 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +5 -5
- package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +8 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +50 -13
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.spec.d.ts +2 -0
- package/dist/client/settings/useBuilderStatus.spec.d.ts.map +1 -0
- package/dist/client/settings/useBuilderStatus.spec.js +64 -0
- package/dist/client/settings/useBuilderStatus.spec.js.map +1 -0
- package/dist/client/sharing/ShareButton.d.ts +5 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +60 -6
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/theme.js +1 -1
- package/dist/client/theme.js.map +1 -1
- package/dist/client/transcription/BuilderTranscriptionCta.d.ts.map +1 -1
- package/dist/client/transcription/BuilderTranscriptionCta.js +2 -3
- package/dist/client/transcription/BuilderTranscriptionCta.js.map +1 -1
- package/dist/client/use-change-version.d.ts +46 -0
- package/dist/client/use-change-version.d.ts.map +1 -0
- package/dist/client/use-change-version.js +135 -0
- package/dist/client/use-change-version.js.map +1 -0
- package/dist/client/use-chat-threads.d.ts +16 -2
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +87 -12
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-chat-threads.spec.d.ts +2 -0
- package/dist/client/use-chat-threads.spec.d.ts.map +1 -0
- package/dist/client/use-chat-threads.spec.js +85 -0
- package/dist/client/use-chat-threads.spec.js.map +1 -0
- package/dist/client/use-db-sync.d.ts +5 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +41 -16
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-pinch-zoom.d.ts +35 -0
- package/dist/client/use-pinch-zoom.d.ts.map +1 -0
- package/dist/client/use-pinch-zoom.js +105 -0
- package/dist/client/use-pinch-zoom.js.map +1 -0
- package/dist/deploy/workspace-deploy.d.ts.map +1 -1
- package/dist/deploy/workspace-deploy.js +99 -5
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +3 -0
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/store.d.ts +5 -0
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +16 -1
- package/dist/extensions/store.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +3 -0
- package/dist/file-upload/actions/upload-image.d.ts.map +1 -0
- package/dist/file-upload/actions/upload-image.js +145 -0
- package/dist/file-upload/actions/upload-image.js.map +1 -0
- package/dist/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +31 -11
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/index.d.ts +1 -0
- package/dist/file-upload/index.d.ts.map +1 -1
- package/dist/file-upload/index.js +1 -0
- package/dist/file-upload/index.js.map +1 -1
- package/dist/file-upload/pre-upload-attachments.d.ts +39 -0
- package/dist/file-upload/pre-upload-attachments.d.ts.map +1 -0
- package/dist/file-upload/pre-upload-attachments.js +110 -0
- package/dist/file-upload/pre-upload-attachments.js.map +1 -0
- package/dist/file-upload/registry.d.ts.map +1 -1
- package/dist/file-upload/registry.js +8 -7
- package/dist/file-upload/registry.js.map +1 -1
- package/dist/onboarding/default-steps.js +1 -1
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/org/context.d.ts +15 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +25 -0
- package/dist/org/context.js.map +1 -1
- package/dist/org/handlers.d.ts +2 -2
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +3 -17
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/index.d.ts +1 -1
- package/dist/org/index.d.ts.map +1 -1
- package/dist/org/index.js +1 -1
- package/dist/org/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +6 -0
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js +30 -6
- package/dist/resources/handlers.js.map +1 -1
- package/dist/resources/script-helpers.d.ts +11 -2
- package/dist/resources/script-helpers.d.ts.map +1 -1
- package/dist/resources/script-helpers.js +20 -3
- package/dist/resources/script-helpers.js.map +1 -1
- package/dist/resources/store.d.ts +28 -3
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +170 -20
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/resources/list.d.ts +1 -1
- package/dist/scripts/resources/list.d.ts.map +1 -1
- package/dist/scripts/resources/list.js +16 -4
- package/dist/scripts/resources/list.js.map +1 -1
- package/dist/scripts/resources/write.d.ts +1 -1
- package/dist/scripts/resources/write.d.ts.map +1 -1
- package/dist/scripts/resources/write.js +47 -3
- package/dist/scripts/resources/write.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +8 -3
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +214 -25
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.d.ts +35 -0
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +139 -8
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/app-url.d.ts +12 -6
- package/dist/server/app-url.d.ts.map +1 -1
- package/dist/server/app-url.js +58 -11
- package/dist/server/app-url.js.map +1 -1
- package/dist/server/auth.d.ts +22 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +272 -59
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +0 -4
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +0 -3
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +23 -0
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +29 -14
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +14 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +88 -11
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/google-auth-plugin.d.ts.map +1 -1
- package/dist/server/google-auth-plugin.js +53 -13
- package/dist/server/google-auth-plugin.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +47 -17
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth-public-origin.d.ts.map +1 -1
- package/dist/server/oauth-public-origin.js +19 -1
- package/dist/server/oauth-public-origin.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +62 -15
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +20 -5
- package/dist/server/poll.js.map +1 -1
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +2 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/llm-connection.d.ts +10 -0
- package/dist/shared/llm-connection.d.ts.map +1 -0
- package/dist/shared/llm-connection.js +29 -0
- package/dist/shared/llm-connection.js.map +1 -0
- package/dist/shared/workspace-app-audience.d.ts +25 -0
- package/dist/shared/workspace-app-audience.d.ts.map +1 -0
- package/dist/shared/workspace-app-audience.js +126 -0
- package/dist/shared/workspace-app-audience.js.map +1 -0
- package/dist/shared/workspace-app-id.d.ts +1 -1
- package/dist/shared/workspace-app-id.d.ts.map +1 -1
- package/dist/shared/workspace-app-id.js +1 -0
- package/dist/shared/workspace-app-id.js.map +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +46 -5
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/actions/list-resource-shares.d.ts.map +1 -1
- package/dist/sharing/actions/list-resource-shares.js +8 -1
- package/dist/sharing/actions/list-resource-shares.js.map +1 -1
- package/dist/sharing/actions/set-resource-visibility.d.ts.map +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +12 -3
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
- package/dist/sharing/actions/share-resource.d.ts.map +1 -1
- package/dist/sharing/actions/share-resource.js +50 -1
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/registry.d.ts +26 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/styles/agent-native.css +91 -0
- package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +72 -0
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +60 -37
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +28 -17
- package/dist/templates/default/.agents/skills/shadcn-ui/SKILL.md +79 -0
- package/dist/templates/default/AGENTS.md +22 -19
- package/dist/templates/default/actions/navigate.ts +3 -0
- package/dist/templates/default/app/hooks/use-navigation-state.ts +29 -5
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +251 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +264 -0
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +130 -0
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +112 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +88 -0
- package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +191 -0
- package/dist/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +74 -0
- package/dist/templates/workspace-core/.agents/skills/client-side-routing/SKILL.md +75 -0
- package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +190 -0
- package/dist/templates/workspace-core/.agents/skills/create-skill/SKILL.md +168 -0
- package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +163 -0
- package/dist/templates/workspace-core/.agents/skills/extension-points/SKILL.md +205 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +720 -0
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +92 -0
- package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +285 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +192 -0
- package/dist/templates/workspace-core/.agents/skills/onboarding/SKILL.md +43 -0
- package/dist/templates/workspace-core/.agents/skills/portability/SKILL.md +84 -0
- package/dist/templates/workspace-core/.agents/skills/qa/SKILL.md +313 -0
- package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +112 -0
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +165 -0
- package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +41 -0
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +239 -0
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +191 -0
- package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +79 -0
- package/dist/templates/workspace-core/.agents/skills/server-plugins/SKILL.md +73 -0
- package/dist/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +79 -0
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +217 -0
- package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +132 -0
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +150 -0
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +124 -0
- package/dist/templates/workspace-core/AGENTS.md +16 -1
- package/dist/templates/workspace-root/AGENTS.md +35 -0
- package/dist/templates/workspace-root/README.md +7 -0
- package/dist/vite/action-types-plugin.d.ts.map +1 -1
- package/dist/vite/action-types-plugin.js +4 -0
- package/dist/vite/action-types-plugin.js.map +1 -1
- package/docs/content/authentication.md +36 -0
- package/docs/content/creating-templates.md +15 -0
- package/docs/content/dispatch.md +3 -3
- package/docs/content/multi-app-workspace.md +5 -0
- package/docs/content/tracking.md +12 -0
- package/docs/content/workspace-management.md +39 -4
- package/package.json +15 -12
- package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +72 -0
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +60 -37
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +28 -17
- package/src/templates/default/.agents/skills/shadcn-ui/SKILL.md +79 -0
- package/src/templates/default/AGENTS.md +22 -19
- package/src/templates/default/actions/navigate.ts +3 -0
- package/src/templates/default/app/hooks/use-navigation-state.ts +29 -5
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +251 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +264 -0
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +130 -0
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +112 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +88 -0
- package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +191 -0
- package/src/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +74 -0
- package/src/templates/workspace-core/.agents/skills/client-side-routing/SKILL.md +75 -0
- package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +190 -0
- package/src/templates/workspace-core/.agents/skills/create-skill/SKILL.md +168 -0
- package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +163 -0
- package/src/templates/workspace-core/.agents/skills/extension-points/SKILL.md +205 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +720 -0
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +92 -0
- package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +285 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +192 -0
- package/src/templates/workspace-core/.agents/skills/onboarding/SKILL.md +43 -0
- package/src/templates/workspace-core/.agents/skills/portability/SKILL.md +84 -0
- package/src/templates/workspace-core/.agents/skills/qa/SKILL.md +313 -0
- package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +112 -0
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +165 -0
- package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +41 -0
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +239 -0
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +191 -0
- package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +79 -0
- package/src/templates/workspace-core/.agents/skills/server-plugins/SKILL.md +73 -0
- package/src/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +79 -0
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +217 -0
- package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +132 -0
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +150 -0
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +124 -0
- package/src/templates/workspace-core/AGENTS.md +16 -1
- package/src/templates/workspace-root/AGENTS.md +35 -0
- package/src/templates/workspace-root/README.md +7 -0
|
@@ -109,6 +109,20 @@ export declare function resolveBuilderCredentials(): Promise<{
|
|
|
109
109
|
* `scope: "user"` (the safe default that doesn't trample the org's shared
|
|
110
110
|
* connection).
|
|
111
111
|
*
|
|
112
|
+
* Stale-credential cleanup: before writing the new values we (1) clear ALL
|
|
113
|
+
* five BUILDER_* keys at the target scope, so optional fields the new
|
|
114
|
+
* connection doesn't carry (e.g. user picked a Builder space that returns
|
|
115
|
+
* no orgName) don't leave the previous connection's metadata behind, and
|
|
116
|
+
* (2) when writing at org scope, also clear the writer's own user-scope
|
|
117
|
+
* BUILDER_* rows so a stale personal override from an earlier connect
|
|
118
|
+
* doesn't shadow the new org write on resolution (user scope wins org
|
|
119
|
+
* scope by design — see `resolveScopedBuilderCredential`). The org-scope
|
|
120
|
+
* row is intentionally left alone when writing at user scope: that row is
|
|
121
|
+
* shared with the rest of the org and a single user's personal override
|
|
122
|
+
* shouldn't blow it away. (Victoria's "I signed in again with my Builder
|
|
123
|
+
* space and it still says no credits" report on 2026-05-11 was exactly
|
|
124
|
+
* this stale-shadow case.)
|
|
125
|
+
*
|
|
112
126
|
* Returns the actual scope/scopeId used so the caller can show "Connected
|
|
113
127
|
* for Builder.io" vs "Connected (personal)" in the UI.
|
|
114
128
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-provider.d.ts","sourceRoot":"","sources":["../../src/server/credential-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B;IAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAK5C;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAElB,IAAI,EAAE;QAChB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB;CAUF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,IAAI,OAAO,CAG3D;AAED,wBAAgB,wCAAwC,IAAI,OAAO,CAIlE;AAiBD,KAAK,uBAAuB,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"credential-provider.d.ts","sourceRoot":"","sources":["../../src/server/credential-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,6 +75,12 @@ async function resolveScopedBuilderCredential(key) {
|
|
|
75
75
|
const email = getRequestUserEmail();
|
|
76
76
|
if (!email)
|
|
77
77
|
return null;
|
|
78
|
+
// Always trace Builder lookups — these come up in "I connected Builder but
|
|
79
|
+
// chat still says Use Builder" support requests, and without scope-by-scope
|
|
80
|
+
// visibility into where the lookup actually went, the only diagnostic move
|
|
81
|
+
// is to ask the user to redo the connect flow. Mirrors `resolveSecret`'s
|
|
82
|
+
// default-on trace gate for BUILDER_* keys.
|
|
83
|
+
let scopeAttempted = "user";
|
|
78
84
|
try {
|
|
79
85
|
const { readAppSecret } = await import("../secrets/storage.js");
|
|
80
86
|
// 1. Per-user override: a user can paste their own key in settings to
|
|
@@ -84,8 +90,10 @@ async function resolveScopedBuilderCredential(key) {
|
|
|
84
90
|
scope: "user",
|
|
85
91
|
scopeId: email,
|
|
86
92
|
});
|
|
87
|
-
if (userSecret)
|
|
93
|
+
if (userSecret) {
|
|
94
|
+
console.log(`[builder-credential] key=${key} email=${email} scope=user hit=true`);
|
|
88
95
|
return { value: userSecret.value, source: "user" };
|
|
96
|
+
}
|
|
89
97
|
// 2. Per-org shared credential: when one teammate connects Builder
|
|
90
98
|
// as an owner/admin we write the OAuth result at org scope so
|
|
91
99
|
// every member of that org gets the AI chat working without
|
|
@@ -93,37 +101,47 @@ async function resolveScopedBuilderCredential(key) {
|
|
|
93
101
|
// silently — the caller never has to know which scope answered.
|
|
94
102
|
const orgId = getRequestOrgId();
|
|
95
103
|
if (orgId) {
|
|
104
|
+
scopeAttempted = "org";
|
|
96
105
|
const orgSecret = await readAppSecret({
|
|
97
106
|
key,
|
|
98
107
|
scope: "org",
|
|
99
108
|
scopeId: orgId,
|
|
100
109
|
});
|
|
101
|
-
if (orgSecret)
|
|
110
|
+
if (orgSecret) {
|
|
111
|
+
console.log(`[builder-credential] key=${key} email=${email} orgId=${orgId} scope=org hit=true`);
|
|
102
112
|
return { value: orgSecret.value, source: "org" };
|
|
113
|
+
}
|
|
103
114
|
// Older setup flows wrote shared credentials at workspace scope.
|
|
104
115
|
// Keep reading those rows so status UIs and runtime resolution agree
|
|
105
116
|
// for users who connected before org-scoped Builder credentials existed.
|
|
117
|
+
scopeAttempted = "workspace";
|
|
106
118
|
const workspaceSecret = await readAppSecret({
|
|
107
119
|
key,
|
|
108
120
|
scope: "workspace",
|
|
109
121
|
scopeId: orgId,
|
|
110
122
|
});
|
|
111
123
|
if (workspaceSecret) {
|
|
124
|
+
console.log(`[builder-credential] key=${key} email=${email} orgId=${orgId} scope=workspace hit=true`);
|
|
112
125
|
return { value: workspaceSecret.value, source: "workspace" };
|
|
113
126
|
}
|
|
127
|
+
console.log(`[builder-credential] key=${key} email=${email} orgId=${orgId} miss tried=user,org,workspace`);
|
|
114
128
|
}
|
|
115
129
|
else {
|
|
130
|
+
scopeAttempted = "workspace-solo";
|
|
116
131
|
const soloWorkspaceSecret = await readAppSecret({
|
|
117
132
|
key,
|
|
118
133
|
scope: "workspace",
|
|
119
134
|
scopeId: `solo:${email}`,
|
|
120
135
|
});
|
|
121
136
|
if (soloWorkspaceSecret) {
|
|
137
|
+
console.log(`[builder-credential] key=${key} email=${email} scope=workspace-solo hit=true`);
|
|
122
138
|
return { value: soloWorkspaceSecret.value, source: "workspace" };
|
|
123
139
|
}
|
|
140
|
+
console.log(`[builder-credential] key=${key} email=${email} orgId=(none) miss tried=user,workspace-solo`);
|
|
124
141
|
}
|
|
125
142
|
}
|
|
126
|
-
catch {
|
|
143
|
+
catch (err) {
|
|
144
|
+
console.log(`[builder-credential] key=${key} email=${email} scope=${scopeAttempted} error=${err?.message ?? err}`);
|
|
127
145
|
// Secrets table not ready — treat as missing.
|
|
128
146
|
}
|
|
129
147
|
return null;
|
|
@@ -216,12 +234,39 @@ const BUILDER_CREDENTIAL_KEYS = [
|
|
|
216
234
|
* `scope: "user"` (the safe default that doesn't trample the org's shared
|
|
217
235
|
* connection).
|
|
218
236
|
*
|
|
237
|
+
* Stale-credential cleanup: before writing the new values we (1) clear ALL
|
|
238
|
+
* five BUILDER_* keys at the target scope, so optional fields the new
|
|
239
|
+
* connection doesn't carry (e.g. user picked a Builder space that returns
|
|
240
|
+
* no orgName) don't leave the previous connection's metadata behind, and
|
|
241
|
+
* (2) when writing at org scope, also clear the writer's own user-scope
|
|
242
|
+
* BUILDER_* rows so a stale personal override from an earlier connect
|
|
243
|
+
* doesn't shadow the new org write on resolution (user scope wins org
|
|
244
|
+
* scope by design — see `resolveScopedBuilderCredential`). The org-scope
|
|
245
|
+
* row is intentionally left alone when writing at user scope: that row is
|
|
246
|
+
* shared with the rest of the org and a single user's personal override
|
|
247
|
+
* shouldn't blow it away. (Victoria's "I signed in again with my Builder
|
|
248
|
+
* space and it still says no credits" report on 2026-05-11 was exactly
|
|
249
|
+
* this stale-shadow case.)
|
|
250
|
+
*
|
|
219
251
|
* Returns the actual scope/scopeId used so the caller can show "Connected
|
|
220
252
|
* for Builder.io" vs "Connected (personal)" in the UI.
|
|
221
253
|
*/
|
|
222
254
|
export async function writeBuilderCredentials(email, creds, options) {
|
|
223
|
-
const { writeAppSecret } = await import("../secrets/storage.js");
|
|
255
|
+
const { writeAppSecret, deleteAppSecret } = await import("../secrets/storage.js");
|
|
224
256
|
const target = resolveCredentialWriteScope(email, options?.orgId ?? null, options?.role ?? null);
|
|
257
|
+
// Clear stale rows before writing the new connection. See the function's
|
|
258
|
+
// doc comment for the two cases this handles.
|
|
259
|
+
const cleanups = BUILDER_CREDENTIAL_KEYS.map((key) => deleteAppSecret({
|
|
260
|
+
key,
|
|
261
|
+
scope: target.scope,
|
|
262
|
+
scopeId: target.scopeId,
|
|
263
|
+
}).catch(() => { }));
|
|
264
|
+
if (target.scope === "org") {
|
|
265
|
+
for (const key of BUILDER_CREDENTIAL_KEYS) {
|
|
266
|
+
cleanups.push(deleteAppSecret({ key, scope: "user", scopeId: email }).catch(() => { }));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
await Promise.all(cleanups);
|
|
225
270
|
const entries = [
|
|
226
271
|
{ key: "BUILDER_PRIVATE_KEY", value: creds.privateKey },
|
|
227
272
|
{ key: "BUILDER_PUBLIC_KEY", value: creds.publicKey },
|
|
@@ -281,6 +326,11 @@ export async function deleteBuilderCredentials(email, options) {
|
|
|
281
326
|
* only when the deploy fallback policy allows it.
|
|
282
327
|
*/
|
|
283
328
|
export async function resolveSecret(key) {
|
|
329
|
+
// Log Builder-credential lookups by default so "I connected Builder but
|
|
330
|
+
// chat says no LLM" reports can be diagnosed from server logs without
|
|
331
|
+
// re-running anything. Keep noise low by gating other keys behind a flag.
|
|
332
|
+
const traceLookup = key.startsWith("BUILDER_") ||
|
|
333
|
+
/^(1|true)$/i.test(process.env.DEBUG_CREDENTIAL_RESOLVE ?? "");
|
|
284
334
|
const email = getRequestUserEmail();
|
|
285
335
|
if (email) {
|
|
286
336
|
try {
|
|
@@ -291,8 +341,12 @@ export async function resolveSecret(key) {
|
|
|
291
341
|
scope: "user",
|
|
292
342
|
scopeId: email,
|
|
293
343
|
});
|
|
294
|
-
if (userSecret?.value)
|
|
344
|
+
if (userSecret?.value) {
|
|
345
|
+
if (traceLookup) {
|
|
346
|
+
console.log(`[resolve-secret] key=${key} email=${email} scope=user hit=true`);
|
|
347
|
+
}
|
|
295
348
|
return userSecret.value;
|
|
349
|
+
}
|
|
296
350
|
const orgId = getRequestOrgId();
|
|
297
351
|
if (orgId) {
|
|
298
352
|
// Fall back to the active org's shared row, when present. Builder
|
|
@@ -302,8 +356,12 @@ export async function resolveSecret(key) {
|
|
|
302
356
|
scope: "org",
|
|
303
357
|
scopeId: orgId,
|
|
304
358
|
});
|
|
305
|
-
if (orgSecret?.value)
|
|
359
|
+
if (orgSecret?.value) {
|
|
360
|
+
if (traceLookup) {
|
|
361
|
+
console.log(`[resolve-secret] key=${key} email=${email} orgId=${orgId} scope=org hit=true`);
|
|
362
|
+
}
|
|
306
363
|
return orgSecret.value;
|
|
364
|
+
}
|
|
307
365
|
// Registered secrets historically used "workspace" scope for
|
|
308
366
|
// org-shared configuration. Keep reading it so Settings status and
|
|
309
367
|
// runtime resolution agree.
|
|
@@ -312,8 +370,12 @@ export async function resolveSecret(key) {
|
|
|
312
370
|
scope: "workspace",
|
|
313
371
|
scopeId: orgId,
|
|
314
372
|
});
|
|
315
|
-
if (workspaceSecret?.value)
|
|
373
|
+
if (workspaceSecret?.value) {
|
|
374
|
+
if (traceLookup) {
|
|
375
|
+
console.log(`[resolve-secret] key=${key} email=${email} orgId=${orgId} scope=workspace hit=true`);
|
|
376
|
+
}
|
|
316
377
|
return workspaceSecret.value;
|
|
378
|
+
}
|
|
317
379
|
}
|
|
318
380
|
else {
|
|
319
381
|
const soloWorkspaceSecret = await readAppSecret({
|
|
@@ -321,24 +383,39 @@ export async function resolveSecret(key) {
|
|
|
321
383
|
scope: "workspace",
|
|
322
384
|
scopeId: `solo:${email}`,
|
|
323
385
|
});
|
|
324
|
-
if (soloWorkspaceSecret?.value)
|
|
386
|
+
if (soloWorkspaceSecret?.value) {
|
|
387
|
+
if (traceLookup) {
|
|
388
|
+
console.log(`[resolve-secret] key=${key} email=${email} scope=workspace-solo hit=true`);
|
|
389
|
+
}
|
|
325
390
|
return soloWorkspaceSecret.value;
|
|
391
|
+
}
|
|
326
392
|
}
|
|
327
393
|
}
|
|
328
|
-
catch {
|
|
394
|
+
catch (err) {
|
|
395
|
+
if (traceLookup) {
|
|
396
|
+
console.log(`[resolve-secret] key=${key} email=${email} scope=error err=${err?.message ?? err}`);
|
|
397
|
+
}
|
|
329
398
|
// Secrets table not ready — treat as missing.
|
|
330
399
|
}
|
|
331
400
|
// Authenticated multi-tenant context: never fall back to process.env.
|
|
332
401
|
// The deploy-level value would silently impersonate the actual key
|
|
333
402
|
// owner across every tenant. Local/single-tenant deployments keep the
|
|
334
403
|
// original env fallback for BYO-server workflows.
|
|
335
|
-
|
|
404
|
+
const envFallback = canUseDeployCredentialFallbackForRequest()
|
|
336
405
|
? process.env[key] || null
|
|
337
406
|
: null;
|
|
407
|
+
if (traceLookup) {
|
|
408
|
+
console.log(`[resolve-secret] key=${key} email=${email} orgId=${getRequestOrgId() ?? "(none)"} scope=${envFallback ? "env-fallback" : "none"} hit=${!!envFallback}`);
|
|
409
|
+
}
|
|
410
|
+
return envFallback;
|
|
338
411
|
}
|
|
339
412
|
// Unauthenticated / local-dev / CLI / background context: env fallback
|
|
340
413
|
// is safe because there's no user to mis-identify.
|
|
341
|
-
|
|
414
|
+
const value = process.env[key] || null;
|
|
415
|
+
if (traceLookup) {
|
|
416
|
+
console.log(`[resolve-secret] key=${key} email=(none) scope=env-anonymous hit=${!!value}`);
|
|
417
|
+
}
|
|
418
|
+
return value;
|
|
342
419
|
}
|
|
343
420
|
// ---------------------------------------------------------------------------
|
|
344
421
|
// Synchronous helpers — env-only fallbacks for contexts where per-user
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-provider.js","sourceRoot":"","sources":["../../src/server/credential-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential provider abstraction.\n *\n * Every feature that needs an external credential (Anthropic API key,\n * Google OAuth tokens, OpenAI key, Slack bot token, etc.) should go through\n * one of the resolve*() helpers here instead of reading `process.env`\n * directly. That way the same feature can work in three modes:\n *\n * 1. User set their own key in .env → use it directly\n * 2. User connected Builder via `/cli-auth` → route through Builder proxy\n * 3. Neither → throw FeatureNotConfigured\n *\n * Templates catch FeatureNotConfigured and show a \"Connect Builder (1 click) /\n * set up your own key (guide)\" card.\n *\n * Today these helpers are used by the Builder-hosted LLM gateway, and the\n * shape is meant to grow to cover future managed credential integrations\n * (e.g. additional Builder-hosted services) without rewrites.\n */\n\nimport { getRequestUserEmail, getRequestOrgId } from \"./request-context.js\";\nimport { isLocalDatabase } from \"../db/client.js\";\n\n/**\n * Decide which `app_secrets` scope a Builder/credential write should use.\n *\n * Org scope (\"everyone in this org sees these credentials\") wins when the\n * connecting user is an owner or admin of an active org — the write\n * privileges shared infra. A plain member or a user without an active\n * org falls through to per-user scope so a teammate can't silently\n * overwrite the org-shared connection.\n */\nexport function resolveCredentialWriteScope(\n email: string,\n orgId: string | null | undefined,\n role: string | null | undefined,\n): { scope: \"user\" | \"org\"; scopeId: string } {\n if (orgId && (role === \"owner\" || role === \"admin\")) {\n return { scope: \"org\", scopeId: orgId };\n }\n return { scope: \"user\", scopeId: email };\n}\n\nexport class FeatureNotConfiguredError extends Error {\n readonly requiredCredential: string;\n readonly builderConnectUrl?: string;\n readonly byokDocsUrl?: string;\n\n constructor(opts: {\n requiredCredential: string;\n message?: string;\n builderConnectUrl?: string;\n byokDocsUrl?: string;\n }) {\n super(\n opts.message ??\n `Feature requires credential \"${opts.requiredCredential}\". Connect Builder or set your own key.`,\n );\n this.name = \"FeatureNotConfiguredError\";\n this.requiredCredential = opts.requiredCredential;\n this.builderConnectUrl = opts.builderConnectUrl;\n this.byokDocsUrl = opts.byokDocsUrl;\n }\n}\n\n/**\n * Deployment-level credential fallback for single-tenant/local operation.\n * Multi-tenant call sites must gate this explicitly before calling.\n */\nexport function readDeployCredentialEnv(key: string): string | undefined {\n return process.env[key] || undefined;\n}\n\n/**\n * Deployment-level credentials are safe as a runtime fallback only in local /\n * single-tenant contexts. In hosted production with a shared database, every\n * signed-in user needs their own user/org/workspace credential so one deploy\n * key does not silently power another tenant's chat.\n */\nexport function isDeployCredentialFallbackAllowed(): boolean {\n if (process.env.NODE_ENV !== \"production\") return true;\n return isLocalDatabase();\n}\n\nexport function canUseDeployCredentialFallbackForRequest(): boolean {\n const email = getRequestUserEmail();\n if (!email) return true;\n return isDeployCredentialFallbackAllowed();\n}\n\n// ---------------------------------------------------------------------------\n// Builder credential resolution:\n//\n// 1. **Request-scoped credentials.** A signed-in user can connect Builder\n// through the CLI-auth flow. Owner/admin connections land at org scope;\n// member/no-org connections land at user scope.\n//\n// 2. **Deployment fallback.** BUILDER_PRIVATE_KEY in env still makes local\n// and single-tenant deploys work out of the box, but it no longer blocks\n// per-user connect. Request-scoped credentials win whenever present.\n//\n// To run multi-tenant SaaS: prefer leaving BUILDER_PRIVATE_KEY unset unless a\n// shared fallback identity is intentional.\n// ---------------------------------------------------------------------------\n\ntype BuilderCredentialSource = \"user\" | \"org\" | \"workspace\" | \"env\";\n\nasync function resolveScopedBuilderCredential(\n key: string,\n): Promise<{ value: string; source: \"user\" | \"org\" | \"workspace\" } | null> {\n const email = getRequestUserEmail();\n if (!email) return null;\n\n try {\n const { readAppSecret } = await import(\"../secrets/storage.js\");\n\n // 1. Per-user override: a user can paste their own key in settings to\n // overrule the org-shared one (handy for a personal sandbox).\n const userSecret = await readAppSecret({\n key,\n scope: \"user\",\n scopeId: email,\n });\n if (userSecret) return { value: userSecret.value, source: \"user\" };\n\n // 2. Per-org shared credential: when one teammate connects Builder\n // as an owner/admin we write the OAuth result at org scope so\n // every member of that org gets the AI chat working without\n // re-running the connect flow. Resolution falls back here\n // silently — the caller never has to know which scope answered.\n const orgId = getRequestOrgId();\n if (orgId) {\n const orgSecret = await readAppSecret({\n key,\n scope: \"org\",\n scopeId: orgId,\n });\n if (orgSecret) return { value: orgSecret.value, source: \"org\" };\n\n // Older setup flows wrote shared credentials at workspace scope.\n // Keep reading those rows so status UIs and runtime resolution agree\n // for users who connected before org-scoped Builder credentials existed.\n const workspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: orgId,\n });\n if (workspaceSecret) {\n return { value: workspaceSecret.value, source: \"workspace\" };\n }\n } else {\n const soloWorkspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: `solo:${email}`,\n });\n if (soloWorkspaceSecret) {\n return { value: soloWorkspaceSecret.value, source: \"workspace\" };\n }\n }\n } catch {\n // Secrets table not ready — treat as missing.\n }\n return null;\n}\n\n/**\n * Resolve a Builder credential for the current request. User/org credentials\n * win; deployment env is only a fallback. This lets local/root .env keys keep\n * a template working while still allowing users to connect their own Builder\n * account from Settings or onboarding.\n */\nexport async function resolveBuilderCredential(\n key: string,\n): Promise<string | null> {\n const scoped = await resolveScopedBuilderCredential(key);\n if (scoped) return scoped.value;\n if (!canUseDeployCredentialFallbackForRequest()) return null;\n return readDeployCredentialEnv(key) ?? null;\n}\n\n/**\n * True when `BUILDER_PRIVATE_KEY` is set at the deployment level. This means\n * a deploy-level fallback exists; it does not prevent per-user connect.\n */\nexport function isBuilderEnvManaged(): boolean {\n return !!process.env.BUILDER_PRIVATE_KEY;\n}\n\n/**\n * Resolve the Builder private key for the current request. User/org OAuth\n * credentials win; deploy-level `BUILDER_PRIVATE_KEY` is the fallback.\n */\nexport async function resolveBuilderPrivateKey(): Promise<string | null> {\n return resolveBuilderCredential(\"BUILDER_PRIVATE_KEY\");\n}\n\n/**\n * Resolve the current user's Builder auth header.\n * Returns `\"Bearer <key>\"` or null.\n */\nexport async function resolveBuilderAuthHeader(): Promise<string | null> {\n const key = await resolveBuilderPrivateKey();\n return key ? `Bearer ${key}` : null;\n}\n\n/**\n * Check whether the current user has a Builder private key configured\n * (per-user or deployment-level).\n */\nexport async function resolveHasBuilderPrivateKey(): Promise<boolean> {\n return !!(await resolveBuilderPrivateKey());\n}\n\n/**\n * Resolve where the effective Builder private key came from. Used by status\n * UIs so they can distinguish a deploy fallback from a user/org connection.\n */\nexport async function resolveBuilderCredentialSource(): Promise<BuilderCredentialSource | null> {\n const scoped = await resolveScopedBuilderCredential(\"BUILDER_PRIVATE_KEY\");\n if (scoped) return scoped.source;\n return canUseDeployCredentialFallbackForRequest() &&\n process.env.BUILDER_PRIVATE_KEY\n ? \"env\"\n : null;\n}\n\n/**\n * Resolve all per-user Builder credentials. Used by the status endpoint\n * and agent-chat-plugin to get orgName, userId, etc.\n */\nexport async function resolveBuilderCredentials(): Promise<{\n privateKey: string | null;\n publicKey: string | null;\n userId: string | null;\n orgName: string | null;\n orgKind: string | null;\n}> {\n const [privateKey, publicKey, userId, orgName, orgKind] = await Promise.all([\n resolveBuilderCredential(\"BUILDER_PRIVATE_KEY\"),\n resolveBuilderCredential(\"BUILDER_PUBLIC_KEY\"),\n resolveBuilderCredential(\"BUILDER_USER_ID\"),\n resolveBuilderCredential(\"BUILDER_ORG_NAME\"),\n resolveBuilderCredential(\"BUILDER_ORG_KIND\"),\n ]);\n return { privateKey, publicKey, userId, orgName, orgKind };\n}\n\nconst BUILDER_CREDENTIAL_KEYS = [\n \"BUILDER_PRIVATE_KEY\",\n \"BUILDER_PUBLIC_KEY\",\n \"BUILDER_USER_ID\",\n \"BUILDER_ORG_NAME\",\n \"BUILDER_ORG_KIND\",\n] as const;\n\n/**\n * Write Builder credentials to `app_secrets`.\n *\n * Scope decision (see `resolveCredentialWriteScope`): when the connecting\n * user is owner/admin of an active org we write at `scope: \"org\"` so every\n * member of that org auto-resolves the credentials via\n * `resolveBuilderCredential`'s org fallback — no per-user re-connect\n * needed. A plain member or a user with no active org writes at\n * `scope: \"user\"` (the safe default that doesn't trample the org's shared\n * connection).\n *\n * Returns the actual scope/scopeId used so the caller can show \"Connected\n * for Builder.io\" vs \"Connected (personal)\" in the UI.\n */\nexport async function writeBuilderCredentials(\n email: string,\n creds: {\n privateKey: string;\n publicKey: string;\n userId?: string | null;\n orgName?: string | null;\n orgKind?: string | null;\n },\n options?: { orgId?: string | null; role?: string | null },\n): Promise<{ scope: \"user\" | \"org\"; scopeId: string }> {\n const { writeAppSecret } = await import(\"../secrets/storage.js\");\n const target = resolveCredentialWriteScope(\n email,\n options?.orgId ?? null,\n options?.role ?? null,\n );\n\n const entries: Array<{ key: string; value: string }> = [\n { key: \"BUILDER_PRIVATE_KEY\", value: creds.privateKey },\n { key: \"BUILDER_PUBLIC_KEY\", value: creds.publicKey },\n ];\n if (creds.userId) {\n entries.push({ key: \"BUILDER_USER_ID\", value: creds.userId });\n }\n if (creds.orgName) {\n entries.push({ key: \"BUILDER_ORG_NAME\", value: creds.orgName });\n }\n if (creds.orgKind) {\n entries.push({ key: \"BUILDER_ORG_KIND\", value: creds.orgKind });\n }\n await Promise.all(\n entries.map(({ key, value }) =>\n writeAppSecret({\n key,\n value,\n scope: target.scope,\n scopeId: target.scopeId,\n }),\n ),\n );\n return target;\n}\n\n/**\n * Delete Builder credentials.\n *\n * Default behaviour: clears only this user's per-user override (so a\n * member can disconnect their personal Builder identity without\n * collapsing the org-wide connection for every teammate). To revoke the\n * org's shared connection, pass `{ orgId, role }` for an owner/admin —\n * matching the same authority gate `writeBuilderCredentials` uses on\n * write. Plain members can never reach the org-scoped row.\n */\nexport async function deleteBuilderCredentials(\n email: string,\n options?: { orgId?: string | null; role?: string | null },\n): Promise<{ scope: \"user\" | \"org\"; scopeId: string }> {\n const { deleteAppSecret } = await import(\"../secrets/storage.js\");\n const target = resolveCredentialWriteScope(\n email,\n options?.orgId ?? null,\n options?.role ?? null,\n );\n await Promise.all(\n BUILDER_CREDENTIAL_KEYS.map((key) =>\n deleteAppSecret({\n key,\n scope: target.scope,\n scopeId: target.scopeId,\n }).catch(() => {}),\n ),\n );\n return target;\n}\n\n// ---------------------------------------------------------------------------\n// Generic request-scoped secret resolution\n//\n// New consumers should prefer this over reading `process.env.X` directly.\n// User-pasted and shared secrets live in `app_secrets` (encrypted). The\n// settings UI / onboarding panels can write user, org, or workspace rows.\n// Deploy-level env vars are the fallback for unauthenticated/CLI/background\n// contexts where there's no user to scope by — never the silent fallback\n// for an authenticated request, since on a multi-tenant deploy that would\n// silently identify every user as whoever set the deploy-level key\n// (KVesta Space, 2026-04).\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a request-scoped secret. Reads from `app_secrets` first (current\n * user override, active org, then workspace row); falls back to `process.env`\n * only when the deploy fallback policy allows it.\n */\nexport async function resolveSecret(key: string): Promise<string | null> {\n const email = getRequestUserEmail();\n if (email) {\n try {\n const { readAppSecret } = await import(\"../secrets/storage.js\");\n // Per-user override first.\n const userSecret = await readAppSecret({\n key,\n scope: \"user\",\n scopeId: email,\n });\n if (userSecret?.value) return userSecret.value;\n\n const orgId = getRequestOrgId();\n if (orgId) {\n // Fall back to the active org's shared row, when present. Builder\n // Connect uses this first-class org scope.\n const orgSecret = await readAppSecret({\n key,\n scope: \"org\",\n scopeId: orgId,\n });\n if (orgSecret?.value) return orgSecret.value;\n\n // Registered secrets historically used \"workspace\" scope for\n // org-shared configuration. Keep reading it so Settings status and\n // runtime resolution agree.\n const workspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: orgId,\n });\n if (workspaceSecret?.value) return workspaceSecret.value;\n } else {\n const soloWorkspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: `solo:${email}`,\n });\n if (soloWorkspaceSecret?.value) return soloWorkspaceSecret.value;\n }\n } catch {\n // Secrets table not ready — treat as missing.\n }\n // Authenticated multi-tenant context: never fall back to process.env.\n // The deploy-level value would silently impersonate the actual key\n // owner across every tenant. Local/single-tenant deployments keep the\n // original env fallback for BYO-server workflows.\n return canUseDeployCredentialFallbackForRequest()\n ? process.env[key] || null\n : null;\n }\n // Unauthenticated / local-dev / CLI / background context: env fallback\n // is safe because there's no user to mis-identify.\n return process.env[key] || null;\n}\n\n// ---------------------------------------------------------------------------\n// Synchronous helpers — env-only fallbacks for contexts where per-user\n// lookup isn't possible (sync isConfigured checks, CLI scripts).\n// ---------------------------------------------------------------------------\n\n/**\n * True when a Builder private key is configured at the deployment level.\n *\n * This is the same env-only check as `isBuilderEnvManaged()`. For \"does this\n * request have access to Builder via user/org/env credentials?\" use the async\n * `resolveHasBuilderPrivateKey()`.\n */\nexport function hasBuilderPrivateKey(): boolean {\n return !!process.env.BUILDER_PRIVATE_KEY;\n}\n\n/** The origin for Builder-proxied API calls. Overridable for testing. */\nexport function getBuilderProxyOrigin(): string {\n return (\n process.env.BUILDER_PROXY_ORIGIN ||\n process.env.AIR_HOST ||\n process.env.BUILDER_API_HOST ||\n \"https://ai-services.builder.io\"\n );\n}\n\n/**\n * Base URL for the public Builder LLM gateway (distinct from the internal\n * proxy origin above — the public gateway lives at\n * api.builder.io/agent-native/gateway, while the internal origin is\n * ai-services.builder.io).\n * Override via BUILDER_GATEWAY_BASE_URL for staging / testing.\n */\nexport function getBuilderGatewayBaseUrl(): string {\n return (\n process.env.BUILDER_GATEWAY_BASE_URL ||\n \"https://api.builder.io/agent-native/gateway/v1\"\n );\n}\n\n/**\n * Base URL for Builder-managed image generation.\n * Override via BUILDER_IMAGE_GENERATION_BASE_URL for staging / testing.\n */\nexport function getBuilderImageGenerationBaseUrl(): string {\n return (\n process.env.BUILDER_IMAGE_GENERATION_BASE_URL ||\n \"https://api.builder.io/agent-native/images/v1\"\n );\n}\n\n/** Authorization header value for Builder-proxied calls (env-only). */\nexport function getBuilderAuthHeader(): string | null {\n const key = process.env.BUILDER_PRIVATE_KEY;\n return key ? `Bearer ${key}` : null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"credential-provider.js","sourceRoot":"","sources":["../../src/server/credential-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential provider abstraction.\n *\n * Every feature that needs an external credential (Anthropic API key,\n * Google OAuth tokens, OpenAI key, Slack bot token, etc.) should go through\n * one of the resolve*() helpers here instead of reading `process.env`\n * directly. That way the same feature can work in three modes:\n *\n * 1. User set their own key in .env → use it directly\n * 2. User connected Builder via `/cli-auth` → route through Builder proxy\n * 3. Neither → throw FeatureNotConfigured\n *\n * Templates catch FeatureNotConfigured and show a \"Connect Builder (1 click) /\n * set up your own key (guide)\" card.\n *\n * Today these helpers are used by the Builder-hosted LLM gateway, and the\n * shape is meant to grow to cover future managed credential integrations\n * (e.g. additional Builder-hosted services) without rewrites.\n */\n\nimport { getRequestUserEmail, getRequestOrgId } from \"./request-context.js\";\nimport { isLocalDatabase } from \"../db/client.js\";\n\n/**\n * Decide which `app_secrets` scope a Builder/credential write should use.\n *\n * Org scope (\"everyone in this org sees these credentials\") wins when the\n * connecting user is an owner or admin of an active org — the write\n * privileges shared infra. A plain member or a user without an active\n * org falls through to per-user scope so a teammate can't silently\n * overwrite the org-shared connection.\n */\nexport function resolveCredentialWriteScope(\n email: string,\n orgId: string | null | undefined,\n role: string | null | undefined,\n): { scope: \"user\" | \"org\"; scopeId: string } {\n if (orgId && (role === \"owner\" || role === \"admin\")) {\n return { scope: \"org\", scopeId: orgId };\n }\n return { scope: \"user\", scopeId: email };\n}\n\nexport class FeatureNotConfiguredError extends Error {\n readonly requiredCredential: string;\n readonly builderConnectUrl?: string;\n readonly byokDocsUrl?: string;\n\n constructor(opts: {\n requiredCredential: string;\n message?: string;\n builderConnectUrl?: string;\n byokDocsUrl?: string;\n }) {\n super(\n opts.message ??\n `Feature requires credential \"${opts.requiredCredential}\". Connect Builder or set your own key.`,\n );\n this.name = \"FeatureNotConfiguredError\";\n this.requiredCredential = opts.requiredCredential;\n this.builderConnectUrl = opts.builderConnectUrl;\n this.byokDocsUrl = opts.byokDocsUrl;\n }\n}\n\n/**\n * Deployment-level credential fallback for single-tenant/local operation.\n * Multi-tenant call sites must gate this explicitly before calling.\n */\nexport function readDeployCredentialEnv(key: string): string | undefined {\n return process.env[key] || undefined;\n}\n\n/**\n * Deployment-level credentials are safe as a runtime fallback only in local /\n * single-tenant contexts. In hosted production with a shared database, every\n * signed-in user needs their own user/org/workspace credential so one deploy\n * key does not silently power another tenant's chat.\n */\nexport function isDeployCredentialFallbackAllowed(): boolean {\n if (process.env.NODE_ENV !== \"production\") return true;\n return isLocalDatabase();\n}\n\nexport function canUseDeployCredentialFallbackForRequest(): boolean {\n const email = getRequestUserEmail();\n if (!email) return true;\n return isDeployCredentialFallbackAllowed();\n}\n\n// ---------------------------------------------------------------------------\n// Builder credential resolution:\n//\n// 1. **Request-scoped credentials.** A signed-in user can connect Builder\n// through the CLI-auth flow. Owner/admin connections land at org scope;\n// member/no-org connections land at user scope.\n//\n// 2. **Deployment fallback.** BUILDER_PRIVATE_KEY in env still makes local\n// and single-tenant deploys work out of the box, but it no longer blocks\n// per-user connect. Request-scoped credentials win whenever present.\n//\n// To run multi-tenant SaaS: prefer leaving BUILDER_PRIVATE_KEY unset unless a\n// shared fallback identity is intentional.\n// ---------------------------------------------------------------------------\n\ntype BuilderCredentialSource = \"user\" | \"org\" | \"workspace\" | \"env\";\n\nasync function resolveScopedBuilderCredential(\n key: string,\n): Promise<{ value: string; source: \"user\" | \"org\" | \"workspace\" } | null> {\n const email = getRequestUserEmail();\n if (!email) return null;\n\n // Always trace Builder lookups — these come up in \"I connected Builder but\n // chat still says Use Builder\" support requests, and without scope-by-scope\n // visibility into where the lookup actually went, the only diagnostic move\n // is to ask the user to redo the connect flow. Mirrors `resolveSecret`'s\n // default-on trace gate for BUILDER_* keys.\n let scopeAttempted = \"user\";\n try {\n const { readAppSecret } = await import(\"../secrets/storage.js\");\n\n // 1. Per-user override: a user can paste their own key in settings to\n // overrule the org-shared one (handy for a personal sandbox).\n const userSecret = await readAppSecret({\n key,\n scope: \"user\",\n scopeId: email,\n });\n if (userSecret) {\n console.log(\n `[builder-credential] key=${key} email=${email} scope=user hit=true`,\n );\n return { value: userSecret.value, source: \"user\" };\n }\n\n // 2. Per-org shared credential: when one teammate connects Builder\n // as an owner/admin we write the OAuth result at org scope so\n // every member of that org gets the AI chat working without\n // re-running the connect flow. Resolution falls back here\n // silently — the caller never has to know which scope answered.\n const orgId = getRequestOrgId();\n if (orgId) {\n scopeAttempted = \"org\";\n const orgSecret = await readAppSecret({\n key,\n scope: \"org\",\n scopeId: orgId,\n });\n if (orgSecret) {\n console.log(\n `[builder-credential] key=${key} email=${email} orgId=${orgId} scope=org hit=true`,\n );\n return { value: orgSecret.value, source: \"org\" };\n }\n\n // Older setup flows wrote shared credentials at workspace scope.\n // Keep reading those rows so status UIs and runtime resolution agree\n // for users who connected before org-scoped Builder credentials existed.\n scopeAttempted = \"workspace\";\n const workspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: orgId,\n });\n if (workspaceSecret) {\n console.log(\n `[builder-credential] key=${key} email=${email} orgId=${orgId} scope=workspace hit=true`,\n );\n return { value: workspaceSecret.value, source: \"workspace\" };\n }\n console.log(\n `[builder-credential] key=${key} email=${email} orgId=${orgId} miss tried=user,org,workspace`,\n );\n } else {\n scopeAttempted = \"workspace-solo\";\n const soloWorkspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: `solo:${email}`,\n });\n if (soloWorkspaceSecret) {\n console.log(\n `[builder-credential] key=${key} email=${email} scope=workspace-solo hit=true`,\n );\n return { value: soloWorkspaceSecret.value, source: \"workspace\" };\n }\n console.log(\n `[builder-credential] key=${key} email=${email} orgId=(none) miss tried=user,workspace-solo`,\n );\n }\n } catch (err) {\n console.log(\n `[builder-credential] key=${key} email=${email} scope=${scopeAttempted} error=${(err as Error)?.message ?? err}`,\n );\n // Secrets table not ready — treat as missing.\n }\n return null;\n}\n\n/**\n * Resolve a Builder credential for the current request. User/org credentials\n * win; deployment env is only a fallback. This lets local/root .env keys keep\n * a template working while still allowing users to connect their own Builder\n * account from Settings or onboarding.\n */\nexport async function resolveBuilderCredential(\n key: string,\n): Promise<string | null> {\n const scoped = await resolveScopedBuilderCredential(key);\n if (scoped) return scoped.value;\n if (!canUseDeployCredentialFallbackForRequest()) return null;\n return readDeployCredentialEnv(key) ?? null;\n}\n\n/**\n * True when `BUILDER_PRIVATE_KEY` is set at the deployment level. This means\n * a deploy-level fallback exists; it does not prevent per-user connect.\n */\nexport function isBuilderEnvManaged(): boolean {\n return !!process.env.BUILDER_PRIVATE_KEY;\n}\n\n/**\n * Resolve the Builder private key for the current request. User/org OAuth\n * credentials win; deploy-level `BUILDER_PRIVATE_KEY` is the fallback.\n */\nexport async function resolveBuilderPrivateKey(): Promise<string | null> {\n return resolveBuilderCredential(\"BUILDER_PRIVATE_KEY\");\n}\n\n/**\n * Resolve the current user's Builder auth header.\n * Returns `\"Bearer <key>\"` or null.\n */\nexport async function resolveBuilderAuthHeader(): Promise<string | null> {\n const key = await resolveBuilderPrivateKey();\n return key ? `Bearer ${key}` : null;\n}\n\n/**\n * Check whether the current user has a Builder private key configured\n * (per-user or deployment-level).\n */\nexport async function resolveHasBuilderPrivateKey(): Promise<boolean> {\n return !!(await resolveBuilderPrivateKey());\n}\n\n/**\n * Resolve where the effective Builder private key came from. Used by status\n * UIs so they can distinguish a deploy fallback from a user/org connection.\n */\nexport async function resolveBuilderCredentialSource(): Promise<BuilderCredentialSource | null> {\n const scoped = await resolveScopedBuilderCredential(\"BUILDER_PRIVATE_KEY\");\n if (scoped) return scoped.source;\n return canUseDeployCredentialFallbackForRequest() &&\n process.env.BUILDER_PRIVATE_KEY\n ? \"env\"\n : null;\n}\n\n/**\n * Resolve all per-user Builder credentials. Used by the status endpoint\n * and agent-chat-plugin to get orgName, userId, etc.\n */\nexport async function resolveBuilderCredentials(): Promise<{\n privateKey: string | null;\n publicKey: string | null;\n userId: string | null;\n orgName: string | null;\n orgKind: string | null;\n}> {\n const [privateKey, publicKey, userId, orgName, orgKind] = await Promise.all([\n resolveBuilderCredential(\"BUILDER_PRIVATE_KEY\"),\n resolveBuilderCredential(\"BUILDER_PUBLIC_KEY\"),\n resolveBuilderCredential(\"BUILDER_USER_ID\"),\n resolveBuilderCredential(\"BUILDER_ORG_NAME\"),\n resolveBuilderCredential(\"BUILDER_ORG_KIND\"),\n ]);\n return { privateKey, publicKey, userId, orgName, orgKind };\n}\n\nconst BUILDER_CREDENTIAL_KEYS = [\n \"BUILDER_PRIVATE_KEY\",\n \"BUILDER_PUBLIC_KEY\",\n \"BUILDER_USER_ID\",\n \"BUILDER_ORG_NAME\",\n \"BUILDER_ORG_KIND\",\n] as const;\n\n/**\n * Write Builder credentials to `app_secrets`.\n *\n * Scope decision (see `resolveCredentialWriteScope`): when the connecting\n * user is owner/admin of an active org we write at `scope: \"org\"` so every\n * member of that org auto-resolves the credentials via\n * `resolveBuilderCredential`'s org fallback — no per-user re-connect\n * needed. A plain member or a user with no active org writes at\n * `scope: \"user\"` (the safe default that doesn't trample the org's shared\n * connection).\n *\n * Stale-credential cleanup: before writing the new values we (1) clear ALL\n * five BUILDER_* keys at the target scope, so optional fields the new\n * connection doesn't carry (e.g. user picked a Builder space that returns\n * no orgName) don't leave the previous connection's metadata behind, and\n * (2) when writing at org scope, also clear the writer's own user-scope\n * BUILDER_* rows so a stale personal override from an earlier connect\n * doesn't shadow the new org write on resolution (user scope wins org\n * scope by design — see `resolveScopedBuilderCredential`). The org-scope\n * row is intentionally left alone when writing at user scope: that row is\n * shared with the rest of the org and a single user's personal override\n * shouldn't blow it away. (Victoria's \"I signed in again with my Builder\n * space and it still says no credits\" report on 2026-05-11 was exactly\n * this stale-shadow case.)\n *\n * Returns the actual scope/scopeId used so the caller can show \"Connected\n * for Builder.io\" vs \"Connected (personal)\" in the UI.\n */\nexport async function writeBuilderCredentials(\n email: string,\n creds: {\n privateKey: string;\n publicKey: string;\n userId?: string | null;\n orgName?: string | null;\n orgKind?: string | null;\n },\n options?: { orgId?: string | null; role?: string | null },\n): Promise<{ scope: \"user\" | \"org\"; scopeId: string }> {\n const { writeAppSecret, deleteAppSecret } =\n await import(\"../secrets/storage.js\");\n const target = resolveCredentialWriteScope(\n email,\n options?.orgId ?? null,\n options?.role ?? null,\n );\n\n // Clear stale rows before writing the new connection. See the function's\n // doc comment for the two cases this handles.\n const cleanups: Array<Promise<unknown>> = BUILDER_CREDENTIAL_KEYS.map((key) =>\n deleteAppSecret({\n key,\n scope: target.scope,\n scopeId: target.scopeId,\n }).catch(() => {}),\n );\n if (target.scope === \"org\") {\n for (const key of BUILDER_CREDENTIAL_KEYS) {\n cleanups.push(\n deleteAppSecret({ key, scope: \"user\", scopeId: email }).catch(() => {}),\n );\n }\n }\n await Promise.all(cleanups);\n\n const entries: Array<{ key: string; value: string }> = [\n { key: \"BUILDER_PRIVATE_KEY\", value: creds.privateKey },\n { key: \"BUILDER_PUBLIC_KEY\", value: creds.publicKey },\n ];\n if (creds.userId) {\n entries.push({ key: \"BUILDER_USER_ID\", value: creds.userId });\n }\n if (creds.orgName) {\n entries.push({ key: \"BUILDER_ORG_NAME\", value: creds.orgName });\n }\n if (creds.orgKind) {\n entries.push({ key: \"BUILDER_ORG_KIND\", value: creds.orgKind });\n }\n await Promise.all(\n entries.map(({ key, value }) =>\n writeAppSecret({\n key,\n value,\n scope: target.scope,\n scopeId: target.scopeId,\n }),\n ),\n );\n return target;\n}\n\n/**\n * Delete Builder credentials.\n *\n * Default behaviour: clears only this user's per-user override (so a\n * member can disconnect their personal Builder identity without\n * collapsing the org-wide connection for every teammate). To revoke the\n * org's shared connection, pass `{ orgId, role }` for an owner/admin —\n * matching the same authority gate `writeBuilderCredentials` uses on\n * write. Plain members can never reach the org-scoped row.\n */\nexport async function deleteBuilderCredentials(\n email: string,\n options?: { orgId?: string | null; role?: string | null },\n): Promise<{ scope: \"user\" | \"org\"; scopeId: string }> {\n const { deleteAppSecret } = await import(\"../secrets/storage.js\");\n const target = resolveCredentialWriteScope(\n email,\n options?.orgId ?? null,\n options?.role ?? null,\n );\n await Promise.all(\n BUILDER_CREDENTIAL_KEYS.map((key) =>\n deleteAppSecret({\n key,\n scope: target.scope,\n scopeId: target.scopeId,\n }).catch(() => {}),\n ),\n );\n return target;\n}\n\n// ---------------------------------------------------------------------------\n// Generic request-scoped secret resolution\n//\n// New consumers should prefer this over reading `process.env.X` directly.\n// User-pasted and shared secrets live in `app_secrets` (encrypted). The\n// settings UI / onboarding panels can write user, org, or workspace rows.\n// Deploy-level env vars are the fallback for unauthenticated/CLI/background\n// contexts where there's no user to scope by — never the silent fallback\n// for an authenticated request, since on a multi-tenant deploy that would\n// silently identify every user as whoever set the deploy-level key\n// (KVesta Space, 2026-04).\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a request-scoped secret. Reads from `app_secrets` first (current\n * user override, active org, then workspace row); falls back to `process.env`\n * only when the deploy fallback policy allows it.\n */\nexport async function resolveSecret(key: string): Promise<string | null> {\n // Log Builder-credential lookups by default so \"I connected Builder but\n // chat says no LLM\" reports can be diagnosed from server logs without\n // re-running anything. Keep noise low by gating other keys behind a flag.\n const traceLookup =\n key.startsWith(\"BUILDER_\") ||\n /^(1|true)$/i.test(process.env.DEBUG_CREDENTIAL_RESOLVE ?? \"\");\n const email = getRequestUserEmail();\n if (email) {\n try {\n const { readAppSecret } = await import(\"../secrets/storage.js\");\n // Per-user override first.\n const userSecret = await readAppSecret({\n key,\n scope: \"user\",\n scopeId: email,\n });\n if (userSecret?.value) {\n if (traceLookup) {\n console.log(\n `[resolve-secret] key=${key} email=${email} scope=user hit=true`,\n );\n }\n return userSecret.value;\n }\n\n const orgId = getRequestOrgId();\n if (orgId) {\n // Fall back to the active org's shared row, when present. Builder\n // Connect uses this first-class org scope.\n const orgSecret = await readAppSecret({\n key,\n scope: \"org\",\n scopeId: orgId,\n });\n if (orgSecret?.value) {\n if (traceLookup) {\n console.log(\n `[resolve-secret] key=${key} email=${email} orgId=${orgId} scope=org hit=true`,\n );\n }\n return orgSecret.value;\n }\n\n // Registered secrets historically used \"workspace\" scope for\n // org-shared configuration. Keep reading it so Settings status and\n // runtime resolution agree.\n const workspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: orgId,\n });\n if (workspaceSecret?.value) {\n if (traceLookup) {\n console.log(\n `[resolve-secret] key=${key} email=${email} orgId=${orgId} scope=workspace hit=true`,\n );\n }\n return workspaceSecret.value;\n }\n } else {\n const soloWorkspaceSecret = await readAppSecret({\n key,\n scope: \"workspace\",\n scopeId: `solo:${email}`,\n });\n if (soloWorkspaceSecret?.value) {\n if (traceLookup) {\n console.log(\n `[resolve-secret] key=${key} email=${email} scope=workspace-solo hit=true`,\n );\n }\n return soloWorkspaceSecret.value;\n }\n }\n } catch (err) {\n if (traceLookup) {\n console.log(\n `[resolve-secret] key=${key} email=${email} scope=error err=${(err as Error)?.message ?? err}`,\n );\n }\n // Secrets table not ready — treat as missing.\n }\n // Authenticated multi-tenant context: never fall back to process.env.\n // The deploy-level value would silently impersonate the actual key\n // owner across every tenant. Local/single-tenant deployments keep the\n // original env fallback for BYO-server workflows.\n const envFallback = canUseDeployCredentialFallbackForRequest()\n ? process.env[key] || null\n : null;\n if (traceLookup) {\n console.log(\n `[resolve-secret] key=${key} email=${email} orgId=${getRequestOrgId() ?? \"(none)\"} scope=${envFallback ? \"env-fallback\" : \"none\"} hit=${!!envFallback}`,\n );\n }\n return envFallback;\n }\n // Unauthenticated / local-dev / CLI / background context: env fallback\n // is safe because there's no user to mis-identify.\n const value = process.env[key] || null;\n if (traceLookup) {\n console.log(\n `[resolve-secret] key=${key} email=(none) scope=env-anonymous hit=${!!value}`,\n );\n }\n return value;\n}\n\n// ---------------------------------------------------------------------------\n// Synchronous helpers — env-only fallbacks for contexts where per-user\n// lookup isn't possible (sync isConfigured checks, CLI scripts).\n// ---------------------------------------------------------------------------\n\n/**\n * True when a Builder private key is configured at the deployment level.\n *\n * This is the same env-only check as `isBuilderEnvManaged()`. For \"does this\n * request have access to Builder via user/org/env credentials?\" use the async\n * `resolveHasBuilderPrivateKey()`.\n */\nexport function hasBuilderPrivateKey(): boolean {\n return !!process.env.BUILDER_PRIVATE_KEY;\n}\n\n/** The origin for Builder-proxied API calls. Overridable for testing. */\nexport function getBuilderProxyOrigin(): string {\n return (\n process.env.BUILDER_PROXY_ORIGIN ||\n process.env.AIR_HOST ||\n process.env.BUILDER_API_HOST ||\n \"https://ai-services.builder.io\"\n );\n}\n\n/**\n * Base URL for the public Builder LLM gateway (distinct from the internal\n * proxy origin above — the public gateway lives at\n * api.builder.io/agent-native/gateway, while the internal origin is\n * ai-services.builder.io).\n * Override via BUILDER_GATEWAY_BASE_URL for staging / testing.\n */\nexport function getBuilderGatewayBaseUrl(): string {\n return (\n process.env.BUILDER_GATEWAY_BASE_URL ||\n \"https://api.builder.io/agent-native/gateway/v1\"\n );\n}\n\n/**\n * Base URL for Builder-managed image generation.\n * Override via BUILDER_IMAGE_GENERATION_BASE_URL for staging / testing.\n */\nexport function getBuilderImageGenerationBaseUrl(): string {\n return (\n process.env.BUILDER_IMAGE_GENERATION_BASE_URL ||\n \"https://api.builder.io/agent-native/images/v1\"\n );\n}\n\n/** Authorization header value for Builder-proxied calls (env-only). */\nexport function getBuilderAuthHeader(): string | null {\n const key = process.env.BUILDER_PRIVATE_KEY;\n return key ? `Bearer ${key}` : null;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-auth-plugin.d.ts","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;
|
|
1
|
+
{"version":3,"file":"google-auth-plugin.d.ts","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAkXD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,CAAC,EAAE,uBAAuB,GAChC,cAAc,CAYhB"}
|
|
@@ -101,18 +101,27 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
101
101
|
return origin ? origin + path : __anPath(path);
|
|
102
102
|
}
|
|
103
103
|
function __anBuilderPreviewReturnOrigin() {
|
|
104
|
+
var candidates = [window.location.href, document.referrer || ''];
|
|
104
105
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
if (window.location.ancestorOrigins) {
|
|
107
|
+
for (var j = 0; j < window.location.ancestorOrigins.length; j++) {
|
|
108
|
+
candidates.push(window.location.ancestorOrigins[j]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch(e) {}
|
|
112
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
113
|
+
try {
|
|
114
|
+
var url = new URL(candidates[i]);
|
|
115
|
+
var host = url.hostname.toLowerCase();
|
|
116
|
+
var isPreviewHost =
|
|
117
|
+
host === 'builderio.xyz' || host.slice(-14) === '.builderio.xyz' ||
|
|
118
|
+
host === 'builderio.dev' || host.slice(-14) === '.builderio.dev' ||
|
|
119
|
+
host === 'builder.codes' || host.slice(-14) === '.builder.codes' ||
|
|
120
|
+
host === 'builder.my' || host.slice(-11) === '.builder.my';
|
|
121
|
+
if (url.protocol === 'https:' && isPreviewHost) return url.origin;
|
|
122
|
+
} catch(e) {}
|
|
115
123
|
}
|
|
124
|
+
return '';
|
|
116
125
|
}
|
|
117
126
|
function __anWorkspaceGatewayReturnOrigin() {
|
|
118
127
|
var previewOrigin = __anBuilderPreviewReturnOrigin();
|
|
@@ -172,6 +181,13 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
172
181
|
return false;
|
|
173
182
|
}
|
|
174
183
|
}
|
|
184
|
+
function __anIsAgentNativeDesktop() {
|
|
185
|
+
try {
|
|
186
|
+
return (navigator.userAgent || '').indexOf('AgentNativeDesktop') !== -1;
|
|
187
|
+
} catch(e) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
175
191
|
function __anIsElectron() {
|
|
176
192
|
try {
|
|
177
193
|
return (navigator.userAgent || '').indexOf('Electron') !== -1;
|
|
@@ -180,7 +196,7 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
180
196
|
}
|
|
181
197
|
}
|
|
182
198
|
function __anResolveAuthFlow() {
|
|
183
|
-
if (__anIsBuilderPreview()
|
|
199
|
+
if (__anIsBuilderPreview()) return 'popup';
|
|
184
200
|
var mode = __AN_GOOGLE_AUTH_MODE || 'auto';
|
|
185
201
|
if (mode === 'popup') return 'popup';
|
|
186
202
|
if (mode === 'redirect') return 'redirect';
|
|
@@ -199,6 +215,13 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
199
215
|
function __anFlowDebugId(flowId) {
|
|
200
216
|
return flowId ? String(flowId).slice(-10) : '';
|
|
201
217
|
}
|
|
218
|
+
function __anShouldShowOAuthDebug() {
|
|
219
|
+
try {
|
|
220
|
+
var loc = window.location || {};
|
|
221
|
+
return (typeof loc.hash === 'string' && loc.hash.indexOf('oauth-debug') !== -1) ||
|
|
222
|
+
(typeof loc.search === 'string' && loc.search.indexOf('oauth_debug=1') !== -1);
|
|
223
|
+
} catch(e) { return false; }
|
|
224
|
+
}
|
|
202
225
|
function __anSetOAuthDebug(message, flowId) {
|
|
203
226
|
var text = message + (flowId ? ' (flow ' + __anFlowDebugId(flowId) + ')' : '');
|
|
204
227
|
try {
|
|
@@ -207,7 +230,7 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
207
230
|
var debug = document.getElementById('debug');
|
|
208
231
|
if (debug) {
|
|
209
232
|
debug.textContent = text;
|
|
210
|
-
debug.classList.add('show');
|
|
233
|
+
if (__anShouldShowOAuthDebug()) debug.classList.add('show');
|
|
211
234
|
}
|
|
212
235
|
}
|
|
213
236
|
function __anShowOAuthError(err, btn, message) {
|
|
@@ -258,8 +281,9 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
258
281
|
}
|
|
259
282
|
function __anStartPopupOAuth(ret, btn, err) {
|
|
260
283
|
var flowId = __anNewOAuthFlowId();
|
|
284
|
+
var target = __anIsBuilderPreview() ? __anOAuthReturnTarget(ret) : ret;
|
|
261
285
|
var params = new URLSearchParams();
|
|
262
|
-
if (
|
|
286
|
+
if (target) params.set('return', target);
|
|
263
287
|
params.set('desktop', '1');
|
|
264
288
|
params.set('flow_id', flowId);
|
|
265
289
|
params.set('redirect', '1');
|
|
@@ -285,6 +309,18 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
285
309
|
__anShowOAuthError(err, btn, 'Could not open Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));
|
|
286
310
|
return;
|
|
287
311
|
}
|
|
312
|
+
__anWaitForOAuthExchange(flowId, target, btn, err);
|
|
313
|
+
}
|
|
314
|
+
function __anStartNativeDesktopOAuth(ret, btn, err) {
|
|
315
|
+
var flowId = __anNewOAuthFlowId();
|
|
316
|
+
var params = new URLSearchParams();
|
|
317
|
+
if (ret) params.set('return', ret);
|
|
318
|
+
params.set('desktop', '1');
|
|
319
|
+
params.set('flow_id', flowId);
|
|
320
|
+
params.set('redirect', '1');
|
|
321
|
+
var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();
|
|
322
|
+
__anSetOAuthDebug('Opening Google sign-in in system browser', flowId);
|
|
323
|
+
__anOpenOAuthUrl(url);
|
|
288
324
|
__anWaitForOAuthExchange(flowId, ret, btn, err);
|
|
289
325
|
}
|
|
290
326
|
function __anOpenOAuthUrl(url) {
|
|
@@ -301,6 +337,10 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
301
337
|
__anStartPopupOAuth(ret, btn, err);
|
|
302
338
|
return;
|
|
303
339
|
}
|
|
340
|
+
if (__anIsAgentNativeDesktop()) {
|
|
341
|
+
__anStartNativeDesktopOAuth(ret, btn, err);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
304
344
|
if (__anIsBuilderPreview()) {
|
|
305
345
|
var params = new URLSearchParams();
|
|
306
346
|
if (ret) params.set('return', __anOAuthReturnTarget(ret));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-auth-plugin.js","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EACL,qBAAqB,GAEtB,MAAM,uBAAuB,CAAC;AAe/B,SAAS,kBAAkB,CAAC,cAA8B;IACxD,MAAM,iBAAiB,GAAG,oBAAoB,EAAE,CAAC;IACjD,MAAM,4BAA4B,GAAG,+BAA+B,EAAE,CAAC;IACvE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA+E0B,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;+CACrB,IAAI,CAAC,SAAS,CAAC,4BAA4B,CAAC;gCAC3D,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiPtD,CAAC;AACT,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAiC;IAEjC,OAAO,gBAAgB,CAAC;QACtB,WAAW,EAAE;YACX,gCAAgC;YAChC,gCAAgC;YAChC,wBAAwB;YACxB,GAAG,CAAC,OAAO,EAAE,WAAW,IAAI,EAAE,CAAC;SAChC;QACD,SAAS,EAAE,kBAAkB,CAC3B,qBAAqB,CAAC,OAAO,EAAE,cAAc,CAAC,CAC/C;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { createAuthPlugin } from \"./auth-plugin.js\";\nimport { getPublicOAuthOrigin } from \"./oauth-public-origin.js\";\nimport { getWorkspaceGatewayReturnOrigin } from \"./oauth-return-url.js\";\nimport {\n resolveGoogleAuthMode,\n type GoogleAuthMode,\n} from \"./google-auth-mode.js\";\n\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\n\nexport interface GoogleAuthPluginOptions {\n /** Additional paths accessible without authentication */\n publicPaths?: string[];\n /**\n * Google sign-in flow: `'popup'`, `'redirect'`, or `'auto'` (default).\n * Falls back to `GOOGLE_AUTH_MODE` env var, then `'auto'`. The Builder.io\n * browser iframe always uses popup regardless (Google blocks framing).\n */\n googleAuthMode?: GoogleAuthMode;\n}\n\nfunction getGoogleLoginHtml(googleAuthMode: GoogleAuthMode): string {\n const publicOAuthOrigin = getPublicOAuthOrigin();\n const workspaceGatewayReturnOrigin = getWorkspaceGatewayReturnOrigin();\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n<title>Sign in</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: #0a0a0a;\n color: #e5e5e5;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n width: 100%;\n max-width: 360px;\n padding: 2rem;\n background: #141414;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 12px;\n text-align: center;\n }\n h1 { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n button {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.625rem;\n padding: 0.625rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 8px;\n font-size: 0.9375rem;\n font-weight: 500;\n cursor: pointer;\n }\n button:hover { opacity: 0.85; }\n button:disabled { opacity: 0.5; cursor: wait; }\n .error { margin-top: 0.75rem; font-size: 0.8125rem; color: #f87171; display: none; }\n .error.show { display: block; }\n .debug {\n display: none;\n margin-top: 0.625rem;\n font-size: 0.6875rem;\n line-height: 1.45;\n color: #777;\n word-break: break-word;\n }\n .debug.show { display: block; }\n svg { width: 18px; height: 18px; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <h1>Sign in</h1>\n <p class=\"subtitle\">Continue with your Google account</p>\n <button id=\"btn\" onclick=\"signIn()\">\n <svg viewBox=\"0 0 24 24\"><path fill=\"#4285F4\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\"/><path fill=\"#34A853\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"/><path fill=\"#FBBC05\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"/><path fill=\"#EA4335\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"/></svg>\n Sign in with Google\n </button>\n <p class=\"error\" id=\"err\"></p>\n <p class=\"debug\" id=\"debug\"></p>\n</div>\n<script>\n function __anBasePath() {\n var marker = '/_agent-native';\n var idx = window.location.pathname.indexOf(marker);\n return idx > 0 ? window.location.pathname.slice(0, idx) : '';\n }\n function __anPath(path) {\n return __anBasePath() + path;\n }\n var __AN_PUBLIC_OAUTH_ORIGIN = ${JSON.stringify(publicOAuthOrigin)};\n var __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN = ${JSON.stringify(workspaceGatewayReturnOrigin)};\n var __AN_GOOGLE_AUTH_MODE = ${JSON.stringify(googleAuthMode)};\n function __anConfiguredOAuthOrigin() {\n if (!__AN_PUBLIC_OAUTH_ORIGIN) return '';\n try {\n var origin = new URL(__AN_PUBLIC_OAUTH_ORIGIN).origin;\n return origin && origin !== window.location.origin ? origin : '';\n } catch(e) {\n return '';\n }\n }\n function __anAuthPath(path) {\n var origin = __anIsBuilderPreview() ? __anConfiguredOAuthOrigin() : '';\n return origin ? origin + path : __anPath(path);\n }\n function __anBuilderPreviewReturnOrigin() {\n try {\n var url = new URL(window.location.href);\n var host = url.hostname.toLowerCase();\n var isPreviewHost =\n host === 'builderio.xyz' || host.slice(-14) === '.builderio.xyz' ||\n host === 'builderio.dev' || host.slice(-14) === '.builderio.dev' ||\n host === 'builder.codes' || host.slice(-14) === '.builder.codes' ||\n host === 'builder.my' || host.slice(-11) === '.builder.my';\n return url.protocol === 'https:' && isPreviewHost ? url.origin : '';\n } catch(e) {\n return '';\n }\n }\n function __anWorkspaceGatewayReturnOrigin() {\n var previewOrigin = __anBuilderPreviewReturnOrigin();\n if (previewOrigin) return previewOrigin;\n if (__AN_WORKSPACE_GATEWAY_RETURN_ORIGIN) return __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN;\n return __anIsBuilderDesktop() ? 'http://127.0.0.1:8080' : '';\n }\n function __anNormalizeWorkspaceReturnPath(ret) {\n try {\n var url = new URL(ret || '/', window.location.origin);\n var path = url.pathname || '/';\n if (path === '/dispatch/dispatch') {\n path = '/dispatch';\n } else if (path.indexOf('/dispatch/') === 0) {\n var rest = path.slice('/dispatch/'.length);\n var first = rest.split('/')[0];\n var dispatchRoutes = {\n overview: true, apps: true, metrics: true, vault: true,\n integrations: true, messaging: true, workspace: true,\n agents: true, destinations: true, identities: true,\n approvals: true, audit: true, team: true, 'thread-debug': true,\n 'new-app': true\n };\n if (first === 'dispatch') {\n path = '/dispatch' + rest.slice(first.length);\n } else if (first && !dispatchRoutes[first]) {\n path = '/' + rest;\n }\n }\n return path + url.search + url.hash;\n } catch(e) {\n return ret || '/';\n }\n }\n function __anOAuthReturnTarget(ret) {\n var path = __anNormalizeWorkspaceReturnPath(ret);\n var origin = __anWorkspaceGatewayReturnOrigin();\n return origin ? origin + path : path;\n }\n function __anIsBuilderPreview() {\n try {\n var params = new URLSearchParams(window.location.search);\n if (params.has('builder.preview') || params.has('builder.frameEditing') || params.has('__builder_editing__')) return true;\n } catch(e) {}\n try {\n var ref = document.referrer || '';\n return ref.indexOf('builder.io') !== -1 || ref.indexOf('builder.my') !== -1 || ref.indexOf('builderio.xyz') !== -1 || ref.indexOf('builderio.dev') !== -1 || ref.indexOf('builder.codes') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsBuilderDesktop() {\n try {\n var ua = navigator.userAgent || '';\n return ua.indexOf('Electron') !== -1 && ua.indexOf('AgentNativeDesktop') === -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsElectron() {\n try {\n return (navigator.userAgent || '').indexOf('Electron') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anResolveAuthFlow() {\n if (__anIsBuilderPreview() && !__anIsBuilderDesktop()) return 'popup';\n var mode = __AN_GOOGLE_AUTH_MODE || 'auto';\n if (mode === 'popup') return 'popup';\n if (mode === 'redirect') return 'redirect';\n return __anIsElectron() ? 'redirect' : 'popup';\n }\n var __anOAuthPollTimer = null;\n var __anOAuthPollCount = 0;\n function __anNewOAuthFlowId() {\n try {\n if (window.crypto && typeof window.crypto.randomUUID === 'function') {\n return window.crypto.randomUUID();\n }\n } catch(e) {}\n return 'builder-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);\n }\n function __anFlowDebugId(flowId) {\n return flowId ? String(flowId).slice(-10) : '';\n }\n function __anSetOAuthDebug(message, flowId) {\n var text = message + (flowId ? ' (flow ' + __anFlowDebugId(flowId) + ')' : '');\n try {\n console.info('[agent-native][google-oauth]', { message: message, flow: __anFlowDebugId(flowId) || undefined });\n } catch(e) {}\n var debug = document.getElementById('debug');\n if (debug) {\n debug.textContent = text;\n debug.classList.add('show');\n }\n }\n function __anShowOAuthError(err, btn, message) {\n if (__anOAuthPollTimer) {\n clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n }\n err.textContent = message;\n err.classList.add('show');\n btn.disabled = false;\n }\n function __anWaitForOAuthExchange(flowId, ret, btn, err) {\n var started = Date.now();\n var timeoutMs = 5 * 60 * 1000;\n __anOAuthPollCount = 0;\n async function check() {\n __anOAuthPollCount++;\n try {\n var res = await fetch(__anPath('/_agent-native/auth/desktop-exchange') + '?flow_id=' + encodeURIComponent(flowId), { credentials: 'include' });\n var data = await res.json().catch(function() { return {}; });\n if (data && (data.email || data.token)) {\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n __anSetOAuthDebug('OAuth exchange redeemed; returning to the app', flowId);\n window.location.href = ret || '/';\n return;\n }\n if (data && data.error) {\n __anSetOAuthDebug('OAuth exchange returned an error: ' + (data.message || data.error), flowId);\n __anShowOAuthError(err, btn, data.message || data.error);\n return;\n }\n if (data && data.pending && (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0)) {\n __anSetOAuthDebug('Waiting for the Google callback; polling attempt ' + __anOAuthPollCount, flowId);\n }\n } catch(e) {\n if (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0) {\n __anSetOAuthDebug('Could not reach the OAuth exchange endpoint: ' + (e && e.message ? e.message : 'network error'), flowId);\n }\n }\n if (Date.now() - started > timeoutMs) {\n __anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never redeemed; check server logs for [agent-native][google-oauth].');\n }\n }\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = setInterval(check, 1000);\n setTimeout(check, 500);\n }\n function __anStartPopupOAuth(ret, btn, err) {\n var flowId = __anNewOAuthFlowId();\n var params = new URLSearchParams();\n if (ret) params.set('return', ret);\n params.set('desktop', '1');\n params.set('flow_id', flowId);\n params.set('redirect', '1');\n var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n __anSetOAuthDebug('Opening Google sign-in popup', flowId);\n try {\n var popup = window.open('', '_blank', 'width=640,height=760');\n if (!popup) {\n __anShowOAuthError(err, btn, 'Google popup was blocked. Allow popups for this site and try again (flow ' + __anFlowDebugId(flowId) + ').');\n return;\n }\n try { popup.opener = null; } catch(e) {}\n try {\n popup.location.href = url;\n } catch(e) {\n try { popup.close(); } catch(closeErr) {}\n __anShowOAuthError(err, btn, 'Could not navigate Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anSetOAuthDebug('Google popup opened; waiting for callback', flowId);\n } catch(e) {\n __anShowOAuthError(err, btn, 'Could not open Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anWaitForOAuthExchange(flowId, ret, btn, err);\n }\n function __anOpenOAuthUrl(url) {\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n window.location.href = url;\n }\n async function signIn() {\n var btn = document.getElementById('btn');\n var err = document.getElementById('err');\n var ret = window.location.pathname + window.location.search;\n btn.disabled = true;\n err.classList.remove('show');\n if (__anResolveAuthFlow() === 'popup') {\n __anStartPopupOAuth(ret, btn, err);\n return;\n }\n if (__anIsBuilderPreview()) {\n var params = new URLSearchParams();\n if (ret) params.set('return', __anOAuthReturnTarget(ret));\n params.set('redirect', '1');\n __anSetOAuthDebug('Opening Google sign-in redirect');\n __anOpenOAuthUrl(__anAuthPath('/_agent-native/google/auth-url') + '?' + params.toString());\n return;\n }\n try {\n var res = await fetch(__anPath('/_agent-native/google/auth-url') + '?return=' + encodeURIComponent(ret));\n var data = await res.json();\n if (data.url) {\n __anOpenOAuthUrl(data.url);\n } else {\n err.textContent = data.message || 'Google OAuth is not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.';\n err.classList.add('show');\n btn.disabled = false;\n }\n } catch (e) {\n err.textContent = 'Failed to connect. Please try again.';\n err.classList.add('show');\n btn.disabled = false;\n }\n }\n</script>\n</body>\n</html>`;\n}\n\n/**\n * Create an auth plugin that uses Google OAuth for authentication.\n *\n * When a user visits the app unauthenticated, they see a \"Sign in with Google\"\n * page. The Google OAuth callback (handled by the template) creates a session\n * tied to the user's Google email. `getSession()` then returns `{ email }` for\n * all subsequent requests.\n *\n * Better Auth handles Google OAuth internally when GOOGLE_CLIENT_ID and\n * GOOGLE_CLIENT_SECRET are set. The template's callback route at\n * /_agent-native/google/callback handles mobile deep linking.\n *\n * Usage in a template's `server/plugins/auth.ts`:\n * ```ts\n * import { createGoogleAuthPlugin } from \"@agent-native/core/server\";\n * export default createGoogleAuthPlugin();\n * ```\n */\nexport function createGoogleAuthPlugin(\n options?: GoogleAuthPluginOptions,\n): NitroPluginDef {\n return createAuthPlugin({\n publicPaths: [\n \"/_agent-native/google/callback\",\n \"/_agent-native/google/auth-url\",\n \"/_agent-native/auth/ba\",\n ...(options?.publicPaths ?? []),\n ],\n loginHtml: getGoogleLoginHtml(\n resolveGoogleAuthMode(options?.googleAuthMode),\n ),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"google-auth-plugin.js","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EACL,qBAAqB,GAEtB,MAAM,uBAAuB,CAAC;AAe/B,SAAS,kBAAkB,CAAC,cAA8B;IACxD,MAAM,iBAAiB,GAAG,oBAAoB,EAAE,CAAC;IACjD,MAAM,4BAA4B,GAAG,+BAA+B,EAAE,CAAC;IACvE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA+E0B,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;+CACrB,IAAI,CAAC,SAAS,CAAC,4BAA4B,CAAC;gCAC3D,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAyRtD,CAAC;AACT,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAiC;IAEjC,OAAO,gBAAgB,CAAC;QACtB,WAAW,EAAE;YACX,gCAAgC;YAChC,gCAAgC;YAChC,wBAAwB;YACxB,GAAG,CAAC,OAAO,EAAE,WAAW,IAAI,EAAE,CAAC;SAChC;QACD,SAAS,EAAE,kBAAkB,CAC3B,qBAAqB,CAAC,OAAO,EAAE,cAAc,CAAC,CAC/C;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { createAuthPlugin } from \"./auth-plugin.js\";\nimport { getPublicOAuthOrigin } from \"./oauth-public-origin.js\";\nimport { getWorkspaceGatewayReturnOrigin } from \"./oauth-return-url.js\";\nimport {\n resolveGoogleAuthMode,\n type GoogleAuthMode,\n} from \"./google-auth-mode.js\";\n\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\n\nexport interface GoogleAuthPluginOptions {\n /** Additional paths accessible without authentication */\n publicPaths?: string[];\n /**\n * Google sign-in flow: `'popup'`, `'redirect'`, or `'auto'` (default).\n * Falls back to `GOOGLE_AUTH_MODE` env var, then `'auto'`. The Builder.io\n * browser iframe always uses popup regardless (Google blocks framing).\n */\n googleAuthMode?: GoogleAuthMode;\n}\n\nfunction getGoogleLoginHtml(googleAuthMode: GoogleAuthMode): string {\n const publicOAuthOrigin = getPublicOAuthOrigin();\n const workspaceGatewayReturnOrigin = getWorkspaceGatewayReturnOrigin();\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n<title>Sign in</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: #0a0a0a;\n color: #e5e5e5;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n width: 100%;\n max-width: 360px;\n padding: 2rem;\n background: #141414;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 12px;\n text-align: center;\n }\n h1 { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n button {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.625rem;\n padding: 0.625rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 8px;\n font-size: 0.9375rem;\n font-weight: 500;\n cursor: pointer;\n }\n button:hover { opacity: 0.85; }\n button:disabled { opacity: 0.5; cursor: wait; }\n .error { margin-top: 0.75rem; font-size: 0.8125rem; color: #f87171; display: none; }\n .error.show { display: block; }\n .debug {\n display: none;\n margin-top: 0.625rem;\n font-size: 0.6875rem;\n line-height: 1.45;\n color: #777;\n word-break: break-word;\n }\n .debug.show { display: block; }\n svg { width: 18px; height: 18px; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <h1>Sign in</h1>\n <p class=\"subtitle\">Continue with your Google account</p>\n <button id=\"btn\" onclick=\"signIn()\">\n <svg viewBox=\"0 0 24 24\"><path fill=\"#4285F4\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\"/><path fill=\"#34A853\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"/><path fill=\"#FBBC05\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"/><path fill=\"#EA4335\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"/></svg>\n Sign in with Google\n </button>\n <p class=\"error\" id=\"err\"></p>\n <p class=\"debug\" id=\"debug\"></p>\n</div>\n<script>\n function __anBasePath() {\n var marker = '/_agent-native';\n var idx = window.location.pathname.indexOf(marker);\n return idx > 0 ? window.location.pathname.slice(0, idx) : '';\n }\n function __anPath(path) {\n return __anBasePath() + path;\n }\n var __AN_PUBLIC_OAUTH_ORIGIN = ${JSON.stringify(publicOAuthOrigin)};\n var __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN = ${JSON.stringify(workspaceGatewayReturnOrigin)};\n var __AN_GOOGLE_AUTH_MODE = ${JSON.stringify(googleAuthMode)};\n function __anConfiguredOAuthOrigin() {\n if (!__AN_PUBLIC_OAUTH_ORIGIN) return '';\n try {\n var origin = new URL(__AN_PUBLIC_OAUTH_ORIGIN).origin;\n return origin && origin !== window.location.origin ? origin : '';\n } catch(e) {\n return '';\n }\n }\n function __anAuthPath(path) {\n var origin = __anIsBuilderPreview() ? __anConfiguredOAuthOrigin() : '';\n return origin ? origin + path : __anPath(path);\n }\n function __anBuilderPreviewReturnOrigin() {\n var candidates = [window.location.href, document.referrer || ''];\n try {\n if (window.location.ancestorOrigins) {\n for (var j = 0; j < window.location.ancestorOrigins.length; j++) {\n candidates.push(window.location.ancestorOrigins[j]);\n }\n }\n } catch(e) {}\n for (var i = 0; i < candidates.length; i++) {\n try {\n var url = new URL(candidates[i]);\n var host = url.hostname.toLowerCase();\n var isPreviewHost =\n host === 'builderio.xyz' || host.slice(-14) === '.builderio.xyz' ||\n host === 'builderio.dev' || host.slice(-14) === '.builderio.dev' ||\n host === 'builder.codes' || host.slice(-14) === '.builder.codes' ||\n host === 'builder.my' || host.slice(-11) === '.builder.my';\n if (url.protocol === 'https:' && isPreviewHost) return url.origin;\n } catch(e) {}\n }\n return '';\n }\n function __anWorkspaceGatewayReturnOrigin() {\n var previewOrigin = __anBuilderPreviewReturnOrigin();\n if (previewOrigin) return previewOrigin;\n if (__AN_WORKSPACE_GATEWAY_RETURN_ORIGIN) return __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN;\n return __anIsBuilderDesktop() ? 'http://127.0.0.1:8080' : '';\n }\n function __anNormalizeWorkspaceReturnPath(ret) {\n try {\n var url = new URL(ret || '/', window.location.origin);\n var path = url.pathname || '/';\n if (path === '/dispatch/dispatch') {\n path = '/dispatch';\n } else if (path.indexOf('/dispatch/') === 0) {\n var rest = path.slice('/dispatch/'.length);\n var first = rest.split('/')[0];\n var dispatchRoutes = {\n overview: true, apps: true, metrics: true, vault: true,\n integrations: true, messaging: true, workspace: true,\n agents: true, destinations: true, identities: true,\n approvals: true, audit: true, team: true, 'thread-debug': true,\n 'new-app': true\n };\n if (first === 'dispatch') {\n path = '/dispatch' + rest.slice(first.length);\n } else if (first && !dispatchRoutes[first]) {\n path = '/' + rest;\n }\n }\n return path + url.search + url.hash;\n } catch(e) {\n return ret || '/';\n }\n }\n function __anOAuthReturnTarget(ret) {\n var path = __anNormalizeWorkspaceReturnPath(ret);\n var origin = __anWorkspaceGatewayReturnOrigin();\n return origin ? origin + path : path;\n }\n function __anIsBuilderPreview() {\n try {\n var params = new URLSearchParams(window.location.search);\n if (params.has('builder.preview') || params.has('builder.frameEditing') || params.has('__builder_editing__')) return true;\n } catch(e) {}\n try {\n var ref = document.referrer || '';\n return ref.indexOf('builder.io') !== -1 || ref.indexOf('builder.my') !== -1 || ref.indexOf('builderio.xyz') !== -1 || ref.indexOf('builderio.dev') !== -1 || ref.indexOf('builder.codes') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsBuilderDesktop() {\n try {\n var ua = navigator.userAgent || '';\n return ua.indexOf('Electron') !== -1 && ua.indexOf('AgentNativeDesktop') === -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsAgentNativeDesktop() {\n try {\n return (navigator.userAgent || '').indexOf('AgentNativeDesktop') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsElectron() {\n try {\n return (navigator.userAgent || '').indexOf('Electron') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anResolveAuthFlow() {\n if (__anIsBuilderPreview()) return 'popup';\n var mode = __AN_GOOGLE_AUTH_MODE || 'auto';\n if (mode === 'popup') return 'popup';\n if (mode === 'redirect') return 'redirect';\n return __anIsElectron() ? 'redirect' : 'popup';\n }\n var __anOAuthPollTimer = null;\n var __anOAuthPollCount = 0;\n function __anNewOAuthFlowId() {\n try {\n if (window.crypto && typeof window.crypto.randomUUID === 'function') {\n return window.crypto.randomUUID();\n }\n } catch(e) {}\n return 'builder-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);\n }\n function __anFlowDebugId(flowId) {\n return flowId ? String(flowId).slice(-10) : '';\n }\n function __anShouldShowOAuthDebug() {\n try {\n var loc = window.location || {};\n return (typeof loc.hash === 'string' && loc.hash.indexOf('oauth-debug') !== -1) ||\n (typeof loc.search === 'string' && loc.search.indexOf('oauth_debug=1') !== -1);\n } catch(e) { return false; }\n }\n function __anSetOAuthDebug(message, flowId) {\n var text = message + (flowId ? ' (flow ' + __anFlowDebugId(flowId) + ')' : '');\n try {\n console.info('[agent-native][google-oauth]', { message: message, flow: __anFlowDebugId(flowId) || undefined });\n } catch(e) {}\n var debug = document.getElementById('debug');\n if (debug) {\n debug.textContent = text;\n if (__anShouldShowOAuthDebug()) debug.classList.add('show');\n }\n }\n function __anShowOAuthError(err, btn, message) {\n if (__anOAuthPollTimer) {\n clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n }\n err.textContent = message;\n err.classList.add('show');\n btn.disabled = false;\n }\n function __anWaitForOAuthExchange(flowId, ret, btn, err) {\n var started = Date.now();\n var timeoutMs = 5 * 60 * 1000;\n __anOAuthPollCount = 0;\n async function check() {\n __anOAuthPollCount++;\n try {\n var res = await fetch(__anPath('/_agent-native/auth/desktop-exchange') + '?flow_id=' + encodeURIComponent(flowId), { credentials: 'include' });\n var data = await res.json().catch(function() { return {}; });\n if (data && (data.email || data.token)) {\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n __anSetOAuthDebug('OAuth exchange redeemed; returning to the app', flowId);\n window.location.href = ret || '/';\n return;\n }\n if (data && data.error) {\n __anSetOAuthDebug('OAuth exchange returned an error: ' + (data.message || data.error), flowId);\n __anShowOAuthError(err, btn, data.message || data.error);\n return;\n }\n if (data && data.pending && (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0)) {\n __anSetOAuthDebug('Waiting for the Google callback; polling attempt ' + __anOAuthPollCount, flowId);\n }\n } catch(e) {\n if (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0) {\n __anSetOAuthDebug('Could not reach the OAuth exchange endpoint: ' + (e && e.message ? e.message : 'network error'), flowId);\n }\n }\n if (Date.now() - started > timeoutMs) {\n __anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never redeemed; check server logs for [agent-native][google-oauth].');\n }\n }\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = setInterval(check, 1000);\n setTimeout(check, 500);\n }\n function __anStartPopupOAuth(ret, btn, err) {\n var flowId = __anNewOAuthFlowId();\n var target = __anIsBuilderPreview() ? __anOAuthReturnTarget(ret) : ret;\n var params = new URLSearchParams();\n if (target) params.set('return', target);\n params.set('desktop', '1');\n params.set('flow_id', flowId);\n params.set('redirect', '1');\n var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n __anSetOAuthDebug('Opening Google sign-in popup', flowId);\n try {\n var popup = window.open('', '_blank', 'width=640,height=760');\n if (!popup) {\n __anShowOAuthError(err, btn, 'Google popup was blocked. Allow popups for this site and try again (flow ' + __anFlowDebugId(flowId) + ').');\n return;\n }\n try { popup.opener = null; } catch(e) {}\n try {\n popup.location.href = url;\n } catch(e) {\n try { popup.close(); } catch(closeErr) {}\n __anShowOAuthError(err, btn, 'Could not navigate Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anSetOAuthDebug('Google popup opened; waiting for callback', flowId);\n } catch(e) {\n __anShowOAuthError(err, btn, 'Could not open Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anWaitForOAuthExchange(flowId, target, btn, err);\n }\n function __anStartNativeDesktopOAuth(ret, btn, err) {\n var flowId = __anNewOAuthFlowId();\n var params = new URLSearchParams();\n if (ret) params.set('return', ret);\n params.set('desktop', '1');\n params.set('flow_id', flowId);\n params.set('redirect', '1');\n var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();\n __anSetOAuthDebug('Opening Google sign-in in system browser', flowId);\n __anOpenOAuthUrl(url);\n __anWaitForOAuthExchange(flowId, ret, btn, err);\n }\n function __anOpenOAuthUrl(url) {\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n window.location.href = url;\n }\n async function signIn() {\n var btn = document.getElementById('btn');\n var err = document.getElementById('err');\n var ret = window.location.pathname + window.location.search;\n btn.disabled = true;\n err.classList.remove('show');\n if (__anResolveAuthFlow() === 'popup') {\n __anStartPopupOAuth(ret, btn, err);\n return;\n }\n if (__anIsAgentNativeDesktop()) {\n __anStartNativeDesktopOAuth(ret, btn, err);\n return;\n }\n if (__anIsBuilderPreview()) {\n var params = new URLSearchParams();\n if (ret) params.set('return', __anOAuthReturnTarget(ret));\n params.set('redirect', '1');\n __anSetOAuthDebug('Opening Google sign-in redirect');\n __anOpenOAuthUrl(__anAuthPath('/_agent-native/google/auth-url') + '?' + params.toString());\n return;\n }\n try {\n var res = await fetch(__anPath('/_agent-native/google/auth-url') + '?return=' + encodeURIComponent(ret));\n var data = await res.json();\n if (data.url) {\n __anOpenOAuthUrl(data.url);\n } else {\n err.textContent = data.message || 'Google OAuth is not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.';\n err.classList.add('show');\n btn.disabled = false;\n }\n } catch (e) {\n err.textContent = 'Failed to connect. Please try again.';\n err.classList.add('show');\n btn.disabled = false;\n }\n }\n</script>\n</body>\n</html>`;\n}\n\n/**\n * Create an auth plugin that uses Google OAuth for authentication.\n *\n * When a user visits the app unauthenticated, they see a \"Sign in with Google\"\n * page. The Google OAuth callback (handled by the template) creates a session\n * tied to the user's Google email. `getSession()` then returns `{ email }` for\n * all subsequent requests.\n *\n * Better Auth handles Google OAuth internally when GOOGLE_CLIENT_ID and\n * GOOGLE_CLIENT_SECRET are set. The template's callback route at\n * /_agent-native/google/callback handles mobile deep linking.\n *\n * Usage in a template's `server/plugins/auth.ts`:\n * ```ts\n * import { createGoogleAuthPlugin } from \"@agent-native/core/server\";\n * export default createGoogleAuthPlugin();\n * ```\n */\nexport function createGoogleAuthPlugin(\n options?: GoogleAuthPluginOptions,\n): NitroPluginDef {\n return createAuthPlugin({\n publicPaths: [\n \"/_agent-native/google/callback\",\n \"/_agent-native/google/auth-url\",\n \"/_agent-native/auth/ba\",\n ...(options?.publicPaths ?? []),\n ],\n loginHtml: getGoogleLoginHtml(\n resolveGoogleAuthMode(options?.googleAuthMode),\n ),\n });\n}\n"]}
|