@agent-native/core 0.7.19 → 0.7.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  3. package/dist/agent/engine/builder-engine.js +45 -2
  4. package/dist/agent/engine/builder-engine.js.map +1 -1
  5. package/dist/agent/loop-settings.d.ts +37 -0
  6. package/dist/agent/loop-settings.d.ts.map +1 -0
  7. package/dist/agent/loop-settings.js +127 -0
  8. package/dist/agent/loop-settings.js.map +1 -0
  9. package/dist/agent/production-agent.d.ts +8 -0
  10. package/dist/agent/production-agent.d.ts.map +1 -1
  11. package/dist/agent/production-agent.js +268 -29
  12. package/dist/agent/production-agent.js.map +1 -1
  13. package/dist/agent/run-manager.d.ts.map +1 -1
  14. package/dist/agent/run-manager.js +76 -3
  15. package/dist/agent/run-manager.js.map +1 -1
  16. package/dist/agent/run-store.d.ts +1 -1
  17. package/dist/agent/run-store.d.ts.map +1 -1
  18. package/dist/agent/run-store.js +65 -2
  19. package/dist/agent/run-store.js.map +1 -1
  20. package/dist/agent/thread-data-builder.d.ts +3 -0
  21. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  22. package/dist/agent/thread-data-builder.js +52 -10
  23. package/dist/agent/thread-data-builder.js.map +1 -1
  24. package/dist/agent/tool-search.d.ts +37 -0
  25. package/dist/agent/tool-search.d.ts.map +1 -0
  26. package/dist/agent/tool-search.js +201 -0
  27. package/dist/agent/tool-search.js.map +1 -0
  28. package/dist/agent/types.d.ts +8 -1
  29. package/dist/agent/types.d.ts.map +1 -1
  30. package/dist/agent/types.js.map +1 -1
  31. package/dist/cli/create.d.ts.map +1 -1
  32. package/dist/cli/create.js +44 -9
  33. package/dist/cli/create.js.map +1 -1
  34. package/dist/cli/workspacify.d.ts +2 -0
  35. package/dist/cli/workspacify.d.ts.map +1 -1
  36. package/dist/cli/workspacify.js +34 -1
  37. package/dist/cli/workspacify.js.map +1 -1
  38. package/dist/client/AssistantChat.d.ts.map +1 -1
  39. package/dist/client/AssistantChat.js +277 -18
  40. package/dist/client/AssistantChat.js.map +1 -1
  41. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  42. package/dist/client/ConnectBuilderCard.js +1 -1
  43. package/dist/client/ConnectBuilderCard.js.map +1 -1
  44. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  45. package/dist/client/MultiTabAssistantChat.js +14 -6
  46. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  47. package/dist/client/NewWorkspaceAppFlow.d.ts +14 -0
  48. package/dist/client/NewWorkspaceAppFlow.d.ts.map +1 -0
  49. package/dist/client/NewWorkspaceAppFlow.js +198 -0
  50. package/dist/client/NewWorkspaceAppFlow.js.map +1 -0
  51. package/dist/client/PoweredByBadge.d.ts +10 -1
  52. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  53. package/dist/client/PoweredByBadge.js +120 -8
  54. package/dist/client/PoweredByBadge.js.map +1 -1
  55. package/dist/client/agent-chat-adapter.d.ts +3 -5
  56. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  57. package/dist/client/agent-chat-adapter.js +26 -19
  58. package/dist/client/agent-chat-adapter.js.map +1 -1
  59. package/dist/client/agent-chat.d.ts.map +1 -1
  60. package/dist/client/agent-chat.js +15 -3
  61. package/dist/client/agent-chat.js.map +1 -1
  62. package/dist/client/analytics.d.ts +1 -1
  63. package/dist/client/analytics.d.ts.map +1 -1
  64. package/dist/client/analytics.js +141 -1
  65. package/dist/client/analytics.js.map +1 -1
  66. package/dist/client/builder-frame.d.ts +10 -0
  67. package/dist/client/builder-frame.d.ts.map +1 -0
  68. package/dist/client/builder-frame.js +94 -0
  69. package/dist/client/builder-frame.js.map +1 -0
  70. package/dist/client/composer/MentionPopover.d.ts.map +1 -1
  71. package/dist/client/composer/MentionPopover.js +5 -1
  72. package/dist/client/composer/MentionPopover.js.map +1 -1
  73. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  74. package/dist/client/composer/TiptapComposer.js +11 -6
  75. package/dist/client/composer/TiptapComposer.js.map +1 -1
  76. package/dist/client/error-format.d.ts +20 -1
  77. package/dist/client/error-format.d.ts.map +1 -1
  78. package/dist/client/error-format.js +53 -5
  79. package/dist/client/error-format.js.map +1 -1
  80. package/dist/client/index.d.ts +3 -1
  81. package/dist/client/index.d.ts.map +1 -1
  82. package/dist/client/index.js +3 -1
  83. package/dist/client/index.js.map +1 -1
  84. package/dist/client/notifications/NotificationsBell.d.ts.map +1 -1
  85. package/dist/client/notifications/NotificationsBell.js +28 -1
  86. package/dist/client/notifications/NotificationsBell.js.map +1 -1
  87. package/dist/client/onboarding/OnboardingPanel.d.ts.map +1 -1
  88. package/dist/client/onboarding/OnboardingPanel.js +88 -6
  89. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  90. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  91. package/dist/client/settings/SettingsPanel.js +145 -9
  92. package/dist/client/settings/SettingsPanel.js.map +1 -1
  93. package/dist/client/settings/useBuilderStatus.d.ts +13 -0
  94. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  95. package/dist/client/settings/useBuilderStatus.js +50 -9
  96. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  97. package/dist/client/sse-event-processor.d.ts +3 -0
  98. package/dist/client/sse-event-processor.d.ts.map +1 -1
  99. package/dist/client/sse-event-processor.js +88 -7
  100. package/dist/client/sse-event-processor.js.map +1 -1
  101. package/dist/client/tools/ToolsListPage.d.ts.map +1 -1
  102. package/dist/client/tools/ToolsListPage.js +16 -1
  103. package/dist/client/tools/ToolsListPage.js.map +1 -1
  104. package/dist/client/tools/ToolsSidebarSection.d.ts.map +1 -1
  105. package/dist/client/tools/ToolsSidebarSection.js +63 -8
  106. package/dist/client/tools/ToolsSidebarSection.js.map +1 -1
  107. package/dist/client/tools/tool-order.d.ts +7 -0
  108. package/dist/client/tools/tool-order.d.ts.map +1 -0
  109. package/dist/client/tools/tool-order.js +47 -0
  110. package/dist/client/tools/tool-order.js.map +1 -0
  111. package/dist/client/transcription/BuilderTranscriptionCta.d.ts.map +1 -1
  112. package/dist/client/transcription/BuilderTranscriptionCta.js +71 -6
  113. package/dist/client/transcription/BuilderTranscriptionCta.js.map +1 -1
  114. package/dist/client/use-send-to-agent-chat.d.ts.map +1 -1
  115. package/dist/client/use-send-to-agent-chat.js +11 -3
  116. package/dist/client/use-send-to-agent-chat.js.map +1 -1
  117. package/dist/client/useProductionAgent.d.ts.map +1 -1
  118. package/dist/client/useProductionAgent.js +1 -1
  119. package/dist/client/useProductionAgent.js.map +1 -1
  120. package/dist/db/client.d.ts.map +1 -1
  121. package/dist/db/client.js +5 -1
  122. package/dist/db/client.js.map +1 -1
  123. package/dist/deploy/build.d.ts +1 -0
  124. package/dist/deploy/build.d.ts.map +1 -1
  125. package/dist/deploy/build.js +4 -1
  126. package/dist/deploy/build.js.map +1 -1
  127. package/dist/oauth-tokens/index.d.ts +1 -1
  128. package/dist/oauth-tokens/index.d.ts.map +1 -1
  129. package/dist/oauth-tokens/index.js +1 -1
  130. package/dist/oauth-tokens/index.js.map +1 -1
  131. package/dist/oauth-tokens/store.d.ts.map +1 -1
  132. package/dist/oauth-tokens/store.js +6 -0
  133. package/dist/oauth-tokens/store.js.map +1 -1
  134. package/dist/observability/store.d.ts.map +1 -1
  135. package/dist/observability/store.js +19 -19
  136. package/dist/observability/store.js.map +1 -1
  137. package/dist/onboarding/default-steps.d.ts.map +1 -1
  138. package/dist/onboarding/default-steps.js +95 -61
  139. package/dist/onboarding/default-steps.js.map +1 -1
  140. package/dist/onboarding/plugin.d.ts.map +1 -1
  141. package/dist/onboarding/plugin.js +17 -8
  142. package/dist/onboarding/plugin.js.map +1 -1
  143. package/dist/org/migrations.js +2 -2
  144. package/dist/org/migrations.js.map +1 -1
  145. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  146. package/dist/scripts/agent-engines/list-agent-engines.js +2 -3
  147. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  148. package/dist/scripts/db/exec.d.ts +2 -1
  149. package/dist/scripts/db/exec.d.ts.map +1 -1
  150. package/dist/scripts/db/exec.js +264 -61
  151. package/dist/scripts/db/exec.js.map +1 -1
  152. package/dist/scripts/db/schema.d.ts.map +1 -1
  153. package/dist/scripts/db/schema.js +16 -4
  154. package/dist/scripts/db/schema.js.map +1 -1
  155. package/dist/scripts/dev/index.d.ts.map +1 -1
  156. package/dist/scripts/dev/index.js +36 -11
  157. package/dist/scripts/dev/index.js.map +1 -1
  158. package/dist/scripts/manage-agent-loop-settings.d.ts +7 -0
  159. package/dist/scripts/manage-agent-loop-settings.d.ts.map +1 -0
  160. package/dist/scripts/manage-agent-loop-settings.js +63 -0
  161. package/dist/scripts/manage-agent-loop-settings.js.map +1 -0
  162. package/dist/scripts/runner.d.ts.map +1 -1
  163. package/dist/scripts/runner.js +11 -0
  164. package/dist/scripts/runner.js.map +1 -1
  165. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  166. package/dist/server/agent-chat-plugin.js +60 -18
  167. package/dist/server/agent-chat-plugin.js.map +1 -1
  168. package/dist/server/app-url.d.ts +5 -4
  169. package/dist/server/app-url.d.ts.map +1 -1
  170. package/dist/server/app-url.js +8 -4
  171. package/dist/server/app-url.js.map +1 -1
  172. package/dist/server/auth.d.ts +8 -0
  173. package/dist/server/auth.d.ts.map +1 -1
  174. package/dist/server/auth.js +82 -29
  175. package/dist/server/auth.js.map +1 -1
  176. package/dist/server/better-auth-instance.d.ts.map +1 -1
  177. package/dist/server/better-auth-instance.js +16 -5
  178. package/dist/server/better-auth-instance.js.map +1 -1
  179. package/dist/server/builder-browser.d.ts +12 -0
  180. package/dist/server/builder-browser.d.ts.map +1 -1
  181. package/dist/server/builder-browser.js +36 -4
  182. package/dist/server/builder-browser.js.map +1 -1
  183. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  184. package/dist/server/core-routes-plugin.js +350 -53
  185. package/dist/server/core-routes-plugin.js.map +1 -1
  186. package/dist/server/credential-provider.d.ts +21 -3
  187. package/dist/server/credential-provider.d.ts.map +1 -1
  188. package/dist/server/credential-provider.js +51 -21
  189. package/dist/server/credential-provider.js.map +1 -1
  190. package/dist/server/google-oauth.d.ts +3 -0
  191. package/dist/server/google-oauth.d.ts.map +1 -1
  192. package/dist/server/google-oauth.js +27 -3
  193. package/dist/server/google-oauth.js.map +1 -1
  194. package/dist/server/index.d.ts +4 -3
  195. package/dist/server/index.d.ts.map +1 -1
  196. package/dist/server/index.js +4 -3
  197. package/dist/server/index.js.map +1 -1
  198. package/dist/server/onboarding-html.js +2 -2
  199. package/dist/server/onboarding-html.js.map +1 -1
  200. package/dist/server/schema-prompt.d.ts.map +1 -1
  201. package/dist/server/schema-prompt.js +2 -1
  202. package/dist/server/schema-prompt.js.map +1 -1
  203. package/dist/server/security-headers.d.ts +3 -0
  204. package/dist/server/security-headers.d.ts.map +1 -1
  205. package/dist/server/security-headers.js +7 -1
  206. package/dist/server/security-headers.js.map +1 -1
  207. package/dist/server/ssr-handler.d.ts.map +1 -1
  208. package/dist/server/ssr-handler.js +31 -6
  209. package/dist/server/ssr-handler.js.map +1 -1
  210. package/dist/templates/default/_gitignore +5 -1
  211. package/dist/templates/default/app/root.tsx +1 -0
  212. package/dist/templates/default/public/favicon.svg +3 -3
  213. package/dist/templates/default/public/icon-180.svg +3 -3
  214. package/dist/templates/default/public/icon-192.svg +3 -3
  215. package/dist/templates/default/public/icon-512.svg +3 -3
  216. package/dist/templates/workspace-core/AGENTS.md +23 -7
  217. package/dist/templates/workspace-core/package.json +2 -1
  218. package/dist/templates/workspace-core/src/credentials.ts +22 -11
  219. package/dist/templates/workspace-root/.env.example +7 -0
  220. package/dist/templates/workspace-root/README.md +6 -3
  221. package/dist/templates/workspace-root/_gitignore +3 -0
  222. package/dist/templates/workspace-root/package.json +3 -1
  223. package/dist/templates/workspace-root/scripts/workspace-dev.ts +375 -0
  224. package/dist/tools/actions.d.ts.map +1 -1
  225. package/dist/tools/actions.js +2 -0
  226. package/dist/tools/actions.js.map +1 -1
  227. package/dist/tools/html-shell.d.ts.map +1 -1
  228. package/dist/tools/html-shell.js +13 -1
  229. package/dist/tools/html-shell.js.map +1 -1
  230. package/dist/tools/store.d.ts.map +1 -1
  231. package/dist/tools/store.js +10 -10
  232. package/dist/tools/store.js.map +1 -1
  233. package/dist/tracking/providers.d.ts +1 -0
  234. package/dist/tracking/providers.d.ts.map +1 -1
  235. package/dist/tracking/providers.js +72 -0
  236. package/dist/tracking/providers.js.map +1 -1
  237. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  238. package/dist/vite/action-types-plugin.js +106 -9
  239. package/dist/vite/action-types-plugin.js.map +1 -1
  240. package/dist/vite/client.d.ts.map +1 -1
  241. package/dist/vite/client.js +62 -1
  242. package/dist/vite/client.js.map +1 -1
  243. package/docs/content/authentication.md +17 -13
  244. package/docs/content/deployment.md +11 -11
  245. package/docs/content/mcp-clients.md +2 -2
  246. package/docs/content/onboarding.md +32 -30
  247. package/docs/content/security.md +1 -1
  248. package/docs/content/tools.md +4 -0
  249. package/package.json +2 -2
  250. package/src/templates/default/_gitignore +5 -1
  251. package/src/templates/default/app/root.tsx +1 -0
  252. package/src/templates/default/public/favicon.svg +3 -3
  253. package/src/templates/default/public/icon-180.svg +3 -3
  254. package/src/templates/default/public/icon-192.svg +3 -3
  255. package/src/templates/default/public/icon-512.svg +3 -3
  256. package/src/templates/workspace-core/AGENTS.md +23 -7
  257. package/src/templates/workspace-core/package.json +2 -1
  258. package/src/templates/workspace-core/src/credentials.ts +22 -11
  259. package/src/templates/workspace-root/.env.example +7 -0
  260. package/src/templates/workspace-root/README.md +6 -3
  261. package/src/templates/workspace-root/_gitignore +3 -0
  262. package/src/templates/workspace-root/package.json +3 -1
  263. package/src/templates/workspace-root/scripts/workspace-dev.ts +375 -0
@@ -1,7 +1,7 @@
1
1
  export { createServer, upsertEnvFile, } from "./create-server.js";
2
2
  export { readBody, streamFile } from "./h3-helpers.js";
3
3
  export { createSSEHandler } from "./sse.js";
4
- export { mountAuthMiddleware, autoMountAuth, getSession, addSession, removeSession, getSessionEmail, runAuthGuard, setDesktopExchange, DEV_MODE_USER_EMAIL, safeReturnPath, } from "./auth.js";
4
+ export { mountAuthMiddleware, autoMountAuth, getSession, addSession, removeSession, getSessionEmail, runAuthGuard, setDesktopExchange, setDesktopExchangeError, DEV_MODE_USER_EMAIL, safeReturnPath, } from "./auth.js";
5
5
  export { requireEnvKey } from "./missing-key.js";
6
6
  export { verifyCaptcha } from "./captcha.js";
7
7
  export { createProductionAgentHandler, } from "../agent/index.js";
@@ -30,8 +30,9 @@ export { formatDateInTimezone, todayInTimezone } from "./date-utils.js";
30
30
  export { createOnboardingPlugin, defaultOnboardingPlugin, } from "../onboarding/plugin.js";
31
31
  export { registerFileUploadProvider, unregisterFileUploadProvider, listFileUploadProviders, getActiveFileUploadProvider, uploadFile, builderFileUploadProvider, } from "../file-upload/index.js";
32
32
  export { createIntegrationsPlugin, defaultIntegrationsPlugin, slackAdapter, telegramAdapter, whatsappAdapter, emailAdapter, } from "../integrations/index.js";
33
- export { isElectron, isMobile, getOrigin, getAppBasePath, getAppUrl, encodeOAuthState, decodeOAuthState, resolveOAuthOwner, createOAuthSession, oauthCallbackResponse, oauthErrorPage, } from "./google-oauth.js";
34
- export { FeatureNotConfiguredError, hasBuilderPrivateKey, getBuilderProxyOrigin, getBuilderAuthHeader, resolveBuilderPrivateKey, resolveBuilderAuthHeader, resolveHasBuilderPrivateKey, resolveBuilderCredentials, resolveBuilderCredential, writeBuilderCredentials, deleteBuilderCredentials, resolveSecret, } from "./credential-provider.js";
33
+ export { isElectron, isMobile, getOrigin, getAppBasePath, getAppUrl, encodeOAuthState, decodeOAuthState, resolveOAuthOwner, createOAuthSession, oauthCallbackResponse, oauthErrorPage, oauthDesktopExchangePage, } from "./google-oauth.js";
34
+ export { FeatureNotConfiguredError, hasBuilderPrivateKey, isBuilderEnvManaged, getBuilderProxyOrigin, getBuilderAuthHeader, resolveBuilderPrivateKey, resolveBuilderAuthHeader, resolveHasBuilderPrivateKey, resolveBuilderCredentials, resolveBuilderCredential, writeBuilderCredentials, deleteBuilderCredentials, resolveSecret, } from "./credential-provider.js";
35
+ export { getBuilderBranchProjectId, isBuilderBranchingEnabled, runBuilderAgent, } from "./builder-browser.js";
35
36
  export { sendEmail, isEmailConfigured, getEmailProvider, } from "./email.js";
36
37
  export { renderEmail, emailStrong, } from "./email-template.js";
