@agent-native/core 0.68.1 → 0.68.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +16 -0
- package/corpus/core/docs/content/authentication.md +16 -2
- package/corpus/core/docs/content/template-analytics.md +14 -9
- package/corpus/core/docs/content/template-clips.md +35 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +2 -4
- package/corpus/core/src/client/CommandMenu.tsx +98 -3
- package/corpus/core/src/client/ConnectBuilderCard.tsx +11 -2
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/extensions/content-patch.ts +12 -2
- package/corpus/core/src/oauth-tokens/google-refresh.ts +32 -17
- package/corpus/core/src/provider-api/index.ts +37 -17
- package/corpus/core/src/server/auth.ts +6 -8
- package/corpus/core/src/server/better-auth-instance.ts +19 -4
- package/corpus/core/src/server/core-routes-plugin.ts +157 -5
- package/corpus/core/src/server/google-oauth-credentials.ts +51 -0
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/server/onboarding-html.ts +2 -1
- package/corpus/core/src/server/social-og-image.ts +6 -1
- package/corpus/core/src/vite/client.ts +83 -1
- package/corpus/templates/analytics/app/routes/$.tsx +9 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +33 -1
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +11 -3
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +25 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +12 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +7 -2
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +37 -78
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +33 -1
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +46 -3
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +203 -2
- package/corpus/templates/calendar/app/lib/event-colors.ts +1 -4
- package/corpus/templates/calendar/app/lib/rsvp-status.tsx +6 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +9 -4
- package/corpus/templates/calendar/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/calendar/app/pages/Settings.tsx +25 -1
- package/corpus/templates/calendar/app/routes/_app.$.tsx +9 -0
- package/corpus/templates/calendar/server/handlers/google-auth.ts +152 -12
- package/corpus/templates/calendar/server/lib/google-calendar.ts +63 -14
- package/corpus/templates/calendar/server/plugins/auth.ts +4 -20
- package/corpus/templates/calendar/shared/api.ts +4 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +17 -0
- package/corpus/templates/clips/actions/sync-calendars.ts +6 -7
- package/corpus/templates/clips/app/components/capture-install-options.tsx +157 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +9 -8
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +4 -6
- package/corpus/templates/clips/app/lib/capture-install-options.ts +14 -0
- package/corpus/templates/clips/app/root.tsx +54 -2
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +9 -13
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -7
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +5 -7
- package/corpus/templates/clips/app/routes/download.tsx +49 -0
- package/corpus/templates/clips/app/routes/record.tsx +4 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +5 -6
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +29 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -7
- package/corpus/templates/clips/server/lib/google-calendar-client.ts +61 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +19 -12
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +6 -6
- package/corpus/templates/content/vite.config.ts +37 -1
- package/corpus/templates/design/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/design/app/routes/$.tsx +9 -0
- package/corpus/templates/mail/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/mail/app/routes/$.tsx +9 -0
- package/corpus/templates/plan/app/global.css +2 -2
- package/corpus/templates/slides/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/slides/app/routes/$.tsx +9 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +81 -25
- package/corpus/templates/videos/app/pages/NotFound.tsx +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +15 -2
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +46 -3
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +11 -2
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/extensions/content-patch.d.ts.map +1 -1
- package/dist/extensions/content-patch.js +10 -2
- package/dist/extensions/content-patch.js.map +1 -1
- package/dist/oauth-tokens/google-refresh.d.ts.map +1 -1
- package/dist/oauth-tokens/google-refresh.js +29 -17
- package/dist/oauth-tokens/google-refresh.js.map +1 -1
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -16
- package/dist/provider-api/index.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +6 -6
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +18 -4
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +5 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +107 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/google-oauth-credentials.d.ts +15 -0
- package/dist/server/google-oauth-credentials.d.ts.map +1 -0
- package/dist/server/google-oauth-credentials.js +34 -0
- package/dist/server/google-oauth-credentials.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +2 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/social-og-image.d.ts.map +1 -1
- package/dist/server/social-og-image.js +5 -1
- package/dist/server/social-og-image.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +83 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/authentication.md +16 -2
- package/docs/content/template-analytics.md +14 -9
- package/docs/content/template-clips.md +35 -2
- package/package.json +1 -1
|
@@ -116,13 +116,23 @@ export async function applyExtensionContentUpdate(
|
|
|
116
116
|
export async function formatExtensionHtml(content: string): Promise<string> {
|
|
117
117
|
try {
|
|
118
118
|
const prettier = await import("prettier");
|
|
119
|
-
|
|
119
|
+
const formatted = await prettier.format(content, {
|
|
120
120
|
parser: "html",
|
|
121
121
|
htmlWhitespaceSensitivity: "ignore",
|
|
122
122
|
});
|
|
123
|
+
return typeof formatted === "string" ? formatted : content;
|
|
123
124
|
} catch (err: any) {
|
|
125
|
+
const message = String(err?.message ?? err);
|
|
126
|
+
if (
|
|
127
|
+
message.includes("Cannot find package 'prettier'") ||
|
|
128
|
+
message.includes('Cannot find package "prettier"') ||
|
|
129
|
+
message.includes("Cannot find module 'prettier'") ||
|
|
130
|
+
message.includes('Cannot find module "prettier"')
|
|
131
|
+
) {
|
|
132
|
+
return content;
|
|
133
|
+
}
|
|
124
134
|
throw new Error(
|
|
125
|
-
`Unable to format extension HTML with Prettier: ${
|
|
135
|
+
`Unable to format extension HTML with Prettier: ${message}`,
|
|
126
136
|
);
|
|
127
137
|
}
|
|
128
138
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { listOAuthAccounts, saveOAuthTokens } from "./store.js";
|
|
15
|
+
import { resolveGoogleProviderCredentialCandidates } from "../server/google-oauth-credentials.js";
|
|
15
16
|
|
|
16
17
|
const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
17
18
|
|
|
@@ -31,29 +32,43 @@ interface RefreshResponse {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
async function refreshOne(refreshToken: string): Promise<RefreshResponse> {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
if (!clientId || !clientSecret) {
|
|
35
|
+
const credentialCandidates = resolveGoogleProviderCredentialCandidates();
|
|
36
|
+
if (!credentialCandidates.length) {
|
|
37
37
|
throw new Error("GOOGLE_CLIENT_ID/SECRET not set");
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
|
|
40
|
+
let data: Record<string, unknown> | null = null;
|
|
41
|
+
let lastStatusText = "refresh failed";
|
|
42
|
+
for (const credentials of credentialCandidates) {
|
|
43
|
+
const res = await fetch(GOOGLE_TOKEN_URL, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
46
|
+
body: new URLSearchParams({
|
|
47
|
+
refresh_token: refreshToken,
|
|
48
|
+
client_id: credentials.clientId,
|
|
49
|
+
client_secret: credentials.clientSecret,
|
|
50
|
+
grant_type: "refresh_token",
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
lastStatusText = res.statusText;
|
|
54
|
+
data = (await res.json()) as Record<string, unknown>;
|
|
55
|
+
if (res.ok) return data as unknown as RefreshResponse;
|
|
56
|
+
if (
|
|
57
|
+
data.error !== "invalid_grant" &&
|
|
58
|
+
data.error !== "unauthorized_client" &&
|
|
59
|
+
data.error !== "invalid_client"
|
|
60
|
+
) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (data) {
|
|
51
66
|
const err = (data.error_description ||
|
|
52
67
|
data.error ||
|
|
53
|
-
|
|
68
|
+
lastStatusText) as string;
|
|
54
69
|
throw new Error(err);
|
|
55
70
|
}
|
|
56
|
-
|
|
71
|
+
throw new Error(lastStatusText);
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
/**
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
saveOAuthTokens,
|
|
20
20
|
} from "../oauth-tokens/index.js";
|
|
21
21
|
import { getCredentialContext } from "../server/request-context.js";
|
|
22
|
+
import { resolveGoogleProviderCredentialCandidates } from "../server/google-oauth-credentials.js";
|
|
22
23
|
import { resolveWorkspaceConnectionCredentialForApp } from "../workspace-connections/credentials.js";
|
|
23
24
|
import type { WorkspaceConnectionTemplateUse } from "../connections/catalog.js";
|
|
24
25
|
import type {
|
|
@@ -3432,36 +3433,55 @@ async function refreshGoogleOAuthToken(
|
|
|
3432
3433
|
},
|
|
3433
3434
|
refreshToken: string,
|
|
3434
3435
|
): Promise<string> {
|
|
3435
|
-
const
|
|
3436
|
-
|
|
3437
|
-
if (!clientId || !clientSecret) {
|
|
3436
|
+
const credentialCandidates = resolveGoogleProviderCredentialCandidates();
|
|
3437
|
+
if (!credentialCandidates.length) {
|
|
3438
3438
|
throw new Error(
|
|
3439
3439
|
"GOOGLE_CLIENT_ID/SECRET not set for Google OAuth refresh.",
|
|
3440
3440
|
);
|
|
3441
3441
|
}
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3445
|
-
body: new URLSearchParams({
|
|
3446
|
-
refresh_token: refreshToken,
|
|
3447
|
-
client_id: clientId,
|
|
3448
|
-
client_secret: clientSecret,
|
|
3449
|
-
grant_type: "refresh_token",
|
|
3450
|
-
}),
|
|
3451
|
-
});
|
|
3452
|
-
const data = (await res.json()) as {
|
|
3442
|
+
|
|
3443
|
+
let data: {
|
|
3453
3444
|
access_token?: string;
|
|
3454
3445
|
expires_in?: number;
|
|
3455
3446
|
token_type?: string;
|
|
3456
3447
|
scope?: string;
|
|
3457
3448
|
error?: string;
|
|
3458
3449
|
error_description?: string;
|
|
3459
|
-
};
|
|
3460
|
-
|
|
3450
|
+
} | null = null;
|
|
3451
|
+
let lastStatusText = "refresh failed";
|
|
3452
|
+
for (const credentials of credentialCandidates) {
|
|
3453
|
+
const res = await fetch("https://oauth2.googleapis.com/token", {
|
|
3454
|
+
method: "POST",
|
|
3455
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3456
|
+
body: new URLSearchParams({
|
|
3457
|
+
refresh_token: refreshToken,
|
|
3458
|
+
client_id: credentials.clientId,
|
|
3459
|
+
client_secret: credentials.clientSecret,
|
|
3460
|
+
grant_type: "refresh_token",
|
|
3461
|
+
}),
|
|
3462
|
+
});
|
|
3463
|
+
lastStatusText = res.statusText;
|
|
3464
|
+
data = (await res.json()) as {
|
|
3465
|
+
access_token?: string;
|
|
3466
|
+
expires_in?: number;
|
|
3467
|
+
token_type?: string;
|
|
3468
|
+
scope?: string;
|
|
3469
|
+
error?: string;
|
|
3470
|
+
error_description?: string;
|
|
3471
|
+
};
|
|
3472
|
+
if (res.ok && data.access_token) break;
|
|
3461
3473
|
if (data.error && PERMANENT_GOOGLE_OAUTH_REFRESH_ERRORS.has(data.error)) {
|
|
3474
|
+
continue;
|
|
3475
|
+
}
|
|
3476
|
+
const detail = data.error_description ?? data.error ?? lastStatusText;
|
|
3477
|
+
throw new Error(`Google OAuth refresh failed: ${detail}`);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
if (!data?.access_token) {
|
|
3481
|
+
if (data?.error && PERMANENT_GOOGLE_OAUTH_REFRESH_ERRORS.has(data.error)) {
|
|
3462
3482
|
await deleteOAuthTokens(options.oauthProvider, options.accountId);
|
|
3463
3483
|
}
|
|
3464
|
-
const detail = data
|
|
3484
|
+
const detail = data?.error_description ?? data?.error ?? lastStatusText;
|
|
3465
3485
|
throw new Error(`Google OAuth refresh failed: ${detail}`);
|
|
3466
3486
|
}
|
|
3467
3487
|
const merged: OAuthTokens = {
|
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
} from "../db/client.js";
|
|
73
73
|
import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
|
|
74
74
|
import type { BetterAuthConfig } from "./better-auth-instance.js";
|
|
75
|
+
import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
75
76
|
import {
|
|
76
77
|
getAllowedCorsOrigin,
|
|
77
78
|
readCorsAllowedOrigins,
|
|
@@ -2462,11 +2463,8 @@ async function mountBetterAuthRoutes(
|
|
|
2462
2463
|
// Auto-add Google OAuth routes when credentials are configured. Templates
|
|
2463
2464
|
// that need broader product scopes (mail/calendar) opt out and provide
|
|
2464
2465
|
// their own Nitro routes at these paths.
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
process.env.GOOGLE_CLIENT_SECRET &&
|
|
2468
|
-
options.mountGoogleOAuthRoutes !== false
|
|
2469
|
-
) {
|
|
2466
|
+
const googleSignInCredentials = resolveGoogleSignInCredentials();
|
|
2467
|
+
if (googleSignInCredentials && options.mountGoogleOAuthRoutes !== false) {
|
|
2470
2468
|
setGenericGoogleOAuthRoutesEnabled(app, true);
|
|
2471
2469
|
for (const gp of [
|
|
2472
2470
|
"/_agent-native/google/callback",
|
|
@@ -2535,7 +2533,7 @@ async function mountBetterAuthRoutes(
|
|
|
2535
2533
|
process.env.VITE_AGENT_NATIVE_WORKSPACE === "1",
|
|
2536
2534
|
});
|
|
2537
2535
|
const params = new URLSearchParams({
|
|
2538
|
-
client_id:
|
|
2536
|
+
client_id: googleSignInCredentials.clientId,
|
|
2539
2537
|
redirect_uri: redirectUri,
|
|
2540
2538
|
response_type: "code",
|
|
2541
2539
|
scope: googleScopes,
|
|
@@ -2647,8 +2645,8 @@ async function mountBetterAuthRoutes(
|
|
|
2647
2645
|
},
|
|
2648
2646
|
body: new URLSearchParams({
|
|
2649
2647
|
code,
|
|
2650
|
-
client_id:
|
|
2651
|
-
client_secret:
|
|
2648
|
+
client_id: googleSignInCredentials.clientId,
|
|
2649
|
+
client_secret: googleSignInCredentials.clientSecret,
|
|
2652
2650
|
redirect_uri: redirectUri,
|
|
2653
2651
|
grant_type: "authorization_code",
|
|
2654
2652
|
}),
|
|
@@ -26,6 +26,7 @@ import { flushTracking, identify, track } from "../tracking/index.js";
|
|
|
26
26
|
import { TEMPLATES } from "../cli/templates-meta.js";
|
|
27
27
|
import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
|
|
28
28
|
import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
|
|
29
|
+
import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
29
30
|
import {
|
|
30
31
|
getDialect,
|
|
31
32
|
getDatabaseUrl,
|
|
@@ -802,7 +803,17 @@ async function createBetterAuthInstance(
|
|
|
802
803
|
...config?.socialProviders,
|
|
803
804
|
};
|
|
804
805
|
|
|
805
|
-
|
|
806
|
+
const extraScopes = config?.googleScopes ?? [];
|
|
807
|
+
const googleCredentials =
|
|
808
|
+
extraScopes.length > 0
|
|
809
|
+
? process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
|
|
810
|
+
? {
|
|
811
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
812
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
813
|
+
}
|
|
814
|
+
: null
|
|
815
|
+
: resolveGoogleSignInCredentials();
|
|
816
|
+
if (googleCredentials) {
|
|
806
817
|
// When the template requests broader scopes (Gmail, Calendar, etc.)
|
|
807
818
|
// ask for them on the primary sign-in flow so a separate "Connect
|
|
808
819
|
// Google" round-trip isn't needed. `accessType: "offline"` plus
|
|
@@ -810,12 +821,11 @@ async function createBetterAuthInstance(
|
|
|
810
821
|
// Google only re-issues a refresh token on consent, so re-signing in
|
|
811
822
|
// (e.g. after switching machines) would otherwise leave us with an
|
|
812
823
|
// access token that can't be refreshed.
|
|
813
|
-
const extraScopes = config?.googleScopes ?? [];
|
|
814
824
|
const baseScopes = ["openid", "email", "profile"];
|
|
815
825
|
const mergedScopes = Array.from(new Set([...baseScopes, ...extraScopes]));
|
|
816
826
|
socialProviders.google = {
|
|
817
|
-
clientId:
|
|
818
|
-
clientSecret:
|
|
827
|
+
clientId: googleCredentials.clientId,
|
|
828
|
+
clientSecret: googleCredentials.clientSecret,
|
|
819
829
|
...(extraScopes.length > 0
|
|
820
830
|
? {
|
|
821
831
|
scope: mergedScopes,
|
|
@@ -843,6 +853,9 @@ async function createBetterAuthInstance(
|
|
|
843
853
|
const requireEmailVerification =
|
|
844
854
|
isEmailConfigured() && !shouldSkipEmailVerification();
|
|
845
855
|
|
|
856
|
+
const shouldMirrorGoogleAccountTokens =
|
|
857
|
+
(config?.googleScopes?.length ?? 0) > 0;
|
|
858
|
+
|
|
846
859
|
const auth = betterAuth({
|
|
847
860
|
basePath,
|
|
848
861
|
baseURL: appUrl,
|
|
@@ -969,6 +982,7 @@ async function createBetterAuthInstance(
|
|
|
969
982
|
// mirroring work; failures never block sign-in.
|
|
970
983
|
create: {
|
|
971
984
|
after: async (account: any) => {
|
|
985
|
+
if (!shouldMirrorGoogleAccountTokens) return;
|
|
972
986
|
await mirrorGoogleAccountToOAuthTokens(account).catch((err) => {
|
|
973
987
|
console.error(
|
|
974
988
|
"[auth] failed to mirror Google account tokens to oauth_tokens (create)",
|
|
@@ -979,6 +993,7 @@ async function createBetterAuthInstance(
|
|
|
979
993
|
},
|
|
980
994
|
update: {
|
|
981
995
|
after: async (account: any) => {
|
|
996
|
+
if (!shouldMirrorGoogleAccountTokens) return;
|
|
982
997
|
await mirrorGoogleAccountToOAuthTokens(account).catch((err) => {
|
|
983
998
|
console.error(
|
|
984
999
|
"[auth] failed to mirror Google account tokens to oauth_tokens (update)",
|
|
@@ -158,6 +158,24 @@ import {
|
|
|
158
158
|
export const FRAMEWORK_ROUTE_PREFIX = "/_agent-native";
|
|
159
159
|
export const FRAMEWORK_EVENTS_ROUTE = `${FRAMEWORK_ROUTE_PREFIX}/events`;
|
|
160
160
|
export const LEGACY_FRAMEWORK_EVENTS_ROUTE = `${FRAMEWORK_ROUTE_PREFIX}/poll-events`;
|
|
161
|
+
const DEFAULT_BUILDER_WAITLIST_FORM_ID = "DYTHuM0jlV";
|
|
162
|
+
const DEFAULT_BUILDER_WAITLIST_FORMS_ORIGIN = "https://forms.agent-native.com";
|
|
163
|
+
const BUILDER_WAITLIST_FORM_SOURCE = "connect_builder_card";
|
|
164
|
+
const BUILDER_WAITLIST_FORM_TIMEOUT_MS = 8000;
|
|
165
|
+
const BUILDER_WAITLIST_TEXT_LIMIT = 4000;
|
|
166
|
+
|
|
167
|
+
interface BuilderWaitlistFormTarget {
|
|
168
|
+
formId: string;
|
|
169
|
+
formsOrigin: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface BuilderWaitlistBody {
|
|
173
|
+
prompt?: unknown;
|
|
174
|
+
orgName?: unknown;
|
|
175
|
+
appUrl?: unknown;
|
|
176
|
+
pageUrl?: unknown;
|
|
177
|
+
source?: unknown;
|
|
178
|
+
}
|
|
161
179
|
|
|
162
180
|
export function resolveFrameworkSseRoutes(sseRoute?: string): string[] {
|
|
163
181
|
return Array.from(
|
|
@@ -171,6 +189,112 @@ export function resolveFrameworkSseRoutes(sseRoute?: string): string[] {
|
|
|
171
189
|
|
|
172
190
|
registerBuiltinEngines();
|
|
173
191
|
|
|
192
|
+
function cleanBuilderWaitlistText(
|
|
193
|
+
value: unknown,
|
|
194
|
+
maxLength = BUILDER_WAITLIST_TEXT_LIMIT,
|
|
195
|
+
): string | undefined {
|
|
196
|
+
if (typeof value !== "string") return undefined;
|
|
197
|
+
const trimmed = value.trim();
|
|
198
|
+
if (!trimmed) return undefined;
|
|
199
|
+
return trimmed.slice(0, maxLength);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function normalizeHttpOrigin(value: string): string | null {
|
|
203
|
+
try {
|
|
204
|
+
const url = new URL(value);
|
|
205
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
206
|
+
return url.origin;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isAgentNativeHostedRequest(event: H3Event): boolean {
|
|
213
|
+
const hostname = getRequestURL(event).hostname.toLowerCase();
|
|
214
|
+
return (
|
|
215
|
+
hostname === "agent-native.com" || hostname.endsWith(".agent-native.com")
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function resolveBuilderWaitlistFormTargetForRequest(
|
|
220
|
+
event: H3Event,
|
|
221
|
+
): BuilderWaitlistFormTarget | null {
|
|
222
|
+
if (process.env.AGENT_NATIVE_DISABLE_BUILDER_WAITLIST_FORM === "1") {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const envFormId = process.env.AGENT_NATIVE_BUILDER_WAITLIST_FORM_ID?.trim();
|
|
227
|
+
const envFormsOrigin =
|
|
228
|
+
process.env.AGENT_NATIVE_BUILDER_WAITLIST_FORMS_ORIGIN?.trim();
|
|
229
|
+
const hasExplicitTarget = Boolean(envFormId || envFormsOrigin);
|
|
230
|
+
if (!hasExplicitTarget && !isAgentNativeHostedRequest(event)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const formId = envFormId || DEFAULT_BUILDER_WAITLIST_FORM_ID;
|
|
235
|
+
const formsOrigin = normalizeHttpOrigin(
|
|
236
|
+
envFormsOrigin || DEFAULT_BUILDER_WAITLIST_FORMS_ORIGIN,
|
|
237
|
+
);
|
|
238
|
+
if (!formsOrigin) {
|
|
239
|
+
throw new Error("Invalid Builder waitlist Forms origin");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { formId, formsOrigin };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function submitBuilderWaitlistForm(
|
|
246
|
+
event: H3Event,
|
|
247
|
+
sessionEmail: string,
|
|
248
|
+
body: BuilderWaitlistBody,
|
|
249
|
+
): Promise<{ submitted: boolean; formId?: string }> {
|
|
250
|
+
const target = resolveBuilderWaitlistFormTargetForRequest(event);
|
|
251
|
+
if (!target) return { submitted: false };
|
|
252
|
+
|
|
253
|
+
const appUrl =
|
|
254
|
+
cleanBuilderWaitlistText(body.pageUrl ?? body.appUrl, 2000) ??
|
|
255
|
+
cleanBuilderWaitlistText(getHeader(event, "referer"), 2000) ??
|
|
256
|
+
getOrigin(event);
|
|
257
|
+
const source =
|
|
258
|
+
cleanBuilderWaitlistText(body.source, 100) ?? BUILDER_WAITLIST_FORM_SOURCE;
|
|
259
|
+
|
|
260
|
+
const controller = new AbortController();
|
|
261
|
+
const timeout = setTimeout(
|
|
262
|
+
() => controller.abort(),
|
|
263
|
+
BUILDER_WAITLIST_FORM_TIMEOUT_MS,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const res = await fetch(
|
|
268
|
+
`${target.formsOrigin}/api/submit/${encodeURIComponent(target.formId)}`,
|
|
269
|
+
{
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({
|
|
273
|
+
data: {
|
|
274
|
+
email: sessionEmail,
|
|
275
|
+
orgName: cleanBuilderWaitlistText(body.orgName, 500),
|
|
276
|
+
appUrl,
|
|
277
|
+
prompt: cleanBuilderWaitlistText(body.prompt),
|
|
278
|
+
source,
|
|
279
|
+
},
|
|
280
|
+
_hp: "",
|
|
281
|
+
_meta: {
|
|
282
|
+
submitterEmail: sessionEmail,
|
|
283
|
+
pageUrl: appUrl,
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
signal: controller.signal,
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
throw new Error(`Forms waitlist submission failed (${res.status})`);
|
|
291
|
+
}
|
|
292
|
+
return { submitted: true, formId: target.formId };
|
|
293
|
+
} finally {
|
|
294
|
+
clearTimeout(timeout);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
174
298
|
function parseBuilderCallbackBoolean(
|
|
175
299
|
value: string | null | undefined,
|
|
176
300
|
): boolean | null {
|
|
@@ -1405,10 +1529,10 @@ export function createCoreRoutesPlugin(
|
|
|
1405
1529
|
);
|
|
1406
1530
|
|
|
1407
1531
|
// Branch-creation waitlist signup. Used by ConnectBuilderCard when the
|
|
1408
|
-
// current request has no Builder branch project configured
|
|
1409
|
-
//
|
|
1410
|
-
//
|
|
1411
|
-
//
|
|
1532
|
+
// current request has no Builder branch project configured. Hosted
|
|
1533
|
+
// Agent Native deployments submit into the Builder-org Forms waitlist;
|
|
1534
|
+
// local/self-hosted deployments keep the analytics signal without
|
|
1535
|
+
// sending private workspace data to Agent Native.
|
|
1412
1536
|
getH3App(nitroApp).use(
|
|
1413
1537
|
`${P}/builder/branch-waitlist`,
|
|
1414
1538
|
defineEventHandler(async (event: H3Event) => {
|
|
@@ -1421,15 +1545,43 @@ export function createCoreRoutesPlugin(
|
|
|
1421
1545
|
setResponseStatus(event, 401);
|
|
1422
1546
|
return { error: "Authentication required" };
|
|
1423
1547
|
}
|
|
1548
|
+
const body = ((await readBody(event).catch(() => ({}))) ??
|
|
1549
|
+
{}) as BuilderWaitlistBody;
|
|
1550
|
+
let formSubmission: { submitted: boolean; formId?: string };
|
|
1551
|
+
try {
|
|
1552
|
+
formSubmission = await submitBuilderWaitlistForm(
|
|
1553
|
+
event,
|
|
1554
|
+
session.email,
|
|
1555
|
+
body,
|
|
1556
|
+
);
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
await trackBuilderLifecycle(
|
|
1559
|
+
event,
|
|
1560
|
+
"builder branch waitlist form failed",
|
|
1561
|
+
session.email,
|
|
1562
|
+
{
|
|
1563
|
+
reason:
|
|
1564
|
+
err instanceof Error ? err.message : "unknown_waitlist_error",
|
|
1565
|
+
stage: "waitlist",
|
|
1566
|
+
},
|
|
1567
|
+
);
|
|
1568
|
+
setResponseStatus(event, 502);
|
|
1569
|
+
return {
|
|
1570
|
+
error:
|
|
1571
|
+
"Couldn't join the waitlist. Please try again in a moment.",
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1424
1574
|
await trackBuilderLifecycle(
|
|
1425
1575
|
event,
|
|
1426
1576
|
"builder branch waitlist joined",
|
|
1427
1577
|
session.email,
|
|
1428
1578
|
{
|
|
1579
|
+
formId: formSubmission.formId ?? null,
|
|
1580
|
+
formSubmitted: formSubmission.submitted,
|
|
1429
1581
|
stage: "waitlist",
|
|
1430
1582
|
},
|
|
1431
1583
|
);
|
|
1432
|
-
return { ok: true };
|
|
1584
|
+
return { ok: true, formSubmitted: formSubmission.submitted };
|
|
1433
1585
|
}),
|
|
1434
1586
|
);
|
|
1435
1587
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface GoogleOAuthCredentials {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function readCredentialPair(
|
|
7
|
+
clientIdKey: string,
|
|
8
|
+
clientSecretKey: string,
|
|
9
|
+
): GoogleOAuthCredentials | null {
|
|
10
|
+
const clientId = process.env[clientIdKey];
|
|
11
|
+
const clientSecret = process.env[clientSecretKey];
|
|
12
|
+
if (!clientId || !clientSecret) return null;
|
|
13
|
+
return { clientId, clientSecret };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Credentials for identity-only Google sign-in. Deploys that also use Google
|
|
18
|
+
* product APIs can set these separately from GOOGLE_CLIENT_ID/SECRET, which
|
|
19
|
+
* remain the backwards-compatible provider OAuth credentials.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveGoogleSignInCredentials(): GoogleOAuthCredentials | null {
|
|
22
|
+
return (
|
|
23
|
+
readCredentialPair(
|
|
24
|
+
"GOOGLE_SIGN_IN_CLIENT_ID",
|
|
25
|
+
"GOOGLE_SIGN_IN_CLIENT_SECRET",
|
|
26
|
+
) ?? readCredentialPair("GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET")
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function hasGoogleSignInCredentials(): boolean {
|
|
31
|
+
return resolveGoogleSignInCredentials() !== null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveGoogleProviderCredentials(): GoogleOAuthCredentials | null {
|
|
35
|
+
return readCredentialPair("GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveGoogleLegacyProviderCredentials(): GoogleOAuthCredentials | null {
|
|
39
|
+
return readCredentialPair(
|
|
40
|
+
"GOOGLE_LEGACY_CLIENT_ID",
|
|
41
|
+
"GOOGLE_LEGACY_CLIENT_SECRET",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveGoogleProviderCredentialCandidates(): GoogleOAuthCredentials[] {
|
|
46
|
+
const primary = resolveGoogleProviderCredentials();
|
|
47
|
+
const legacy = resolveGoogleLegacyProviderCredentials();
|
|
48
|
+
if (!primary) return legacy ? [legacy] : [];
|
|
49
|
+
if (!legacy || legacy.clientId === primary.clientId) return [primary];
|
|
50
|
+
return [primary, legacy];
|
|
51
|
+
}
|
|
@@ -264,6 +264,14 @@ export {
|
|
|
264
264
|
type SpawnTaskOptions,
|
|
265
265
|
} from "./agent-teams.js";
|
|
266
266
|
export { isOAuthConnected, getOAuthAccounts } from "./oauth-helpers.js";
|
|
267
|
+
export {
|
|
268
|
+
hasGoogleSignInCredentials,
|
|
269
|
+
resolveGoogleLegacyProviderCredentials,
|
|
270
|
+
resolveGoogleProviderCredentialCandidates,
|
|
271
|
+
resolveGoogleProviderCredentials,
|
|
272
|
+
resolveGoogleSignInCredentials,
|
|
273
|
+
type GoogleOAuthCredentials,
|
|
274
|
+
} from "./google-oauth-credentials.js";
|
|
267
275
|
export { wrapWithAnalytics } from "./analytics.js";
|
|
268
276
|
export {
|
|
269
277
|
getH3App,
|
|
@@ -28,9 +28,10 @@ import {
|
|
|
28
28
|
withAgentNativeSocialImageCacheBuster,
|
|
29
29
|
} from "../shared/social-meta.js";
|
|
30
30
|
import { normalizeAppBasePath } from "./app-base-path.js";
|
|
31
|
+
import { hasGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
31
32
|
|
|
32
33
|
function hasGoogleOAuth(): boolean {
|
|
33
|
-
return
|
|
34
|
+
return hasGoogleSignInCredentials();
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function getConnectionLabel(): string {
|
|
@@ -330,7 +330,12 @@ export function renderAgentNativeOgImageSvg(
|
|
|
330
330
|
export async function renderAgentNativeOgImagePng(
|
|
331
331
|
input: AgentNativeOgImageInput = {},
|
|
332
332
|
): Promise<Uint8Array> {
|
|
333
|
-
const
|
|
333
|
+
const overridePackage =
|
|
334
|
+
typeof process !== "undefined"
|
|
335
|
+
? process.env.AGENT_NATIVE_RESVG_PACKAGE
|
|
336
|
+
: undefined;
|
|
337
|
+
const resvgPackage = overridePackage || "@resvg/resvg-js";
|
|
338
|
+
const { Resvg } = await import(/* @vite-ignore */ resvgPackage);
|
|
334
339
|
// Feed resvg the embedded Liberation Sans font explicitly. System fonts can't
|
|
335
340
|
// be relied on: Linux serverless runtimes (Netlify/Lambda) ship neither Arial
|
|
336
341
|
// nor Inter, so without a bundled font every `<text>` rendered blank.
|