@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
|
@@ -41,6 +41,7 @@ import { DeleteRecordingMenu } from "@/components/player/delete-recording-menu";
|
|
|
41
41
|
import { usePlayerShortcuts } from "@/hooks/use-player-shortcuts";
|
|
42
42
|
import { useViewTracking } from "@/hooks/use-view-tracking";
|
|
43
43
|
import { Button } from "@/components/ui/button";
|
|
44
|
+
import { CaptureInstallButton } from "@/components/capture-install-options";
|
|
44
45
|
import {
|
|
45
46
|
DropdownMenu,
|
|
46
47
|
DropdownMenuContent,
|
|
@@ -1004,12 +1005,10 @@ function PublicAgentEmptyState() {
|
|
|
1004
1005
|
Loom alternative
|
|
1005
1006
|
</p>
|
|
1006
1007
|
<div className="mt-7 flex w-full max-w-[220px] flex-col gap-2">
|
|
1007
|
-
<
|
|
1008
|
-
<
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
</a>
|
|
1012
|
-
</Button>
|
|
1008
|
+
<CaptureInstallButton className="w-full gap-2" align="center">
|
|
1009
|
+
<IconDownload className="h-4 w-4" />
|
|
1010
|
+
{downloadLabel}
|
|
1011
|
+
</CaptureInstallButton>
|
|
1013
1012
|
<Button asChild variant="outline" className="w-full">
|
|
1014
1013
|
<a href={appPath("/signup")}>Sign up</a>
|
|
1015
1014
|
</Button>
|
|
@@ -6,6 +6,8 @@ Name: Agent-Native Clips
|
|
|
6
6
|
|
|
7
7
|
Chrome Web Store item ID: `baoipacpchggcdigagnajakiidcgcffn`
|
|
8
8
|
|
|
9
|
+
Chrome Web Store URL: `https://chromewebstore.google.com/detail/baoipacpchggcdigagnajakiidcgcffn`
|
|
10
|
+
|
|
9
11
|
Summary: Start Clips recordings from Chrome with optional redacted console and network diagnostics.
|
|
10
12
|
|
|
11
13
|
Category: Productivity
|
|
@@ -39,3 +41,30 @@ Diagnostics are bounded and redacted before they are saved. Clips does not colle
|
|
|
39
41
|
## Review Notes
|
|
40
42
|
|
|
41
43
|
The extension opens `https://clips.agent-native.com/record` by default. Local development can point the settings page to a localhost Clips instance.
|
|
44
|
+
|
|
45
|
+
## Submission Artifact
|
|
46
|
+
|
|
47
|
+
Build the latest Chrome Web Store ZIP from the repo root:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm --filter clips-chrome-extension package
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Upload the generated artifact:
|
|
54
|
+
|
|
55
|
+
```txt
|
|
56
|
+
templates/clips/chrome-extension/releases/clips-chrome-extension-0.1.0.zip
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Web App Rollout Gate
|
|
60
|
+
|
|
61
|
+
Keep the web app's Chrome extension UI hidden until the Web Store listing is
|
|
62
|
+
approved and there is a stable public URL.
|
|
63
|
+
|
|
64
|
+
- `VITE_CLIPS_CHROME_EXTENSION_ENABLED=1` reveals the Chrome option beside
|
|
65
|
+
Clips desktop prompts.
|
|
66
|
+
- `VITE_CLIPS_CHROME_EXTENSION_URL` overrides the default install URL
|
|
67
|
+
(`https://chromewebstore.google.com/detail/baoipacpchggcdigagnajakiidcgcffn`)
|
|
68
|
+
if a deployment uses a different listing.
|
|
69
|
+
- Leave both unset while submitting the draft so production continues to send
|
|
70
|
+
users directly to the desktop app.
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
detectPlatform,
|
|
13
13
|
getEvent,
|
|
14
14
|
pickJoinUrl,
|
|
15
|
-
|
|
15
|
+
resolveGoogleOAuthCredentialCandidates,
|
|
16
|
+
refreshAccessTokenWithFallback,
|
|
16
17
|
type CalendarEvent,
|
|
17
18
|
} from "./google-calendar-client.js";
|
|
18
19
|
|
|
@@ -77,9 +78,8 @@ export function shouldMarkNeedsReauth(message: string): boolean {
|
|
|
77
78
|
export async function resolveCalendarAccessToken(
|
|
78
79
|
account: CalendarAccountForEvents,
|
|
79
80
|
): Promise<string | null> {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
if (!clientId || !clientSecret || !account.ownerEmail) return null;
|
|
81
|
+
const credentialCandidates = resolveGoogleOAuthCredentialCandidates();
|
|
82
|
+
if (!credentialCandidates.length || !account.ownerEmail) return null;
|
|
83
83
|
|
|
84
84
|
let bundle: AccessTokenBundle | null = null;
|
|
85
85
|
if (account.accessTokenSecretRef) {
|
|
@@ -112,10 +112,9 @@ export async function resolveCalendarAccessToken(
|
|
|
112
112
|
|
|
113
113
|
let refreshed;
|
|
114
114
|
try {
|
|
115
|
-
refreshed = await
|
|
115
|
+
refreshed = await refreshAccessTokenWithFallback({
|
|
116
116
|
refreshToken: refreshSecret.value,
|
|
117
|
-
|
|
118
|
-
clientSecret,
|
|
117
|
+
credentials: credentialCandidates,
|
|
119
118
|
});
|
|
120
119
|
} catch {
|
|
121
120
|
// TODO(needs-reauth classification): a transient refresh failure (network
|
|
@@ -82,6 +82,35 @@ export interface ExchangeCodeArgs {
|
|
|
82
82
|
redirectUri: string;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export interface GoogleOAuthClientCredentials {
|
|
86
|
+
clientId: string;
|
|
87
|
+
clientSecret: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readCredentialPair(
|
|
91
|
+
clientIdKey: string,
|
|
92
|
+
clientSecretKey: string,
|
|
93
|
+
): GoogleOAuthClientCredentials | null {
|
|
94
|
+
const clientId = process.env[clientIdKey];
|
|
95
|
+
const clientSecret = process.env[clientSecretKey];
|
|
96
|
+
if (!clientId || !clientSecret) return null;
|
|
97
|
+
return { clientId, clientSecret };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function resolveGoogleOAuthCredentialCandidates(): GoogleOAuthClientCredentials[] {
|
|
101
|
+
const primary = readCredentialPair(
|
|
102
|
+
"GOOGLE_CLIENT_ID",
|
|
103
|
+
"GOOGLE_CLIENT_SECRET",
|
|
104
|
+
);
|
|
105
|
+
const legacy = readCredentialPair(
|
|
106
|
+
"GOOGLE_LEGACY_CLIENT_ID",
|
|
107
|
+
"GOOGLE_LEGACY_CLIENT_SECRET",
|
|
108
|
+
);
|
|
109
|
+
if (!primary) return legacy ? [legacy] : [];
|
|
110
|
+
if (!legacy || legacy.clientId === primary.clientId) return [primary];
|
|
111
|
+
return [primary, legacy];
|
|
112
|
+
}
|
|
113
|
+
|
|
85
114
|
/** Exchange an authorization code for tokens. Throws on non-2xx. */
|
|
86
115
|
export async function exchangeCode(
|
|
87
116
|
args: ExchangeCodeArgs,
|
|
@@ -129,6 +158,38 @@ export async function refreshAccessToken(args: {
|
|
|
129
158
|
return (await res.json()) as GoogleTokenResponse;
|
|
130
159
|
}
|
|
131
160
|
|
|
161
|
+
function isPermanentRefreshFailure(error: unknown): boolean {
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error || "");
|
|
163
|
+
const lower = message.toLowerCase();
|
|
164
|
+
return (
|
|
165
|
+
lower.includes("invalid_grant") ||
|
|
166
|
+
lower.includes("unauthorized_client") ||
|
|
167
|
+
lower.includes("invalid_client")
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function refreshAccessTokenWithFallback(args: {
|
|
172
|
+
refreshToken: string;
|
|
173
|
+
credentials: GoogleOAuthClientCredentials[];
|
|
174
|
+
}): Promise<GoogleTokenResponse> {
|
|
175
|
+
let lastError: unknown;
|
|
176
|
+
for (const credentials of args.credentials) {
|
|
177
|
+
try {
|
|
178
|
+
return await refreshAccessToken({
|
|
179
|
+
refreshToken: args.refreshToken,
|
|
180
|
+
clientId: credentials.clientId,
|
|
181
|
+
clientSecret: credentials.clientSecret,
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
lastError = error;
|
|
185
|
+
if (!isPermanentRefreshFailure(error)) throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
throw lastError instanceof Error
|
|
189
|
+
? lastError
|
|
190
|
+
: new Error("Google token refresh failed.");
|
|
191
|
+
}
|
|
192
|
+
|
|
132
193
|
/** Best-effort revoke. Returns true if Google returned 2xx, false otherwise. */
|
|
133
194
|
export async function revokeToken(token: string): Promise<boolean> {
|
|
134
195
|
try {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
encodeOAuthState,
|
|
9
9
|
getSession,
|
|
10
10
|
isElectron,
|
|
11
|
+
resolveGoogleSignInCredentials,
|
|
11
12
|
resolveOAuthRedirectUri,
|
|
12
13
|
safeReturnPath,
|
|
13
14
|
} from "@agent-native/core/server";
|
|
@@ -34,17 +35,6 @@ function oauthRedirectResponse(url: string) {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export default defineEventHandler(async (event: H3Event) => {
|
|
37
|
-
const clientId = process.env.GOOGLE_CLIENT_ID;
|
|
38
|
-
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
39
|
-
if (!clientId || !clientSecret) {
|
|
40
|
-
setResponseStatus(event, 422);
|
|
41
|
-
return {
|
|
42
|
-
error: "missing_credentials",
|
|
43
|
-
message:
|
|
44
|
-
"Google OAuth credentials are not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.",
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
38
|
try {
|
|
49
39
|
const q = getQuery(event);
|
|
50
40
|
const redirectUri = resolveOAuthRedirectUri(event);
|
|
@@ -67,6 +57,23 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
67
57
|
const returnUrl = requestedReturn !== "/" ? requestedReturn : undefined;
|
|
68
58
|
const calendarConnect =
|
|
69
59
|
q.calendar === "1" || q.calendar === "true" || q.product === "calendar";
|
|
60
|
+
const credentials = calendarConnect
|
|
61
|
+
? process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
|
|
62
|
+
? {
|
|
63
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
64
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
65
|
+
}
|
|
66
|
+
: null
|
|
67
|
+
: resolveGoogleSignInCredentials();
|
|
68
|
+
if (!credentials) {
|
|
69
|
+
setResponseStatus(event, 422);
|
|
70
|
+
return {
|
|
71
|
+
error: "missing_credentials",
|
|
72
|
+
message: calendarConnect
|
|
73
|
+
? "Google Calendar OAuth credentials are not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET."
|
|
74
|
+
: "Google sign-in credentials are not configured. Set GOOGLE_SIGN_IN_CLIENT_ID and GOOGLE_SIGN_IN_CLIENT_SECRET.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
70
77
|
|
|
71
78
|
if (calendarConnect && !owner) {
|
|
72
79
|
setResponseStatus(event, 401);
|
|
@@ -87,7 +94,7 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
87
94
|
});
|
|
88
95
|
|
|
89
96
|
const params = new URLSearchParams({
|
|
90
|
-
client_id: clientId,
|
|
97
|
+
client_id: credentials.clientId,
|
|
91
98
|
redirect_uri: redirectUri,
|
|
92
99
|
response_type: "code",
|
|
93
100
|
state,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getAppUrl,
|
|
11
11
|
oauthCallbackResponse,
|
|
12
12
|
oauthErrorPage,
|
|
13
|
+
resolveGoogleSignInCredentials,
|
|
13
14
|
resolveOAuthOwner,
|
|
14
15
|
setDesktopExchange,
|
|
15
16
|
type OAuthStatePayload,
|
|
@@ -51,11 +52,10 @@ async function handleGoogleSignInCallback(
|
|
|
51
52
|
return { error: "Missing authorization code" };
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
if (!clientId || !clientSecret) {
|
|
55
|
+
const credentials = resolveGoogleSignInCredentials();
|
|
56
|
+
if (!credentials) {
|
|
57
57
|
return oauthErrorPage(
|
|
58
|
-
"Google
|
|
58
|
+
"Google sign-in is not configured (missing client id/secret).",
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -66,8 +66,8 @@ async function handleGoogleSignInCallback(
|
|
|
66
66
|
},
|
|
67
67
|
body: new URLSearchParams({
|
|
68
68
|
code,
|
|
69
|
-
client_id: clientId,
|
|
70
|
-
client_secret: clientSecret,
|
|
69
|
+
client_id: credentials.clientId,
|
|
70
|
+
client_secret: credentials.clientSecret,
|
|
71
71
|
redirect_uri: state.redirectUri,
|
|
72
72
|
grant_type: "authorization_code",
|
|
73
73
|
}),
|
|
@@ -346,6 +346,42 @@ const dynamicLocalComponentDirs = (() => {
|
|
|
346
346
|
}
|
|
347
347
|
})();
|
|
348
348
|
|
|
349
|
+
const cloudflareSsrStubs =
|
|
350
|
+
process.env.NITRO_PRESET === "cloudflare_pages"
|
|
351
|
+
? [
|
|
352
|
+
"@assistant-ui/react",
|
|
353
|
+
"@tiptap/core",
|
|
354
|
+
"@tiptap/extension-blockquote",
|
|
355
|
+
"@tiptap/extension-code-block-lowlight",
|
|
356
|
+
"@tiptap/extension-collaboration",
|
|
357
|
+
"@tiptap/extension-collaboration-caret",
|
|
358
|
+
"@tiptap/extension-image",
|
|
359
|
+
"@tiptap/extension-link",
|
|
360
|
+
"@tiptap/extension-placeholder",
|
|
361
|
+
"@tiptap/extension-table",
|
|
362
|
+
"@tiptap/extension-table-cell",
|
|
363
|
+
"@tiptap/extension-table-header",
|
|
364
|
+
"@tiptap/extension-table-row",
|
|
365
|
+
"@tiptap/extension-task-item",
|
|
366
|
+
"@tiptap/extension-task-list",
|
|
367
|
+
"@tiptap/pm",
|
|
368
|
+
"@tiptap/react",
|
|
369
|
+
"@tiptap/starter-kit",
|
|
370
|
+
"@xterm/addon-fit",
|
|
371
|
+
"@xterm/addon-web-links",
|
|
372
|
+
"@xterm/xterm",
|
|
373
|
+
"katex",
|
|
374
|
+
"lowlight",
|
|
375
|
+
"prettier",
|
|
376
|
+
"react-markdown",
|
|
377
|
+
"remark-gfm",
|
|
378
|
+
"remark-mdx",
|
|
379
|
+
"tiptap-markdown",
|
|
380
|
+
"yjs",
|
|
381
|
+
"y-protocols",
|
|
382
|
+
]
|
|
383
|
+
: [];
|
|
384
|
+
|
|
349
385
|
export default defineConfig({
|
|
350
386
|
plugins: [contentLocalComponentsPlugin(), reactRouter()],
|
|
351
387
|
fsAllow: [
|
|
@@ -354,7 +390,7 @@ export default defineConfig({
|
|
|
354
390
|
],
|
|
355
391
|
// shiki only runs in AssistantChat's useEffect — keep it out of the
|
|
356
392
|
// CF Pages Functions bundle (25 MiB limit).
|
|
357
|
-
ssrStubs: ["shiki"],
|
|
393
|
+
ssrStubs: ["shiki", ...cloudflareSsrStubs],
|
|
358
394
|
optimizeDeps: {
|
|
359
395
|
include: [
|
|
360
396
|
"yjs",
|
|
@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
|
|
|
4
4
|
|
|
5
5
|
export default function NotFound() {
|
|
6
6
|
return (
|
|
7
|
-
<div className="min-h-
|
|
7
|
+
<div className="flex min-h-full w-full flex-col items-center justify-center bg-background px-4 py-12">
|
|
8
8
|
<h1 className="text-6xl font-bold text-muted-foreground/60 mb-4">404</h1>
|
|
9
9
|
<p className="text-sm text-muted-foreground mb-6">
|
|
10
10
|
The page you are looking for does not exist.
|
|
@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
|
|
|
4
4
|
|
|
5
5
|
export function NotFound() {
|
|
6
6
|
return (
|
|
7
|
-
<div className="flex h-
|
|
7
|
+
<div className="flex min-h-full w-full items-center justify-center bg-background px-4 py-12">
|
|
8
8
|
<div className="text-center">
|
|
9
9
|
<IconMail className="h-12 w-12 text-muted-foreground/40 mx-auto mb-4" />
|
|
10
10
|
<h1 className="text-2xl font-semibold text-foreground">404</h1>
|
|
@@ -380,9 +380,9 @@
|
|
|
380
380
|
--rail-gap: 2rem;
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
-
@
|
|
383
|
+
@container plan-doc (min-width: 64rem) {
|
|
384
384
|
.plan-content-surface[data-plan-layout="wide"] .plan-document-shell {
|
|
385
|
-
--plan-wide-component-width: min(1560px, calc(
|
|
385
|
+
--plan-wide-component-width: min(1560px, calc(100cqw - 5rem));
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
.plan-content-surface[data-plan-layout="wide"] .plan-document-flow-stack {
|
|
@@ -10,7 +10,7 @@ export default function NotFound() {
|
|
|
10
10
|
}, [location.pathname]);
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
|
-
<div className="min-h-
|
|
13
|
+
<div className="flex min-h-0 w-full flex-1 items-center justify-center bg-background px-4 py-12">
|
|
14
14
|
<div className="text-center">
|
|
15
15
|
<h1 className="text-6xl font-bold text-muted-foreground/40 mb-2">
|
|
16
16
|
404
|
|
@@ -30,19 +30,55 @@ interface GoogleUserInfo {
|
|
|
30
30
|
verified_email?: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
interface GoogleOAuthCredentials {
|
|
34
|
+
clientId: string;
|
|
35
|
+
clientSecret: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readCredentialPair(
|
|
39
|
+
clientIdKey: string,
|
|
40
|
+
clientSecretKey: string,
|
|
41
|
+
): GoogleOAuthCredentials | null {
|
|
42
|
+
const clientId = process.env[clientIdKey];
|
|
43
|
+
const clientSecret = process.env[clientSecretKey];
|
|
44
|
+
if (!clientId || !clientSecret) return null;
|
|
45
|
+
return { clientId, clientSecret };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveGoogleProviderCredentialCandidates(): GoogleOAuthCredentials[] {
|
|
49
|
+
const primary = readCredentialPair(
|
|
50
|
+
"GOOGLE_CLIENT_ID",
|
|
51
|
+
"GOOGLE_CLIENT_SECRET",
|
|
52
|
+
);
|
|
53
|
+
const legacy = readCredentialPair(
|
|
54
|
+
"GOOGLE_LEGACY_CLIENT_ID",
|
|
55
|
+
"GOOGLE_LEGACY_CLIENT_SECRET",
|
|
56
|
+
);
|
|
57
|
+
if (!primary) return legacy ? [legacy] : [];
|
|
58
|
+
if (!legacy || legacy.clientId === primary.clientId) return [primary];
|
|
59
|
+
return [primary, legacy];
|
|
60
|
+
}
|
|
61
|
+
|
|
33
62
|
function getOAuthCredentials(): { clientId: string; clientSecret: string } {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
if (!clientId || !clientSecret) {
|
|
63
|
+
const credentials = resolveGoogleProviderCredentialCandidates()[0];
|
|
64
|
+
if (!credentials) {
|
|
37
65
|
throw new Error(
|
|
38
66
|
"Google OAuth is not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.",
|
|
39
67
|
);
|
|
40
68
|
}
|
|
41
|
-
return
|
|
69
|
+
return credentials;
|
|
42
70
|
}
|
|
43
71
|
|
|
44
72
|
export function isGoogleDocsOAuthConfigured(): boolean {
|
|
45
|
-
return
|
|
73
|
+
return resolveGoogleProviderCredentialCandidates().length > 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isPermanentGoogleRefreshError(error: string | undefined): boolean {
|
|
77
|
+
return (
|
|
78
|
+
error === "invalid_grant" ||
|
|
79
|
+
error === "unauthorized_client" ||
|
|
80
|
+
error === "invalid_client"
|
|
81
|
+
);
|
|
46
82
|
}
|
|
47
83
|
|
|
48
84
|
export function getGooglePickerConfig(): {
|
|
@@ -98,35 +134,55 @@ async function refreshGoogleDocsToken(
|
|
|
98
134
|
throw new Error("Google Docs connection expired. Please reconnect.");
|
|
99
135
|
}
|
|
100
136
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
method: "POST",
|
|
104
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
105
|
-
body: new URLSearchParams({
|
|
106
|
-
refresh_token: tokens.refresh_token,
|
|
107
|
-
client_id: clientId,
|
|
108
|
-
client_secret: clientSecret,
|
|
109
|
-
grant_type: "refresh_token",
|
|
110
|
-
}),
|
|
111
|
-
});
|
|
112
|
-
const data = (await response.json()) as {
|
|
137
|
+
const credentialCandidates = resolveGoogleProviderCredentialCandidates();
|
|
138
|
+
let data: {
|
|
113
139
|
access_token?: string;
|
|
114
140
|
expires_in?: number;
|
|
115
141
|
token_type?: string;
|
|
116
142
|
scope?: string;
|
|
117
143
|
error?: string;
|
|
118
144
|
error_description?: string;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
} | null = null;
|
|
146
|
+
let lastStatusText = "Could not refresh Google token.";
|
|
147
|
+
for (const credentials of credentialCandidates) {
|
|
148
|
+
const response = await fetch(TOKEN_URL, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
151
|
+
body: new URLSearchParams({
|
|
152
|
+
refresh_token: tokens.refresh_token,
|
|
153
|
+
client_id: credentials.clientId,
|
|
154
|
+
client_secret: credentials.clientSecret,
|
|
155
|
+
grant_type: "refresh_token",
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
lastStatusText = response.statusText;
|
|
159
|
+
data = (await response.json()) as {
|
|
160
|
+
access_token?: string;
|
|
161
|
+
expires_in?: number;
|
|
162
|
+
token_type?: string;
|
|
163
|
+
scope?: string;
|
|
164
|
+
error?: string;
|
|
165
|
+
error_description?: string;
|
|
166
|
+
};
|
|
167
|
+
if (response.ok && data.access_token) break;
|
|
168
|
+
if (!isPermanentGoogleRefreshError(data.error)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
data.error_description ||
|
|
171
|
+
data.error ||
|
|
172
|
+
"Could not refresh Google token.",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!data?.access_token) {
|
|
178
|
+
if (isPermanentGoogleRefreshError(data?.error)) {
|
|
126
179
|
await deleteOAuthTokens(GOOGLE_DOCS_PROVIDER, accountId);
|
|
127
180
|
}
|
|
128
181
|
throw new Error(
|
|
129
|
-
data
|
|
182
|
+
data?.error_description ||
|
|
183
|
+
data?.error ||
|
|
184
|
+
lastStatusText ||
|
|
185
|
+
"Could not refresh Google token.",
|
|
130
186
|
);
|
|
131
187
|
}
|
|
132
188
|
|
|
@@ -12,7 +12,7 @@ const NotFound = () => {
|
|
|
12
12
|
}, [location.pathname]);
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<div className="min-h-
|
|
15
|
+
<div className="flex min-h-full w-full items-center justify-center bg-background px-4 py-12">
|
|
16
16
|
<div className="text-center">
|
|
17
17
|
<h1 className="text-5xl font-bold text-muted-foreground/30 mb-3">
|
|
18
18
|
404
|