37
38
  export { getAppProductionUrl, getFirstPartyProdUrl } from "./app-url.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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","sourcesContent":["export {\n createServer,\n upsertEnvFile,\n type CreateServerOptions,\n type EnvKeyConfig,\n} from \"./create-server.js\";\n\nexport { readBody, streamFile } from \"./h3-helpers.js\";\nexport { createSSEHandler, type SSEHandlerOptions } from \"./sse.js\";\nexport {\n mountAuthMiddleware,\n autoMountAuth,\n getSession,\n addSession,\n removeSession,\n getSessionEmail,\n runAuthGuard,\n setDesktopExchange,\n DEV_MODE_USER_EMAIL,\n safeReturnPath,\n type AuthSession,\n type AuthOptions,\n} from \"./auth.js\";\nexport { requireEnvKey, type MissingKeyResponse } from \"./missing-key.js\";\nexport { verifyCaptcha, type CaptchaVerifyResult } from \"./captcha.js\";\nexport {\n createProductionAgentHandler,\n type ActionEntry,\n type ScriptEntry,\n type ProductionAgentOptions,\n type ActionTool,\n type ScriptTool,\n type AgentMessage,\n type AgentChatRequest,\n type AgentChatEvent,\n type AgentChatReference,\n type MentionProvider,\n type MentionProviderItem,\n} from \"../agent/index.js\";\nexport { createDevScriptRegistry } from \"../scripts/dev/index.js\";\n\nexport {\n createPollHandler,\n recordChange,\n getVersion,\n getChangesSince,\n} from \"./poll.js\";\nexport { createAuthPlugin, defaultAuthPlugin } from \"./auth-plugin.js\";\n// Re-export the org plugin so the auto-discovery's DEFAULT_PLUGIN_REGISTRY\n// (which references \"defaultOrgPlugin\" from @agent-native/core/server) can\n// resolve it during the deploy build worker-entry generation.\nexport { createOrgPlugin, defaultOrgPlugin } from \"../org/plugin.js\";\nexport {\n createGoogleAuthPlugin,\n type GoogleAuthPluginOptions,\n} from \"./google-auth-plugin.js\";\nexport {\n createAgentChatPlugin,\n defaultAgentChatPlugin,\n type AgentChatPluginOptions,\n} from \"./agent-chat-plugin.js\";\nexport {\n createThread,\n getThread,\n listThreads,\n updateThreadData,\n deleteThread,\n type ChatThread,\n type ChatThreadSummary,\n} from \"../chat-threads/store.js\";\nexport {\n createResourcesPlugin,\n defaultResourcesPlugin,\n} from \"./resources-plugin.js\";\nexport {\n createCoreRoutesPlugin,\n defaultCoreRoutesPlugin,\n FRAMEWORK_ROUTE_PREFIX,\n type CoreRoutesPluginOptions,\n} from \"./core-routes-plugin.js\";\nexport {\n createTerminalPlugin,\n defaultTerminalPlugin,\n type TerminalPluginOptions,\n} from \"../terminal/terminal-plugin.js\";\nexport {\n createCollabPlugin,\n type CollabPluginOptions,\n} from \"./collab-plugin.js\";\n\nexport {\n spawnTask,\n getTask,\n getTaskByThread,\n listTasks,\n sendToTask,\n markTaskErrored,\n type AgentTask,\n type SpawnTaskOptions,\n} from \"./agent-teams.js\";\nexport { isOAuthConnected, getOAuthAccounts } from \"./oauth-helpers.js\";\nexport { wrapWithAnalytics } from \"./analytics.js\";\nexport {\n getH3App,\n awaitBootstrap,\n type H3AppShim,\n} from \"./framework-request-handler.js\";\nexport {\n autoDiscoverActions,\n autoDiscoverScripts,\n loadActionsFromStaticRegistry,\n mergeCoreSharingActions,\n} from \"./action-discovery.js\";\nexport {\n mountActionRoutes,\n type MountActionRoutesOptions,\n} from \"./action-routes.js\";\nexport {\n runWithRequestContext,\n hasRequestContext,\n getRequestContext,\n getRequestUserEmail,\n getRequestOrgId,\n getRequestTimezone,\n getCredentialContext,\n isIntegrationCallerRequest,\n type RequestContext,\n} from \"./request-context.js\";\nexport { formatDateInTimezone, todayInTimezone } from \"./date-utils.js\";\n\nexport {\n createOnboardingPlugin,\n defaultOnboardingPlugin,\n} from \"../onboarding/plugin.js\";\n\nexport {\n registerFileUploadProvider,\n unregisterFileUploadProvider,\n listFileUploadProviders,\n getActiveFileUploadProvider,\n uploadFile,\n builderFileUploadProvider,\n type FileUploadInput,\n type FileUploadProvider,\n type FileUploadResult,\n} from \"../file-upload/index.js\";\n\nexport {\n createIntegrationsPlugin,\n defaultIntegrationsPlugin,\n slackAdapter,\n telegramAdapter,\n whatsappAdapter,\n emailAdapter,\n type PlatformAdapter,\n type IncomingMessage,\n type OutgoingMessage,\n type IntegrationStatus,\n type IntegrationsPluginOptions,\n} from \"../integrations/index.js\";\n\nexport {\n isElectron,\n isMobile,\n getOrigin,\n getAppBasePath,\n getAppUrl,\n encodeOAuthState,\n decodeOAuthState,\n resolveOAuthOwner,\n createOAuthSession,\n oauthCallbackResponse,\n oauthErrorPage,\n type OAuthStatePayload,\n type OAuthOwnerResult,\n type OAuthSessionResult,\n} from \"./google-oauth.js\";\n\nexport {\n FeatureNotConfiguredError,\n hasBuilderPrivateKey,\n getBuilderProxyOrigin,\n getBuilderAuthHeader,\n resolveBuilderPrivateKey,\n resolveBuilderAuthHeader,\n resolveHasBuilderPrivateKey,\n resolveBuilderCredentials,\n resolveBuilderCredential,\n writeBuilderCredentials,\n deleteBuilderCredentials,\n resolveSecret,\n} from \"./credential-provider.js\";\n\nexport {\n sendEmail,\n isEmailConfigured,\n getEmailProvider,\n type EmailProvider,\n type SendEmailArgs,\n} from \"./email.js\";\nexport {\n renderEmail,\n emailStrong,\n type RenderEmailArgs,\n type RenderedEmail,\n type EmailCta,\n} from \"./email-template.js\";\nexport { getAppProductionUrl, getFirstPartyProdUrl } from \"./app-url.js\";\nexport {\n signShortLivedToken,\n verifyShortLivedToken,\n type ShortLivedTokenClaims,\n type VerifyResult as ShortLivedTokenVerifyResult,\n} from \"./short-lived-token.js\";\n\n// SSR handler is NOT re-exported here — it uses a virtual module\n// (virtual:react-router/server-build) that only exists at Vite dev/build time.\n// Including it in this barrel would break the esbuild CF Pages bundler.\n// Templates import directly: import { ssrHandler } from \"@agent-native/core/server/ssr-handler\"\n\n// Nitro plugin helper — re-exported so templates don't need nitro as a direct dependency.\n// defineNitroPlugin is an identity function; this typed wrapper lets templates use it\n// without resolving `nitro/runtime` (which requires Nitro's virtual modules at runtime).\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\nexport function defineNitroPlugin(def: NitroPluginDef): NitroPluginDef {\n return def;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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","sourcesContent":["export {\n createServer,\n upsertEnvFile,\n type CreateServerOptions,\n type EnvKeyConfig,\n} from \"./create-server.js\";\n\nexport { readBody, streamFile } from \"./h3-helpers.js\";\nexport { createSSEHandler, type SSEHandlerOptions } from \"./sse.js\";\nexport {\n mountAuthMiddleware,\n autoMountAuth,\n getSession,\n addSession,\n removeSession,\n getSessionEmail,\n runAuthGuard,\n setDesktopExchange,\n setDesktopExchangeError,\n DEV_MODE_USER_EMAIL,\n safeReturnPath,\n type DesktopExchangeErrorPayload,\n type AuthSession,\n type AuthOptions,\n} from \"./auth.js\";\nexport { requireEnvKey, type MissingKeyResponse } from \"./missing-key.js\";\nexport { verifyCaptcha, type CaptchaVerifyResult } from \"./captcha.js\";\nexport {\n createProductionAgentHandler,\n type ActionEntry,\n type ScriptEntry,\n type ProductionAgentOptions,\n type ActionTool,\n type ScriptTool,\n type AgentMessage,\n type AgentChatRequest,\n type AgentChatEvent,\n type AgentChatReference,\n type MentionProvider,\n type MentionProviderItem,\n} from \"../agent/index.js\";\nexport { createDevScriptRegistry } from \"../scripts/dev/index.js\";\n\nexport {\n createPollHandler,\n recordChange,\n getVersion,\n getChangesSince,\n} from \"./poll.js\";\nexport { createAuthPlugin, defaultAuthPlugin } from \"./auth-plugin.js\";\n// Re-export the org plugin so the auto-discovery's DEFAULT_PLUGIN_REGISTRY\n// (which references \"defaultOrgPlugin\" from @agent-native/core/server) can\n// resolve it during the deploy build worker-entry generation.\nexport { createOrgPlugin, defaultOrgPlugin } from \"../org/plugin.js\";\nexport {\n createGoogleAuthPlugin,\n type GoogleAuthPluginOptions,\n} from \"./google-auth-plugin.js\";\nexport {\n createAgentChatPlugin,\n defaultAgentChatPlugin,\n type AgentChatPluginOptions,\n} from \"./agent-chat-plugin.js\";\nexport {\n createThread,\n getThread,\n listThreads,\n updateThreadData,\n deleteThread,\n type ChatThread,\n type ChatThreadSummary,\n} from \"../chat-threads/store.js\";\nexport {\n createResourcesPlugin,\n defaultResourcesPlugin,\n} from \"./resources-plugin.js\";\nexport {\n createCoreRoutesPlugin,\n defaultCoreRoutesPlugin,\n FRAMEWORK_ROUTE_PREFIX,\n type CoreRoutesPluginOptions,\n} from \"./core-routes-plugin.js\";\nexport {\n createTerminalPlugin,\n defaultTerminalPlugin,\n type TerminalPluginOptions,\n} from \"../terminal/terminal-plugin.js\";\nexport {\n createCollabPlugin,\n type CollabPluginOptions,\n} from \"./collab-plugin.js\";\n\nexport {\n spawnTask,\n getTask,\n getTaskByThread,\n listTasks,\n sendToTask,\n markTaskErrored,\n type AgentTask,\n type SpawnTaskOptions,\n} from \"./agent-teams.js\";\nexport { isOAuthConnected, getOAuthAccounts } from \"./oauth-helpers.js\";\nexport { wrapWithAnalytics } from \"./analytics.js\";\nexport {\n getH3App,\n awaitBootstrap,\n type H3AppShim,\n} from \"./framework-request-handler.js\";\nexport {\n autoDiscoverActions,\n autoDiscoverScripts,\n loadActionsFromStaticRegistry,\n mergeCoreSharingActions,\n} from \"./action-discovery.js\";\nexport {\n mountActionRoutes,\n type MountActionRoutesOptions,\n} from \"./action-routes.js\";\nexport {\n runWithRequestContext,\n hasRequestContext,\n getRequestContext,\n getRequestUserEmail,\n getRequestOrgId,\n getRequestTimezone,\n getCredentialContext,\n isIntegrationCallerRequest,\n type RequestContext,\n} from \"./request-context.js\";\nexport { formatDateInTimezone, todayInTimezone } from \"./date-utils.js\";\n\nexport {\n createOnboardingPlugin,\n defaultOnboardingPlugin,\n} from \"../onboarding/plugin.js\";\n\nexport {\n registerFileUploadProvider,\n unregisterFileUploadProvider,\n listFileUploadProviders,\n getActiveFileUploadProvider,\n uploadFile,\n builderFileUploadProvider,\n type FileUploadInput,\n type FileUploadProvider,\n type FileUploadResult,\n} from \"../file-upload/index.js\";\n\nexport {\n createIntegrationsPlugin,\n defaultIntegrationsPlugin,\n slackAdapter,\n telegramAdapter,\n whatsappAdapter,\n emailAdapter,\n type PlatformAdapter,\n type IncomingMessage,\n type OutgoingMessage,\n type IntegrationStatus,\n type IntegrationsPluginOptions,\n} from \"../integrations/index.js\";\n\nexport {\n isElectron,\n isMobile,\n getOrigin,\n getAppBasePath,\n getAppUrl,\n encodeOAuthState,\n decodeOAuthState,\n resolveOAuthOwner,\n createOAuthSession,\n oauthCallbackResponse,\n oauthErrorPage,\n oauthDesktopExchangePage,\n type OAuthStatePayload,\n type OAuthOwnerResult,\n type OAuthSessionResult,\n} from \"./google-oauth.js\";\n\nexport {\n FeatureNotConfiguredError,\n hasBuilderPrivateKey,\n isBuilderEnvManaged,\n getBuilderProxyOrigin,\n getBuilderAuthHeader,\n resolveBuilderPrivateKey,\n resolveBuilderAuthHeader,\n resolveHasBuilderPrivateKey,\n resolveBuilderCredentials,\n resolveBuilderCredential,\n writeBuilderCredentials,\n deleteBuilderCredentials,\n resolveSecret,\n} from \"./credential-provider.js\";\nexport {\n getBuilderBranchProjectId,\n isBuilderBranchingEnabled,\n runBuilderAgent,\n type RunBuilderAgentResult,\n} from \"./builder-browser.js\";\n\nexport {\n sendEmail,\n isEmailConfigured,\n getEmailProvider,\n type EmailProvider,\n type SendEmailArgs,\n} from \"./email.js\";\nexport {\n renderEmail,\n emailStrong,\n type RenderEmailArgs,\n type RenderedEmail,\n type EmailCta,\n} from \"./email-template.js\";\nexport { getAppProductionUrl, getFirstPartyProdUrl } from \"./app-url.js\";\nexport {\n signShortLivedToken,\n verifyShortLivedToken,\n type ShortLivedTokenClaims,\n type VerifyResult as ShortLivedTokenVerifyResult,\n} from \"./short-lived-token.js\";\n\n// SSR handler is NOT re-exported here — it uses a virtual module\n// (virtual:react-router/server-build) that only exists at Vite dev/build time.\n// Including it in this barrel would break the esbuild CF Pages bundler.\n// Templates import directly: import { ssrHandler } from \"@agent-native/core/server/ssr-handler\"\n\n// Nitro plugin helper — re-exported so templates don't need nitro as a direct dependency.\n// defineNitroPlugin is an identity function; this typed wrapper lets templates use it\n// without resolving `nitro/runtime` (which requires Nitro's virtual modules at runtime).\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\nexport function defineNitroPlugin(def: NitroPluginDef): NitroPluginDef {\n return def;\n}\n"]}
@@ -128,7 +128,7 @@ export function getOnboardingHtml(opts = {}) {
128
128
  letter-spacing: -0.02em;
129
129
  }
