@agent-native/core 0.140.0 → 0.141.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  3. package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-stay-complete-and-atomic.md +6 -0
  4. package/corpus/templates/analytics/server/lib/dashboard-report.ts +0 -2
  5. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +11 -6
  6. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +8 -18
  7. package/corpus/templates/analytics/server/plugins/db.ts +174 -0
  8. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +38 -38
  9. package/corpus/templates/calendar/changelog/2026-08-05-out-of-office-blocks-now-open-from-their-scheduled-time-rang.md +6 -0
  10. package/corpus/templates/calendar/server/lib/booking-emails.ts +70 -154
  11. package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +28 -64
  12. package/corpus/templates/clips/actions/get-recording-player-data.ts +1 -0
  13. package/corpus/templates/clips/actions/invite-member.ts +16 -39
  14. package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -3
  15. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +10 -0
  16. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +2 -2
  17. package/corpus/templates/clips/app/components/player/video-player.tsx +20 -6
  18. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +33 -0
  19. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +5 -1
  20. package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -1
  21. package/corpus/templates/clips/changelog/2026-08-05-comment-and-video-reaction-pickers-now-include-eyes-and-mind.md +6 -0
  22. package/corpus/templates/clips/changelog/2026-08-05-fresh-recordings-now-open-with-the-complete-video.md +6 -0
  23. package/corpus/templates/clips/server/lib/transactional-email-templates.ts +0 -24
  24. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
  25. package/corpus/templates/content/server/lib/comment-notifications.ts +26 -57
  26. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +1 -44
  27. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1 -265
  28. package/corpus/templates/design/actions/create-design-system.ts +10 -9
  29. package/corpus/templates/design/actions/get-design-system.ts +0 -53
  30. package/corpus/templates/design/actions/import-design-tokens.ts +71 -5
  31. package/corpus/templates/design/actions/index-design-tokens.ts +80 -61
  32. package/corpus/templates/design/actions/update-design-system.ts +10 -7
  33. package/corpus/templates/design/app/components/design/EditPanel.tsx +2 -45
  34. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +0 -12
  35. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +6 -10
  36. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +3 -19
  37. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +10 -36
  38. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +0 -15
  39. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +1 -19
  40. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +0 -6
  41. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +23 -53
  42. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +4 -142
  43. package/corpus/templates/design/app/components/design/types.ts +0 -6
  44. package/corpus/templates/design/app/i18n-data.ts +0 -20
  45. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +1 -1
  46. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -78
  47. package/corpus/templates/design/app/pages/design-editor/html-layer-positioning.ts +1 -6
  48. package/corpus/templates/design/e2e/helpers.ts +0 -17
  49. package/corpus/templates/design/shared/resolve-tweaks.ts +18 -10
  50. package/corpus/templates/forms/server/lib/response-email.ts +0 -2
  51. package/corpus/templates/plan/actions/request-plan-access.ts +12 -33
  52. package/corpus/templates/plan/server/lib/comment-notifications.ts +27 -53
  53. package/corpus/templates/slides/server/lib/comment-notifications.ts +21 -44
  54. package/dist/a2a/handlers.js +9 -10
  55. package/dist/brand-kit/index.d.ts +0 -1
  56. package/dist/brand-kit/index.js +0 -1
  57. package/dist/brand-kit/types.d.ts +0 -25
  58. package/dist/collab/awareness.d.ts +2 -2
  59. package/dist/collab/struct-routes.d.ts +1 -1
  60. package/dist/db/client.js +8 -8
  61. package/dist/db/migrations.js +39 -19
  62. package/dist/localization/default-messages.d.ts +0 -45
  63. package/dist/localization/default-messages.js +0 -45
  64. package/dist/notifications/routes.d.ts +6 -6
  65. package/dist/observability/routes.d.ts +3 -3
  66. package/dist/org/handlers.js +1 -8
  67. package/dist/progress/routes.d.ts +1 -1
  68. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  69. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  70. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  71. package/dist/resources/handlers.d.ts +1 -1
  72. package/dist/secrets/routes.d.ts +9 -9
  73. package/dist/server/action-discovery.js +0 -22
  74. package/dist/server/auth.js +3 -1
  75. package/dist/server/better-auth-instance.js +2 -17
  76. package/dist/server/email.d.ts +0 -9
  77. package/dist/server/email.js +3 -44
  78. package/dist/server/index.d.ts +0 -1
  79. package/dist/server/index.js +0 -1
  80. package/dist/server/transcribe-voice.d.ts +1 -1
  81. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
  82. package/docs/content/authentication.mdx +25 -19
  83. package/package.json +1 -1
  84. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
  85. package/corpus/templates/analytics/server/lib/emails.ts +0 -51
  86. package/corpus/templates/analytics/server/plugins/transactional-emails.ts +0 -5
  87. package/corpus/templates/calendar/server/lib/emails.ts +0 -167
  88. package/corpus/templates/calendar/server/plugins/transactional-emails.ts +0 -5
  89. package/corpus/templates/clips/server/lib/emails.ts +0 -256
  90. package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +0 -5
  91. package/corpus/templates/content/server/lib/emails.ts +0 -70
  92. package/corpus/templates/content/server/plugins/transactional-emails.ts +0 -5
  93. package/corpus/templates/design/app/components/design/edit-panel/design-system-swatches.ts +0 -215
  94. package/corpus/templates/design/changelog/2026-08-04-design-system-tokens-show-by-name-in-the-editor.md +0 -6
  95. package/corpus/templates/design/server/lib/design-system-tokens.ts +0 -35
  96. package/corpus/templates/design/shared/authored-color-styles.ts +0 -401
  97. package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +0 -4
  98. package/corpus/templates/dispatch/app/routes/transactional-email.tsx +0 -4
  99. package/corpus/templates/forms/server/lib/emails.ts +0 -52
  100. package/corpus/templates/forms/server/plugins/transactional-emails.ts +0 -5
  101. package/corpus/templates/plan/server/lib/emails.ts +0 -67
  102. package/corpus/templates/plan/server/plugins/transactional-emails.ts +0 -5
  103. package/corpus/templates/slides/server/lib/emails.ts +0 -40
  104. package/corpus/templates/slides/server/plugins/transactional-emails.ts +0 -5
  105. package/dist/brand-kit/tokens.d.ts +0 -92
  106. package/dist/brand-kit/tokens.js +0 -280
  107. package/dist/email-catalog/actions/list-email-activity.d.ts +0 -6
  108. package/dist/email-catalog/actions/list-email-activity.js +0 -15
  109. package/dist/email-catalog/actions/list-email-engagement.d.ts +0 -6
  110. package/dist/email-catalog/actions/list-email-engagement.js +0 -15
  111. package/dist/email-catalog/actions/list-email-log.d.ts +0 -8
  112. package/dist/email-catalog/actions/list-email-log.js +0 -22
  113. package/dist/email-catalog/actions/list-transactional-emails.d.ts +0 -23
  114. package/dist/email-catalog/actions/list-transactional-emails.js +0 -61
  115. package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +0 -10
  116. package/dist/email-catalog/actions/render-transactional-email-preview.js +0 -24
  117. package/dist/email-catalog/authorize.d.ts +0 -2
  118. package/dist/email-catalog/authorize.js +0 -10
  119. package/dist/email-catalog/log.d.ts +0 -57
  120. package/dist/email-catalog/log.js +0 -117
  121. package/dist/email-catalog/provider-metrics.d.ts +0 -34
  122. package/dist/email-catalog/provider-metrics.js +0 -165
  123. package/dist/email-catalog/registry.d.ts +0 -79
  124. package/dist/email-catalog/registry.js +0 -71
  125. package/dist/email-catalog/schema.d.ts +0 -212
  126. package/dist/email-catalog/schema.js +0 -49
  127. package/dist/email-catalog/system-emails.d.ts +0 -14
  128. package/dist/email-catalog/system-emails.js +0 -68
