Skip to content

Commit d7d3a26

Browse files
teallarsonclaude
andauthored
Fix false auth_required for Google Calendar WhoAmI (#37)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4856abb commit d7d3a26

File tree

9 files changed

+223
-29
lines changed

9 files changed

+223
-29
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@arcadeai/create-agent",
3-
"version": "0.5.4",
3+
"version": "0.5.5",
44
"type": "module",
55
"description": "Scaffold an Arcade-powered AI agent",
66
"license": "MIT",

templates/_shared/nextjs-ui/app/dashboard/page.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { useArcadeConnection } from "@/hooks/use-arcade-connection";
1414
import { useSourceCheck } from "@/hooks/use-source-check";
1515
import { usePlanStream } from "@/hooks/use-plan-stream";
1616
import {
17+
Alert,
18+
AlertTitle,
19+
AlertDescription,
1720
Skeleton,
1821
Button,
1922
Card,
@@ -22,7 +25,7 @@ import {
2225
CardHeader,
2326
CardTitle,
2427
} from "@arcadeai/design-system";
25-
import { Loader2, ShieldAlert, AlertTriangle, RotateCcw } from "lucide-react";
28+
import { Info, Loader2, ShieldAlert, AlertTriangle, RotateCcw } from "lucide-react";
2629

2730
// --- Config health warnings ---
2831

@@ -209,6 +212,15 @@ function DashboardContent() {
209212
>
210213
I&apos;ve already signed in &mdash; retry
211214
</button>
215+
<Alert className="text-left">
216+
<Info className="size-4" />
217+
<AlertTitle>Why Arcade?</AlertTitle>
218+
<AlertDescription>
219+
The agent uses Arcade as an MCP Gateway to read from your tools on your behalf.
220+
Signing in here links your Arcade identity so the gateway knows which
221+
user&apos;s tools to access.
222+
</AlertDescription>
223+
</Alert>
212224
</CardContent>
213225
</Card>
214226
)}
@@ -313,6 +325,19 @@ function DashboardContent() {
313325

314326
{loading && !hasItems && authUrls.length === 0 && (
315327
<div className="space-y-6">
328+
<Alert>
329+
<Info className="size-4" />
330+
<AlertTitle>What&apos;s happening</AlertTitle>
331+
<AlertDescription>
332+
The agent is reading from your connected sources and classifying what it finds.
333+
This behavior is driven by the system prompt in{" "}
334+
<code className="rounded bg-muted px-1 py-0.5 text-xs">
335+
app/api/plan/route.ts
336+
</code>{" "}
337+
— edit it to change what gets fetched, how items are prioritized, or what the
338+
agent focuses on.
339+
</AlertDescription>
340+
</Alert>
316341
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
317342
{Array.from({ length: 3 }).map((_, i) => (
318343
<Skeleton key={i} className="h-24 rounded-xl" />
@@ -343,6 +368,19 @@ function DashboardContent() {
343368
)}
344369
</Button>
345370
</div>
371+
<Alert>
372+
<Info className="size-4" />
373+
<AlertTitle>Make it yours</AlertTitle>
374+
<AlertDescription>
375+
Start with the system prompt in{" "}
376+
<code className="rounded bg-muted px-1 py-0.5 text-xs">
377+
app/api/plan/route.ts
378+
</code>{" "}
379+
— that&apos;s where the agent&apos;s behavior is defined. From there, check out{" "}
380+
<code className="rounded bg-muted px-1 py-0.5 text-xs">AGENT_PLAYBOOK.md</code>{" "}
381+
for a full walkthrough of customization points.
382+
</AlertDescription>
383+
</Alert>
346384
<StatsBar
347385
stats={stats}
348386
activeSource={activeSource}

templates/_shared/nextjs-ui/components/auth/login-form.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { useRef, useState } from "react";
44
import { useRouter } from "next/navigation";
55
import { authClient } from "@/lib/auth-client";
66
import {
7+
Alert,
8+
AlertTitle,
9+
AlertDescription,
710
Button,
811
Card,
912
CardContent,
@@ -12,6 +15,7 @@ import {
1215
Input,
1316
Label,
1417
} from "@arcadeai/design-system";
18+
import { Info } from "lucide-react";
1519

1620
export function LoginForm() {
1721
const router = useRouter();
@@ -65,6 +69,15 @@ export function LoginForm() {
6569
</svg>
6670
<span className="text-xl font-semibold tracking-tight">Arcade Agent</span>
6771
</div>
72+
<Alert className="mb-4 w-full max-w-sm">
73+
<Info className="size-4" />
74+
<AlertTitle>Two separate sign-ins</AlertTitle>
75+
<AlertDescription>
76+
This creates a local account for your agent app — it&apos;s just for session management
77+
and stays in your own database. You&apos;ll connect your Arcade account on the next screen
78+
to give the agent access to your tools.
79+
</AlertDescription>
80+
</Alert>
6881
<Card className="w-full max-w-sm">
6982
<CardHeader className="pb-2">
7083
<div className="mb-3 flex rounded-lg bg-muted p-1">

templates/_shared/nextjs-ui/components/dashboard/empty-state.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
"use client";
2-
3-
import { Inbox, Loader2 } from "lucide-react";
4-
import { Button } from "@arcadeai/design-system";
1+
import { Inbox, Info, Loader2 } from "lucide-react";
2+
import { Alert, AlertTitle, AlertDescription, Button } from "@arcadeai/design-system";
53

64
interface EmptyStateProps {
75
onPlan: () => void;
@@ -21,6 +19,17 @@ export function EmptyState({ onPlan, loading }: EmptyStateProps) {
2119
build your action plan.
2220
</p>
2321
</div>
22+
<Alert className="max-w-md text-left">
23+
<Info className="size-4" />
24+
<AlertTitle>How this works</AlertTitle>
25+
<AlertDescription>
26+
When you click &ldquo;Plan my day&rdquo;, the agent connects to your Slack, Gmail, Google
27+
Calendar, Linear, and GitHub through Arcade&apos;s MCP Gateway. It will first check if any
28+
of those need authentication — if so, you&apos;ll be prompted to authorize before the run
29+
starts. Then it reads your recent messages, emails, events, issues, and pull requests, and
30+
classifies each item by priority and suggests next steps.
31+
</AlertDescription>
32+
</Alert>
2433
<Button size="lg" disabled={loading} onClick={onPlan}>
2534
{loading ? (
2635
<>

templates/_shared/nextjs-ui/components/dashboard/source-auth-gate.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
"use client";
2-
3-
import { Check, ArrowUpRight } from "lucide-react";
4-
import { Button } from "@arcadeai/design-system";
1+
import { Check, ArrowUpRight, Info } from "lucide-react";
2+
import { Alert, AlertTitle, AlertDescription, Button } from "@arcadeai/design-system";
53
import type { SourceStatus } from "@/types/inbox";
64
import { getSource } from "@/lib/sources";
75

@@ -36,6 +34,16 @@ export function SourceAuthGate({
3634
</p>
3735
</div>
3836

37+
<Alert className="mb-6">
38+
<Info className="size-4" />
39+
<AlertTitle>Why authorize?</AlertTitle>
40+
<AlertDescription>
41+
Each tool connects on your behalf using OAuth — the agent only gets read access to scan
42+
for items to triage. You can skip any source you don&apos;t use, and revoke access anytime
43+
from your Arcade dashboard.
44+
</AlertDescription>
45+
</Alert>
46+
3947
<div className="mb-6 space-y-2">
4048
{Object.entries(sourceStatuses).map(([source, status]) => {
4149
const config = getSource(source);

templates/_shared/partials/extract-auth-url.hbs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
function extractAuthUrlFromToolOutput(output: unknown): string | null {
2-
const looksLikeAuthUrl = (value: unknown): value is string =>
3-
typeof value === "string" &&
4-
/oauth|authorize/i.test(value);
5-
62
const fromRecord = (value: unknown): string | null => {
73
if (!value || typeof value !== "object") return null;
84
const obj = value as Record<string, unknown>;
9-
const direct =
10-
(typeof obj.authorization_url === "string" && obj.authorization_url) ||
11-
(looksLikeAuthUrl(obj.url) ? obj.url : null);
12-
if (direct) return direct;
5+
if (typeof obj.authorization_url === "string" && obj.authorization_url)
6+
return obj.authorization_url;
137

148
if (obj.structuredContent && typeof obj.structuredContent === "object") {
159
const nested = obj.structuredContent as Record<string, unknown>;
16-
const nestedUrl =
17-
(typeof nested.authorization_url === "string" &&
18-
nested.authorization_url) ||
19-
(looksLikeAuthUrl(nested.url) ? nested.url : null);
20-
if (nestedUrl) return nestedUrl;
10+
if (typeof nested.authorization_url === "string" && nested.authorization_url)
11+
return nested.authorization_url;
2112
}
2213
return null;
2314
};

templates/langchain/app/routes/arcade.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,14 @@ def _extract_auth_url_from_result(content: str) -> str | None:
123123
"""Check if a tool result contains an Arcade authorization URL."""
124124
try:
125125
parsed = json.loads(content)
126-
url = (
127-
parsed.get("authorization_url")
128-
or parsed.get("url")
129-
or (parsed.get("structuredContent") or {}).get("authorization_url")
130-
)
126+
url = parsed.get("authorization_url") or (
127+
parsed.get("structuredContent") or {}
128+
).get("authorization_url")
131129
if url:
132130
return url
133131
except (json.JSONDecodeError, AttributeError):
134132
pass
135-
# Fallback: regex
133+
# Fallback: regex — only match clear OAuth authorization endpoints
136134
match = re.search(
137135
r"https://[^\s\"'>\]]+/oauth/[^\s\"'>\]]+|https://[^\s\"'>\]]+authorize[^\s\"'>\]]*",
138136
content or "",

templates/langchain/app/templates/dashboard.html

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,32 @@ <h2 class="text-lg font-semibold mb-2">Connect to Arcade</h2>
120120
I've already signed in &mdash; retry
121121
</button>
122122
</div>
123+
<div
124+
id="callout-why-arcade"
125+
class="rounded-lg border border-border bg-card px-4 py-3 text-sm text-left mt-4"
126+
>
127+
<div class="flex items-start gap-3">
128+
<svg
129+
class="w-4 h-4 shrink-0 mt-0.5 text-foreground"
130+
fill="none"
131+
stroke="currentColor"
132+
viewBox="0 0 24 24"
133+
stroke-width="2"
134+
>
135+
<circle cx="12" cy="12" r="10" />
136+
<path d="M12 16v-4" />
137+
<path d="M12 8h.01" />
138+
</svg>
139+
<div class="flex-1">
140+
<p class="font-medium text-foreground mb-1">Why Arcade?</p>
141+
<p class="text-muted-foreground">
142+
The agent uses Arcade as an MCP Gateway to read from your tools on your behalf.
143+
Signing in here links your Arcade identity so the gateway knows which user's tools
144+
to access.
145+
</p>
146+
</div>
147+
</div>
148+
</div>
123149
<div id="gate-auth-waiting" class="hidden">
124150
<div class="flex items-center justify-center gap-2 mb-3">
125151
<div
@@ -207,6 +233,34 @@ <h2 class="text-2xl font-semibold tracking-tight mb-2">Ready to triage?</h2>
207233
Your agent will scan your inbox, calendar, tasks, and PRs, then prioritize everything
208234
and build your action plan.
209235
</p>
236+
<div
237+
id="callout-how-it-works"
238+
class="rounded-lg border border-border bg-card px-4 py-3 text-sm text-left max-w-md mt-2"
239+
>
240+
<div class="flex items-start gap-3">
241+
<svg
242+
class="w-4 h-4 shrink-0 mt-0.5 text-foreground"
243+
fill="none"
244+
stroke="currentColor"
245+
viewBox="0 0 24 24"
246+
stroke-width="2"
247+
>
248+
<circle cx="12" cy="12" r="10" />
249+
<path d="M12 16v-4" />
250+
<path d="M12 8h.01" />
251+
</svg>
252+
<div class="flex-1">
253+
<p class="font-medium text-foreground mb-1">How this works</p>
254+
<p class="text-muted-foreground">
255+
When you click "Plan my day", the agent connects to your Slack, Gmail, Google
256+
Calendar, Linear, and GitHub through Arcade's MCP Gateway. It will first check if
257+
any of those need authentication — if so, you'll be prompted to authorize before
258+
the run starts. Then it reads your recent messages, emails, events, issues, and
259+
pull requests, and classifies each item by priority and suggests next steps.
260+
</p>
261+
</div>
262+
</div>
263+
</div>
210264
<button
211265
id="plan-btn"
212266
class="px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground transition-colors font-medium"
@@ -217,6 +271,34 @@ <h2 class="text-2xl font-semibold tracking-tight mb-2">Ready to triage?</h2>
217271

218272
<!-- Loading skeletons -->
219273
<div id="loading-skeletons" class="hidden space-y-6">
274+
<div
275+
id="callout-whats-happening"
276+
class="rounded-lg border border-border bg-card px-4 py-3 text-sm text-left"
277+
>
278+
<div class="flex items-start gap-3">
279+
<svg
280+
class="w-4 h-4 shrink-0 mt-0.5 text-foreground"
281+
fill="none"
282+
stroke="currentColor"
283+
viewBox="0 0 24 24"
284+
stroke-width="2"
285+
>
286+
<circle cx="12" cy="12" r="10" />
287+
<path d="M12 16v-4" />
288+
<path d="M12 8h.01" />
289+
</svg>
290+
<div class="flex-1">
291+
<p class="font-medium text-foreground mb-1">What's happening</p>
292+
<p class="text-muted-foreground">
293+
The agent is reading from your connected sources and classifying what it finds.
294+
This behavior is driven by the system prompt in
295+
<code class="rounded bg-muted px-1 py-0.5 text-xs">app/system-prompt.md</code>
296+
edit it to change what gets fetched, how items are prioritized, or what the agent
297+
focuses on.
298+
</p>
299+
</div>
300+
</div>
301+
</div>
220302
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
221303
<div class="h-24 rounded-xl bg-muted animate-pulse"></div>
222304
<div class="h-24 rounded-xl bg-muted animate-pulse"></div>
@@ -248,6 +330,34 @@ <h2 class="text-lg font-semibold flex items-center gap-2">
248330
Replan my day
249331
</button>
250332
</div>
333+
<div
334+
id="callout-make-it-yours"
335+
class="rounded-lg border border-border bg-card px-4 py-3 text-sm text-left"
336+
>
337+
<div class="flex items-start gap-3">
338+
<svg
339+
class="w-4 h-4 shrink-0 mt-0.5 text-foreground"
340+
fill="none"
341+
stroke="currentColor"
342+
viewBox="0 0 24 24"
343+
stroke-width="2"
344+
>
345+
<circle cx="12" cy="12" r="10" />
346+
<path d="M12 16v-4" />
347+
<path d="M12 8h.01" />
348+
</svg>
349+
<div class="flex-1">
350+
<p class="font-medium text-foreground mb-1">Make it yours</p>
351+
<p class="text-muted-foreground">
352+
Start with the system prompt in
353+
<code class="rounded bg-muted px-1 py-0.5 text-xs">app/system-prompt.md</code>
354+
that's where the agent's behavior is defined. From there, check out
355+
<code class="rounded bg-muted px-1 py-0.5 text-xs">AGENT_PLAYBOOK.md</code> for a
356+
full walkthrough of customization points.
357+
</p>
358+
</div>
359+
</div>
360+
</div>
251361
<div id="task-container" class="space-y-3"></div>
252362
</div>
253363
</div>

templates/langchain/app/templates/login.html

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,33 @@
1010
<span class="text-xl font-semibold tracking-tight">Arcade Agent</span>
1111
</div>
1212

13+
<div
14+
id="callout-two-signins"
15+
class="rounded-lg border border-border bg-card px-4 py-3 text-sm mb-4 w-full"
16+
>
17+
<div class="flex items-start gap-3">
18+
<svg
19+
class="w-4 h-4 shrink-0 mt-0.5 text-foreground"
20+
fill="none"
21+
stroke="currentColor"
22+
viewBox="0 0 24 24"
23+
stroke-width="2"
24+
>
25+
<circle cx="12" cy="12" r="10" />
26+
<path d="M12 16v-4" />
27+
<path d="M12 8h.01" />
28+
</svg>
29+
<div class="flex-1">
30+
<p class="font-medium text-foreground mb-1">Two separate sign-ins</p>
31+
<p class="text-muted-foreground">
32+
This creates a local account for your agent app — it's just for session management and
33+
stays in your own database. You'll connect your Arcade account on the next screen to
34+
give the agent access to your tools.
35+
</p>
36+
</div>
37+
</div>
38+
</div>
39+
1340
<div class="bg-card border border-border rounded-xl p-6 shadow-sm">
1441
<!-- Tab switcher -->
1542
<div class="mb-4 flex rounded-lg bg-muted p-1">

0 commit comments

Comments
 (0)