130
130
  .app-name img.brand-mark {
131
- height: 1.75rem;
131
+ height: 2.0125rem;
132
132
  width: auto;
133
133
  display: block;
134
134
  flex-shrink: 0;
@@ -194,7 +194,7 @@ export function getOnboardingHtml(opts = {}) {
194
194
  .split { flex-direction: column; min-height: auto; }
195
195
  .marketing-panel { padding: 2rem 1.5rem 1.5rem; }
196
196
  .app-name { font-size: 1.375rem; }
197
- .app-name img.brand-mark { height: 1.25rem; }
197
+ .app-name img.brand-mark { height: 1.4375rem; }
198
198
  .app-tagline { font-size: 1rem; margin-bottom: 1rem; }
199
199
  .app-desc { margin-bottom: 1rem; }
200
200
  .feature-list { gap: 0.5rem; }
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-html.js","sourceRoot":"","sources":["../../src/server/onboarding-html.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * First-run onboarding page for agent-native apps.\n *\n * Shown when Better Auth is active and the user isn't signed in.\n * Provides two paths:\n * 1. Create account (email/password) — real identity from day one\n * 2. Use locally — sets AUTH_MODE=local for offline/solo dev (dev only)\n *\n * After first account exists, this page acts as a normal login page.\n * The \"Use locally\" escape hatch is hidden in production to ensure\n * real accounts are used (needed for per-user usage tracking/limits).\n * It's also hidden when DATABASE_URL points at a non-local DB (Postgres,\n * Turso, D1) — local@localhost has no per-user scoping, so enabling it\n * against a shared DB would silently fail server-side.\n */\n\nimport { isLocalDatabase } from \"../db/client.js\";\n\nfunction isProductionEnv(): boolean {\n const env = process.env.NODE_ENV;\n return env !== \"development\" && env !== \"test\";\n}\n\nfunction hasGoogleOAuth(): boolean {\n return !!(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET);\n}\n\nfunction getConnectionLabel(): string {\n const url = process.env.DATABASE_URL || \"\";\n if (!url) return \"SQLite (local file)\";\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n if (url.includes(\"neon.tech\")) return \"Neon Postgres\";\n if (url.includes(\"supabase\")) return \"Supabase Postgres\";\n return \"Postgres\";\n }\n if (url.startsWith(\"file:\")) return \"SQLite (local file)\";\n if (url.startsWith(\"libsql://\") || url.includes(\"turso.io\")) return \"Turso\";\n return \"SQL database\";\n}\n\nexport interface OnboardingHtmlOptions {\n /**\n * Hide email/password forms and show ONLY the Google sign-in button.\n * Useful for templates (mail, calendar) where Google is required anyway.\n * If Google OAuth env vars are not configured, an error message is shown.\n */\n googleOnly?: boolean;\n /**\n * Product marketing content shown alongside the sign-in form.\n * When provided, the page uses a split layout: marketing on the left,\n * sign-in form on the right (stacked on mobile).\n */\n marketing?: {\n appName: string;\n tagline: string;\n description?: string;\n features?: string[];\n };\n}\n\nconst MIGRATE_FLAG_KEY = \"an_migrate_from_local\";\n\nexport function getOnboardingHtml(opts: OnboardingHtmlOptions = {}): string {\n const showLocalMode =\n !isProductionEnv() && !opts.googleOnly && isLocalDatabase();\n const showGoogle = hasGoogleOAuth();\n const googleOnly = !!opts.googleOnly;\n const localModeBlock = showLocalMode\n ? `\n <div class=\"divider\" id=\"local-divider\">or</div>\n\n <button class=\"btn-secondary\" id=\"local-btn\" onclick=\"useLocally()\">Use locally without an account</button>\n <p class=\"local-info\" id=\"local-info\">Skip auth for solo local development. You can create an account later.</p>`\n : \"\";\n\n const localModeScript = showLocalMode\n ? `\n async function useLocally() {\n var btn = document.getElementById('local-btn');\n btn.disabled = true;\n btn.textContent = 'Setting up...';\n try {\n try {\n if (localStorage.getItem('${MIGRATE_FLAG_KEY}')) {\n localStorage.removeItem('${MIGRATE_FLAG_KEY}');\n }\n } catch (e) {}\n var res = await fetch(__anPath('/_agent-native/auth/local-mode'), { method: 'POST' });\n if (res.ok) {\n window.location.reload();\n } else {\n var data = await res.json().catch(function() { return {}; });\n var info = document.getElementById('local-info');\n if (info && data && data.error) {\n info.textContent = data.error;\n info.style.color = '#f87171';\n }\n btn.textContent = 'Not available';\n btn.disabled = true;\n }\n } catch(e) {\n btn.textContent = 'Failed — try again';\n btn.disabled = false;\n }\n }`\n : \"\";\n\n const marketing = opts.marketing;\n const hasMarketing = !!marketing;\n const esc = (s: string) =>\n s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n\n const marketingStyles = hasMarketing\n ? `\n body.has-marketing { padding: 0; position: relative; overflow-x: hidden; }\n #starfield {\n position: fixed;\n inset: 0;\n width: 100%;\n height: 100%;\n opacity: 0.35;\n pointer-events: none;\n z-index: 0;\n }\n .split {\n position: relative;\n z-index: 1;\n display: flex;\n min-height: 100vh;\n width: 100%;\n max-width: 1100px;\n margin: 0 auto;\n }\n .marketing-panel {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n padding: 3rem 3.5rem;\n }\n .marketing-content { max-width: 480px; }\n .app-name {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 2rem;\n font-weight: 700;\n color: #fff;\n margin-bottom: 0.625rem;\n letter-spacing: -0.02em;\n }\n .app-name img.brand-mark {\n height: 1.75rem;\n width: auto;\n display: block;\n flex-shrink: 0;\n }\n .app-tagline {\n font-size: 1.25rem;\n color: #a1a1aa;\n line-height: 1.6;\n margin-bottom: 2rem;\n }\n .app-desc {\n font-size: 1rem;\n color: #71717a;\n line-height: 1.6;\n margin-bottom: 2rem;\n }\n .feature-list {\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 0.875rem;\n }\n .feature-list li {\n display: flex;\n align-items: flex-start;\n gap: 0.625rem;\n font-size: 1rem;\n color: #a1a1aa;\n line-height: 1.5;\n }\n .feature-list li::before {\n content: '';\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n margin-top: 6px;\n border-radius: 50%;\n background: #3f3f46;\n border: 1px solid #52525b;\n }\n .oss-link {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n margin-top: 2rem;\n font-size: 0.8125rem;\n color: #71717a;\n text-decoration: none;\n }\n .oss-link:hover { color: #a1a1aa; }\n .oss-link svg { width: 15px; height: 15px; flex-shrink: 0; }\n .form-panel {\n flex: 0 0 440px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 2rem;\n }\n .form-panel .card { max-width: 400px; }\n .form-panel .local-note { max-width: 400px; }\n @media (max-width: 900px) {\n .split { flex-direction: column; min-height: auto; }\n .marketing-panel { padding: 2rem 1.5rem 1.5rem; }\n .app-name { font-size: 1.375rem; }\n .app-name img.brand-mark { height: 1.25rem; }\n .app-tagline { font-size: 1rem; margin-bottom: 1rem; }\n .app-desc { margin-bottom: 1rem; }\n .feature-list { gap: 0.5rem; }\n .form-panel { flex: none; padding: 1.5rem 1rem; }\n }\n`\n : \"\";\n\n const marketingPanelHtml = hasMarketing\n ? `<canvas id=\"starfield\"></canvas>\n<div class=\"split\">\n <div class=\"marketing-panel\">\n <div class=\"marketing-content\">\n <h2 class=\"app-name\">\n <img class=\"brand-mark\" src=\"/agent-native-icon-dark.svg\" alt=\"\" aria-hidden=\"true\" />\n <span>${esc(marketing!.appName)}</span>\n </h2>\n <p class=\"app-tagline\">${esc(marketing!.tagline)}</p>\n${marketing!.description ? ` <p class=\"app-desc\">${esc(marketing!.description)}</p>\\n` : \"\"}${\n marketing!.features?.length\n ? ` <ul class=\"feature-list\">\\n${marketing!.features.map((f) => ` <li>${esc(f)}</li>`).join(\"\\n\")}\\n </ul>\\n`\n : \"\"\n } <a class=\"oss-link\" href=\"https://github.com/BuilderIO/agent-native\" target=\"_blank\" rel=\"noreferrer\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2 2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 00-1.3-3.2 4.2 4.2 0 00-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3 0 00-6.2 0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2 0 00-.1 3.2A4.6 4.6 0 004 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21\"/></svg>\n Open source\n </a>\n </div>\n </div>\n <div class=\"form-panel\">`\n : \"\";\n\n const marketingCloseHtml = hasMarketing ? `\\n </div>\\n</div>` : \"\";\n\n const starfieldScript = hasMarketing\n ? `\n (function initStarfield() {\n var canvas = document.getElementById('starfield');\n if (!canvas) return;\n var gl = canvas.getContext('webgl', { alpha: false, antialias: false });\n if (!gl) return;\n\n var vs = gl.createShader(gl.VERTEX_SHADER);\n gl.shaderSource(vs, 'attribute vec2 position;void main(){gl_Position=vec4(position,0.0,1.0);}');\n gl.compileShader(vs);\n\n var fs = gl.createShader(gl.FRAGMENT_SHADER);\n gl.shaderSource(fs, [\n 'precision highp float;',\n 'uniform float iTime;uniform vec2 iResolution;',\n '#define S(a,b,t) smoothstep(a,b,t)',\n '#define NUM_LAYERS 4.',\n 'float N21(vec2 p){vec3 a=fract(vec3(p.xyx)*vec3(213.897,653.453,253.098));a+=dot(a,a.yzx+79.76);return fract((a.x+a.y)*a.z);}',\n 'vec2 GetPos(vec2 id,vec2 offs,float t){float n=N21(id+offs);float n1=fract(n*10.);float n2=fract(n*100.);float a=t+n;return offs+vec2(sin(a*n1),cos(a*n2))*.4;}',\n 'float df_line(vec2 a,vec2 b,vec2 p){vec2 pa=p-a,ba=b-a;float h=clamp(dot(pa,ba)/dot(ba,ba),0.,1.);return length(pa-ba*h);}',\n 'float line(vec2 a,vec2 b,vec2 uv){float r1=.025;float r2=.006;float d=df_line(a,b,uv);float d2=length(a-b);float fade=S(1.5,.5,d2);fade+=S(.05,.02,abs(d2-.75));return S(r1,r2,d)*fade;}',\n 'float NetLayer(vec2 st,float n,float t){',\n ' vec2 id=floor(st)+n;st=fract(st)-.5;',\n ' vec2 p0=GetPos(id,vec2(-1,-1),t);vec2 p1=GetPos(id,vec2(0,-1),t);vec2 p2=GetPos(id,vec2(1,-1),t);',\n ' vec2 p3=GetPos(id,vec2(-1,0),t);vec2 p4=GetPos(id,vec2(0,0),t);vec2 p5=GetPos(id,vec2(1,0),t);',\n ' vec2 p6=GetPos(id,vec2(-1,1),t);vec2 p7=GetPos(id,vec2(0,1),t);vec2 p8=GetPos(id,vec2(1,1),t);',\n ' float m=0.;float sparkle=0.;float d;float s;float pulse;',\n ' m+=line(p4,p0,st);d=length(st-p0);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p0.x)+fract(p0.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p1,st);d=length(st-p1);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p1.x)+fract(p1.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p2,st);d=length(st-p2);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p2.x)+fract(p2.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p3,st);d=length(st-p3);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p3.x)+fract(p3.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p4,st);d=length(st-p4);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p4.x)+fract(p4.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p5,st);d=length(st-p5);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p5.x)+fract(p5.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p6,st);d=length(st-p6);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p6.x)+fract(p6.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p7,st);d=length(st-p7);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p7.x)+fract(p7.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p8,st);d=length(st-p8);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p8.x)+fract(p8.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p1,p3,st);m+=line(p1,p5,st);m+=line(p7,p5,st);m+=line(p7,p3,st);',\n ' float sPhase=(sin(t+n)+sin(t*.1))*.25+.5;sPhase+=pow(sin(t*.1)*.5+.5,50.)*5.;m+=sparkle*sPhase;',\n ' return m;',\n '}',\n 'void mainImage(out vec4 fragColor,in vec2 fragCoord){',\n ' vec2 uv=(fragCoord-iResolution.xy*.5)/iResolution.y;',\n ' float t=iTime*.03;float s=sin(t);float c=cos(t);mat2 rot=mat2(c,-s,s,c);vec2 st=uv*rot;',\n ' float m=0.;',\n ' for(float i=0.;i<1.;i+=1./NUM_LAYERS){float z=fract(t+i);float size=mix(15.,1.,z);float fade=S(0.,.6,z)*S(1.,.8,z);m+=fade*NetLayer(st*size,i,iTime*0.3);}',\n ' vec3 col=vec3(0.35)*m;col*=1.-dot(uv,uv);',\n ' float tt=min(iTime,5.0);col*=S(0.,20.,tt);',\n ' col=clamp(col,0.,1.);fragColor=vec4(col,1.);',\n '}',\n 'void main(){mainImage(gl_FragColor,gl_FragCoord.xy);}'\n ].join('\\\\n'));\n gl.compileShader(fs);\n\n var prog = gl.createProgram();\n gl.attachShader(prog, vs);\n gl.attachShader(prog, fs);\n gl.linkProgram(prog);\n gl.useProgram(prog);\n\n var buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]), gl.STATIC_DRAW);\n var pos = gl.getAttribLocation(prog, 'position');\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n var uTime = gl.getUniformLocation(prog, 'iTime');\n var uRes = gl.getUniformLocation(prog, 'iResolution');\n\n function resize() {\n var w = window.innerWidth, h = window.innerHeight;\n var dpr = Math.min(window.devicePixelRatio, 1.5);\n canvas.width = w * dpr; canvas.height = h * dpr;\n gl.viewport(0, 0, canvas.width, canvas.height);\n }\n resize();\n window.addEventListener('resize', resize);\n\n var start = performance.now(), last = 0;\n function render(now) {\n requestAnimationFrame(render);\n if (now - last < 33) return;\n last = now;\n gl.uniform1f(uTime, (now - start) * 0.001);\n gl.uniform2f(uRes, canvas.width, canvas.height);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n }\n requestAnimationFrame(render);\n })();`\n : \"\";\n\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>${hasMarketing ? esc(marketing!.appName) + \" — Sign in\" : \"Welcome\"}</title>\n${\n hasMarketing\n ? `<meta name=\"description\" content=\"${esc(marketing!.tagline)}\">\n<meta property=\"og:title\" content=\"${esc(marketing!.appName)}\">\n<meta property=\"og:description\" content=\"${esc(marketing!.tagline)}\">`\n : \"\"\n}\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 flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n padding: 1rem;\n }\n .card {\n width: 100%;\n max-width: 400px;\n padding: 2rem;\n background: #141414;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 12px;\n }\n h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.25rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n .tabs {\n display: inline-flex;\n width: 100%;\n padding: 4px;\n margin-bottom: 1.5rem;\n background: rgba(255,255,255,0.06);\n border-radius: 8px;\n }\n .tab {\n flex: 1;\n padding: 0.5rem 0.75rem;\n background: none;\n border: none;\n color: #888;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n border-radius: 6px;\n }\n .tab.active {\n background: #1e1e1e;\n color: #fff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.3);\n }\n .tab:hover:not(.active) { color: #bbb; }\n .form { display: none; }\n .form.active { display: block; }\n label { display: block; font-size: 0.8125rem; color: #888; margin-bottom: 0.375rem; }\n input {\n width: 100%;\n padding: 0.5rem 0.75rem;\n background: transparent;\n border: 1px solid rgba(255,255,255,0.12);\n border-radius: 6px;\n color: #e5e5e5;\n font-size: 0.875rem;\n outline: none;\n margin-bottom: 0.875rem;\n }\n input:focus { border-color: rgba(255,255,255,0.3); box-shadow: 0 0 0 1px rgba(255,255,255,0.1); }\n input::placeholder { color: #555; }\n button[type=\"submit\"], .btn-primary {\n width: 100%;\n margin-top: 0.25rem;\n padding: 0.5rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 6px;\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n }\n button[type=\"submit\"]:hover, .btn-primary:hover { background: #e5e5e5; }\n button[type=\"submit\"]:disabled { opacity: 0.5; cursor: not-allowed; }\n .btn-secondary {\n width: 100%;\n margin-top: 0.75rem;\n padding: 0.5rem;\n background: transparent;\n color: #888;\n border: 1px solid rgba(255,255,255,0.1);\n border-radius: 6px;\n font-size: 0.8125rem;\n cursor: pointer;\n }\n .btn-secondary:hover { color: #bbb; border-color: rgba(255,255,255,0.2); }\n .msg { margin-top: 0.75rem; font-size: 0.8125rem; display: none; }\n .msg.error { color: #f87171; }\n .msg.success { color: #4ade80; }\n .msg.show { display: block; }\n .divider {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin: 1.25rem 0;\n font-size: 0.75rem;\n color: #555;\n }\n .divider::before, .divider::after {\n content: '';\n flex: 1;\n height: 1px;\n background: rgba(255,255,255,0.08);\n }\n .local-info {\n font-size: 0.75rem;\n color: #666;\n margin-top: 0.5rem;\n line-height: 1.4;\n }\n .upgrade-note {\n margin-bottom: 1rem;\n padding: 0.75rem;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 8px;\n background: rgba(255,255,255,0.03);\n font-size: 0.75rem;\n line-height: 1.5;\n color: #a1a1aa;\n display: none;\n }\n .upgrade-note.show { display: block; }\n .btn-google {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.625rem;\n padding: 0.5rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 6px;\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n }\n .btn-google:hover { background: #e5e5e5; }\n .btn-google:disabled { opacity: 0.5; cursor: wait; }\n .btn-google svg { width: 18px; height: 18px; flex-shrink: 0; }\n .google-error { margin-top: 0.5rem; font-size: 0.8125rem; color: #f87171; display: none; }\n .google-error.show { display: block; }\n .local-note {\n display: none;\n max-width: 400px;\n width: 100%;\n margin-top: 1rem;\n padding: 0.625rem 0.875rem;\n font-size: 0.6875rem;\n line-height: 1.5;\n color: #666;\n border: 1px dashed rgba(255,255,255,0.08);\n border-radius: 8px;\n text-align: center;\n }\n .local-note.show { display: block; }\n .local-note strong { color: #999; font-weight: 500; }\n .local-note a { color: #888; text-decoration: none; }\n .local-note a:hover { color: #bbb; }\n${marketingStyles}\n</style>\n</head>\n<body${hasMarketing ? ' class=\"has-marketing\"' : \"\"}>\n${marketingPanelHtml}\n<div class=\"card\">\n <h1 id=\"heading\">Welcome</h1>\n <p class=\"subtitle\" id=\"subtitle\">Create an account to get started</p>\n <p class=\"upgrade-note\" id=\"upgrade-note\">\n You started this flow from <code>local@localhost</code>. Continue signing in to upgrade this workspace to a real account and migrate your local data. If you want to cancel that and keep using local mode, use the secondary button below.\n </p>\n\n${\n showGoogle\n ? `\n <button class=\"btn-google\" id=\"google-btn\" onclick=\"signInWithGoogle()\">\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=\"google-error\" id=\"google-err\"></p>\n${googleOnly ? \"\" : `\\n <div class=\"divider\">or</div>\\n`}\n`\n : googleOnly\n ? `\n <p style=\"color:#f87171;font-size:0.875rem;text-align:center;padding:1rem 0\">\n Google sign-in is not configured. Set <code>GOOGLE_CLIENT_ID</code> and\n <code>GOOGLE_CLIENT_SECRET</code> environment variables to enable login.\n </p>\n`\n : \"\"\n}\n${\n googleOnly\n ? \"\"\n : ` <div class=\"tabs\">\n <button class=\"tab\" data-tab=\"signup\">Create account</button>\n <button class=\"tab\" data-tab=\"login\">Sign in</button>\n </div>\n\n <form id=\"signup-form\" class=\"form\">\n <label for=\"s-email\">Email</label>\n <input id=\"s-email\" type=\"email\" autocomplete=\"email\" autofocus placeholder=\"you@example.com\" required />\n <label for=\"s-pass\">Password</label>\n <input id=\"s-pass\" type=\"password\" autocomplete=\"new-password\" placeholder=\"At least 8 characters\" required minlength=\"8\" />\n <label for=\"s-pass2\">Confirm password</label>\n <input id=\"s-pass2\" type=\"password\" autocomplete=\"new-password\" placeholder=\"Confirm password\" required minlength=\"8\" />\n <button type=\"submit\">Create account</button>\n <p class=\"msg\" id=\"s-msg\"></p>\n </form>\n\n <form id=\"login-form\" class=\"form\">\n <label for=\"l-email\">Email</label>\n <input id=\"l-email\" type=\"email\" autocomplete=\"email\" placeholder=\"you@example.com\" required />\n <label for=\"l-pass\">Password</label>\n <input id=\"l-pass\" type=\"password\" autocomplete=\"current-password\" placeholder=\"Enter password\" required />\n <button type=\"submit\">Sign in</button>\n <p class=\"msg error\" id=\"l-msg\"></p>\n <p style=\"margin-top:0.75rem;font-size:0.75rem;text-align:right\">\n <a href=\"#\" id=\"forgot-link\" style=\"color:#888;text-decoration:underline;text-underline-offset:2px\">Forgot password?</a>\n </p>\n </form>\n\n <form id=\"forgot-form\" class=\"form\">\n <label for=\"f-email\">Email</label>\n <input id=\"f-email\" type=\"email\" autocomplete=\"email\" placeholder=\"you@example.com\" required />\n <button type=\"submit\">Send reset link</button>\n <p class=\"msg\" id=\"f-msg\"></p>\n <p style=\"margin-top:0.75rem;font-size:0.75rem;text-align:center\">\n <a href=\"#\" id=\"back-to-login\" style=\"color:#888;text-decoration:underline;text-underline-offset:2px\">Back to sign in</a>\n </p>\n </form>`\n}\n${localModeBlock}\n</div>\n<p class=\"local-note\" id=\"local-note\">\n Your account is stored in this app's own DB (<strong>${getConnectionLabel()}</strong>), not a third-party service.\n</p>${marketingCloseHtml}\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 (function revealLocalNote() {\n var h = location.hostname;\n if (h === 'localhost' || h === '127.0.0.1' || h === '::1' || h.endsWith('.local')) {\n var n = document.getElementById('local-note');\n if (n) n.classList.add('show');\n }\n })();\n${\n googleOnly\n ? \"\"\n : ` var TAB_STORAGE_KEY = 'an.onboarding.tab';\n var tabs = document.querySelectorAll('.tab');\n var forms = document.querySelectorAll('.form');\n var subtitles = { signup: 'Create an account to get started', login: 'Sign in to your account' };\n var headings = { signup: 'Welcome', login: 'Welcome back' };\n function setActiveTab(name, opts) {\n if (name !== 'signup' && name !== 'login') return;\n var form = document.getElementById(name + '-form');\n if (!form) return;\n tabs.forEach(function(x) { x.classList.remove('active'); });\n forms.forEach(function(x) { x.classList.remove('active'); });\n var btn = document.querySelector('.tab[data-tab=\"' + name + '\"]');\n if (btn) btn.classList.add('active');\n form.classList.add('active');\n var sub = document.getElementById('subtitle');\n if (sub && subtitles[name]) sub.textContent = subtitles[name];\n var heading = document.getElementById('heading');\n if (heading && headings[name]) heading.textContent = headings[name];\n if (opts && opts.persist) {\n try { localStorage.setItem(TAB_STORAGE_KEY, name); } catch (e) {}\n }\n }\n (function initActiveTab() {\n var initial = 'signup';\n try {\n var params = new URLSearchParams(location.search);\n var qp = params.get('tab');\n if (qp === 'login' || qp === 'signup') {\n initial = qp;\n } else if (params.has('verified')) {\n initial = 'login';\n } else {\n var stored = localStorage.getItem(TAB_STORAGE_KEY);\n if (stored === 'login' || stored === 'signup') initial = stored;\n }\n } catch (e) {}\n setActiveTab(initial, { persist: false });\n try {\n if (new URLSearchParams(location.search).has('verified')) {\n var msg = document.getElementById('l-msg');\n if (msg) {\n msg.textContent = 'Email verified! Sign in to continue.';\n msg.classList.remove('error');\n msg.classList.add('show', 'success');\n }\n }\n } catch (e) {}\n })();\n tabs.forEach(function(t) { t.addEventListener('click', function() {\n setActiveTab(t.dataset.tab, { persist: true });\n }); });\n\n document.getElementById('signup-form').addEventListener('submit', async function(e) {\n e.preventDefault();\n var form = e.currentTarget;\n var btn = form.querySelector('button[type=\"submit\"]');\n var msg = document.getElementById('s-msg');\n msg.classList.remove('show', 'error', 'success');\n var pass = document.getElementById('s-pass').value;\n var pass2 = document.getElementById('s-pass2').value;\n if (pass !== pass2) {\n msg.textContent = 'Passwords do not match';\n msg.classList.add('show', 'error');\n return;\n }\n var originalLabel = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Creating account…';\n try {\n var email = document.getElementById('s-email').value;\n var res = await fetch(__anPath('/_agent-native/auth/register'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email, password: pass }),\n });\n var data = await res.json().catch(function() { return {}; });\n if (res.ok) {\n // If email verification is required, the server won't return a session.\n // Try logging in — if it fails (unverified), show a \"check your email\" message.\n var loginRes = await fetch(__anPath('/_agent-native/auth/login'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email, password: pass }),\n });\n if (loginRes.ok) {\n msg.textContent = 'Account created — signing you in…';\n msg.classList.add('show', 'success');\n window.location.reload();\n return;\n }\n // Login failed — likely email verification required.\n // Switch to login tab first, then show the message there so\n // the user actually sees it (the signup form is hidden after switch).\n btn.disabled = false;\n btn.textContent = originalLabel;\n setActiveTab('login', { persist: true });\n var loginMsg = document.getElementById('l-msg');\n if (loginMsg) {\n loginMsg.textContent = 'Account created! Check your email to verify, then sign in.';\n loginMsg.classList.remove('error');\n loginMsg.classList.add('show', 'success');\n }\n return;\n }\n msg.textContent = data.error || 'Registration failed';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = originalLabel;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = originalLabel;\n }\n });\n\n var forgotLink = document.getElementById('forgot-link');\n var backToLogin = document.getElementById('back-to-login');\n if (forgotLink) forgotLink.addEventListener('click', function(e) {\n e.preventDefault();\n document.getElementById('login-form').classList.remove('active');\n document.getElementById('forgot-form').classList.add('active');\n var sub = document.getElementById('subtitle');\n if (sub) sub.textContent = 'Reset your password';\n var heading = document.getElementById('heading');\n if (heading) heading.textContent = 'Reset password';\n var fEmail = document.getElementById('f-email');\n var lEmail = document.getElementById('l-email');\n if (lEmail && lEmail.value) fEmail.value = lEmail.value;\n setTimeout(function() { fEmail.focus(); }, 0);\n });\n if (backToLogin) backToLogin.addEventListener('click', function(e) {\n e.preventDefault();\n document.getElementById('forgot-form').classList.remove('active');\n document.getElementById('login-form').classList.add('active');\n var sub = document.getElementById('subtitle');\n if (sub) sub.textContent = subtitles.login;\n var heading = document.getElementById('heading');\n if (heading) heading.textContent = headings.login;\n });\n\n var forgotForm = document.getElementById('forgot-form');\n if (forgotForm) forgotForm.addEventListener('submit', async function(e) {\n e.preventDefault();\n var btn = e.currentTarget.querySelector('button[type=\"submit\"]');\n var msg = document.getElementById('f-msg');\n msg.classList.remove('show', 'error', 'success');\n var original = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Sending…';\n try {\n var email = document.getElementById('f-email').value;\n var res = await fetch(__anPath('/_agent-native/auth/ba/request-password-reset'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email }),\n });\n if (res.ok) {\n msg.textContent = 'If that email exists, a reset link is on its way.';\n msg.classList.add('show', 'success');\n btn.textContent = 'Sent';\n return;\n }\n var data = await res.json().catch(function() { return {}; });\n msg.textContent = (data && (data.message || data.error)) || 'Could not send reset email.';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n }\n });\n\n document.getElementById('login-form').addEventListener('submit', async function(e) {\n e.preventDefault();\n var form = e.currentTarget;\n var btn = form.querySelector('button[type=\"submit\"]');\n var msg = document.getElementById('l-msg');\n msg.classList.remove('show');\n var originalLabel = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Signing in…';\n try {\n var res = await fetch(__anPath('/_agent-native/auth/login'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email: document.getElementById('l-email').value,\n password: document.getElementById('l-pass').value,\n }),\n });\n if (res.ok) {\n window.location.reload();\n return;\n }\n var data = await res.json().catch(function() { return {}; });\n msg.textContent = data.error || 'Invalid email or password';\n msg.classList.add('show');\n btn.disabled = false;\n btn.textContent = originalLabel;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show');\n btn.disabled = false;\n btn.textContent = originalLabel;\n }\n });\n`\n}${localModeScript}\n${\n showLocalMode\n ? `\n (function syncUpgradeFromLocalUi() {\n var subtitle = document.querySelector('.subtitle');\n var note = document.getElementById('upgrade-note');\n var localBtn = document.getElementById('local-btn');\n var localInfo = document.getElementById('local-info');\n var divider = document.getElementById('local-divider');\n if (!subtitle || !note || !localBtn || !localInfo || !divider) return;\n try {\n if (!localStorage.getItem('${MIGRATE_FLAG_KEY}')) return;\n } catch (e) {\n return;\n }\n subtitle.textContent = 'Sign in to upgrade your local workspace';\n note.classList.add('show');\n localBtn.textContent = 'Stay in local mode';\n localInfo.textContent = 'Use this if you want to cancel the upgrade and go back to local@localhost on this device.';\n divider.textContent = 'or stay local';\n })();\n`\n : \"\"\n}\n${\n showGoogle\n ? `\n function __anGetReturnPath() {\n // If we landed here via /_agent-native/sign-in?return=X (force-sign-in\n // entrypoint from a public page), prefer the inner return URL.\n // Otherwise the loginHtml is being served at the URL the user actually\n // wanted to reach (a bookmarked / deep-linked private path), so use it.\n try {\n var inner = new URLSearchParams(window.location.search).get('return');\n if (inner) return inner;\n } catch(e) {}\n return window.location.pathname + window.location.search;\n }\n async function signInWithGoogle() {\n var btn = document.getElementById('google-btn');\n var err = document.getElementById('google-err');\n btn.disabled = true;\n err.classList.remove('show');\n try {\n var ret = __anGetReturnPath();\n var authUrl = __anPath('/_agent-native/google/auth-url') + '?return=' + encodeURIComponent(ret);\n var res = await fetch(authUrl);\n var data = await res.json();\n if (data.url) {\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n window.open(data.url, '_blank');\n btn.disabled = false;\n btn.textContent = 'Waiting for sign-in…';\n var poll = setInterval(function() {\n fetch(__anPath('/_agent-native/auth/session')).then(function(r) { return r.json(); }).then(function(s) {\n if (s && s.email) { clearInterval(poll); window.location.reload(); }\n }).catch(function() {});\n }, 1500);\n } else {\n err.textContent = data.message || 'Google OAuth is not configured.';\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 : \"\"\n}\n${starfieldScript}\n</script>\n</body>\n</html>`;\n}\n\n/** @deprecated Use getOnboardingHtml() instead */\nexport const ONBOARDING_HTML = getOnboardingHtml();\n\n/**\n * HTML for the password reset page — shown when the user clicks the link in\n * their reset email. Posts `{ newPassword, token }` to Better Auth's\n * `/reset-password` endpoint, then redirects to the login page.\n */\nexport function getResetPasswordHtml(): string {\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>Reset password</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0a0a0a; color: #e5e5e5; display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 1rem; }\n .card { width: 100%; max-width: 400px; padding: 2rem; background: #141414; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; }\n h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.25rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n label { display: block; font-size: 0.8125rem; color: #888; margin-bottom: 0.375rem; }\n input { width: 100%; padding: 0.5rem 0.75rem; background: transparent; border: 1px solid rgba(255,255,255,0.12); border-radius: 6px; color: #e5e5e5; font-size: 0.875rem; outline: none; margin-bottom: 0.875rem; }\n input:focus { border-color: rgba(255,255,255,0.3); box-shadow: 0 0 0 1px rgba(255,255,255,0.1); }\n input::placeholder { color: #555; }\n button[type=\"submit\"] { width: 100%; margin-top: 0.25rem; padding: 0.5rem; background: #fff; color: #000; border: none; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }\n button[type=\"submit\"]:hover { background: #e5e5e5; }\n button[type=\"submit\"]:disabled { opacity: 0.5; cursor: not-allowed; }\n .msg { margin-top: 0.75rem; font-size: 0.8125rem; display: none; }\n .msg.error { color: #f87171; }\n .msg.success { color: #4ade80; }\n .msg.show { display: block; }\n .back { display: inline-block; margin-top: 1rem; font-size: 0.75rem; color: #888; text-decoration: none; }\n .back:hover { color: #bbb; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <h1>Choose a new password</h1>\n <p class=\"subtitle\">Set a new password for your account.</p>\n <form id=\"reset-form\">\n <label for=\"p1\">New password</label>\n <input id=\"p1\" type=\"password\" autocomplete=\"new-password\" autofocus placeholder=\"At least 8 characters\" required minlength=\"8\" />\n <label for=\"p2\">Confirm password</label>\n <input id=\"p2\" type=\"password\" autocomplete=\"new-password\" placeholder=\"Confirm password\" required minlength=\"8\" />\n <button type=\"submit\">Save new password</button>\n <p class=\"msg\" id=\"msg\"></p>\n </form>\n <a class=\"back\" id=\"back-link\" href=\"/\">Back to sign in</a>\n</div>\n<script>\n (function() {\n // Derive the app's base path so apps mounted under a prefix\n // (e.g. /mail, /calendar) get sent home instead of to the root domain.\n var RESET_PATH = '/_agent-native/auth/reset';\n var pathname = window.location.pathname;\n var idx = pathname.indexOf(RESET_PATH);\n var basePath = (idx >= 0 ? pathname.slice(0, idx) : '') || '';\n var homeHref = basePath + '/';\n var backLink = document.getElementById('back-link');\n if (backLink) backLink.setAttribute('href', homeHref);\n var params = new URLSearchParams(location.search);\n var token = params.get('token') || '';\n var msg = document.getElementById('msg');\n if (!token) {\n msg.textContent = 'Missing or invalid reset token. Request a new reset link.';\n msg.classList.add('show', 'error');\n document.getElementById('reset-form').style.display = 'none';\n return;\n }\n document.getElementById('reset-form').addEventListener('submit', async function(e) {\n e.preventDefault();\n var btn = e.currentTarget.querySelector('button[type=\"submit\"]');\n var p1 = document.getElementById('p1').value;\n var p2 = document.getElementById('p2').value;\n msg.classList.remove('show', 'error', 'success');\n if (p1 !== p2) {\n msg.textContent = 'Passwords do not match';\n msg.classList.add('show', 'error');\n return;\n }\n var original = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Saving…';\n try {\n var res = await fetch(basePath + '/_agent-native/auth/ba/reset-password', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ newPassword: p1, token: token }),\n });\n if (res.ok) {\n msg.textContent = 'Password updated — redirecting to sign in…';\n msg.classList.add('show', 'success');\n setTimeout(function() { window.location.href = homeHref; }, 1200);\n return;\n }\n var data = await res.json().catch(function() { return {}; });\n msg.textContent = (data && (data.message || data.error)) || 'Reset failed. The link may have expired — request a new one.';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n }\n });\n })();\n</script>\n</body>\n</html>`;\n}\n"]}
1
+ {"version":3,"file":"onboarding-html.js","sourceRoot":"","sources":["../../src/server/onboarding-html.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * First-run onboarding page for agent-native apps.\n *\n * Shown when Better Auth is active and the user isn't signed in.\n * Provides two paths:\n * 1. Create account (email/password) — real identity from day one\n * 2. Use locally — sets AUTH_MODE=local for offline/solo dev (dev only)\n *\n * After first account exists, this page acts as a normal login page.\n * The \"Use locally\" escape hatch is hidden in production to ensure\n * real accounts are used (needed for per-user usage tracking/limits).\n * It's also hidden when DATABASE_URL points at a non-local DB (Postgres,\n * Turso, D1) — local@localhost has no per-user scoping, so enabling it\n * against a shared DB would silently fail server-side.\n */\n\nimport { isLocalDatabase } from \"../db/client.js\";\n\nfunction isProductionEnv(): boolean {\n const env = process.env.NODE_ENV;\n return env !== \"development\" && env !== \"test\";\n}\n\nfunction hasGoogleOAuth(): boolean {\n return !!(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET);\n}\n\nfunction getConnectionLabel(): string {\n const url = process.env.DATABASE_URL || \"\";\n if (!url) return \"SQLite (local file)\";\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n if (url.includes(\"neon.tech\")) return \"Neon Postgres\";\n if (url.includes(\"supabase\")) return \"Supabase Postgres\";\n return \"Postgres\";\n }\n if (url.startsWith(\"file:\")) return \"SQLite (local file)\";\n if (url.startsWith(\"libsql://\") || url.includes(\"turso.io\")) return \"Turso\";\n return \"SQL database\";\n}\n\nexport interface OnboardingHtmlOptions {\n /**\n * Hide email/password forms and show ONLY the Google sign-in button.\n * Useful for templates (mail, calendar) where Google is required anyway.\n * If Google OAuth env vars are not configured, an error message is shown.\n */\n googleOnly?: boolean;\n /**\n * Product marketing content shown alongside the sign-in form.\n * When provided, the page uses a split layout: marketing on the left,\n * sign-in form on the right (stacked on mobile).\n */\n marketing?: {\n appName: string;\n tagline: string;\n description?: string;\n features?: string[];\n };\n}\n\nconst MIGRATE_FLAG_KEY = \"an_migrate_from_local\";\n\nexport function getOnboardingHtml(opts: OnboardingHtmlOptions = {}): string {\n const showLocalMode =\n !isProductionEnv() && !opts.googleOnly && isLocalDatabase();\n const showGoogle = hasGoogleOAuth();\n const googleOnly = !!opts.googleOnly;\n const localModeBlock = showLocalMode\n ? `\n <div class=\"divider\" id=\"local-divider\">or</div>\n\n <button class=\"btn-secondary\" id=\"local-btn\" onclick=\"useLocally()\">Use locally without an account</button>\n <p class=\"local-info\" id=\"local-info\">Skip auth for solo local development. You can create an account later.</p>`\n : \"\";\n\n const localModeScript = showLocalMode\n ? `\n async function useLocally() {\n var btn = document.getElementById('local-btn');\n btn.disabled = true;\n btn.textContent = 'Setting up...';\n try {\n try {\n if (localStorage.getItem('${MIGRATE_FLAG_KEY}')) {\n localStorage.removeItem('${MIGRATE_FLAG_KEY}');\n }\n } catch (e) {}\n var res = await fetch(__anPath('/_agent-native/auth/local-mode'), { method: 'POST' });\n if (res.ok) {\n window.location.reload();\n } else {\n var data = await res.json().catch(function() { return {}; });\n var info = document.getElementById('local-info');\n if (info && data && data.error) {\n info.textContent = data.error;\n info.style.color = '#f87171';\n }\n btn.textContent = 'Not available';\n btn.disabled = true;\n }\n } catch(e) {\n btn.textContent = 'Failed — try again';\n btn.disabled = false;\n }\n }`\n : \"\";\n\n const marketing = opts.marketing;\n const hasMarketing = !!marketing;\n const esc = (s: string) =>\n s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n\n const marketingStyles = hasMarketing\n ? `\n body.has-marketing { padding: 0; position: relative; overflow-x: hidden; }\n #starfield {\n position: fixed;\n inset: 0;\n width: 100%;\n height: 100%;\n opacity: 0.35;\n pointer-events: none;\n z-index: 0;\n }\n .split {\n position: relative;\n z-index: 1;\n display: flex;\n min-height: 100vh;\n width: 100%;\n max-width: 1100px;\n margin: 0 auto;\n }\n .marketing-panel {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n padding: 3rem 3.5rem;\n }\n .marketing-content { max-width: 480px; }\n .app-name {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 2rem;\n font-weight: 700;\n color: #fff;\n margin-bottom: 0.625rem;\n letter-spacing: -0.02em;\n }\n .app-name img.brand-mark {\n height: 2.0125rem;\n width: auto;\n display: block;\n flex-shrink: 0;\n }\n .app-tagline {\n font-size: 1.25rem;\n color: #a1a1aa;\n line-height: 1.6;\n margin-bottom: 2rem;\n }\n .app-desc {\n font-size: 1rem;\n color: #71717a;\n line-height: 1.6;\n margin-bottom: 2rem;\n }\n .feature-list {\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 0.875rem;\n }\n .feature-list li {\n display: flex;\n align-items: flex-start;\n gap: 0.625rem;\n font-size: 1rem;\n color: #a1a1aa;\n line-height: 1.5;\n }\n .feature-list li::before {\n content: '';\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n margin-top: 6px;\n border-radius: 50%;\n background: #3f3f46;\n border: 1px solid #52525b;\n }\n .oss-link {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n margin-top: 2rem;\n font-size: 0.8125rem;\n color: #71717a;\n text-decoration: none;\n }\n .oss-link:hover { color: #a1a1aa; }\n .oss-link svg { width: 15px; height: 15px; flex-shrink: 0; }\n .form-panel {\n flex: 0 0 440px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 2rem;\n }\n .form-panel .card { max-width: 400px; }\n .form-panel .local-note { max-width: 400px; }\n @media (max-width: 900px) {\n .split { flex-direction: column; min-height: auto; }\n .marketing-panel { padding: 2rem 1.5rem 1.5rem; }\n .app-name { font-size: 1.375rem; }\n .app-name img.brand-mark { height: 1.4375rem; }\n .app-tagline { font-size: 1rem; margin-bottom: 1rem; }\n .app-desc { margin-bottom: 1rem; }\n .feature-list { gap: 0.5rem; }\n .form-panel { flex: none; padding: 1.5rem 1rem; }\n }\n`\n : \"\";\n\n const marketingPanelHtml = hasMarketing\n ? `<canvas id=\"starfield\"></canvas>\n<div class=\"split\">\n <div class=\"marketing-panel\">\n <div class=\"marketing-content\">\n <h2 class=\"app-name\">\n <img class=\"brand-mark\" src=\"/agent-native-icon-dark.svg\" alt=\"\" aria-hidden=\"true\" />\n <span>${esc(marketing!.appName)}</span>\n </h2>\n <p class=\"app-tagline\">${esc(marketing!.tagline)}</p>\n${marketing!.description ? ` <p class=\"app-desc\">${esc(marketing!.description)}</p>\\n` : \"\"}${\n marketing!.features?.length\n ? ` <ul class=\"feature-list\">\\n${marketing!.features.map((f) => ` <li>${esc(f)}</li>`).join(\"\\n\")}\\n </ul>\\n`\n : \"\"\n } <a class=\"oss-link\" href=\"https://github.com/BuilderIO/agent-native\" target=\"_blank\" rel=\"noreferrer\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2 2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 00-1.3-3.2 4.2 4.2 0 00-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3 0 00-6.2 0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2 0 00-.1 3.2A4.6 4.6 0 004 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21\"/></svg>\n Open source\n </a>\n </div>\n </div>\n <div class=\"form-panel\">`\n : \"\";\n\n const marketingCloseHtml = hasMarketing ? `\\n </div>\\n</div>` : \"\";\n\n const starfieldScript = hasMarketing\n ? `\n (function initStarfield() {\n var canvas = document.getElementById('starfield');\n if (!canvas) return;\n var gl = canvas.getContext('webgl', { alpha: false, antialias: false });\n if (!gl) return;\n\n var vs = gl.createShader(gl.VERTEX_SHADER);\n gl.shaderSource(vs, 'attribute vec2 position;void main(){gl_Position=vec4(position,0.0,1.0);}');\n gl.compileShader(vs);\n\n var fs = gl.createShader(gl.FRAGMENT_SHADER);\n gl.shaderSource(fs, [\n 'precision highp float;',\n 'uniform float iTime;uniform vec2 iResolution;',\n '#define S(a,b,t) smoothstep(a,b,t)',\n '#define NUM_LAYERS 4.',\n 'float N21(vec2 p){vec3 a=fract(vec3(p.xyx)*vec3(213.897,653.453,253.098));a+=dot(a,a.yzx+79.76);return fract((a.x+a.y)*a.z);}',\n 'vec2 GetPos(vec2 id,vec2 offs,float t){float n=N21(id+offs);float n1=fract(n*10.);float n2=fract(n*100.);float a=t+n;return offs+vec2(sin(a*n1),cos(a*n2))*.4;}',\n 'float df_line(vec2 a,vec2 b,vec2 p){vec2 pa=p-a,ba=b-a;float h=clamp(dot(pa,ba)/dot(ba,ba),0.,1.);return length(pa-ba*h);}',\n 'float line(vec2 a,vec2 b,vec2 uv){float r1=.025;float r2=.006;float d=df_line(a,b,uv);float d2=length(a-b);float fade=S(1.5,.5,d2);fade+=S(.05,.02,abs(d2-.75));return S(r1,r2,d)*fade;}',\n 'float NetLayer(vec2 st,float n,float t){',\n ' vec2 id=floor(st)+n;st=fract(st)-.5;',\n ' vec2 p0=GetPos(id,vec2(-1,-1),t);vec2 p1=GetPos(id,vec2(0,-1),t);vec2 p2=GetPos(id,vec2(1,-1),t);',\n ' vec2 p3=GetPos(id,vec2(-1,0),t);vec2 p4=GetPos(id,vec2(0,0),t);vec2 p5=GetPos(id,vec2(1,0),t);',\n ' vec2 p6=GetPos(id,vec2(-1,1),t);vec2 p7=GetPos(id,vec2(0,1),t);vec2 p8=GetPos(id,vec2(1,1),t);',\n ' float m=0.;float sparkle=0.;float d;float s;float pulse;',\n ' m+=line(p4,p0,st);d=length(st-p0);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p0.x)+fract(p0.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p1,st);d=length(st-p1);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p1.x)+fract(p1.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p2,st);d=length(st-p2);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p2.x)+fract(p2.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p3,st);d=length(st-p3);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p3.x)+fract(p3.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p4,st);d=length(st-p4);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p4.x)+fract(p4.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p5,st);d=length(st-p5);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p5.x)+fract(p5.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p6,st);d=length(st-p6);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p6.x)+fract(p6.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p7,st);d=length(st-p7);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p7.x)+fract(p7.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p4,p8,st);d=length(st-p8);s=(.005/(d*d));s*=S(1.,.7,d);pulse=sin((fract(p8.x)+fract(p8.y)+t)*5.)*.4+.6;pulse=pow(pulse,20.);sparkle+=s*pulse;',\n ' m+=line(p1,p3,st);m+=line(p1,p5,st);m+=line(p7,p5,st);m+=line(p7,p3,st);',\n ' float sPhase=(sin(t+n)+sin(t*.1))*.25+.5;sPhase+=pow(sin(t*.1)*.5+.5,50.)*5.;m+=sparkle*sPhase;',\n ' return m;',\n '}',\n 'void mainImage(out vec4 fragColor,in vec2 fragCoord){',\n ' vec2 uv=(fragCoord-iResolution.xy*.5)/iResolution.y;',\n ' float t=iTime*.03;float s=sin(t);float c=cos(t);mat2 rot=mat2(c,-s,s,c);vec2 st=uv*rot;',\n ' float m=0.;',\n ' for(float i=0.;i<1.;i+=1./NUM_LAYERS){float z=fract(t+i);float size=mix(15.,1.,z);float fade=S(0.,.6,z)*S(1.,.8,z);m+=fade*NetLayer(st*size,i,iTime*0.3);}',\n ' vec3 col=vec3(0.35)*m;col*=1.-dot(uv,uv);',\n ' float tt=min(iTime,5.0);col*=S(0.,20.,tt);',\n ' col=clamp(col,0.,1.);fragColor=vec4(col,1.);',\n '}',\n 'void main(){mainImage(gl_FragColor,gl_FragCoord.xy);}'\n ].join('\\\\n'));\n gl.compileShader(fs);\n\n var prog = gl.createProgram();\n gl.attachShader(prog, vs);\n gl.attachShader(prog, fs);\n gl.linkProgram(prog);\n gl.useProgram(prog);\n\n var buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]), gl.STATIC_DRAW);\n var pos = gl.getAttribLocation(prog, 'position');\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n var uTime = gl.getUniformLocation(prog, 'iTime');\n var uRes = gl.getUniformLocation(prog, 'iResolution');\n\n function resize() {\n var w = window.innerWidth, h = window.innerHeight;\n var dpr = Math.min(window.devicePixelRatio, 1.5);\n canvas.width = w * dpr; canvas.height = h * dpr;\n gl.viewport(0, 0, canvas.width, canvas.height);\n }\n resize();\n window.addEventListener('resize', resize);\n\n var start = performance.now(), last = 0;\n function render(now) {\n requestAnimationFrame(render);\n if (now - last < 33) return;\n last = now;\n gl.uniform1f(uTime, (now - start) * 0.001);\n gl.uniform2f(uRes, canvas.width, canvas.height);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n }\n requestAnimationFrame(render);\n })();`\n : \"\";\n\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>${hasMarketing ? esc(marketing!.appName) + \" — Sign in\" : \"Welcome\"}</title>\n${\n hasMarketing\n ? `<meta name=\"description\" content=\"${esc(marketing!.tagline)}\">\n<meta property=\"og:title\" content=\"${esc(marketing!.appName)}\">\n<meta property=\"og:description\" content=\"${esc(marketing!.tagline)}\">`\n : \"\"\n}\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 flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n padding: 1rem;\n }\n .card {\n width: 100%;\n max-width: 400px;\n padding: 2rem;\n background: #141414;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 12px;\n }\n h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.25rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n .tabs {\n display: inline-flex;\n width: 100%;\n padding: 4px;\n margin-bottom: 1.5rem;\n background: rgba(255,255,255,0.06);\n border-radius: 8px;\n }\n .tab {\n flex: 1;\n padding: 0.5rem 0.75rem;\n background: none;\n border: none;\n color: #888;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n border-radius: 6px;\n }\n .tab.active {\n background: #1e1e1e;\n color: #fff;\n box-shadow: 0 1px 2px rgba(0,0,0,0.3);\n }\n .tab:hover:not(.active) { color: #bbb; }\n .form { display: none; }\n .form.active { display: block; }\n label { display: block; font-size: 0.8125rem; color: #888; margin-bottom: 0.375rem; }\n input {\n width: 100%;\n padding: 0.5rem 0.75rem;\n background: transparent;\n border: 1px solid rgba(255,255,255,0.12);\n border-radius: 6px;\n color: #e5e5e5;\n font-size: 0.875rem;\n outline: none;\n margin-bottom: 0.875rem;\n }\n input:focus { border-color: rgba(255,255,255,0.3); box-shadow: 0 0 0 1px rgba(255,255,255,0.1); }\n input::placeholder { color: #555; }\n button[type=\"submit\"], .btn-primary {\n width: 100%;\n margin-top: 0.25rem;\n padding: 0.5rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 6px;\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n }\n button[type=\"submit\"]:hover, .btn-primary:hover { background: #e5e5e5; }\n button[type=\"submit\"]:disabled { opacity: 0.5; cursor: not-allowed; }\n .btn-secondary {\n width: 100%;\n margin-top: 0.75rem;\n padding: 0.5rem;\n background: transparent;\n color: #888;\n border: 1px solid rgba(255,255,255,0.1);\n border-radius: 6px;\n font-size: 0.8125rem;\n cursor: pointer;\n }\n .btn-secondary:hover { color: #bbb; border-color: rgba(255,255,255,0.2); }\n .msg { margin-top: 0.75rem; font-size: 0.8125rem; display: none; }\n .msg.error { color: #f87171; }\n .msg.success { color: #4ade80; }\n .msg.show { display: block; }\n .divider {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin: 1.25rem 0;\n font-size: 0.75rem;\n color: #555;\n }\n .divider::before, .divider::after {\n content: '';\n flex: 1;\n height: 1px;\n background: rgba(255,255,255,0.08);\n }\n .local-info {\n font-size: 0.75rem;\n color: #666;\n margin-top: 0.5rem;\n line-height: 1.4;\n }\n .upgrade-note {\n margin-bottom: 1rem;\n padding: 0.75rem;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 8px;\n background: rgba(255,255,255,0.03);\n font-size: 0.75rem;\n line-height: 1.5;\n color: #a1a1aa;\n display: none;\n }\n .upgrade-note.show { display: block; }\n .btn-google {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.625rem;\n padding: 0.5rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 6px;\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n }\n .btn-google:hover { background: #e5e5e5; }\n .btn-google:disabled { opacity: 0.5; cursor: wait; }\n .btn-google svg { width: 18px; height: 18px; flex-shrink: 0; }\n .google-error { margin-top: 0.5rem; font-size: 0.8125rem; color: #f87171; display: none; }\n .google-error.show { display: block; }\n .local-note {\n display: none;\n max-width: 400px;\n width: 100%;\n margin-top: 1rem;\n padding: 0.625rem 0.875rem;\n font-size: 0.6875rem;\n line-height: 1.5;\n color: #666;\n border: 1px dashed rgba(255,255,255,0.08);\n border-radius: 8px;\n text-align: center;\n }\n .local-note.show { display: block; }\n .local-note strong { color: #999; font-weight: 500; }\n .local-note a { color: #888; text-decoration: none; }\n .local-note a:hover { color: #bbb; }\n${marketingStyles}\n</style>\n</head>\n<body${hasMarketing ? ' class=\"has-marketing\"' : \"\"}>\n${marketingPanelHtml}\n<div class=\"card\">\n <h1 id=\"heading\">Welcome</h1>\n <p class=\"subtitle\" id=\"subtitle\">Create an account to get started</p>\n <p class=\"upgrade-note\" id=\"upgrade-note\">\n You started this flow from <code>local@localhost</code>. Continue signing in to upgrade this workspace to a real account and migrate your local data. If you want to cancel that and keep using local mode, use the secondary button below.\n </p>\n\n${\n showGoogle\n ? `\n <button class=\"btn-google\" id=\"google-btn\" onclick=\"signInWithGoogle()\">\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=\"google-error\" id=\"google-err\"></p>\n${googleOnly ? \"\" : `\\n <div class=\"divider\">or</div>\\n`}\n`\n : googleOnly\n ? `\n <p style=\"color:#f87171;font-size:0.875rem;text-align:center;padding:1rem 0\">\n Google sign-in is not configured. Set <code>GOOGLE_CLIENT_ID</code> and\n <code>GOOGLE_CLIENT_SECRET</code> environment variables to enable login.\n </p>\n`\n : \"\"\n}\n${\n googleOnly\n ? \"\"\n : ` <div class=\"tabs\">\n <button class=\"tab\" data-tab=\"signup\">Create account</button>\n <button class=\"tab\" data-tab=\"login\">Sign in</button>\n </div>\n\n <form id=\"signup-form\" class=\"form\">\n <label for=\"s-email\">Email</label>\n <input id=\"s-email\" type=\"email\" autocomplete=\"email\" autofocus placeholder=\"you@example.com\" required />\n <label for=\"s-pass\">Password</label>\n <input id=\"s-pass\" type=\"password\" autocomplete=\"new-password\" placeholder=\"At least 8 characters\" required minlength=\"8\" />\n <label for=\"s-pass2\">Confirm password</label>\n <input id=\"s-pass2\" type=\"password\" autocomplete=\"new-password\" placeholder=\"Confirm password\" required minlength=\"8\" />\n <button type=\"submit\">Create account</button>\n <p class=\"msg\" id=\"s-msg\"></p>\n </form>\n\n <form id=\"login-form\" class=\"form\">\n <label for=\"l-email\">Email</label>\n <input id=\"l-email\" type=\"email\" autocomplete=\"email\" placeholder=\"you@example.com\" required />\n <label for=\"l-pass\">Password</label>\n <input id=\"l-pass\" type=\"password\" autocomplete=\"current-password\" placeholder=\"Enter password\" required />\n <button type=\"submit\">Sign in</button>\n <p class=\"msg error\" id=\"l-msg\"></p>\n <p style=\"margin-top:0.75rem;font-size:0.75rem;text-align:right\">\n <a href=\"#\" id=\"forgot-link\" style=\"color:#888;text-decoration:underline;text-underline-offset:2px\">Forgot password?</a>\n </p>\n </form>\n\n <form id=\"forgot-form\" class=\"form\">\n <label for=\"f-email\">Email</label>\n <input id=\"f-email\" type=\"email\" autocomplete=\"email\" placeholder=\"you@example.com\" required />\n <button type=\"submit\">Send reset link</button>\n <p class=\"msg\" id=\"f-msg\"></p>\n <p style=\"margin-top:0.75rem;font-size:0.75rem;text-align:center\">\n <a href=\"#\" id=\"back-to-login\" style=\"color:#888;text-decoration:underline;text-underline-offset:2px\">Back to sign in</a>\n </p>\n </form>`\n}\n${localModeBlock}\n</div>\n<p class=\"local-note\" id=\"local-note\">\n Your account is stored in this app's own DB (<strong>${getConnectionLabel()}</strong>), not a third-party service.\n</p>${marketingCloseHtml}\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 (function revealLocalNote() {\n var h = location.hostname;\n if (h === 'localhost' || h === '127.0.0.1' || h === '::1' || h.endsWith('.local')) {\n var n = document.getElementById('local-note');\n if (n) n.classList.add('show');\n }\n })();\n${\n googleOnly\n ? \"\"\n : ` var TAB_STORAGE_KEY = 'an.onboarding.tab';\n var tabs = document.querySelectorAll('.tab');\n var forms = document.querySelectorAll('.form');\n var subtitles = { signup: 'Create an account to get started', login: 'Sign in to your account' };\n var headings = { signup: 'Welcome', login: 'Welcome back' };\n function setActiveTab(name, opts) {\n if (name !== 'signup' && name !== 'login') return;\n var form = document.getElementById(name + '-form');\n if (!form) return;\n tabs.forEach(function(x) { x.classList.remove('active'); });\n forms.forEach(function(x) { x.classList.remove('active'); });\n var btn = document.querySelector('.tab[data-tab=\"' + name + '\"]');\n if (btn) btn.classList.add('active');\n form.classList.add('active');\n var sub = document.getElementById('subtitle');\n if (sub && subtitles[name]) sub.textContent = subtitles[name];\n var heading = document.getElementById('heading');\n if (heading && headings[name]) heading.textContent = headings[name];\n if (opts && opts.persist) {\n try { localStorage.setItem(TAB_STORAGE_KEY, name); } catch (e) {}\n }\n }\n (function initActiveTab() {\n var initial = 'signup';\n try {\n var params = new URLSearchParams(location.search);\n var qp = params.get('tab');\n if (qp === 'login' || qp === 'signup') {\n initial = qp;\n } else if (params.has('verified')) {\n initial = 'login';\n } else {\n var stored = localStorage.getItem(TAB_STORAGE_KEY);\n if (stored === 'login' || stored === 'signup') initial = stored;\n }\n } catch (e) {}\n setActiveTab(initial, { persist: false });\n try {\n if (new URLSearchParams(location.search).has('verified')) {\n var msg = document.getElementById('l-msg');\n if (msg) {\n msg.textContent = 'Email verified! Sign in to continue.';\n msg.classList.remove('error');\n msg.classList.add('show', 'success');\n }\n }\n } catch (e) {}\n })();\n tabs.forEach(function(t) { t.addEventListener('click', function() {\n setActiveTab(t.dataset.tab, { persist: true });\n }); });\n\n document.getElementById('signup-form').addEventListener('submit', async function(e) {\n e.preventDefault();\n var form = e.currentTarget;\n var btn = form.querySelector('button[type=\"submit\"]');\n var msg = document.getElementById('s-msg');\n msg.classList.remove('show', 'error', 'success');\n var pass = document.getElementById('s-pass').value;\n var pass2 = document.getElementById('s-pass2').value;\n if (pass !== pass2) {\n msg.textContent = 'Passwords do not match';\n msg.classList.add('show', 'error');\n return;\n }\n var originalLabel = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Creating account…';\n try {\n var email = document.getElementById('s-email').value;\n var res = await fetch(__anPath('/_agent-native/auth/register'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email, password: pass }),\n });\n var data = await res.json().catch(function() { return {}; });\n if (res.ok) {\n // If email verification is required, the server won't return a session.\n // Try logging in — if it fails (unverified), show a \"check your email\" message.\n var loginRes = await fetch(__anPath('/_agent-native/auth/login'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email, password: pass }),\n });\n if (loginRes.ok) {\n msg.textContent = 'Account created — signing you in…';\n msg.classList.add('show', 'success');\n window.location.reload();\n return;\n }\n // Login failed — likely email verification required.\n // Switch to login tab first, then show the message there so\n // the user actually sees it (the signup form is hidden after switch).\n btn.disabled = false;\n btn.textContent = originalLabel;\n setActiveTab('login', { persist: true });\n var loginMsg = document.getElementById('l-msg');\n if (loginMsg) {\n loginMsg.textContent = 'Account created! Check your email to verify, then sign in.';\n loginMsg.classList.remove('error');\n loginMsg.classList.add('show', 'success');\n }\n return;\n }\n msg.textContent = data.error || 'Registration failed';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = originalLabel;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = originalLabel;\n }\n });\n\n var forgotLink = document.getElementById('forgot-link');\n var backToLogin = document.getElementById('back-to-login');\n if (forgotLink) forgotLink.addEventListener('click', function(e) {\n e.preventDefault();\n document.getElementById('login-form').classList.remove('active');\n document.getElementById('forgot-form').classList.add('active');\n var sub = document.getElementById('subtitle');\n if (sub) sub.textContent = 'Reset your password';\n var heading = document.getElementById('heading');\n if (heading) heading.textContent = 'Reset password';\n var fEmail = document.getElementById('f-email');\n var lEmail = document.getElementById('l-email');\n if (lEmail && lEmail.value) fEmail.value = lEmail.value;\n setTimeout(function() { fEmail.focus(); }, 0);\n });\n if (backToLogin) backToLogin.addEventListener('click', function(e) {\n e.preventDefault();\n document.getElementById('forgot-form').classList.remove('active');\n document.getElementById('login-form').classList.add('active');\n var sub = document.getElementById('subtitle');\n if (sub) sub.textContent = subtitles.login;\n var heading = document.getElementById('heading');\n if (heading) heading.textContent = headings.login;\n });\n\n var forgotForm = document.getElementById('forgot-form');\n if (forgotForm) forgotForm.addEventListener('submit', async function(e) {\n e.preventDefault();\n var btn = e.currentTarget.querySelector('button[type=\"submit\"]');\n var msg = document.getElementById('f-msg');\n msg.classList.remove('show', 'error', 'success');\n var original = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Sending…';\n try {\n var email = document.getElementById('f-email').value;\n var res = await fetch(__anPath('/_agent-native/auth/ba/request-password-reset'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email }),\n });\n if (res.ok) {\n msg.textContent = 'If that email exists, a reset link is on its way.';\n msg.classList.add('show', 'success');\n btn.textContent = 'Sent';\n return;\n }\n var data = await res.json().catch(function() { return {}; });\n msg.textContent = (data && (data.message || data.error)) || 'Could not send reset email.';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n }\n });\n\n document.getElementById('login-form').addEventListener('submit', async function(e) {\n e.preventDefault();\n var form = e.currentTarget;\n var btn = form.querySelector('button[type=\"submit\"]');\n var msg = document.getElementById('l-msg');\n msg.classList.remove('show');\n var originalLabel = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Signing in…';\n try {\n var res = await fetch(__anPath('/_agent-native/auth/login'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email: document.getElementById('l-email').value,\n password: document.getElementById('l-pass').value,\n }),\n });\n if (res.ok) {\n window.location.reload();\n return;\n }\n var data = await res.json().catch(function() { return {}; });\n msg.textContent = data.error || 'Invalid email or password';\n msg.classList.add('show');\n btn.disabled = false;\n btn.textContent = originalLabel;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show');\n btn.disabled = false;\n btn.textContent = originalLabel;\n }\n });\n`\n}${localModeScript}\n${\n showLocalMode\n ? `\n (function syncUpgradeFromLocalUi() {\n var subtitle = document.querySelector('.subtitle');\n var note = document.getElementById('upgrade-note');\n var localBtn = document.getElementById('local-btn');\n var localInfo = document.getElementById('local-info');\n var divider = document.getElementById('local-divider');\n if (!subtitle || !note || !localBtn || !localInfo || !divider) return;\n try {\n if (!localStorage.getItem('${MIGRATE_FLAG_KEY}')) return;\n } catch (e) {\n return;\n }\n subtitle.textContent = 'Sign in to upgrade your local workspace';\n note.classList.add('show');\n localBtn.textContent = 'Stay in local mode';\n localInfo.textContent = 'Use this if you want to cancel the upgrade and go back to local@localhost on this device.';\n divider.textContent = 'or stay local';\n })();\n`\n : \"\"\n}\n${\n showGoogle\n ? `\n function __anGetReturnPath() {\n // If we landed here via /_agent-native/sign-in?return=X (force-sign-in\n // entrypoint from a public page), prefer the inner return URL.\n // Otherwise the loginHtml is being served at the URL the user actually\n // wanted to reach (a bookmarked / deep-linked private path), so use it.\n try {\n var inner = new URLSearchParams(window.location.search).get('return');\n if (inner) return inner;\n } catch(e) {}\n return window.location.pathname + window.location.search;\n }\n async function signInWithGoogle() {\n var btn = document.getElementById('google-btn');\n var err = document.getElementById('google-err');\n btn.disabled = true;\n err.classList.remove('show');\n try {\n var ret = __anGetReturnPath();\n var authUrl = __anPath('/_agent-native/google/auth-url') + '?return=' + encodeURIComponent(ret);\n var res = await fetch(authUrl);\n var data = await res.json();\n if (data.url) {\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n window.open(data.url, '_blank');\n btn.disabled = false;\n btn.textContent = 'Waiting for sign-in…';\n var poll = setInterval(function() {\n fetch(__anPath('/_agent-native/auth/session')).then(function(r) { return r.json(); }).then(function(s) {\n if (s && s.email) { clearInterval(poll); window.location.reload(); }\n }).catch(function() {});\n }, 1500);\n } else {\n err.textContent = data.message || 'Google OAuth is not configured.';\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 : \"\"\n}\n${starfieldScript}\n</script>\n</body>\n</html>`;\n}\n\n/** @deprecated Use getOnboardingHtml() instead */\nexport const ONBOARDING_HTML = getOnboardingHtml();\n\n/**\n * HTML for the password reset page — shown when the user clicks the link in\n * their reset email. Posts `{ newPassword, token }` to Better Auth's\n * `/reset-password` endpoint, then redirects to the login page.\n */\nexport function getResetPasswordHtml(): string {\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>Reset password</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0a0a0a; color: #e5e5e5; display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 1rem; }\n .card { width: 100%; max-width: 400px; padding: 2rem; background: #141414; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; }\n h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.25rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n label { display: block; font-size: 0.8125rem; color: #888; margin-bottom: 0.375rem; }\n input { width: 100%; padding: 0.5rem 0.75rem; background: transparent; border: 1px solid rgba(255,255,255,0.12); border-radius: 6px; color: #e5e5e5; font-size: 0.875rem; outline: none; margin-bottom: 0.875rem; }\n input:focus { border-color: rgba(255,255,255,0.3); box-shadow: 0 0 0 1px rgba(255,255,255,0.1); }\n input::placeholder { color: #555; }\n button[type=\"submit\"] { width: 100%; margin-top: 0.25rem; padding: 0.5rem; background: #fff; color: #000; border: none; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }\n button[type=\"submit\"]:hover { background: #e5e5e5; }\n button[type=\"submit\"]:disabled { opacity: 0.5; cursor: not-allowed; }\n .msg { margin-top: 0.75rem; font-size: 0.8125rem; display: none; }\n .msg.error { color: #f87171; }\n .msg.success { color: #4ade80; }\n .msg.show { display: block; }\n .back { display: inline-block; margin-top: 1rem; font-size: 0.75rem; color: #888; text-decoration: none; }\n .back:hover { color: #bbb; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <h1>Choose a new password</h1>\n <p class=\"subtitle\">Set a new password for your account.</p>\n <form id=\"reset-form\">\n <label for=\"p1\">New password</label>\n <input id=\"p1\" type=\"password\" autocomplete=\"new-password\" autofocus placeholder=\"At least 8 characters\" required minlength=\"8\" />\n <label for=\"p2\">Confirm password</label>\n <input id=\"p2\" type=\"password\" autocomplete=\"new-password\" placeholder=\"Confirm password\" required minlength=\"8\" />\n <button type=\"submit\">Save new password</button>\n <p class=\"msg\" id=\"msg\"></p>\n </form>\n <a class=\"back\" id=\"back-link\" href=\"/\">Back to sign in</a>\n</div>\n<script>\n (function() {\n // Derive the app's base path so apps mounted under a prefix\n // (e.g. /mail, /calendar) get sent home instead of to the root domain.\n var RESET_PATH = '/_agent-native/auth/reset';\n var pathname = window.location.pathname;\n var idx = pathname.indexOf(RESET_PATH);\n var basePath = (idx >= 0 ? pathname.slice(0, idx) : '') || '';\n var homeHref = basePath + '/';\n var backLink = document.getElementById('back-link');\n if (backLink) backLink.setAttribute('href', homeHref);\n var params = new URLSearchParams(location.search);\n var token = params.get('token') || '';\n var msg = document.getElementById('msg');\n if (!token) {\n msg.textContent = 'Missing or invalid reset token. Request a new reset link.';\n msg.classList.add('show', 'error');\n document.getElementById('reset-form').style.display = 'none';\n return;\n }\n document.getElementById('reset-form').addEventListener('submit', async function(e) {\n e.preventDefault();\n var btn = e.currentTarget.querySelector('button[type=\"submit\"]');\n var p1 = document.getElementById('p1').value;\n var p2 = document.getElementById('p2').value;\n msg.classList.remove('show', 'error', 'success');\n if (p1 !== p2) {\n msg.textContent = 'Passwords do not match';\n msg.classList.add('show', 'error');\n return;\n }\n var original = btn.textContent;\n btn.disabled = true;\n btn.textContent = 'Saving…';\n try {\n var res = await fetch(basePath + '/_agent-native/auth/ba/reset-password', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ newPassword: p1, token: token }),\n });\n if (res.ok) {\n msg.textContent = 'Password updated — redirecting to sign in…';\n msg.classList.add('show', 'success');\n setTimeout(function() { window.location.href = homeHref; }, 1200);\n return;\n }\n var data = await res.json().catch(function() { return {}; });\n msg.textContent = (data && (data.message || data.error)) || 'Reset failed. The link may have expired — request a new one.';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n } catch (err) {\n msg.textContent = 'Network error — please try again';\n msg.classList.add('show', 'error');\n btn.disabled = false;\n btn.textContent = original;\n }\n });\n })();\n</script>\n</body>\n</html>`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"schema-prompt.d.ts","sourceRoot":"","sources":["../../src/server/schema-prompt.ts"],"names":[],"mappings":"AAgMA,gFAAgF;AAChF,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AA6CD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqIlB"}
1
+ {"version":3,"file":"schema-prompt.d.ts","sourceRoot":"","sources":["../../src/server/schema-prompt.ts"],"names":[],"mappings":"AAgMA,gFAAgF;AAChF,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AA6CD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwIlB"}
@@ -245,11 +245,12 @@ export async function loadSchemaPromptBlock(opts) {
245
245
  lines.push("## SQL tools");
246
246
  lines.push("- `db-schema` — refresh the full schema with indexes and foreign keys");
247
247
  lines.push("- `db-query` — run a SELECT (read-only; results already filtered to the current user/org)");
248
- lines.push("- `db-exec` — run INSERT / UPDATE / DELETE (writes already scoped; owner_email and org_id are auto-injected on INSERT)");
248
+ lines.push("- `db-exec` — run INSERT / UPDATE / DELETE / REPLACE (writes already scoped; owner_email and org_id are auto-injected on INSERT). For multiple related writes, pass `statements` so they run in one transaction instead of separate tool calls. Schema changes are blocked.");
249
249
  lines.push("- `db-patch` — surgical search-and-replace on a large text column. Send `{find, replace}` pairs instead of the full new value. Use this for edits to large fields (documents, slide HTML, dashboard/form JSON) — it avoids re-sending multi-kilobyte strings and saves tokens. Targets exactly one row (narrow `--where` by primary key). Uses the same per-user/per-org scoping as db-exec.");
250
250
  lines.push("");
251
251
  lines.push("### When to pick which SQL tool");
252
252
  lines.push("- Set a short column outright, update multiple columns, or do computed updates (`calories = calories + 50`) → `db-exec UPDATE`.");
253
+ lines.push('- Insert/update several rows as one logical operation → `db-exec` with `statements: \'[{"sql":"...","args":[...]}]\'` so the batch commits or rolls back together.');
253
254
  lines.push("- Change a small slice of a large text/JSON column → `db-patch`. Much cheaper token-wise than re-sending the whole column.");
254
255
  lines.push("- A template-specific action exists for the table (`edit-document`, `update-slide`, etc.) → use that action. It encodes business rules and pushes live Yjs updates to any open collaborative editor; raw SQL does neither.");
255
256
  lines.push("- Read data → `db-query`. Never re-add `WHERE owner_email = ...` — scoping already applies it.");
@@ -1 +1 @@
1
- {"version":3,"file":"schema-prompt.js","sourceRoot":"","sources":["../../src/server/schema-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Auto-introspected SQL schema context block for the agent's system prompt.\n *\n * On every chat turn, the framework appends a compact, always-fresh summary\n * of the app's SQL database — every table, every column, every foreign key —\n * so the agent knows exactly what data model it's working with. The schema\n * is pulled live from `information_schema` (Postgres) or `PRAGMA table_info`\n * (SQLite), cached briefly to keep latency down but never hard-coded.\n *\n * The block also:\n * - points at the db-query / db-exec / db-patch / db-schema tools for runtime access\n * - lists Postgres column descriptions (`COMMENT ON COLUMN ...`) if present\n * - explains the current user/org data scoping so the agent doesn't re-filter\n * by hand (which would be redundant and easy to get wrong)\n */\nimport {\n getDbExec,\n getDatabaseUrl,\n isPostgres,\n type DbExec,\n} from \"../db/client.js\";\n\ninterface ColumnSchema {\n name: string;\n type: string;\n notnull: boolean;\n pk: boolean;\n comment: string | null;\n}\n\ninterface ForeignKey {\n from: string;\n table: string;\n to: string;\n}\n\ninterface TableSchema {\n name: string;\n columns: ColumnSchema[];\n foreignKeys: ForeignKey[];\n comment: string | null;\n}\n\n// Short-lived in-memory cache — schema rarely changes between messages, but\n// we want new tables to show up within a few seconds during active dev.\nconst CACHE_TTL_MS = 15_000;\nlet _cache: {\n key: string;\n expires: number;\n tables: TableSchema[];\n dialect: \"postgres\" | \"sqlite\";\n} | null = null;\n\nfunction cacheKey(): string {\n return (isPostgres() ? \"pg:\" : \"lite:\") + (getDatabaseUrl() || \"\");\n}\n\n// ─── Postgres introspection ─────────────────────────────────────────────────\n\nasync function introspectPostgres(db: DbExec): Promise<TableSchema[]> {\n const tablesRes = await db.execute({\n sql: `SELECT table_name AS name,\n obj_description((quote_ident(table_schema) || '.' || quote_ident(table_name))::regclass, 'pg_class') AS comment\n FROM information_schema.tables\n WHERE table_schema = 'public' AND table_type = 'BASE TABLE'\n ORDER BY table_name`,\n args: [],\n });\n\n const tables: TableSchema[] = [];\n\n for (const t of tablesRes.rows as any[]) {\n const name = t.name as string;\n\n const colsRes = await db.execute({\n sql: `SELECT c.column_name AS name,\n c.data_type AS type,\n CASE WHEN c.is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,\n col_description((quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass, c.ordinal_position) AS comment\n FROM information_schema.columns c\n WHERE c.table_name = ? AND c.table_schema = 'public'\n ORDER BY c.ordinal_position`,\n args: [name],\n });\n\n const pksRes = await db.execute({\n sql: `SELECT kcu.column_name AS name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.table_name = ? AND tc.constraint_type = 'PRIMARY KEY'`,\n args: [name],\n });\n const pkSet = new Set((pksRes.rows as any[]).map((r) => r.name as string));\n\n const fksRes = await db.execute({\n sql: `SELECT kcu.column_name AS col_from,\n ccu.table_name AS ref_table,\n ccu.column_name AS ref_col\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n JOIN information_schema.constraint_column_usage ccu\n ON tc.constraint_name = ccu.constraint_name\n WHERE tc.table_name = ? AND tc.constraint_type = 'FOREIGN KEY'`,\n args: [name],\n });\n\n tables.push({\n name,\n comment: (t.comment as string | null) ?? null,\n columns: (colsRes.rows as any[]).map((c) => ({\n name: c.name as string,\n type: (c.type as string) || \"any\",\n notnull: Number(c.notnull) === 1,\n pk: pkSet.has(c.name as string),\n comment: (c.comment as string | null) ?? null,\n })),\n foreignKeys: (fksRes.rows as any[]).map((f) => ({\n from: f.col_from as string,\n table: f.ref_table as string,\n to: f.ref_col as string,\n })),\n });\n }\n\n return tables;\n}\n\n// ─── SQLite / libSQL / D1 introspection ────────────────────────────────────\n\nasync function introspectSqlite(db: DbExec): Promise<TableSchema[]> {\n const tablesRes = await db.execute(\n `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`,\n );\n\n const tables: TableSchema[] = [];\n\n for (const row of tablesRes.rows as any[]) {\n const name = row.name as string;\n if (!name) continue;\n // Quote the identifier for PRAGMA calls; SQLite requires doubling embedded quotes.\n const escaped = name.replace(/\"/g, '\"\"');\n\n const colsRes = await db.execute(`PRAGMA table_info(\"${escaped}\")`);\n const fksRes = await db.execute(`PRAGMA foreign_key_list(\"${escaped}\")`);\n\n tables.push({\n name,\n comment: null, // SQLite has no column/table comments\n columns: (colsRes.rows as any[]).map((c) => ({\n name: c.name as string,\n type: ((c.type as string) || \"\").toLowerCase() || \"any\",\n notnull: Number(c.notnull) === 1,\n pk: Number(c.pk) === 1,\n comment: null,\n })),\n foreignKeys: (fksRes.rows as any[]).map((f) => ({\n from: f.from as string,\n table: f.table as string,\n to: f.to as string,\n })),\n });\n }\n\n return tables;\n}\n\n// ─── Cached entry point ─────────────────────────────────────────────────────\n\nasync function getSchema(): Promise<{\n tables: TableSchema[];\n dialect: \"postgres\" | \"sqlite\";\n}> {\n const key = cacheKey();\n const now = Date.now();\n if (_cache && _cache.key === key && _cache.expires > now) {\n return { tables: _cache.tables, dialect: _cache.dialect };\n }\n\n const db = getDbExec();\n const dialect: \"postgres\" | \"sqlite\" = isPostgres() ? \"postgres\" : \"sqlite\";\n const tables =\n dialect === \"postgres\"\n ? await introspectPostgres(db)\n : await introspectSqlite(db);\n\n _cache = { key, expires: now + CACHE_TTL_MS, tables, dialect };\n return { tables, dialect };\n}\n\n/** Manually drop the cache — useful from tests or after running a migration. */\nexport function invalidateSchemaPromptCache(): void {\n _cache = null;\n}\n\n// ─── Formatting ─────────────────────────────────────────────────────────────\n\nfunction shortType(type: string): string {\n // Trim verbose Postgres type names for compactness in the prompt.\n const t = type.toLowerCase();\n if (t === \"character varying\") return \"varchar\";\n if (t === \"timestamp without time zone\") return \"timestamp\";\n if (t === \"timestamp with time zone\") return \"timestamptz\";\n if (t === \"double precision\") return \"double\";\n return t;\n}\n\nfunction formatTable(table: TableSchema): string {\n const fkByCol = new Map<string, string>();\n for (const fk of table.foreignKeys) {\n fkByCol.set(fk.from, `${fk.table}.${fk.to}`);\n }\n\n const cols = table.columns.map((c) => {\n const flags: string[] = [];\n if (c.pk) flags.push(\"pk\");\n if (!c.notnull && !c.pk) flags.push(\"null\");\n const fk = fkByCol.get(c.name);\n if (fk) flags.push(`→${fk}`);\n\n // Flag scoping columns so the agent understands per-user/per-org filtering.\n if (c.name === \"owner_email\") flags.push(\"user-scope\");\n if (c.name === \"org_id\") flags.push(\"org-scope\");\n\n const flagStr = flags.length ? ` [${flags.join(\", \")}]` : \"\";\n const commentStr = c.comment ? ` -- ${c.comment.replace(/\\s+/g, \" \")}` : \"\";\n return ` ${c.name} ${shortType(c.type)}${flagStr}${commentStr}`;\n });\n\n const header = table.comment\n ? ` ${table.name} -- ${table.comment.replace(/\\s+/g, \" \")}`\n : ` ${table.name}`;\n\n return [header, ...cols].join(\"\\n\");\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\n/**\n * Build the `<sql-database>` block appended to the system prompt on every turn.\n *\n * `owner` and `orgId` come from the per-request context (AGENT_USER_EMAIL /\n * AGENT_ORG_ID) and are surfaced so the agent knows who it is acting on behalf\n * of — and understands that rows are already filtered for that identity.\n */\nexport async function loadSchemaPromptBlock(opts: {\n owner?: string | null;\n orgId?: string | null;\n /** If true, mention db-query/db-exec/db-patch/db-schema as available tools. */\n hasRawDbTools?: boolean;\n}): Promise<string> {\n let tables: TableSchema[];\n let dialect: \"postgres\" | \"sqlite\";\n try {\n const res = await getSchema();\n tables = res.tables;\n dialect = res.dialect;\n } catch (err) {\n // DB not ready, or introspection blew up — don't take the chat down.\n return \"\";\n }\n\n if (tables.length === 0) return \"\";\n\n // Partition framework-internal tables from template tables so the agent\n // focuses on the data model it's most likely to touch.\n const CORE_TABLES = new Set([\n \"application_state\",\n \"settings\",\n \"oauth_tokens\",\n \"sessions\",\n \"resources\",\n \"chat_threads\",\n \"_collab_docs\",\n \"usage_events\",\n \"usage_totals\",\n \"user\",\n \"account\",\n \"verification\",\n \"organization\",\n \"member\",\n \"invitation\",\n ]);\n\n const templateTables = tables.filter((t) => !CORE_TABLES.has(t.name));\n const coreTables = tables.filter((t) => CORE_TABLES.has(t.name));\n\n const lines: string[] = [];\n lines.push(\"<sql-database>\");\n lines.push(\n `The app's state lives in a SQL database (${dialect}). The schema below is auto-introspected fresh each turn — treat it as authoritative.`,\n );\n lines.push(\"\");\n\n if (templateTables.length > 0) {\n lines.push(\"## Template tables\");\n lines.push(\"\");\n for (const t of templateTables) {\n lines.push(formatTable(t));\n lines.push(\"\");\n }\n }\n\n if (coreTables.length > 0) {\n lines.push(\n \"## Framework tables (auth, resources, chat threads, app-state, etc.) — usually read/written via dedicated tools, not raw SQL\",\n );\n lines.push(\"\");\n for (const t of coreTables) {\n lines.push(formatTable(t));\n lines.push(\"\");\n }\n }\n\n // Tooling references.\n if (opts.hasRawDbTools) {\n lines.push(\"## SQL tools\");\n lines.push(\n \"- `db-schema` — refresh the full schema with indexes and foreign keys\",\n );\n lines.push(\n \"- `db-query` — run a SELECT (read-only; results already filtered to the current user/org)\",\n );\n lines.push(\n \"- `db-exec` — run INSERT / UPDATE / DELETE (writes already scoped; owner_email and org_id are auto-injected on INSERT)\",\n );\n lines.push(\n \"- `db-patch` — surgical search-and-replace on a large text column. Send `{find, replace}` pairs instead of the full new value. Use this for edits to large fields (documents, slide HTML, dashboard/form JSON) — it avoids re-sending multi-kilobyte strings and saves tokens. Targets exactly one row (narrow `--where` by primary key). Uses the same per-user/per-org scoping as db-exec.\",\n );\n lines.push(\"\");\n lines.push(\"### When to pick which SQL tool\");\n lines.push(\n \"- Set a short column outright, update multiple columns, or do computed updates (`calories = calories + 50`) → `db-exec UPDATE`.\",\n );\n lines.push(\n \"- Change a small slice of a large text/JSON column → `db-patch`. Much cheaper token-wise than re-sending the whole column.\",\n );\n lines.push(\n \"- A template-specific action exists for the table (`edit-document`, `update-slide`, etc.) → use that action. It encodes business rules and pushes live Yjs updates to any open collaborative editor; raw SQL does neither.\",\n );\n lines.push(\n \"- Read data → `db-query`. Never re-add `WHERE owner_email = ...` — scoping already applies it.\",\n );\n lines.push(\"\");\n lines.push(\"### External data sources vs the app database\");\n lines.push(\n \"The `db-*` tools ONLY query the app's own SQL database (the tables listed above). They do NOT reach external data warehouses, analytics platforms, or third-party services.\",\n );\n lines.push(\n \"If the user asks about tables that are NOT in the schema above, use the appropriate template action instead — for example `bigquery` for BigQuery warehouse tables, `ga4-report` for Google Analytics, `hubspot-deals` for HubSpot, etc. Check your available actions for the right data-source-specific tool.\",\n );\n lines.push(\n \"**Never use `db-query` for external data.** It will fail because those tables don't exist in the app database.\",\n );\n lines.push(\"\");\n } else {\n lines.push(\n \"SQL is accessed through the template actions listed above. The schema is shown for context — so you understand the data model those actions operate on.\",\n );\n lines.push(\"\");\n }\n\n // Data scoping context.\n const ownerLine = opts.owner ? opts.owner : \"(unresolved)\";\n const orgLine = opts.orgId ? opts.orgId : \"(none)\";\n lines.push(\"## Data scoping (enforced at the SQL layer)\");\n lines.push(`- Current user: \\`${ownerLine}\\``);\n lines.push(`- Current org: \\`${orgLine}\\``);\n lines.push(\n \"- Tables with an `owner_email` column are automatically filtered to the current user via temporary views before every query.\",\n );\n lines.push(\n \"- Tables with an `org_id` column are automatically filtered to the current org as well.\",\n );\n lines.push(\n \"- On INSERT, `owner_email` and `org_id` are auto-injected — do NOT set them manually.\",\n );\n lines.push(\n \"- Do NOT add `WHERE owner_email = ...` or `WHERE org_id = ...` to your queries — the filter is already applied, and re-adding it will confuse the scoped view.\",\n );\n lines.push(\"</sql-database>\");\n\n return \"\\n\\n\" + lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"schema-prompt.js","sourceRoot":"","sources":["../../src/server/schema-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,GAEX,MAAM,iBAAiB,CAAC;AAuBzB,4EAA4E;AAC5E,wEAAwE;AACxE,MAAM,YAAY,GAAG,MAAM,CAAC;AAC5B,IAAI,MAAM,GAKC,IAAI,CAAC;AAEhB,SAAS,QAAQ;IACf,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,+EAA+E;AAE/E,KAAK,UAAU,kBAAkB,CAAC,EAAU;IAC1C,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;QACjC,GAAG,EAAE;;;;8BAIqB;QAC1B,IAAI,EAAE,EAAE;KACT,CAAC,CAAC;IAEH,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,IAAa,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAc,CAAC;QAE9B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;YAC/B,GAAG,EAAE;;;;;;wCAM6B;YAClC,IAAI,EAAE,CAAC,IAAI,CAAC;SACb,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;YAC9B,GAAG,EAAE;;;;;2EAKgE;YACrE,IAAI,EAAE,CAAC,IAAI,CAAC;SACb,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAE,MAAM,CAAC,IAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAc,CAAC,CAAC,CAAC;QAE3E,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;YAC9B,GAAG,EAAE;;;;;;;;2EAQgE;YACrE,IAAI,EAAE,CAAC,IAAI,CAAC;SACb,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC;YACV,IAAI;YACJ,OAAO,EAAG,CAAC,CAAC,OAAyB,IAAI,IAAI;YAC7C,OAAO,EAAG,OAAO,CAAC,IAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3C,IAAI,EAAE,CAAC,CAAC,IAAc;gBACtB,IAAI,EAAG,CAAC,CAAC,IAAe,IAAI,KAAK;gBACjC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;gBAChC,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAc,CAAC;gBAC/B,OAAO,EAAG,CAAC,CAAC,OAAyB,IAAI,IAAI;aAC9C,CAAC,CAAC;YACH,WAAW,EAAG,MAAM,CAAC,IAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC9C,IAAI,EAAE,CAAC,CAAC,QAAkB;gBAC1B,KAAK,EAAE,CAAC,CAAC,SAAmB;gBAC5B,EAAE,EAAE,CAAC,CAAC,OAAiB;aACxB,CAAC,CAAC;SACJ,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAE9E,KAAK,UAAU,gBAAgB,CAAC,EAAU;IACxC,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAChC,8FAA8F,CAC/F,CAAC;IAEF,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,IAAa,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAc,CAAC;QAChC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,mFAAmF;QACnF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAEzC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,sBAAsB,OAAO,IAAI,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,4BAA4B,OAAO,IAAI,CAAC,CAAC;QAEzE,MAAM,CAAC,IAAI,CAAC;YACV,IAAI;YACJ,OAAO,EAAE,IAAI,EAAE,sCAAsC;YACrD,OAAO,EAAG,OAAO,CAAC,IAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3C,IAAI,EAAE,CAAC,CAAC,IAAc;gBACtB,IAAI,EAAE,CAAE,CAAC,CAAC,IAAe,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,IAAI,KAAK;gBACvD,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;gBAChC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC;gBACtB,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YACH,WAAW,EAAG,MAAM,CAAC,IAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC9C,IAAI,EAAE,CAAC,CAAC,IAAc;gBACtB,KAAK,EAAE,CAAC,CAAC,KAAe;gBACxB,EAAE,EAAE,CAAC,CAAC,EAAY;aACnB,CAAC,CAAC;SACJ,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAE/E,KAAK,UAAU,SAAS;IAItB,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,MAAM,IAAI,MAAM,CAAC,GAAG,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC;QACzD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACvB,MAAM,OAAO,GAA0B,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC5E,MAAM,MAAM,GACV,OAAO,KAAK,UAAU;QACpB,CAAC,CAAC,MAAM,kBAAkB,CAAC,EAAE,CAAC;QAC9B,CAAC,CAAC,MAAM,gBAAgB,CAAC,EAAE,CAAC,CAAC;IAEjC,MAAM,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC/D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,2BAA2B;IACzC,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC;AAED,+EAA+E;AAE/E,SAAS,SAAS,CAAC,IAAY;IAC7B,kEAAkE;IAClE,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,IAAI,CAAC,KAAK,mBAAmB;QAAE,OAAO,SAAS,CAAC;IAChD,IAAI,CAAC,KAAK,6BAA6B;QAAE,OAAO,WAAW,CAAC;IAC5D,IAAI,CAAC,KAAK,0BAA0B;QAAE,OAAO,aAAa,CAAC;IAC3D,IAAI,CAAC,KAAK,kBAAkB;QAAE,OAAO,QAAQ,CAAC;IAC9C,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB;IACrC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACnC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAE7B,4EAA4E;QAC5E,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa;YAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACvD,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAEjD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,OAAO,OAAO,CAAC,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,GAAG,UAAU,EAAE,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO;QAC1B,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,QAAQ,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE;QAC7D,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC;IAEtB,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAK3C;IACC,IAAI,MAAqB,CAAC;IAC1B,IAAI,OAA8B,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QACpB,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,qEAAqE;QACrE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,wEAAwE;IACxE,uDAAuD;IACvD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;QAC1B,mBAAmB;QACnB,UAAU;QACV,cAAc;QACd,UAAU;QACV,WAAW;QACX,cAAc;QACd,cAAc;QACd,cAAc;QACd,cAAc;QACd,MAAM;QACN,SAAS;QACT,cAAc;QACd,cAAc;QACd,QAAQ;QACR,YAAY;KACb,CAAC,CAAC;IAEH,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEjE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC7B,KAAK,CAAC,IAAI,CACR,4CAA4C,OAAO,uFAAuF,CAC3I,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CACR,8HAA8H,CAC/H,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3B,KAAK,CAAC,IAAI,CACR,uEAAuE,CACxE,CAAC;QACF,KAAK,CAAC,IAAI,CACR,2FAA2F,CAC5F,CAAC;QACF,KAAK,CAAC,IAAI,CACR,6QAA6Q,CAC9Q,CAAC;QACF,KAAK,CAAC,IAAI,CACR,8XAA8X,CAC/X,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,CACR,iIAAiI,CAClI,CAAC;QACF,KAAK,CAAC,IAAI,CACR,oKAAoK,CACrK,CAAC;QACF,KAAK,CAAC,IAAI,CACR,4HAA4H,CAC7H,CAAC;QACF,KAAK,CAAC,IAAI,CACR,4NAA4N,CAC7N,CAAC;QACF,KAAK,CAAC,IAAI,CACR,gGAAgG,CACjG,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CACR,6KAA6K,CAC9K,CAAC;QACF,KAAK,CAAC,IAAI,CACR,gTAAgT,CACjT,CAAC;QACF,KAAK,CAAC,IAAI,CACR,gHAAgH,CACjH,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,yJAAyJ,CAC1J,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,wBAAwB;IACxB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC;IAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnD,KAAK,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IAC1D,KAAK,CAAC,IAAI,CAAC,qBAAqB,SAAS,IAAI,CAAC,CAAC;IAC/C,KAAK,CAAC,IAAI,CAAC,qBAAqB,OAAO,IAAI,CAAC,CAAC;IAC7C,KAAK,CAAC,IAAI,CACR,8HAA8H,CAC/H,CAAC;IACF,KAAK,CAAC,IAAI,CACR,yFAAyF,CAC1F,CAAC;IACF,KAAK,CAAC,IAAI,CACR,uFAAuF,CACxF,CAAC;IACF,KAAK,CAAC,IAAI,CACR,gKAAgK,CACjK,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAE9B,OAAO,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/**\n * Auto-introspected SQL schema context block for the agent's system prompt.\n *\n * On every chat turn, the framework appends a compact, always-fresh summary\n * of the app's SQL database — every table, every column, every foreign key —\n * so the agent knows exactly what data model it's working with. The schema\n * is pulled live from `information_schema` (Postgres) or `PRAGMA table_info`\n * (SQLite), cached briefly to keep latency down but never hard-coded.\n *\n * The block also:\n * - points at the db-query / db-exec / db-patch / db-schema tools for runtime access\n * - lists Postgres column descriptions (`COMMENT ON COLUMN ...`) if present\n * - explains the current user/org data scoping so the agent doesn't re-filter\n * by hand (which would be redundant and easy to get wrong)\n */\nimport {\n getDbExec,\n getDatabaseUrl,\n isPostgres,\n type DbExec,\n} from \"../db/client.js\";\n\ninterface ColumnSchema {\n name: string;\n type: string;\n notnull: boolean;\n pk: boolean;\n comment: string | null;\n}\n\ninterface ForeignKey {\n from: string;\n table: string;\n to: string;\n}\n\ninterface TableSchema {\n name: string;\n columns: ColumnSchema[];\n foreignKeys: ForeignKey[];\n comment: string | null;\n}\n\n// Short-lived in-memory cache — schema rarely changes between messages, but\n// we want new tables to show up within a few seconds during active dev.\nconst CACHE_TTL_MS = 15_000;\nlet _cache: {\n key: string;\n expires: number;\n tables: TableSchema[];\n dialect: \"postgres\" | \"sqlite\";\n} | null = null;\n\nfunction cacheKey(): string {\n return (isPostgres() ? \"pg:\" : \"lite:\") + (getDatabaseUrl() || \"\");\n}\n\n// ─── Postgres introspection ─────────────────────────────────────────────────\n\nasync function introspectPostgres(db: DbExec): Promise<TableSchema[]> {\n const tablesRes = await db.execute({\n sql: `SELECT table_name AS name,\n obj_description((quote_ident(table_schema) || '.' || quote_ident(table_name))::regclass, 'pg_class') AS comment\n FROM information_schema.tables\n WHERE table_schema = 'public' AND table_type = 'BASE TABLE'\n ORDER BY table_name`,\n args: [],\n });\n\n const tables: TableSchema[] = [];\n\n for (const t of tablesRes.rows as any[]) {\n const name = t.name as string;\n\n const colsRes = await db.execute({\n sql: `SELECT c.column_name AS name,\n c.data_type AS type,\n CASE WHEN c.is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,\n col_description((quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass, c.ordinal_position) AS comment\n FROM information_schema.columns c\n WHERE c.table_name = ? AND c.table_schema = 'public'\n ORDER BY c.ordinal_position`,\n args: [name],\n });\n\n const pksRes = await db.execute({\n sql: `SELECT kcu.column_name AS name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.table_name = ? AND tc.constraint_type = 'PRIMARY KEY'`,\n args: [name],\n });\n const pkSet = new Set((pksRes.rows as any[]).map((r) => r.name as string));\n\n const fksRes = await db.execute({\n sql: `SELECT kcu.column_name AS col_from,\n ccu.table_name AS ref_table,\n ccu.column_name AS ref_col\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n JOIN information_schema.constraint_column_usage ccu\n ON tc.constraint_name = ccu.constraint_name\n WHERE tc.table_name = ? AND tc.constraint_type = 'FOREIGN KEY'`,\n args: [name],\n });\n\n tables.push({\n name,\n comment: (t.comment as string | null) ?? null,\n columns: (colsRes.rows as any[]).map((c) => ({\n name: c.name as string,\n type: (c.type as string) || \"any\",\n notnull: Number(c.notnull) === 1,\n pk: pkSet.has(c.name as string),\n comment: (c.comment as string | null) ?? null,\n })),\n foreignKeys: (fksRes.rows as any[]).map((f) => ({\n from: f.col_from as string,\n table: f.ref_table as string,\n to: f.ref_col as string,\n })),\n });\n }\n\n return tables;\n}\n\n// ─── SQLite / libSQL / D1 introspection ────────────────────────────────────\n\nasync function introspectSqlite(db: DbExec): Promise<TableSchema[]> {\n const tablesRes = await db.execute(\n `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`,\n );\n\n const tables: TableSchema[] = [];\n\n for (const row of tablesRes.rows as any[]) {\n const name = row.name as string;\n if (!name) continue;\n // Quote the identifier for PRAGMA calls; SQLite requires doubling embedded quotes.\n const escaped = name.replace(/\"/g, '\"\"');\n\n const colsRes = await db.execute(`PRAGMA table_info(\"${escaped}\")`);\n const fksRes = await db.execute(`PRAGMA foreign_key_list(\"${escaped}\")`);\n\n tables.push({\n name,\n comment: null, // SQLite has no column/table comments\n columns: (colsRes.rows as any[]).map((c) => ({\n name: c.name as string,\n type: ((c.type as string) || \"\").toLowerCase() || \"any\",\n notnull: Number(c.notnull) === 1,\n pk: Number(c.pk) === 1,\n comment: null,\n })),\n foreignKeys: (fksRes.rows as any[]).map((f) => ({\n from: f.from as string,\n table: f.table as string,\n to: f.to as string,\n })),\n });\n }\n\n return tables;\n}\n\n// ─── Cached entry point ─────────────────────────────────────────────────────\n\nasync function getSchema(): Promise<{\n tables: TableSchema[];\n dialect: \"postgres\" | \"sqlite\";\n}> {\n const key = cacheKey();\n const now = Date.now();\n if (_cache && _cache.key === key && _cache.expires > now) {\n return { tables: _cache.tables, dialect: _cache.dialect };\n }\n\n const db = getDbExec();\n const dialect: \"postgres\" | \"sqlite\" = isPostgres() ? \"postgres\" : \"sqlite\";\n const tables =\n dialect === \"postgres\"\n ? await introspectPostgres(db)\n : await introspectSqlite(db);\n\n _cache = { key, expires: now + CACHE_TTL_MS, tables, dialect };\n return { tables, dialect };\n}\n\n/** Manually drop the cache — useful from tests or after running a migration. */\nexport function invalidateSchemaPromptCache(): void {\n _cache = null;\n}\n\n// ─── Formatting ─────────────────────────────────────────────────────────────\n\nfunction shortType(type: string): string {\n // Trim verbose Postgres type names for compactness in the prompt.\n const t = type.toLowerCase();\n if (t === \"character varying\") return \"varchar\";\n if (t === \"timestamp without time zone\") return \"timestamp\";\n if (t === \"timestamp with time zone\") return \"timestamptz\";\n if (t === \"double precision\") return \"double\";\n return t;\n}\n\nfunction formatTable(table: TableSchema): string {\n const fkByCol = new Map<string, string>();\n for (const fk of table.foreignKeys) {\n fkByCol.set(fk.from, `${fk.table}.${fk.to}`);\n }\n\n const cols = table.columns.map((c) => {\n const flags: string[] = [];\n if (c.pk) flags.push(\"pk\");\n if (!c.notnull && !c.pk) flags.push(\"null\");\n const fk = fkByCol.get(c.name);\n if (fk) flags.push(`→${fk}`);\n\n // Flag scoping columns so the agent understands per-user/per-org filtering.\n if (c.name === \"owner_email\") flags.push(\"user-scope\");\n if (c.name === \"org_id\") flags.push(\"org-scope\");\n\n const flagStr = flags.length ? ` [${flags.join(\", \")}]` : \"\";\n const commentStr = c.comment ? ` -- ${c.comment.replace(/\\s+/g, \" \")}` : \"\";\n return ` ${c.name} ${shortType(c.type)}${flagStr}${commentStr}`;\n });\n\n const header = table.comment\n ? ` ${table.name} -- ${table.comment.replace(/\\s+/g, \" \")}`\n : ` ${table.name}`;\n\n return [header, ...cols].join(\"\\n\");\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\n/**\n * Build the `<sql-database>` block appended to the system prompt on every turn.\n *\n * `owner` and `orgId` come from the per-request context (AGENT_USER_EMAIL /\n * AGENT_ORG_ID) and are surfaced so the agent knows who it is acting on behalf\n * of — and understands that rows are already filtered for that identity.\n */\nexport async function loadSchemaPromptBlock(opts: {\n owner?: string | null;\n orgId?: string | null;\n /** If true, mention db-query/db-exec/db-patch/db-schema as available tools. */\n hasRawDbTools?: boolean;\n}): Promise<string> {\n let tables: TableSchema[];\n let dialect: \"postgres\" | \"sqlite\";\n try {\n const res = await getSchema();\n tables = res.tables;\n dialect = res.dialect;\n } catch (err) {\n // DB not ready, or introspection blew up — don't take the chat down.\n return \"\";\n }\n\n if (tables.length === 0) return \"\";\n\n // Partition framework-internal tables from template tables so the agent\n // focuses on the data model it's most likely to touch.\n const CORE_TABLES = new Set([\n \"application_state\",\n \"settings\",\n \"oauth_tokens\",\n \"sessions\",\n \"resources\",\n \"chat_threads\",\n \"_collab_docs\",\n \"usage_events\",\n \"usage_totals\",\n \"user\",\n \"account\",\n \"verification\",\n \"organization\",\n \"member\",\n \"invitation\",\n ]);\n\n const templateTables = tables.filter((t) => !CORE_TABLES.has(t.name));\n const coreTables = tables.filter((t) => CORE_TABLES.has(t.name));\n\n const lines: string[] = [];\n lines.push(\"<sql-database>\");\n lines.push(\n `The app's state lives in a SQL database (${dialect}). The schema below is auto-introspected fresh each turn — treat it as authoritative.`,\n );\n lines.push(\"\");\n\n if (templateTables.length > 0) {\n lines.push(\"## Template tables\");\n lines.push(\"\");\n for (const t of templateTables) {\n lines.push(formatTable(t));\n lines.push(\"\");\n }\n }\n\n if (coreTables.length > 0) {\n lines.push(\n \"## Framework tables (auth, resources, chat threads, app-state, etc.) — usually read/written via dedicated tools, not raw SQL\",\n );\n lines.push(\"\");\n for (const t of coreTables) {\n lines.push(formatTable(t));\n lines.push(\"\");\n }\n }\n\n // Tooling references.\n if (opts.hasRawDbTools) {\n lines.push(\"## SQL tools\");\n lines.push(\n \"- `db-schema` — refresh the full schema with indexes and foreign keys\",\n );\n lines.push(\n \"- `db-query` — run a SELECT (read-only; results already filtered to the current user/org)\",\n );\n lines.push(\n \"- `db-exec` — run INSERT / UPDATE / DELETE / REPLACE (writes already scoped; owner_email and org_id are auto-injected on INSERT). For multiple related writes, pass `statements` so they run in one transaction instead of separate tool calls. Schema changes are blocked.\",\n );\n lines.push(\n \"- `db-patch` — surgical search-and-replace on a large text column. Send `{find, replace}` pairs instead of the full new value. Use this for edits to large fields (documents, slide HTML, dashboard/form JSON) — it avoids re-sending multi-kilobyte strings and saves tokens. Targets exactly one row (narrow `--where` by primary key). Uses the same per-user/per-org scoping as db-exec.\",\n );\n lines.push(\"\");\n lines.push(\"### When to pick which SQL tool\");\n lines.push(\n \"- Set a short column outright, update multiple columns, or do computed updates (`calories = calories + 50`) → `db-exec UPDATE`.\",\n );\n lines.push(\n '- Insert/update several rows as one logical operation → `db-exec` with `statements: \\'[{\"sql\":\"...\",\"args\":[...]}]\\'` so the batch commits or rolls back together.',\n );\n lines.push(\n \"- Change a small slice of a large text/JSON column → `db-patch`. Much cheaper token-wise than re-sending the whole column.\",\n );\n lines.push(\n \"- A template-specific action exists for the table (`edit-document`, `update-slide`, etc.) → use that action. It encodes business rules and pushes live Yjs updates to any open collaborative editor; raw SQL does neither.\",\n );\n lines.push(\n \"- Read data → `db-query`. Never re-add `WHERE owner_email = ...` — scoping already applies it.\",\n );\n lines.push(\"\");\n lines.push(\"### External data sources vs the app database\");\n lines.push(\n \"The `db-*` tools ONLY query the app's own SQL database (the tables listed above). They do NOT reach external data warehouses, analytics platforms, or third-party services.\",\n );\n lines.push(\n \"If the user asks about tables that are NOT in the schema above, use the appropriate template action instead — for example `bigquery` for BigQuery warehouse tables, `ga4-report` for Google Analytics, `hubspot-deals` for HubSpot, etc. Check your available actions for the right data-source-specific tool.\",\n );\n lines.push(\n \"**Never use `db-query` for external data.** It will fail because those tables don't exist in the app database.\",\n );\n lines.push(\"\");\n } else {\n lines.push(\n \"SQL is accessed through the template actions listed above. The schema is shown for context — so you understand the data model those actions operate on.\",\n );\n lines.push(\"\");\n }\n\n // Data scoping context.\n const ownerLine = opts.owner ? opts.owner : \"(unresolved)\";\n const orgLine = opts.orgId ? opts.orgId : \"(none)\";\n lines.push(\"## Data scoping (enforced at the SQL layer)\");\n lines.push(`- Current user: \\`${ownerLine}\\``);\n lines.push(`- Current org: \\`${orgLine}\\``);\n lines.push(\n \"- Tables with an `owner_email` column are automatically filtered to the current user via temporary views before every query.\",\n );\n lines.push(\n \"- Tables with an `org_id` column are automatically filtered to the current org as well.\",\n );\n lines.push(\n \"- On INSERT, `owner_email` and `org_id` are auto-injected — do NOT set them manually.\",\n );\n lines.push(\n \"- Do NOT add `WHERE owner_email = ...` or `WHERE org_id = ...` to your queries — the filter is already applied, and re-adding it will confuse the scoped view.\",\n );\n lines.push(\"</sql-database>\");\n\n return \"\\n\\n\" + lines.join(\"\\n\");\n}\n"]}
@@ -22,6 +22,9 @@
22
22
  * embedded in the same-origin app shell can opt out by setting its own
23
23
  * header inside the route handler — h3's `setResponseHeader` overwrites,
24
24
  * so a route emitting `SAMEORIGIN` wins over our middleware default.
25
+ * We skip this header entirely in dev (NODE_ENV !== "production") so the
26
+ * desktop app's local dev frame (localhost:3334) can iframe templates
27
+ * running on other localhost ports (e.g. mail at 8085).
25
28
  * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query
26
29
  * from outbound Referer headers when the request crosses origin, so a
27
30
  * public-share viewer's outbound link clicks never leak the share token.
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AA6BH;;;;;;GAMG;AACH,wBAAgB,+BAA+B,8EAkB9C"}
1
+ {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AA6BH;;;;;;GAMG;AACH,wBAAgB,+BAA+B,8EAqB9C"}
@@ -22,6 +22,9 @@
22
22
  * embedded in the same-origin app shell can opt out by setting its own
23
23
  * header inside the route handler — h3's `setResponseHeader` overwrites,
24
24
  * so a route emitting `SAMEORIGIN` wins over our middleware default.
25
+ * We skip this header entirely in dev (NODE_ENV !== "production") so the
26
+ * desktop app's local dev frame (localhost:3334) can iframe templates
27
+ * running on other localhost ports (e.g. mail at 8085).
25
28
  * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query
26
29
  * from outbound Referer headers when the request crosses origin, so a
27
30
  * public-share viewer's outbound link clicks never leak the share token.
@@ -73,9 +76,12 @@ function isHttpsRequest(event) {
73
76
  * `setResponseHeader` after this runs — the latest write wins.
74
77
  */
75
78
  export function createSecurityHeadersMiddleware() {
79
+ const isProduction = process.env.NODE_ENV === "production";
76
80
  return defineEventHandler((event) => {
77
81
  setResponseHeader(event, "X-Content-Type-Options", "nosniff");
78
- setResponseHeader(event, "X-Frame-Options", "DENY");
82
+ if (isProduction) {
83
+ setResponseHeader(event, "X-Frame-Options", "DENY");
84
+ }
79
85
  setResponseHeader(event, "Referrer-Policy", "strict-origin-when-cross-origin");
80
86
  setResponseHeader(event, "Permissions-Policy", PERMISSIONS_POLICY);
81
87
  setResponseHeader(event, "Cross-Origin-Opener-Policy", "same-origin");
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAC;AAE3D,MAAM,IAAI,GAAG,8CAA8C,CAAC;AAC5D,MAAM,kBAAkB,GACtB,+DAA+D,CAAC;AAElE;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAU;IAChC,MAAM,GAAG,GACP,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,mBAAmB,CAAC;QAChD,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,OAAO;QACjE,OAAO,IAAI,CAAC;IACd,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1D,uDAAuD;IACvD,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC;IACnC,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACpC,2DAA2D;IAC3D,IAAI,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B;IAC7C,OAAO,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;QAClC,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,EAAE,SAAS,CAAC,CAAC;QAC9D,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACpD,iBAAiB,CACf,KAAK,EACL,iBAAiB,EACjB,iCAAiC,CAClC,CAAC;QACF,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,CAAC,CAAC;QACnE,iBAAiB,CAAC,KAAK,EAAE,4BAA4B,EAAE,aAAa,CAAC,CAAC;QACtE,iBAAiB,CAAC,KAAK,EAAE,8BAA8B,EAAE,WAAW,CAAC,CAAC;QACtE,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,iBAAiB,CAAC,KAAK,EAAE,2BAA2B,EAAE,IAAI,CAAC,CAAC;QAC9D,CAAC;QACD,2EAA2E;QAC3E,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Security response headers middleware.\n *\n * Sets a baseline set of \"no-brainer\" security headers on every framework HTTP\n * response. These headers are layered defenses: each one mitigates a specific\n * class of attack, and together they harden the surface against clickjacking,\n * MIME-sniffing, referrer leakage, mixed-content downgrades, and cross-origin\n * window/embed access.\n *\n * The headers we emit:\n *\n * - `Strict-Transport-Security` — forces HTTPS for the browser's lifetime\n * of the cached value, preventing SSL-strip MITM. Only emitted when the\n * request scheme is `https` (we don't want to break local-dev HTTP, and\n * emitting HSTS over HTTP is a no-op per the spec but causes confusion).\n * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so\n * a tool /render route serving user-authored HTML can't be misinterpreted\n * as some other content type by a clever Accept header.\n * - `X-Frame-Options: DENY` — prevents the entire app from being iframed by\n * other origins (clickjacking the agent chat, booking pages, etc.). The\n * tool /render endpoint and any other route that legitimately needs to be\n * embedded in the same-origin app shell can opt out by setting its own\n * header inside the route handler — h3's `setResponseHeader` overwrites,\n * so a route emitting `SAMEORIGIN` wins over our middleware default.\n * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query\n * from outbound Referer headers when the request crosses origin, so a\n * public-share viewer's outbound link clicks never leak the share token.\n * - `Permissions-Policy: camera=(), microphone=(), geolocation=(),\n * screen-wake-lock=()` — blocks iframed children from inheriting camera\n * / mic / location grants. Templates that need camera/mic for recording\n * UI override this on their own routes.\n * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so\n * a popup-window opener reference can't read or modify our document.\n * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from\n * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking\n * the simplest data-leak chain when combined with auth cookies.\n *\n * NOTE: We don't set `Cross-Origin-Embedder-Policy` because it requires every\n * embedded subresource to opt in via CORP/CORS, which would break Builder's\n * iframe editor and template embed use cases. COOP + CORP without COEP gives\n * us most of the protection.\n */\n\nimport { defineEventHandler, setResponseHeader } from \"h3\";\n\nconst HSTS = \"max-age=31536000; includeSubDomains; preload\";\nconst PERMISSIONS_POLICY =\n \"camera=(), microphone=(), geolocation=(), screen-wake-lock=()\";\n\n/**\n * Returns true when the request was received over HTTPS. We trust both the\n * underlying connection (when the server is terminating TLS itself) and the\n * `x-forwarded-proto` header (set by Netlify, Vercel, Cloudflare, and any\n * other reverse proxy that fronts the framework).\n */\nfunction isHttpsRequest(event: any): boolean {\n const xfp =\n event?.node?.req?.headers?.[\"x-forwarded-proto\"] ??\n event?.headers?.get?.(\"x-forwarded-proto\");\n if (typeof xfp === \"string\" && xfp.split(\",\")[0].trim() === \"https\")\n return true;\n if (Array.isArray(xfp) && xfp[0] === \"https\") return true;\n // h3 sets `event.url.protocol` to \"http:\" or \"https:\".\n const proto = event?.url?.protocol;\n if (proto === \"https:\") return true;\n // Direct Node `req.connection.encrypted` (older runtimes).\n if (event?.node?.req?.connection?.encrypted) return true;\n return false;\n}\n\n/**\n * Create the security-headers h3 middleware. Mount this BEFORE other route\n * handlers so the headers are present on every response (including 4xx/5xx\n * error pages). Route handlers that need to relax a specific header (e.g.\n * `X-Frame-Options: SAMEORIGIN` on the tool render route) can call\n * `setResponseHeader` after this runs — the latest write wins.\n */\nexport function createSecurityHeadersMiddleware() {\n return defineEventHandler((event) => {\n setResponseHeader(event, \"X-Content-Type-Options\", \"nosniff\");\n setResponseHeader(event, \"X-Frame-Options\", \"DENY\");\n setResponseHeader(\n event,\n \"Referrer-Policy\",\n \"strict-origin-when-cross-origin\",\n );\n setResponseHeader(event, \"Permissions-Policy\", PERMISSIONS_POLICY);\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n setResponseHeader(event, \"Cross-Origin-Resource-Policy\", \"same-site\");\n if (isHttpsRequest(event)) {\n setResponseHeader(event, \"Strict-Transport-Security\", HSTS);\n }\n // Continue to the next handler — we only set headers, don't return a body.\n return undefined;\n });\n}\n"]}
1
+ {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAC;AAE3D,MAAM,IAAI,GAAG,8CAA8C,CAAC;AAC5D,MAAM,kBAAkB,GACtB,+DAA+D,CAAC;AAElE;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAU;IAChC,MAAM,GAAG,GACP,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,mBAAmB,CAAC;QAChD,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,OAAO;QACjE,OAAO,IAAI,CAAC;IACd,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1D,uDAAuD;IACvD,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC;IACnC,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACpC,2DAA2D;IAC3D,IAAI,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;IAC3D,OAAO,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;QAClC,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,EAAE,SAAS,CAAC,CAAC;QAC9D,IAAI,YAAY,EAAE,CAAC;YACjB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC;QACD,iBAAiB,CACf,KAAK,EACL,iBAAiB,EACjB,iCAAiC,CAClC,CAAC;QACF,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,CAAC,CAAC;QACnE,iBAAiB,CAAC,KAAK,EAAE,4BAA4B,EAAE,aAAa,CAAC,CAAC;QACtE,iBAAiB,CAAC,KAAK,EAAE,8BAA8B,EAAE,WAAW,CAAC,CAAC;QACtE,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,iBAAiB,CAAC,KAAK,EAAE,2BAA2B,EAAE,IAAI,CAAC,CAAC;QAC9D,CAAC;QACD,2EAA2E;QAC3E,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Security response headers middleware.\n *\n * Sets a baseline set of \"no-brainer\" security headers on every framework HTTP\n * response. These headers are layered defenses: each one mitigates a specific\n * class of attack, and together they harden the surface against clickjacking,\n * MIME-sniffing, referrer leakage, mixed-content downgrades, and cross-origin\n * window/embed access.\n *\n * The headers we emit:\n *\n * - `Strict-Transport-Security` — forces HTTPS for the browser's lifetime\n * of the cached value, preventing SSL-strip MITM. Only emitted when the\n * request scheme is `https` (we don't want to break local-dev HTTP, and\n * emitting HSTS over HTTP is a no-op per the spec but causes confusion).\n * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so\n * a tool /render route serving user-authored HTML can't be misinterpreted\n * as some other content type by a clever Accept header.\n * - `X-Frame-Options: DENY` — prevents the entire app from being iframed by\n * other origins (clickjacking the agent chat, booking pages, etc.). The\n * tool /render endpoint and any other route that legitimately needs to be\n * embedded in the same-origin app shell can opt out by setting its own\n * header inside the route handler — h3's `setResponseHeader` overwrites,\n * so a route emitting `SAMEORIGIN` wins over our middleware default.\n * We skip this header entirely in dev (NODE_ENV !== \"production\") so the\n * desktop app's local dev frame (localhost:3334) can iframe templates\n * running on other localhost ports (e.g. mail at 8085).\n * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query\n * from outbound Referer headers when the request crosses origin, so a\n * public-share viewer's outbound link clicks never leak the share token.\n * - `Permissions-Policy: camera=(), microphone=(), geolocation=(),\n * screen-wake-lock=()` — blocks iframed children from inheriting camera\n * / mic / location grants. Templates that need camera/mic for recording\n * UI override this on their own routes.\n * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so\n * a popup-window opener reference can't read or modify our document.\n * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from\n * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking\n * the simplest data-leak chain when combined with auth cookies.\n *\n * NOTE: We don't set `Cross-Origin-Embedder-Policy` because it requires every\n * embedded subresource to opt in via CORP/CORS, which would break Builder's\n * iframe editor and template embed use cases. COOP + CORP without COEP gives\n * us most of the protection.\n */\n\nimport { defineEventHandler, setResponseHeader } from \"h3\";\n\nconst HSTS = \"max-age=31536000; includeSubDomains; preload\";\nconst PERMISSIONS_POLICY =\n \"camera=(), microphone=(), geolocation=(), screen-wake-lock=()\";\n\n/**\n * Returns true when the request was received over HTTPS. We trust both the\n * underlying connection (when the server is terminating TLS itself) and the\n * `x-forwarded-proto` header (set by Netlify, Vercel, Cloudflare, and any\n * other reverse proxy that fronts the framework).\n */\nfunction isHttpsRequest(event: any): boolean {\n const xfp =\n event?.node?.req?.headers?.[\"x-forwarded-proto\"] ??\n event?.headers?.get?.(\"x-forwarded-proto\");\n if (typeof xfp === \"string\" && xfp.split(\",\")[0].trim() === \"https\")\n return true;\n if (Array.isArray(xfp) && xfp[0] === \"https\") return true;\n // h3 sets `event.url.protocol` to \"http:\" or \"https:\".\n const proto = event?.url?.protocol;\n if (proto === \"https:\") return true;\n // Direct Node `req.connection.encrypted` (older runtimes).\n if (event?.node?.req?.connection?.encrypted) return true;\n return false;\n}\n\n/**\n * Create the security-headers h3 middleware. Mount this BEFORE other route\n * handlers so the headers are present on every response (including 4xx/5xx\n * error pages). Route handlers that need to relax a specific header (e.g.\n * `X-Frame-Options: SAMEORIGIN` on the tool render route) can call\n * `setResponseHeader` after this runs — the latest write wins.\n */\nexport function createSecurityHeadersMiddleware() {\n const isProduction = process.env.NODE_ENV === \"production\";\n return defineEventHandler((event) => {\n setResponseHeader(event, \"X-Content-Type-Options\", \"nosniff\");\n if (isProduction) {\n setResponseHeader(event, \"X-Frame-Options\", \"DENY\");\n }\n setResponseHeader(\n event,\n \"Referrer-Policy\",\n \"strict-origin-when-cross-origin\",\n );\n setResponseHeader(event, \"Permissions-Policy\", PERMISSIONS_POLICY);\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n setResponseHeader(event, \"Cross-Origin-Resource-Policy\", \"same-site\");\n if (isHttpsRequest(event)) {\n setResponseHeader(event, \"Strict-Transport-Security\", HSTS);\n }\n // Continue to the next handler — we only set headers, don't return a body.\n return undefined;\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AAiHA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAkD5E"}
1
+ {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AA+IA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAkD5E"}