@@ -18,7 +18,6 @@ import { TEMPLATES } from "../cli/templates-meta.js";
18
18
  import { getDbExec, isPostgres } from "../db/client.js";
19
19
  import { getDialect, getDatabaseUrl, getDatabaseAuthToken, closePgliteClients, getPgliteClient, isPgliteUrl, loadPgliteDrizzle, pgPoolOptions, neonPoolMax, attachNeonPoolErrorLogger, sharedDbPool, onSharedDbPoolsClosed, onSharedDbPoolReplaced, } from "../db/client.js";
20
20
  import { ensureTableExists } from "../db/ddl-guard.js";
21
- import { CORE_RESET_PASSWORD_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, } from "../email-catalog/system-emails.js";
22
21
  import { saveOAuthTokens } from "../oauth-tokens/store.js";
23
22
  import { acceptPendingInvitationsForEmail } from "../org/accept-pending.js";
24
23
  import { getAuthEmailForUserId, getRequiredAuthProviderForEmail, } from "../org/auth-policy.js";
@@ -919,14 +918,7 @@ async function createBetterAuthInstance(config) {
919
918
  email: user.email,
920
919
  resetUrl,
921
920
  });
922
- await sendEmail({
923
- to: user.email,
924
- subject,
925
- html,
926
- text,
927
- appSender,
928
- templateId: CORE_RESET_PASSWORD_EMAIL_ID,
929
- });
921
+ await sendEmail({ to: user.email, subject, html, text, appSender });
930
922
  },
931
923
  },
932
924
  emailVerification: {
@@ -950,14 +942,7 @@ async function createBetterAuthInstance(config) {
950
942
  email: user.email,
951
943
  verifyUrl,
952
944
  });
953
- await sendEmail({
954
- to: user.email,
955
- subject,
956
- html,
957
- text,
958
- appSender,
959
- templateId: CORE_VERIFY_SIGNUP_EMAIL_ID,
960
- });
945
+ await sendEmail({ to: user.email, subject, html, text, appSender });
961
946
  },
962
947
  },
963
948
  socialProviders,
@@ -58,15 +58,6 @@ export interface SendEmailArgs {
58
58
  references?: string;
59
59
  attachments?: EmailAttachment[];
60
60
  timeoutMs?: number;
61
- /**
62
- * Registered transactional email id (see `defineTransactionalEmail`), e.g.
63
- * `calendar.booking-confirmed`. Tags the message at the provider so delivery
64
- * and open metrics attribute to one email instead of to the whole account,
65
- * and keys the row written to `email_log`. Omit for genuinely one-off sends.
66
- */
67
- templateId?: string;
68
- /** App slug that owns the send. Defaults to the running app. */
69
- app?: string;
70
61
  }
