@agent-native/core 0.68.0 → 0.68.2
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 +18 -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 +30 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +22 -3
- package/corpus/core/src/agent/engine/types.ts +1 -0
- package/corpus/core/src/agent/production-agent.ts +2 -0
- package/corpus/core/src/agent/types.ts +1 -0
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +45 -6
- package/corpus/core/src/client/CommandMenu.tsx +98 -3
- package/corpus/core/src/client/ConnectBuilderCard.tsx +11 -2
- package/corpus/core/src/client/active-run-state.ts +16 -0
- package/corpus/core/src/client/guided-questions.tsx +73 -17
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- 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/templates/workspace-root/.env.example +4 -0
- 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 +13 -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 +26 -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/design/actions/show-design-questions.ts +15 -1
- package/corpus/templates/design/app/hooks/use-agent-generating.ts +47 -8
- package/corpus/templates/design/app/pages/DesignEditor.tsx +6 -0
- 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/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +21 -3
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/types.d.ts +2 -0
- package/dist/agent/engine/types.d.ts.map +1 -1
- package/dist/agent/engine/types.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +3 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +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/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +42 -7
- package/dist/client/AssistantChat.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/active-run-state.d.ts +2 -0
- package/dist/client/active-run-state.d.ts.map +1 -1
- package/dist/client/active-run-state.js +10 -0
- package/dist/client/active-run-state.js.map +1 -1
- package/dist/client/guided-questions.d.ts +2 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +52 -7
- package/dist/client/guided-questions.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/client/sse-event-processor.d.ts +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +7 -1
- package/dist/client/sse-event-processor.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/templates/workspace-root/.env.example +4 -0
- package/docs/content/authentication.md +16 -2
- package/docs/content/template-analytics.md +14 -9
- package/docs/content/template-clips.md +30 -2
- package/package.json +1 -1
- package/src/templates/workspace-root/.env.example +4 -0
|
@@ -19,9 +19,18 @@ import {
|
|
|
19
19
|
getThemeInitScript,
|
|
20
20
|
useCommandMenuShortcut,
|
|
21
21
|
} from "@agent-native/core/client";
|
|
22
|
-
import { IconSun, IconMoon } from "@tabler/icons-react";
|
|
22
|
+
import { IconCheck, IconSun, IconMoon } from "@tabler/icons-react";
|
|
23
23
|
import { useTheme } from "next-themes";
|
|
24
24
|
import { Toaster } from "@/components/ui/sonner";
|
|
25
|
+
import { Button } from "@/components/ui/button";
|
|
26
|
+
import {
|
|
27
|
+
Dialog,
|
|
28
|
+
DialogContent,
|
|
29
|
+
DialogDescription,
|
|
30
|
+
DialogFooter,
|
|
31
|
+
DialogHeader,
|
|
32
|
+
DialogTitle,
|
|
33
|
+
} from "@/components/ui/dialog";
|
|
25
34
|
import type { LinksFunction } from "react-router";
|
|
26
35
|
import stylesheet from "./global.css?url";
|
|
27
36
|
import { configureTracking } from "@agent-native/core/client";
|
|
@@ -117,8 +126,30 @@ type ExternalChromeRuntime = {
|
|
|
117
126
|
) => void;
|
|
118
127
|
};
|
|
119
128
|
|
|
129
|
+
const CLIPS_COMMAND_DOCS = [
|
|
130
|
+
{
|
|
131
|
+
title: "Use the Chrome extension for browser logs",
|
|
132
|
+
description:
|
|
133
|
+
"Record a browser tab with redacted console logs, JavaScript exceptions, and fetch/XHR diagnostics.",
|
|
134
|
+
href: "https://www.agent-native.com/docs/template-clips#browser-logs-and-developer-diagnostics",
|
|
135
|
+
keywords: [
|
|
136
|
+
"logs",
|
|
137
|
+
"browser logs",
|
|
138
|
+
"developer logs",
|
|
139
|
+
"console logs",
|
|
140
|
+
"network logs",
|
|
141
|
+
"fetch",
|
|
142
|
+
"xhr",
|
|
143
|
+
"diagnostics",
|
|
144
|
+
"chrome extension",
|
|
145
|
+
"recording",
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
] satisfies React.ComponentProps<typeof CommandMenu.DocsGroup>["docs"];
|
|
149
|
+
|
|
120
150
|
function ClipsExtensionAuthBridge() {
|
|
121
151
|
const location = useLocation();
|
|
152
|
+
const [showAuthSuccess, setShowAuthSuccess] = useState(false);
|
|
122
153
|
|
|
123
154
|
useEffect(() => {
|
|
124
155
|
const params = new URLSearchParams(location.search);
|
|
@@ -163,6 +194,7 @@ function ClipsExtensionAuthBridge() {
|
|
|
163
194
|
cleaned.searchParams.delete("clipsExtensionAuth");
|
|
164
195
|
cleaned.searchParams.delete("clipsExtensionId");
|
|
165
196
|
window.history.replaceState(window.history.state, "", cleaned);
|
|
197
|
+
setShowAuthSuccess(true);
|
|
166
198
|
},
|
|
167
199
|
);
|
|
168
200
|
}
|
|
@@ -173,7 +205,26 @@ function ClipsExtensionAuthBridge() {
|
|
|
173
205
|
};
|
|
174
206
|
}, [location.search]);
|
|
175
207
|
|
|
176
|
-
return
|
|
208
|
+
return (
|
|
209
|
+
<Dialog open={showAuthSuccess} onOpenChange={setShowAuthSuccess}>
|
|
210
|
+
<DialogContent className="max-w-sm text-center sm:text-center">
|
|
211
|
+
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-500">
|
|
212
|
+
<IconCheck className="h-9 w-9" strokeWidth={2.5} />
|
|
213
|
+
</div>
|
|
214
|
+
<DialogHeader className="items-center text-center sm:text-center">
|
|
215
|
+
<DialogTitle>Signed in</DialogTitle>
|
|
216
|
+
<DialogDescription className="max-w-xs">
|
|
217
|
+
Open the Clips extension again to start recording.
|
|
218
|
+
</DialogDescription>
|
|
219
|
+
</DialogHeader>
|
|
220
|
+
<DialogFooter className="sm:justify-center">
|
|
221
|
+
<Button type="button" onClick={() => setShowAuthSuccess(false)}>
|
|
222
|
+
Got it
|
|
223
|
+
</Button>
|
|
224
|
+
</DialogFooter>
|
|
225
|
+
</DialogContent>
|
|
226
|
+
</Dialog>
|
|
227
|
+
);
|
|
177
228
|
}
|
|
178
229
|
|
|
179
230
|
/**
|
|
@@ -211,6 +262,7 @@ function AppContent() {
|
|
|
211
262
|
<CommandMenu.Group heading="Actions">
|
|
212
263
|
<CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
|
|
213
264
|
</CommandMenu.Group>
|
|
265
|
+
<CommandMenu.DocsGroup docs={CLIPS_COMMAND_DOCS} />
|
|
214
266
|
<CommandMenu.Group heading="Appearance">
|
|
215
267
|
<ThemeToggleItem />
|
|
216
268
|
</CommandMenu.Group>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Link } from "react-router";
|
|
3
2
|
import { toast } from "sonner";
|
|
4
3
|
import {
|
|
5
4
|
IconArrowsExchange,
|
|
@@ -34,6 +33,7 @@ import {
|
|
|
34
33
|
TooltipTrigger,
|
|
35
34
|
} from "@/components/ui/tooltip";
|
|
36
35
|
import { useDesktopPromo } from "@/hooks/use-desktop-promo";
|
|
36
|
+
import { CaptureInstallButton } from "@/components/capture-install-options";
|
|
37
37
|
|
|
38
38
|
export function meta() {
|
|
39
39
|
return [{ title: "Dictate · Clips" }];
|
|
@@ -609,12 +609,10 @@ function EmptyState({ isDesktopApp }: { isDesktopApp: boolean }) {
|
|
|
609
609
|
work in any app — Slack, your editor, anywhere.
|
|
610
610
|
</p>
|
|
611
611
|
<div className="mt-5 flex items-center justify-center">
|
|
612
|
-
<
|
|
613
|
-
<
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
</Link>
|
|
617
|
-
</Button>
|
|
612
|
+
<CaptureInstallButton size="sm" className="gap-1.5">
|
|
613
|
+
<IconDownload className="h-3.5 w-3.5" />
|
|
614
|
+
Download Clips desktop app
|
|
615
|
+
</CaptureInstallButton>
|
|
618
616
|
</div>
|
|
619
617
|
<div className="mt-3 flex items-center justify-center gap-2 text-xs text-muted-foreground">
|
|
620
618
|
<Kbd>Fn</Kbd>
|
|
@@ -646,12 +644,10 @@ function DownloadDesktopAppCard() {
|
|
|
646
644
|
desktop app is the way to use this.
|
|
647
645
|
</p>
|
|
648
646
|
</div>
|
|
649
|
-
<
|
|
650
|
-
<
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
</Link>
|
|
654
|
-
</Button>
|
|
647
|
+
<CaptureInstallButton size="sm" className="gap-1.5">
|
|
648
|
+
<IconDownload className="h-3.5 w-3.5" />
|
|
649
|
+
Download
|
|
650
|
+
</CaptureInstallButton>
|
|
655
651
|
</div>
|
|
656
652
|
);
|
|
657
653
|
}
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
TooltipContent,
|
|
62
62
|
TooltipTrigger,
|
|
63
63
|
} from "@/components/ui/tooltip";
|
|
64
|
+
import { CaptureInstallButton } from "@/components/capture-install-options";
|
|
64
65
|
|
|
65
66
|
export function meta() {
|
|
66
67
|
return [{ title: "Meeting · Clips" }];
|
|
@@ -699,17 +700,14 @@ export default function MeetingDetailRoute() {
|
|
|
699
700
|
transcript and AI notes will appear here automatically.
|
|
700
701
|
</span>
|
|
701
702
|
{!isDesktopApp && (
|
|
702
|
-
<
|
|
703
|
-
asChild
|
|
703
|
+
<CaptureInstallButton
|
|
704
704
|
size="sm"
|
|
705
705
|
variant="secondary"
|
|
706
706
|
className="ml-auto h-8 gap-1.5 cursor-pointer"
|
|
707
707
|
>
|
|
708
|
-
<
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
</NavLink>
|
|
712
|
-
</Button>
|
|
708
|
+
<IconExternalLink className="h-3.5 w-3.5" />
|
|
709
|
+
Get desktop app
|
|
710
|
+
</CaptureInstallButton>
|
|
713
711
|
)}
|
|
714
712
|
</div>
|
|
715
713
|
)}
|
|
@@ -45,6 +45,7 @@ import { Input } from "@/components/ui/input";
|
|
|
45
45
|
import { DayHeader, formatDayLabel } from "@/components/meetings/day-header";
|
|
46
46
|
import type { AttendeeStackParticipant } from "@/components/meetings/attendee-stack";
|
|
47
47
|
import { PageHeader } from "@/components/library/page-header";
|
|
48
|
+
import { CaptureInstallButton } from "@/components/capture-install-options";
|
|
48
49
|
|
|
49
50
|
export function meta() {
|
|
50
51
|
return [{ title: "Meetings · Clips" }];
|
|
@@ -668,17 +669,14 @@ function MeetingsHeader({
|
|
|
668
669
|
</div>
|
|
669
670
|
{showDesktopCta && (
|
|
670
671
|
<div className="flex w-fit shrink-0 flex-col items-start gap-1 sm:items-end">
|
|
671
|
-
<
|
|
672
|
-
asChild
|
|
672
|
+
<CaptureInstallButton
|
|
673
673
|
size="sm"
|
|
674
674
|
variant="secondary"
|
|
675
675
|
className="h-8 w-fit gap-1.5 cursor-pointer"
|
|
676
676
|
>
|
|
677
|
-
<
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
</NavLink>
|
|
681
|
-
</Button>
|
|
677
|
+
<IconAppWindow className="h-4 w-4" />
|
|
678
|
+
Get desktop app
|
|
679
|
+
</CaptureInstallButton>
|
|
682
680
|
<p className="max-w-56 text-[11px] leading-snug text-muted-foreground">
|
|
683
681
|
Required for meeting reminders and transcription.
|
|
684
682
|
</p>
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
+
IconBrowser,
|
|
3
4
|
IconBrandApple,
|
|
4
5
|
IconBrandWindows,
|
|
6
|
+
IconExternalLink,
|
|
5
7
|
IconPlayerRecord,
|
|
6
8
|
} from "@tabler/icons-react";
|
|
7
9
|
import { appBasePath, appPath } from "@agent-native/core/client";
|
|
8
10
|
import { Button } from "@/components/ui/button";
|
|
9
11
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
12
|
+
import {
|
|
13
|
+
clipsChromeExtensionEnabled,
|
|
14
|
+
clipsChromeExtensionUrl,
|
|
15
|
+
} from "@/lib/capture-install-options";
|
|
10
16
|
|
|
11
17
|
export function meta() {
|
|
12
18
|
return [
|
|
@@ -237,6 +243,49 @@ export default function DownloadPage() {
|
|
|
237
243
|
)}
|
|
238
244
|
</div>
|
|
239
245
|
</div>
|
|
246
|
+
|
|
247
|
+
{clipsChromeExtensionEnabled && (
|
|
248
|
+
<section className="mt-10 w-full max-w-xl rounded-2xl border border-border bg-card p-4 text-left shadow-sm">
|
|
249
|
+
<div className="flex items-start gap-3">
|
|
250
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
251
|
+
<IconBrowser className="h-4 w-4" />
|
|
252
|
+
</div>
|
|
253
|
+
<div className="min-w-0 flex-1">
|
|
254
|
+
<h2 className="text-sm font-semibold text-foreground">
|
|
255
|
+
Chrome extension for browser logs
|
|
256
|
+
</h2>
|
|
257
|
+
<p className="mt-1 text-sm leading-relaxed text-muted-foreground">
|
|
258
|
+
Use the extension when you want a recording plus redacted
|
|
259
|
+
console and fetch/XHR diagnostics from the tab you launch
|
|
260
|
+
from. The desktop app is still the smoothest everyday
|
|
261
|
+
recorder.
|
|
262
|
+
</p>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
<Button
|
|
266
|
+
asChild={Boolean(clipsChromeExtensionUrl)}
|
|
267
|
+
disabled={!clipsChromeExtensionUrl}
|
|
268
|
+
variant="outline"
|
|
269
|
+
className="mt-4 w-full gap-2"
|
|
270
|
+
>
|
|
271
|
+
{clipsChromeExtensionUrl ? (
|
|
272
|
+
<a
|
|
273
|
+
href={clipsChromeExtensionUrl}
|
|
274
|
+
target="_blank"
|
|
275
|
+
rel="noreferrer"
|
|
276
|
+
>
|
|
277
|
+
<IconExternalLink className="h-4 w-4" />
|
|
278
|
+
Install Chrome extension
|
|
279
|
+
</a>
|
|
280
|
+
) : (
|
|
281
|
+
<>
|
|
282
|
+
<IconExternalLink className="h-4 w-4" />
|
|
283
|
+
Chrome Web Store URL pending
|
|
284
|
+
</>
|
|
285
|
+
)}
|
|
286
|
+
</Button>
|
|
287
|
+
</section>
|
|
288
|
+
)}
|
|
240
289
|
</div>
|
|
241
290
|
</main>
|
|
242
291
|
</div>
|
|
@@ -71,6 +71,7 @@ async function writeAppState(key: string, value: unknown): Promise<void> {
|
|
|
71
71
|
}
|
|
72
72
|
import { Button } from "@/components/ui/button";
|
|
73
73
|
import { Spinner } from "@/components/ui/spinner";
|
|
74
|
+
import { CaptureInstallButton } from "@/components/capture-install-options";
|
|
74
75
|
import { toast } from "sonner";
|
|
75
76
|
|
|
76
77
|
import { PreRecordPanel } from "@/components/recorder/pre-record-panel";
|
|
@@ -542,13 +543,12 @@ function DesktopRecorderCallout() {
|
|
|
542
543
|
</p>
|
|
543
544
|
</div>
|
|
544
545
|
</div>
|
|
545
|
-
<
|
|
546
|
-
asChild
|
|
546
|
+
<CaptureInstallButton
|
|
547
547
|
size="sm"
|
|
548
548
|
className="mt-4 w-full bg-primary text-primary-foreground hover:bg-primary/90"
|
|
549
549
|
>
|
|
550
|
-
|
|
551
|
-
</
|
|
550
|
+
Download desktop app
|
|
551
|
+
</CaptureInstallButton>
|
|
552
552
|
</aside>
|
|
553
553
|
);
|
|
554
554
|
}
|
|
@@ -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>
|
|
@@ -39,3 +39,29 @@ Diagnostics are bounded and redacted before they are saved. Clips does not colle
|
|
|
39
39
|
## Review Notes
|
|
40
40
|
|
|
41
41
|
The extension opens `https://clips.agent-native.com/record` by default. Local development can point the settings page to a localhost Clips instance.
|
|
42
|
+
|
|
43
|
+
## Submission Artifact
|
|
44
|
+
|
|
45
|
+
Build the latest Chrome Web Store ZIP from the repo root:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm --filter clips-chrome-extension package
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Upload the generated artifact:
|
|
52
|
+
|
|
53
|
+
```txt
|
|
54
|
+
templates/clips/chrome-extension/releases/clips-chrome-extension-0.1.0.zip
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Web App Rollout Gate
|
|
58
|
+
|
|
59
|
+
Keep the web app's Chrome extension UI hidden until the Web Store listing is
|
|
60
|
+
approved and there is a stable public URL.
|
|
61
|
+
|
|
62
|
+
- `VITE_CLIPS_CHROME_EXTENSION_ENABLED=1` reveals the Chrome option beside
|
|
63
|
+
Clips desktop prompts.
|
|
64
|
+
- `VITE_CLIPS_CHROME_EXTENSION_URL=<chrome-web-store-url>` powers the install
|
|
65
|
+
links.
|
|
66
|
+
- Leave both unset while submitting the draft so production continues to send
|
|
67
|
+
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
|
}),
|
|
@@ -47,6 +47,18 @@ const questionSchema = z.object({
|
|
|
47
47
|
includeDecide: z.boolean().optional(),
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
function normalizeDesignQuestions(
|
|
51
|
+
questions: z.infer<typeof questionSchema>[],
|
|
52
|
+
): z.infer<typeof questionSchema>[] {
|
|
53
|
+
return questions.map((question) => ({
|
|
54
|
+
...question,
|
|
55
|
+
// The agent supplies Explore/Decide choices explicitly when needed.
|
|
56
|
+
// Default injection duplicates cards on every question in the form.
|
|
57
|
+
includeExplore: question.includeExplore ?? false,
|
|
58
|
+
includeDecide: question.includeDecide ?? false,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
export default defineAction({
|
|
51
63
|
description:
|
|
52
64
|
"Show a Claude Design-style question form in the Design editor before " +
|
|
@@ -95,6 +107,8 @@ export default defineAction({
|
|
|
95
107
|
}) => {
|
|
96
108
|
await assertAccess("design", designId, "editor");
|
|
97
109
|
|
|
110
|
+
const normalizedQuestions = normalizeDesignQuestions(questions);
|
|
111
|
+
|
|
98
112
|
await writeAppState(designQuestionsStateKey(designId), {
|
|
99
113
|
designId,
|
|
100
114
|
title: title ?? "Quick questions before I design",
|
|
@@ -103,7 +117,7 @@ export default defineAction({
|
|
|
103
117
|
"Pick what matters. Use Other for specifics, or let the agent decide.",
|
|
104
118
|
skipLabel: skipLabel ?? "Decide for me",
|
|
105
119
|
submitLabel: submitLabel ?? "Continue",
|
|
106
|
-
questions,
|
|
120
|
+
questions: normalizedQuestions,
|
|
107
121
|
});
|
|
108
122
|
|
|
109
123
|
return {
|