71
62
  export declare function isEmailConfigured(): Promise<boolean>;
72
63
  /**
@@ -10,8 +10,6 @@
10
10
  * so the reset-password flow still works end-to-end for local development.
11
11
  */
12
12
  import { FAVICON_PNG_BASE64 } from "../assets/branding/favicon-base64.js";
13
- import { recordEmailSend } from "../email-catalog/log.js";
14
- import { getAppSlug } from "./app-name.js";
15
13
  import { resolveSecret } from "./credential-provider.js";
16
14
  import { AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID } from "./email-template.js";
17
15
  let cachedAgentNativeLogo;
@@ -156,7 +154,7 @@ function resolveAppSender(configuredFrom, appSender) {
156
154
  replyTo: appSender.replyTo,
157
155
  };
158
156
  }
159
- async function deliverEmail(args, signal) {
157
+ async function sendEmailWithSignal(args, signal) {
160
158
  const config = await resolveEmailTransport();
161
159
  signal?.throwIfAborted();
162
160
  const provider = config.provider;
@@ -208,7 +206,7 @@ async function deliverEmail(args, signal) {
208
206
  const body = await res.text().catch(() => "");
209
207
  throw new Error(`Resend error ${res.status}: ${body}`);
210
208
  }
211
- return { provider, from };
209
+ return;
212
210
  }
213
211
  if (provider === "sendgrid") {
214
212
  const personalization = {
@@ -229,12 +227,6 @@ async function deliverEmail(args, signal) {
229
227
  };
230
228
  if (replyTo)
231
229
  sgPayload.reply_to = parseSendGridFrom(replyTo);
232
- // Categories are how per-email delivery/open stats are attributed. Without
233
- // them every send lands in one undifferentiated account-wide bucket, which
234
- // is indistinguishable from an email that never sent.
235
- const categories = [args.templateId, args.app ?? getAppSlug()].filter((value) => Boolean(value));
236
- if (categories.length)
237
- sgPayload.categories = categories;
238
230
  const sgHeaders = {};
239
231
  if (args.inReplyTo)
240
232
  sgHeaders["In-Reply-To"] = args.inReplyTo;
@@ -266,7 +258,7 @@ async function deliverEmail(args, signal) {
266
258
  const body = await res.text().catch(() => "");
267
259
  throw new Error(`SendGrid error ${res.status}: ${body}`);
268
260
  }
269
- return { provider, from };
261
+ return;
270
262
  }
271
263
  // Dev fallback — no provider configured. Logging the full body exposes
272
264
  // reset tokens, so only do it outside production. In production, refuse
@@ -278,39 +270,6 @@ async function deliverEmail(args, signal) {
278
270
  `Save RESEND_API_KEY or SENDGRID_API_KEY in settings to send real emails.\n` +
279
271
  `---\nTo: ${args.to}\nFrom: ${from}\nSubject: ${args.subject}\n\n` +
280
272
  `${args.text || stripHtml(args.html)}\n---\n`);
281
- return { provider, from };
282
- }
283
- /**
284
- * Deliver, then record the attempt. Recording lives here rather than in each
285
- * provider branch so a new transport cannot be added without being logged.
286
- */
287
- async function sendEmailWithSignal(args, signal) {
288
- let outcome;
289
- try {
290
- outcome = await deliverEmail(args, signal);
291
- }
292
- catch (error) {
293
- await recordEmailSend({
294
- templateId: args.templateId,
295
- app: args.app ?? getAppSlug() ?? "unknown",
296
- recipient: args.to,
297
- sender: outcome?.from ?? args.from ?? "unknown",
298
- subject: args.subject,
299
- status: "failed",
300
- error: error instanceof Error ? error.message : String(error),
301
- provider: outcome?.provider ?? "unknown",
302
- });
303
- throw error;
304
- }
305
- await recordEmailSend({
306
- templateId: args.templateId,
307
- app: args.app ?? getAppSlug() ?? "unknown",
308
- recipient: args.to,
309
- sender: outcome.from,
310
- subject: args.subject,
311
- status: "sent",
312
- provider: outcome.provider,
313
- });
314
273
  }
315
274
  export async function sendEmail(args) {
316
275
  const requestedTimeoutMs = Number(args.timeoutMs);
@@ -66,7 +66,6 @@ export { builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSyst
66
66
  export { getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, runBuilderAgent, type RunBuilderAgentResult, } from "./builder-browser.js";
67
67
  export { ensureFusionContainer, sendFusionBranchMessage, pushFusionBranch, reserveFusionHostingSlug, deployFusionProject, getFusionDeploys, getFusionBranchEditorUrl, getFusionHostingUrl, type FusionBranchRef, type EnsureFusionContainerResult, type SendFusionMessageResult, } from "./fusion-app.js";
68
68
  export { sendEmail, isEmailConfigured, getEmailProvider, type EmailAttachment, type EmailProvider, type SendEmailArgs, } from "./email.js";
69
- export { defineTransactionalEmail, listTransactionalEmails, getTransactionalEmail, renderTransactionalEmailPreview, type TransactionalEmailDefinition, type RegisteredTransactionalEmail, } from "../email-catalog/registry.js";
70
69
  export { notifyActivity, runActivityNotification, resolveActivityRecipients, type ActivityDeliveryFailure, type ActivityNotificationResult, type ActivityNotificationStatus, type NotifyActivityInput, type ResolveActivityRecipientsInput, } from "./activity-notifications.js";
71
70
  export { renderEmail, emailStrong, emailLink, type RenderEmailArgs, type RenderedEmail, type EmailCta, } from "./email-template.js";
72
71
  export { getAppProductionUrl, getFirstPartyProdUrl } from "./app-url.js";
@@ -67,7 +67,6 @@ export { builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSyst
67
67
  export { getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, runBuilderAgent, } from "./builder-browser.js";
68
68
  export { ensureFusionContainer, sendFusionBranchMessage, pushFusionBranch, reserveFusionHostingSlug, deployFusionProject, getFusionDeploys, getFusionBranchEditorUrl, getFusionHostingUrl, } from "./fusion-app.js";
69
69
  export { sendEmail, isEmailConfigured, getEmailProvider, } from "./email.js";
70
- export { defineTransactionalEmail, listTransactionalEmails, getTransactionalEmail, renderTransactionalEmailPreview, } from "../email-catalog/registry.js";
71
70
  export { notifyActivity, runActivityNotification, resolveActivityRecipients, } from "./activity-notifications.js";
72
71
  export { renderEmail, emailStrong, emailLink, } from "./email-template.js";
73
72
  export { getAppProductionUrl, getFirstPartyProdUrl } from "./app-url.js";
@@ -20,7 +20,7 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- error?: undefined;
24
23
  text: string;
24
+ error?: undefined;
25
25
  }>>;
26
26
  //# sourceMappingURL=transcribe-voice.d.ts.map
@@ -20,9 +20,10 @@ Auth is powered by **Better Auth** with account-first design. Every new user cre
20
20
  | Mode | Behavior |
21
21
  | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
22
22
  | **Development (default)** | Real Better Auth — same flow as production. There is **no auth bypass**. On first run the framework auto-creates a throwaway dev account and signs you in without printing its credentials (disable with `AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1`), so you are not stuck at a login wall. `getSession()` returns the signed-in user or `null` — it never falls back to a sentinel identity. |
23
- | **Production (default)** | Better Auth with email/password + social providers (Google, GitHub). Organizations built in. |
23
+ | **Production (default)** | Magic-link-first Better Auth when outbound email is ready, with email/password fallback and social providers (Google, GitHub). Organizations built in. |
24
24
  | **`AUTH_MODE=local`** | **Not** a browser auth bypass, and never returns `local@localhost`. It only affects CLI/agent identity: it lets `pnpm action` / the local agent loop auto-bind to the single real signed-in dev user from the `sessions` table (see `scripts/dev-session.ts`). Browser login is unchanged. |
25
- | **`AUTH_SKIP_EMAIL_VERIFICATION=1`** | QA/preview escape hatch for real email/password accounts. Signup skips email verification and does not send the signup verification email. Local dev/test skips verification by default; set `AUTH_SKIP_EMAIL_VERIFICATION=0` only when testing verification itself. Use `+qa` emails for test accounts. |
25
+ | **`AUTH_SKIP_EMAIL_VERIFICATION=1`** | QA/preview escape hatch for password-fallback accounts. Signup skips email verification and does not send the signup verification email. Local dev/test skips verification by default; set `AUTH_SKIP_EMAIL_VERIFICATION=0` only when testing verification itself. It does not change magic-link delivery. Use `+qa` emails for test accounts. |
26
+ | **`AUTH_MAGIC_LINK=0`** | Force the email/password fallback even when outbound email is ready. |
26
27
  | **`AUTH_DISABLED=true`** | Skip login/signup entirely — every request runs as `dev@local.test`. For local dev, cloud previews, and internal demos only; not for production with real users. |
27
28
  | **`ACCESS_TOKEN` / `ACCESS_TOKENS`** | Static bearer fallback for MCP/connect clients that cannot use OAuth. Not browser auth and never a token login page. |
28
29
  | **Custom** | Pass your own `getSession` to `autoMountAuth(app, { getSession })`. |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Authentication"
3
- description: "Better Auth integration with email/password, social providers, organizations, and MCP bearer credentials."
3
+ description: "Magic-link-first Better Auth integration with password fallback, social providers, organizations, and MCP bearer credentials."
4
4
  ---
5
5
 
6
6
  # Authentication
@@ -11,7 +11,7 @@ Agent-native apps use [Better Auth](https://better-auth.com) for authentication
11
11
 
12
12
  Auth is configured automatically via `autoMountAuth(app)` in the auth server plugin. There are three modes:
13
13
 
14
- - **Default:** Better Auth with email/password + social providers. Onboarding page shown on first visit.
14
+ - **Default:** Magic-link-first Better Auth when outbound email is ready, with email/password fallback and social providers. Onboarding page shown on first visit.
15
15
  - **Remote MCP OAuth:** Standard OAuth 2.1 for MCP hosts such as Claude Code and ChatGPT connectors.
16
16
  - **Custom:** Bring your own auth via `getSession` callback.
17
17
 
@@ -24,7 +24,7 @@ Auth is configured automatically via `autoMountAuth(app)` in the auth server plu
24
24
  <span class="diagram-pill accent">Default</span
25
25
  ><strong>Better Auth</strong
26
26
  ><small class="diagram-muted"
27
- >email/password &middot; Google &middot; GitHub</small
27
+ >magic link / password &middot; Google &middot; GitHub</small
28
28
  >
29
29
  </div>
30
30
  <div class="diagram-card">
@@ -84,11 +84,11 @@ Auth is configured automatically via `autoMountAuth(app)` in the auth server plu
84
84
 
85
85
  The browser flow is the same Better Auth flow everywhere — there is **no dev auth bypass**, and `getSession()` never falls back to a `local@localhost` sentinel. What changes between environments is signup friction, not the login wall:
86
86
 
87
- | Environment | First-load behavior | Email verification |
88
- | ---------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- |
89
- | **Local dev** | Auto-creates a throwaway dev account and signs you in (no login wall) | Skipped by default (and when no email provider) |
90
- | **QA / preview** | Normal signup, but verification can be skipped so testers don't wait on email | Skip with `AUTH_SKIP_EMAIL_VERIFICATION=1` |
91
- | **Production** | Normal Better Auth signup/login | Required (when an email provider is configured) |
87
+ | Environment | First-load behavior | Email verification |
88
+ | ---------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
89
+ | **Local dev** | Auto-creates a throwaway dev account and signs you in (no login wall) | Skipped by default (and when no email provider) |
90
+ | **QA / preview** | Normal signup, but verification can be skipped so testers don't wait on email | Skip with `AUTH_SKIP_EMAIL_VERIFICATION=1` |
91
+ | **Production** | Magic-link signup/login when outbound email is ready; email/password fallback otherwise | Required for magic links (when an email provider is configured) |
92
92
 
93
93
  A few flags tune this; full details are in the [Environment Variables](#environment-variables) table:
94
94
 
@@ -104,9 +104,12 @@ A few flags tune this; full details are in the [Environment Variables](#environm
104
104
 
105
105
  ## Better Auth (Default) {#better-auth}
106
106
 
107
- By default, Better Auth powers authentication. It provides:
107
+ By default, Better Auth powers authentication. When outbound email is ready,
108
+ magic-link signup/login is the primary browser flow. Email/password remains
109
+ the fallback when email is unavailable, or when `AUTH_MAGIC_LINK=0` is set. It
110
+ provides:
108
111
 
109
- - Email/password registration and login
112
+ - Magic-link registration and login, with email/password fallback
110
113
  - Social providers (Google, GitHub, and 35+ others)
111
114
  - Organizations with roles and invitations
112
115
  - JWT tokens for API and A2A access
@@ -115,7 +118,8 @@ By default, Better Auth powers authentication. It provides:
115
118
  Better Auth routes are mounted at `/_agent-native/auth/ba/*`. The framework also provides backward-compatible endpoints:
116
119
 
117
120
  - `GET /_agent-native/auth/session` — get current session
118
- - `POST /_agent-native/auth/login` — email/password login
121
+ - `POST /_agent-native/auth/magic-link` — request a magic-link email
122
+ - `POST /_agent-native/auth/login` — email/password fallback login
119
123
  - `POST /_agent-native/auth/register` — create account
120
124
  - `POST /_agent-native/auth/logout` — sign out
121
125
 
@@ -142,9 +146,10 @@ subdomains, set `AGENT_NATIVE_SHARE_COOKIE_DOMAIN=1` alongside
142
146
 
143
147
  ## QA Accounts {#qa-accounts}
144
148
 
145
- Local development and tests skip signup email verification by default, so you
146
- can create real email/password accounts without waiting on an inbox. To force
147
- verification locally while testing that flow, set `AUTH_SKIP_EMAIL_VERIFICATION=0`.
149
+ Local development and tests skip password-signup email verification by default,
150
+ so the email/password fallback can be tested without waiting on an inbox. To
151
+ force verification locally while testing that fallback, set
152
+ `AUTH_SKIP_EMAIL_VERIFICATION=0`.
148
153
 
149
154
  For hosted QA environments where testers need real accounts but should not wait
150
155
  on email delivery, set:
@@ -153,9 +158,9 @@ on email delivery, set:
153
158
  AUTH_SKIP_EMAIL_VERIFICATION=1
154
159
  ```
155
160
 
156
- When this flag is set, email/password signup does not require email
157
- verification and the signup verification email is not sent. Use it only for QA
158
- or preview environments, and name test accounts with a `+qa` address
161
+ When this flag is set, password fallback signup does not require email
162
+ verification and the signup verification email is not sent. It does not change
163
+ magic-link delivery. Use it only for QA or preview environments, and name test accounts with a `+qa` address
159
164
  (`name+qa@example.com`) so they are easy to identify.
160
165
 
161
166
  ## Social Providers {#social-providers}
@@ -382,7 +387,7 @@ Templates with **public pages** (share links, embeds, marketing pages) often nee
382
387
  /_agent-native/sign-in?return=<same-origin-path>
383
388
  ```
384
389
 
385
- When an anonymous viewer hits this URL, the framework's login page is served. After a successful sign-in (any flow — token, email/password, or Google OAuth), the browser returns to `return`.
390
+ When an anonymous viewer hits this URL, the framework's login page is served. After a successful sign-in (magic link, email/password, token, or Google OAuth), the browser returns to `return`.
386
391
 
387
392
  The `return` parameter is validated as a **same-origin path**. Network-path references (`//evil.com/...`), absolute URLs, `data:` / `javascript:` schemes, and embedded control characters all fall back to `/`. The validated path is reconstructed from the URL parser, not echoed back from the input.
388
393
 
@@ -429,7 +434,8 @@ The default `/_agent-native/google/auth-url` route does this automatically — o
429
434
  | Variable | Purpose |
430
435
  | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
431
436
  | `BETTER_AUTH_SECRET` | Signing key for Better Auth (auto-generated if not set) |
432
- | `AUTH_SKIP_EMAIL_VERIFICATION` | Set to `1` in QA/preview environments to let email/password signups proceed without verification; local dev/test skips by default |
437
+ | `AUTH_SKIP_EMAIL_VERIFICATION` | Set to `1` in QA/preview environments to let password-fallback signups proceed without verification; local dev/test skips by default |
438
+ | `AUTH_MAGIC_LINK` | Set to `0`, `false`, or `off` to force the email/password fallback even when outbound email is ready |
433
439
  | `AUTH_DISABLED` | Set to `true` or `1` to skip login/signup; all requests run as one shared user (local dev/preview only — not for production with real users) |
434
440
  | `AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT` | Set to `1` to disable localhost auto-sign-in on a fresh dev database |
435
441
  | `AUTH_MODE` | `local` resolves CLI/agent identity only (which dev user `pnpm action` runs as); never a browser login bypass |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.140.0",
3
+ "version": "0.141.0",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -20,9 +20,10 @@ Auth is powered by **Better Auth** with account-first design. Every new user cre
20
20
  | Mode | Behavior |
21
21
  | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
22
22
  | **Development (default)** | Real Better Auth — same flow as production. There is **no auth bypass**. On first run the framework auto-creates a throwaway dev account and signs you in without printing its credentials (disable with `AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1`), so you are not stuck at a login wall. `getSession()` returns the signed-in user or `null` — it never falls back to a sentinel identity. |
23
- | **Production (default)** | Better Auth with email/password + social providers (Google, GitHub). Organizations built in. |
23
+ | **Production (default)** | Magic-link-first Better Auth when outbound email is ready, with email/password fallback and social providers (Google, GitHub). Organizations built in. |
24
24
  | **`AUTH_MODE=local`** | **Not** a browser auth bypass, and never returns `local@localhost`. It only affects CLI/agent identity: it lets `pnpm action` / the local agent loop auto-bind to the single real signed-in dev user from the `sessions` table (see `scripts/dev-session.ts`). Browser login is unchanged. |
25
- | **`AUTH_SKIP_EMAIL_VERIFICATION=1`** | QA/preview escape hatch for real email/password accounts. Signup skips email verification and does not send the signup verification email. Local dev/test skips verification by default; set `AUTH_SKIP_EMAIL_VERIFICATION=0` only when testing verification itself. Use `+qa` emails for test accounts. |
25
+ | **`AUTH_SKIP_EMAIL_VERIFICATION=1`** | QA/preview escape hatch for password-fallback accounts. Signup skips email verification and does not send the signup verification email. Local dev/test skips verification by default; set `AUTH_SKIP_EMAIL_VERIFICATION=0` only when testing verification itself. It does not change magic-link delivery. Use `+qa` emails for test accounts. |
26
+ | **`AUTH_MAGIC_LINK=0`** | Force the email/password fallback even when outbound email is ready. |
26
27
  | **`AUTH_DISABLED=true`** | Skip login/signup entirely — every request runs as `dev@local.test`. For local dev, cloud previews, and internal demos only; not for production with real users. |
27
28
  | **`ACCESS_TOKEN` / `ACCESS_TOKENS`** | Static bearer fallback for MCP/connect clients that cannot use OAuth. Not browser auth and never a token login page. |
28
29
  | **Custom** | Pass your own `getSession` to `autoMountAuth(app, { getSession })`. |
@@ -1,51 +0,0 @@
1
- /**
2
- * Catalog entries for the transactional emails Analytics sends.
3
- *
4
- * Registered from `server/plugins/transactional-emails.ts` so Dispatch can list
5
- * and preview them without the app having sent anything yet.
6
- */
7
-
8
- import { defineTransactionalEmail } from "@agent-native/core/email-catalog";
9
- import { emailStrong, renderEmail } from "@agent-native/core/server";
10
-
11
- export const ANALYTICS_DASHBOARD_REPORT_EMAIL_ID = "analytics.dashboard-report";
12
-
13
- let registered = false;
14
-
15
- export function registerAnalyticsEmails(): void {
16
- if (registered) return;
17
- registered = true;
18
-
19
- defineTransactionalEmail({
20
- id: ANALYTICS_DASHBOARD_REPORT_EMAIL_ID,
21
- name: "Scheduled dashboard report",
22
- trigger:
23
- "A dashboard report subscription comes due and at least one panel produced usable data. A run where every queried panel failed throws instead of mailing, and a degraded run is skipped when the subscription asks for complete reports only.",
24
- recipientLabel: "Subscription recipients",
25
- recipient:
26
- "The normalized recipient list stored on the subscription. One email per address, all carrying the same rendered snapshot.",
27
- senderLabel: "Default sender",
28
- sender:
29
- "The configured default sender. This call site sets no `from`, `fromName`, `replyTo`, or `appSender`.",
30
- // The real renderer runs every panel query and rasterizes charts, so it
31
- // cannot back a preview that must stay offline. This shows the frame a
32
- // recipient sees; the panel body is whatever that run produced.
33
- preview: () => ({
34
- subject: "Daily dashboard: Growth overview — 3/4/2025",
35
- ...renderEmail({
36
- preheader: "Your daily Growth overview report is ready.",
37
- heading: "Daily dashboard: Growth overview",
38
- paragraphs: [
39
- `This report renders each panel of ${emailStrong("Growth overview")} as of the run time, with charts drawn server-side and attached inline.`,
40
- "Panels that could not be queried are called out in place rather than dropped, so a partial report never reads as a complete one.",
41
- ],
42
- cta: {
43
- label: "Open dashboard",
44
- url: "https://example.com/dashboards/dash_sample",
45
- },
46
- footer:
47
- "You received this because you are on the recipient list for this dashboard report.",
48
- }),
49
- }),
50
- });
51
- }
@@ -1,5 +0,0 @@
1
- import { registerAnalyticsEmails } from "../lib/emails";
2
-
3
- export default function registerTransactionalEmails(): void {
4
- registerAnalyticsEmails();
5
- }
@@ -1,167 +0,0 @@
1
- /**
2
- * Catalog entries for the transactional emails Calendar sends.
3
- *
4
- * Registered from `server/plugins/transactional-emails.ts` so Dispatch can list
5
- * and preview them without the app having sent anything yet.
6
- */
7
-
8
- import { defineTransactionalEmail } from "@agent-native/core/email-catalog";
9
-
10
- import {
11
- renderBookingCancelledEmail,
12
- renderBookingCancelledHostEmail,
13
- renderBookingConfirmedEmail,
14
- renderBookingReceivedEmail,
15
- } from "./booking-emails.js";
16
- import { renderEventGuestNote } from "./event-guest-notifications.js";
17
-
18
- /** Obviously-fake sample data — these render in a preview pane, never send. */
19
- const SAMPLE_TITLE = "Intro call";
20
- const SAMPLE_WHEN = "Tuesday, March 4, 2025, 10:00 AM - 10:30 AM PST";
21
- const SAMPLE_HOST = "dana.hill@example.com";
22
- const SAMPLE_GUEST = "sam.rivera@example.com";
23
- const SAMPLE_GUEST_NAME = "Sam Rivera";
24
- const SAMPLE_MANAGE_URL = "https://example.com/book/dana/manage/sample-token";
25
- const SAMPLE_BOOK_AGAIN_URL = "https://example.com/book/dana/intro-call";
26
- const SAMPLE_MEETING_LINK = "https://meet.example.com/sample-intro-call";
27
-
28
- export const CALENDAR_BOOKING_CONFIRMED_EMAIL_ID = "calendar.booking-confirmed";
29
- export const CALENDAR_BOOKING_RECEIVED_EMAIL_ID = "calendar.booking-received";
30
- export const CALENDAR_BOOKING_CANCELLED_EMAIL_ID = "calendar.booking-cancelled";
31
- export const CALENDAR_BOOKING_CANCELLED_HOST_EMAIL_ID =
32
- "calendar.booking-cancelled-host";
33
- export const CALENDAR_EVENT_UPDATE_NOTE_EMAIL_ID = "calendar.event-update-note";
34
- export const CALENDAR_EVENT_CANCELLATION_NOTE_EMAIL_ID =
35
- "calendar.event-cancellation-note";
36
-
37
- let registered = false;
38
-
39
- export function registerCalendarEmails(): void {
40
- if (registered) return;
41
- registered = true;
42
-
43
- defineTransactionalEmail({
44
- id: CALENDAR_BOOKING_CONFIRMED_EMAIL_ID,
45
- name: "Booking confirmed (guest)",
46
- trigger:
47
- "A guest completes a booking on a public `/book/{username}/{slug}` page and the booking row is created.",
48
- recipientLabel: "Booking guest",
49
- recipient:
50
- "The email address the guest typed into the booking form (`booking.email`).",
51
- senderLabel: "Default, reply-to host",
52
- sender:
53
- "The configured EMAIL_FROM, with reply-to set to the booking link owner's address.",
54
- preview: () =>
55
- renderBookingConfirmedEmail({
56
- title: SAMPLE_TITLE,
57
- when: SAMPLE_WHEN,
58
- host: SAMPLE_HOST,
59
- manageUrl: SAMPLE_MANAGE_URL,
60
- meetingLink: SAMPLE_MEETING_LINK,
61
- }),
62
- });
63
-
64
- defineTransactionalEmail({
65
- id: CALENDAR_BOOKING_RECEIVED_EMAIL_ID,
66
- name: "New booking (host)",
67
- trigger:
68
- "Sent alongside the guest confirmation, immediately after a public booking is created.",
69
- recipientLabel: "Booking link owner",
70
- recipient:
71
- "The owner of the booking link, looked up from the link's slug at send time.",
72
- senderLabel: "Default, reply-to guest",
73
- sender:
74
- "The configured EMAIL_FROM, with reply-to set to the guest so the host can reply directly.",
75
- preview: () =>
76
- renderBookingReceivedEmail({
77
- title: SAMPLE_TITLE,
78
- when: SAMPLE_WHEN,
79
- attendeeName: SAMPLE_GUEST_NAME,
80
- attendee: SAMPLE_GUEST,
81
- manageUrl: SAMPLE_MANAGE_URL,
82
- meetingLink: SAMPLE_MEETING_LINK,
83
- }),
84
- });
85
-
86
- defineTransactionalEmail({
87
- id: CALENDAR_BOOKING_CANCELLED_EMAIL_ID,
88
- name: "Booking cancelled (guest)",
89
- trigger:
90
- "A booking that was not already cancelled is cancelled, either by the guest through the manage/cancel token link or by someone with access to the booking link.",
91
- recipientLabel: "Booking guest",
92
- recipient: "The guest address stored on the booking row.",
93
- senderLabel: "Default, reply-to host",
94
- sender:
95
- "The configured EMAIL_FROM, with reply-to set to the host when a host address could be resolved from the link slug.",
96
- preview: () =>
97
- renderBookingCancelledEmail({
98
- title: SAMPLE_TITLE,
99
- when: SAMPLE_WHEN,
100
- host: SAMPLE_HOST,
101
- bookAgainUrl: SAMPLE_BOOK_AGAIN_URL,
102
- }),
103
- });
104
-
105
- defineTransactionalEmail({
106
- id: CALENDAR_BOOKING_CANCELLED_HOST_EMAIL_ID,
107
- name: "Booking cancelled (host)",
108
- trigger:
109
- "Sent after the guest cancellation notice, and only when a host address could be resolved from the booking link slug.",
110
- recipientLabel: "Booking link owner",
111
- recipient: "The booking link owner's address.",
112
- senderLabel: "Default, reply-to guest",
113
- sender:
114
- "The configured EMAIL_FROM, with reply-to set to the guest who was booked.",
115
- preview: () =>
116
- renderBookingCancelledHostEmail({
117
- title: SAMPLE_TITLE,
118
- when: SAMPLE_WHEN,
119
- attendeeName: SAMPLE_GUEST_NAME,
120
- attendee: SAMPLE_GUEST,
121
- }),
122
- });
123
-
124
- defineTransactionalEmail({
125
- id: CALENDAR_EVENT_UPDATE_NOTE_EMAIL_ID,
126
- name: "Event update note",
127
- trigger:
128
- "`update-event` runs with a non-empty guest notification message. Google Calendar still sends its own update invite; this carries only the organizer's note.",
129
- recipientLabel: "Event attendees",
130
- recipient:
131
- "Every attendee on the event with a syntactically valid address, excluding the organizer's own `self` attendee row. One email per address.",
132
- senderLabel: "Default, reply-to organizer",
133
- sender:
134
- "The configured EMAIL_FROM, with reply-to set to the organizer running the update.",
135
- preview: () =>
136
- renderEventGuestNote({
137
- title: "Design review",
138
- organizer: "Dana Hill",
139
- message: "Moving this an hour later so the whole team can join.",
140
- when: SAMPLE_WHEN,
141
- kind: "update",
142
- htmlLink: "https://calendar.example.com/event/sample",
143
- }),
144
- });
145
-
146
- defineTransactionalEmail({
147
- id: CALENDAR_EVENT_CANCELLATION_NOTE_EMAIL_ID,
148
- name: "Event cancellation note",
149
- trigger:
150
- "`delete-event` runs with guest notification requested and a non-empty message. Google Calendar sends the cancellation itself; this carries only the organizer's note.",
151
- recipientLabel: "Event attendees",
152
- recipient:
153
- "Every attendee on the deleted event with a syntactically valid address, excluding the organizer's own `self` attendee row. One email per address.",
154
- senderLabel: "Default, reply-to organizer",
155
- sender:
156
- "The configured EMAIL_FROM, with reply-to set to the organizer running the deletion.",
157
- preview: () =>
158
- renderEventGuestNote({
159
- title: "Design review",
160
- organizer: "Dana Hill",
161
- message: "Cancelling this week — we will pick it up after the launch.",
162
- when: SAMPLE_WHEN,
163
- kind: "cancellation",
164
- appliesTo: "all events in the series",
165
- }),
166
- });
167
- }
@@ -1,5 +0,0 @@
1
- import { registerCalendarEmails } from "../lib/emails.js";
2
-
3
- export default function registerTransactionalEmails(): void {
4
- registerCalendarEmails();
5
- }