veryfront 0.0.45 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/ai/components.js +3 -3
  2. package/dist/ai/components.js.map +2 -2
  3. package/dist/ai/index.d.ts +18 -3
  4. package/dist/ai/index.js +12 -2
  5. package/dist/ai/index.js.map +2 -2
  6. package/dist/cli.js +4 -5
  7. package/dist/components.js +1 -1
  8. package/dist/components.js.map +1 -1
  9. package/dist/config.d.ts +7 -0
  10. package/dist/config.js +1 -1
  11. package/dist/config.js.map +1 -1
  12. package/dist/data.js +1 -1
  13. package/dist/data.js.map +1 -1
  14. package/dist/index.js +2 -5
  15. package/dist/index.js.map +2 -2
  16. package/dist/integrations/_base/connector.json +11 -0
  17. package/dist/integrations/_base/files/SETUP.md +132 -0
  18. package/dist/integrations/_base/files/app/api/integrations/status/route.ts +38 -0
  19. package/dist/integrations/_base/files/app/setup/page.tsx +461 -0
  20. package/dist/integrations/_base/files/lib/oauth.ts +145 -0
  21. package/dist/integrations/_base/files/lib/token-store.ts +109 -0
  22. package/dist/integrations/calendar/connector.json +77 -0
  23. package/dist/integrations/calendar/files/ai/tools/create-event.ts +83 -0
  24. package/dist/integrations/calendar/files/ai/tools/find-free-time.ts +108 -0
  25. package/dist/integrations/calendar/files/ai/tools/list-events.ts +98 -0
  26. package/dist/integrations/calendar/files/app/api/auth/calendar/callback/route.ts +114 -0
  27. package/dist/integrations/calendar/files/app/api/auth/calendar/route.ts +29 -0
  28. package/dist/integrations/calendar/files/lib/calendar-client.ts +309 -0
  29. package/dist/integrations/calendar/files/lib/oauth.ts +145 -0
  30. package/dist/integrations/calendar/files/lib/token-store.ts +109 -0
  31. package/dist/integrations/github/connector.json +84 -0
  32. package/dist/integrations/github/files/ai/tools/create-issue.ts +75 -0
  33. package/dist/integrations/github/files/ai/tools/get-pr-diff.ts +82 -0
  34. package/dist/integrations/github/files/ai/tools/list-prs.ts +93 -0
  35. package/dist/integrations/github/files/ai/tools/list-repos.ts +81 -0
  36. package/dist/integrations/github/files/app/api/auth/github/callback/route.ts +132 -0
  37. package/dist/integrations/github/files/app/api/auth/github/route.ts +29 -0
  38. package/dist/integrations/github/files/lib/github-client.ts +282 -0
  39. package/dist/integrations/github/files/lib/oauth.ts +145 -0
  40. package/dist/integrations/github/files/lib/token-store.ts +109 -0
  41. package/dist/integrations/gmail/connector.json +78 -0
  42. package/dist/integrations/gmail/files/ai/tools/list-emails.ts +92 -0
  43. package/dist/integrations/gmail/files/ai/tools/search-emails.ts +92 -0
  44. package/dist/integrations/gmail/files/ai/tools/send-email.ts +77 -0
  45. package/dist/integrations/gmail/files/app/api/auth/gmail/callback/route.ts +114 -0
  46. package/dist/integrations/gmail/files/app/api/auth/gmail/route.ts +29 -0
  47. package/dist/integrations/gmail/files/lib/gmail-client.ts +259 -0
  48. package/dist/integrations/gmail/files/lib/oauth.ts +145 -0
  49. package/dist/integrations/gmail/files/lib/token-store.ts +109 -0
  50. package/dist/integrations/slack/connector.json +74 -0
  51. package/dist/integrations/slack/files/ai/tools/get-messages.ts +65 -0
  52. package/dist/integrations/slack/files/ai/tools/list-channels.ts +63 -0
  53. package/dist/integrations/slack/files/ai/tools/send-message.ts +49 -0
  54. package/dist/integrations/slack/files/app/api/auth/slack/callback/route.ts +132 -0
  55. package/dist/integrations/slack/files/app/api/auth/slack/route.ts +29 -0
  56. package/dist/integrations/slack/files/lib/oauth.ts +145 -0
  57. package/dist/integrations/slack/files/lib/slack-client.ts +215 -0
  58. package/dist/integrations/slack/files/lib/token-store.ts +109 -0
  59. package/dist/templates/ai/app/page.tsx +4 -4
  60. package/dist/templates/ai/tsconfig.json +1 -0
  61. package/package.json +1 -1
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "_base",
3
+ "displayName": "Base Integration Files",
4
+ "description": "Shared files for all integrations (setup guide, status API)",
5
+ "internal": true,
6
+ "auth": {
7
+ "type": "none"
8
+ },
9
+ "envVars": [],
10
+ "tools": []
11
+ }
@@ -0,0 +1,132 @@
1
+ # Service Integration Setup Guide
2
+
3
+ This guide will help you set up OAuth credentials for your AI agent's service integrations.
4
+
5
+ ## Quick Start
6
+
7
+ 1. Start the development server: `veryfront dev`
8
+ 2. Visit `http://localhost:3000/setup` for an interactive setup guide
9
+ 3. Follow the step-by-step instructions for each service
10
+
11
+ ## Service-Specific Setup
12
+
13
+ ### Gmail & Google Calendar
14
+
15
+ Both Gmail and Calendar use Google OAuth. You only need one set of credentials.
16
+
17
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
18
+ 2. Create a new project or select an existing one
19
+ 3. Enable APIs:
20
+ - Gmail API
21
+ - Google Calendar API
22
+ 4. Create OAuth 2.0 credentials:
23
+ - Application type: **Web application**
24
+ - Authorized redirect URIs:
25
+ - `http://localhost:3000/api/auth/gmail/callback`
26
+ - `http://localhost:3000/api/auth/calendar/callback`
27
+ 5. Copy credentials to `.env`:
28
+ ```
29
+ GOOGLE_CLIENT_ID=your-client-id
30
+ GOOGLE_CLIENT_SECRET=your-client-secret
31
+ ```
32
+
33
+ ### Slack
34
+
35
+ 1. Go to [Slack API Apps](https://api.slack.com/apps)
36
+ 2. Click "Create New App" > "From scratch"
37
+ 3. Add OAuth scopes under "OAuth & Permissions":
38
+ - `channels:read` - View basic channel info
39
+ - `chat:write` - Send messages
40
+ - `users:read` - View users
41
+ 4. Add redirect URL:
42
+ - `http://localhost:3000/api/auth/slack/callback`
43
+ 5. Install to your workspace
44
+ 6. Copy credentials to `.env`:
45
+ ```
46
+ SLACK_CLIENT_ID=your-client-id
47
+ SLACK_CLIENT_SECRET=your-client-secret
48
+ ```
49
+
50
+ ### GitHub
51
+
52
+ 1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
53
+ 2. Click "New OAuth App"
54
+ 3. Fill in the form:
55
+ - Homepage URL: `http://localhost:3000`
56
+ - Authorization callback URL: `http://localhost:3000/api/auth/github/callback`
57
+ 4. Copy credentials to `.env`:
58
+ ```
59
+ GITHUB_CLIENT_ID=your-client-id
60
+ GITHUB_CLIENT_SECRET=your-client-secret
61
+ ```
62
+
63
+ ### Jira (Atlassian)
64
+
65
+ 1. Go to [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/)
66
+ 2. Create a new OAuth 2.0 integration
67
+ 3. Add required scopes:
68
+ - `read:jira-work`
69
+ - `write:jira-work`
70
+ 4. Set callback URL:
71
+ - `http://localhost:3000/api/auth/jira/callback`
72
+ 5. Copy credentials to `.env`:
73
+ ```
74
+ JIRA_CLIENT_ID=your-client-id
75
+ JIRA_CLIENT_SECRET=your-client-secret
76
+ JIRA_CLOUD_ID=your-atlassian-cloud-id
77
+ ```
78
+
79
+ ### Notion
80
+
81
+ 1. Go to [Notion Integrations](https://www.notion.so/my-integrations)
82
+ 2. Click "New integration"
83
+ 3. Configure:
84
+ - Name: Your app name
85
+ - Associated workspace: Select your workspace
86
+ - Capabilities: Select required capabilities
87
+ 4. Copy the Internal Integration Token to `.env`:
88
+ ```
89
+ NOTION_API_KEY=your-integration-token
90
+ ```
91
+ 5. **Important**: Share the pages you want to access with your integration
92
+
93
+ ## Testing Connections
94
+
95
+ After setting up credentials:
96
+
97
+ 1. Start the dev server: `veryfront dev`
98
+ 2. Visit each connection URL to authorize:
99
+ - Gmail: `http://localhost:3000/api/auth/gmail`
100
+ - Slack: `http://localhost:3000/api/auth/slack`
101
+ - Calendar: `http://localhost:3000/api/auth/calendar`
102
+ - GitHub: `http://localhost:3000/api/auth/github`
103
+ 3. Check connection status at `http://localhost:3000/setup`
104
+
105
+ ## Troubleshooting
106
+
107
+ ### "Invalid redirect URI" error
108
+ - Make sure the callback URL in your OAuth app matches exactly
109
+ - Include the full path: `/api/auth/{service}/callback`
110
+
111
+ ### "Access denied" error
112
+ - Check that all required scopes are added
113
+ - For Slack, ensure the app is installed to your workspace
114
+
115
+ ### Tokens expire
116
+ - Tokens are stored in memory by default (reset on server restart)
117
+ - For production, implement persistent storage in `lib/token-store.ts`
118
+
119
+ ## Production Deployment
120
+
121
+ Before deploying:
122
+
123
+ 1. Update redirect URIs to your production domain
124
+ 2. Implement persistent token storage (database/KV)
125
+ 3. Add proper error handling and logging
126
+ 4. Consider rate limiting for API calls
127
+
128
+ ## Need Help?
129
+
130
+ - Check the interactive setup guide at `/setup`
131
+ - Review the API documentation for each service
132
+ - Ensure environment variables are properly set
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Integration Status API
3
+ *
4
+ * Returns the connection status of all configured integrations.
5
+ * Used by the setup guide to show which services are connected.
6
+ */
7
+
8
+ import { tokenStore } from "../../../../lib/token-store.ts";
9
+
10
+ // Define available integrations - will be populated based on project config
11
+ const INTEGRATIONS = [
12
+ { id: "gmail", name: "Gmail", icon: "mail" },
13
+ { id: "slack", name: "Slack", icon: "slack" },
14
+ { id: "calendar", name: "Calendar", icon: "calendar" },
15
+ { id: "github", name: "GitHub", icon: "github" },
16
+ { id: "jira", name: "Jira", icon: "jira" },
17
+ { id: "notion", name: "Notion", icon: "notion" },
18
+ ];
19
+
20
+ export async function GET(_req: Request) {
21
+ // Get actual user ID from session in production
22
+ const userId = "current-user";
23
+
24
+ const statuses = await Promise.all(
25
+ INTEGRATIONS.map(async (integration) => {
26
+ const token = await tokenStore.getToken(userId, integration.id);
27
+ return {
28
+ id: integration.id,
29
+ name: integration.name,
30
+ icon: integration.icon,
31
+ connected: !!token,
32
+ connectUrl: `/api/auth/${integration.id}`,
33
+ };
34
+ }),
35
+ );
36
+
37
+ return Response.json({ integrations: statuses });
38
+ }
@@ -0,0 +1,461 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ interface Integration {
6
+ id: string;
7
+ name: string;
8
+ icon: string;
9
+ connected: boolean;
10
+ connectUrl: string;
11
+ }
12
+
13
+ interface SetupStep {
14
+ id: string;
15
+ title: string;
16
+ description: string;
17
+ completed: boolean;
18
+ action?: () => void;
19
+ link?: string;
20
+ }
21
+
22
+ const OAUTH_SETUP_GUIDES: Record<string, { title: string; steps: string[]; link: string }> = {
23
+ gmail: {
24
+ title: "Google OAuth Setup (Gmail & Calendar)",
25
+ steps: [
26
+ "Go to Google Cloud Console",
27
+ "Create a new project or select existing one",
28
+ "Enable Gmail API and Calendar API",
29
+ "Create OAuth 2.0 credentials (Web application)",
30
+ "Add redirect URI: http://localhost:3000/api/auth/gmail/callback",
31
+ "Copy Client ID and Secret to your .env file",
32
+ ],
33
+ link: "https://console.cloud.google.com/apis/credentials",
34
+ },
35
+ calendar: {
36
+ title: "Google Calendar Setup",
37
+ steps: [
38
+ "Uses same credentials as Gmail",
39
+ "Make sure Calendar API is enabled",
40
+ "Add redirect URI: http://localhost:3000/api/auth/calendar/callback",
41
+ ],
42
+ link: "https://console.cloud.google.com/apis/credentials",
43
+ },
44
+ slack: {
45
+ title: "Slack App Setup",
46
+ steps: [
47
+ "Go to Slack API Apps page",
48
+ "Create New App > From scratch",
49
+ "Add OAuth Scopes: channels:read, chat:write, users:read",
50
+ "Install to Workspace",
51
+ "Copy Client ID and Secret to your .env file",
52
+ "Add redirect URL: http://localhost:3000/api/auth/slack/callback",
53
+ ],
54
+ link: "https://api.slack.com/apps",
55
+ },
56
+ github: {
57
+ title: "GitHub OAuth App Setup",
58
+ steps: [
59
+ "Go to GitHub Developer Settings",
60
+ "Click 'New OAuth App'",
61
+ "Set Homepage URL to http://localhost:3000",
62
+ "Set callback URL to http://localhost:3000/api/auth/github/callback",
63
+ "Copy Client ID and Secret to your .env file",
64
+ ],
65
+ link: "https://github.com/settings/developers",
66
+ },
67
+ jira: {
68
+ title: "Atlassian (Jira) Setup",
69
+ steps: [
70
+ "Go to Atlassian Developer Console",
71
+ "Create OAuth 2.0 integration",
72
+ "Add Jira API scopes",
73
+ "Set callback URL: http://localhost:3000/api/auth/jira/callback",
74
+ "Copy Client ID and Secret to your .env file",
75
+ ],
76
+ link: "https://developer.atlassian.com/console/myapps/",
77
+ },
78
+ notion: {
79
+ title: "Notion Integration Setup",
80
+ steps: [
81
+ "Go to Notion Integrations page",
82
+ "Create new integration",
83
+ "Copy the Internal Integration Token",
84
+ "Add token to your .env file",
85
+ "Share desired pages with your integration",
86
+ ],
87
+ link: "https://www.notion.so/my-integrations",
88
+ },
89
+ };
90
+
91
+ export default function SetupPage() {
92
+ const [integrations, setIntegrations] = useState<Integration[]>([]);
93
+ const [loading, setLoading] = useState(true);
94
+ const [expandedGuide, setExpandedGuide] = useState<string | null>(null);
95
+ const [envChecked, setEnvChecked] = useState(false);
96
+
97
+ useEffect(() => {
98
+ fetchStatus();
99
+ }, []);
100
+
101
+ async function fetchStatus() {
102
+ try {
103
+ const res = await fetch("/api/integrations/status");
104
+ const data = await res.json();
105
+ setIntegrations(data.integrations);
106
+ } catch (error) {
107
+ console.error("Failed to fetch integration status:", error);
108
+ } finally {
109
+ setLoading(false);
110
+ }
111
+ }
112
+
113
+ const connectedCount = integrations.filter((i) => i.connected).length;
114
+ const totalCount = integrations.length;
115
+ const progress = totalCount > 0 ? (connectedCount / totalCount) * 100 : 0;
116
+
117
+ const setupSteps: SetupStep[] = [
118
+ {
119
+ id: "env",
120
+ title: "Configure Environment Variables",
121
+ description: "Add your OAuth credentials to the .env file",
122
+ completed: envChecked,
123
+ action: () => setEnvChecked(true),
124
+ },
125
+ {
126
+ id: "oauth",
127
+ title: "Create OAuth Apps",
128
+ description: "Set up OAuth applications for each service",
129
+ completed: false,
130
+ },
131
+ {
132
+ id: "connect",
133
+ title: "Connect Services",
134
+ description: "Authorize your app to access each service",
135
+ completed: connectedCount === totalCount && totalCount > 0,
136
+ },
137
+ ];
138
+
139
+ return (
140
+ <div className="min-h-screen bg-neutral-50 dark:bg-neutral-900">
141
+ <div className="max-w-4xl mx-auto px-4 py-12">
142
+ {/* Header */}
143
+ <div className="text-center mb-12">
144
+ <h1 className="text-4xl font-bold text-neutral-900 dark:text-white mb-4">
145
+ Setup Your AI Agent
146
+ </h1>
147
+ <p className="text-lg text-neutral-600 dark:text-neutral-400">
148
+ Connect your services to enable AI-powered automation
149
+ </p>
150
+ </div>
151
+
152
+ {/* Progress Bar */}
153
+ <div className="bg-white dark:bg-neutral-800 rounded-2xl p-6 shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8">
154
+ <div className="flex items-center justify-between mb-2">
155
+ <span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
156
+ Setup Progress
157
+ </span>
158
+ <span className="text-sm font-medium text-neutral-900 dark:text-white">
159
+ {connectedCount} / {totalCount} services connected
160
+ </span>
161
+ </div>
162
+ <div className="w-full bg-neutral-200 dark:bg-neutral-700 rounded-full h-3">
163
+ <div
164
+ className="bg-gradient-to-r from-green-500 to-emerald-500 h-3 rounded-full transition-all duration-500"
165
+ style={{ width: `${progress}%` }}
166
+ />
167
+ </div>
168
+ </div>
169
+
170
+ {/* Setup Steps */}
171
+ <div className="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8 overflow-hidden">
172
+ <div className="p-6 border-b border-neutral-200 dark:border-neutral-700">
173
+ <h2 className="text-xl font-semibold text-neutral-900 dark:text-white">
174
+ Quick Start Guide
175
+ </h2>
176
+ </div>
177
+ <div className="divide-y divide-neutral-200 dark:divide-neutral-700">
178
+ {setupSteps.map((step, index) => (
179
+ <div
180
+ key={step.id}
181
+ className="p-6 flex items-start gap-4"
182
+ >
183
+ <div
184
+ className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
185
+ step.completed
186
+ ? "bg-green-500 text-white"
187
+ : "bg-neutral-200 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400"
188
+ }`}
189
+ >
190
+ {step.completed
191
+ ? (
192
+ <svg
193
+ className="w-5 h-5"
194
+ fill="none"
195
+ stroke="currentColor"
196
+ viewBox="0 0 24 24"
197
+ >
198
+ <path
199
+ strokeLinecap="round"
200
+ strokeLinejoin="round"
201
+ strokeWidth={2}
202
+ d="M5 13l4 4L19 7"
203
+ />
204
+ </svg>
205
+ )
206
+ : <span className="font-semibold">{index + 1}</span>}
207
+ </div>
208
+ <div className="flex-1">
209
+ <h3 className="font-semibold text-neutral-900 dark:text-white">
210
+ {step.title}
211
+ </h3>
212
+ <p className="text-sm text-neutral-600 dark:text-neutral-400 mt-1">
213
+ {step.description}
214
+ </p>
215
+ </div>
216
+ </div>
217
+ ))}
218
+ </div>
219
+ </div>
220
+
221
+ {/* Service Connections */}
222
+ <div className="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 overflow-hidden">
223
+ <div className="p-6 border-b border-neutral-200 dark:border-neutral-700">
224
+ <h2 className="text-xl font-semibold text-neutral-900 dark:text-white">
225
+ Service Connections
226
+ </h2>
227
+ <p className="text-sm text-neutral-600 dark:text-neutral-400 mt-1">
228
+ Click on a service to see setup instructions or connect
229
+ </p>
230
+ </div>
231
+
232
+ {loading
233
+ ? <div className="p-12 text-center text-neutral-500">Loading...</div>
234
+ : (
235
+ <div className="divide-y divide-neutral-200 dark:divide-neutral-700">
236
+ {integrations.map((integration) => (
237
+ <div key={integration.id}>
238
+ <div className="p-6 flex items-center justify-between">
239
+ <div className="flex items-center gap-4">
240
+ <div className="w-12 h-12 bg-neutral-100 dark:bg-neutral-700 rounded-xl flex items-center justify-center">
241
+ <ServiceIcon name={integration.icon} />
242
+ </div>
243
+ <div>
244
+ <h3 className="font-semibold text-neutral-900 dark:text-white">
245
+ {integration.name}
246
+ </h3>
247
+ <p
248
+ className={`text-sm ${
249
+ integration.connected
250
+ ? "text-green-600 dark:text-green-400"
251
+ : "text-neutral-500"
252
+ }`}
253
+ >
254
+ {integration.connected ? "Connected" : "Not connected"}
255
+ </p>
256
+ </div>
257
+ </div>
258
+ <div className="flex items-center gap-3">
259
+ <button
260
+ type="button"
261
+ onClick={() =>
262
+ setExpandedGuide(
263
+ expandedGuide === integration.id ? null : integration.id,
264
+ )}
265
+ className="px-4 py-2 text-sm font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white"
266
+ >
267
+ {expandedGuide === integration.id ? "Hide Guide" : "Setup Guide"}
268
+ </button>
269
+ {integration.connected
270
+ ? (
271
+ <span className="inline-flex items-center gap-1.5 px-4 py-2 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 rounded-xl text-sm font-medium">
272
+ <span className="w-2 h-2 bg-green-500 rounded-full" />
273
+ Connected
274
+ </span>
275
+ )
276
+ : (
277
+ <a
278
+ href={integration.connectUrl}
279
+ className="px-4 py-2 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-xl text-sm font-medium hover:opacity-90 transition-opacity"
280
+ >
281
+ Connect
282
+ </a>
283
+ )}
284
+ </div>
285
+ </div>
286
+
287
+ {/* Expanded Setup Guide */}
288
+ {expandedGuide === integration.id && (() => {
289
+ const guide = OAUTH_SETUP_GUIDES[integration.id];
290
+ if (!guide) return null;
291
+ return (
292
+ <div className="px-6 pb-6">
293
+ <div className="bg-neutral-50 dark:bg-neutral-900 rounded-xl p-6 border border-neutral-200 dark:border-neutral-700">
294
+ <h4 className="font-semibold text-neutral-900 dark:text-white mb-4">
295
+ {guide.title}
296
+ </h4>
297
+ <ol className="space-y-3">
298
+ {guide.steps.map((step, i) => (
299
+ <li key={i} className="flex items-start gap-3">
300
+ <span className="w-6 h-6 bg-neutral-200 dark:bg-neutral-700 rounded-full flex items-center justify-center text-sm font-medium text-neutral-600 dark:text-neutral-400 flex-shrink-0">
301
+ {i + 1}
302
+ </span>
303
+ <span className="text-neutral-700 dark:text-neutral-300">
304
+ {step}
305
+ </span>
306
+ </li>
307
+ ))}
308
+ </ol>
309
+ <a
310
+ href={guide.link}
311
+ target="_blank"
312
+ rel="noopener noreferrer"
313
+ className="mt-4 inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 text-sm font-medium hover:underline"
314
+ >
315
+ Open Developer Console
316
+ <svg
317
+ className="w-4 h-4"
318
+ fill="none"
319
+ stroke="currentColor"
320
+ viewBox="0 0 24 24"
321
+ >
322
+ <path
323
+ strokeLinecap="round"
324
+ strokeLinejoin="round"
325
+ strokeWidth={2}
326
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
327
+ />
328
+ </svg>
329
+ </a>
330
+ </div>
331
+ </div>
332
+ );
333
+ })()}
334
+ </div>
335
+ ))}
336
+ </div>
337
+ )}
338
+ </div>
339
+
340
+ {/* All Connected Message */}
341
+ {connectedCount === totalCount && totalCount > 0 && (
342
+ <div className="mt-8 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-2xl p-6 border border-green-200 dark:border-green-800 text-center">
343
+ <div className="text-4xl mb-4">🎉</div>
344
+ <h3 className="text-xl font-semibold text-green-800 dark:text-green-200 mb-2">
345
+ All Services Connected!
346
+ </h3>
347
+ <p className="text-green-700 dark:text-green-300 mb-4">
348
+ Your AI agent is ready to use. Start chatting to automate your workflows.
349
+ </p>
350
+ <a
351
+ href="/"
352
+ className="inline-flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-xl font-medium hover:bg-green-700 transition-colors"
353
+ >
354
+ Start Using Your Agent
355
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
356
+ <path
357
+ strokeLinecap="round"
358
+ strokeLinejoin="round"
359
+ strokeWidth={2}
360
+ d="M13 7l5 5m0 0l-5 5m5-5H6"
361
+ />
362
+ </svg>
363
+ </a>
364
+ </div>
365
+ )}
366
+ </div>
367
+ </div>
368
+ );
369
+ }
370
+
371
+ function ServiceIcon({ name }: { name: string }) {
372
+ const iconMap: Record<string, JSX.Element> = {
373
+ mail: (
374
+ <svg className="w-6 h-6 text-red-500" fill="currentColor" viewBox="0 0 24 24">
375
+ <path
376
+ d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
377
+ stroke="currentColor"
378
+ strokeWidth="2"
379
+ fill="none"
380
+ />
381
+ </svg>
382
+ ),
383
+ slack: (
384
+ <svg className="w-6 h-6" viewBox="0 0 24 24" fill="none">
385
+ <path
386
+ d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
387
+ fill="#E01E5A"
388
+ />
389
+ <path
390
+ d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
391
+ fill="#36C5F0"
392
+ />
393
+ <path
394
+ d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312z"
395
+ fill="#2EB67D"
396
+ />
397
+ <path
398
+ d="M15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"
399
+ fill="#ECB22E"
400
+ />
401
+ </svg>
402
+ ),
403
+ calendar: (
404
+ <svg className="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
405
+ <path
406
+ strokeLinecap="round"
407
+ strokeLinejoin="round"
408
+ strokeWidth={2}
409
+ d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
410
+ />
411
+ </svg>
412
+ ),
413
+ github: (
414
+ <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
415
+ <path
416
+ fillRule="evenodd"
417
+ d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
418
+ clipRule="evenodd"
419
+ />
420
+ </svg>
421
+ ),
422
+ jira: (
423
+ <svg className="w-6 h-6" viewBox="0 0 24 24">
424
+ <defs>
425
+ <linearGradient id="jira-gradient" x1="98.031%" x2="58.888%" y1=".161%" y2="40.766%">
426
+ <stop offset="0%" stopColor="#0052CC" />
427
+ <stop offset="100%" stopColor="#2684FF" />
428
+ </linearGradient>
429
+ </defs>
430
+ <path
431
+ fill="url(#jira-gradient)"
432
+ d="M11.571 11.513H0a5.218 5.218 0 005.232 5.215h2.13v2.057A5.215 5.215 0 0012.575 24V12.518a1.005 1.005 0 00-1.005-1.005z"
433
+ />
434
+ <path
435
+ fill="#2684FF"
436
+ d="M17.151 5.97H5.58a5.215 5.215 0 005.215 5.214h2.129v2.058a5.218 5.218 0 005.232 5.215V6.975a1.005 1.005 0 00-1.005-1.005z"
437
+ />
438
+ <path
439
+ fill="#2684FF"
440
+ d="M22.723.426H11.152a5.215 5.215 0 005.215 5.215h2.129v2.057a5.218 5.218 0 005.232 5.215V1.431a1.005 1.005 0 00-1.005-1.005z"
441
+ />
442
+ </svg>
443
+ ),
444
+ notion: (
445
+ <svg className="w-6 h-6" viewBox="0 0 24 24" fill="currentColor">
446
+ <path d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.98-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466l1.823 1.447zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.84-.046.933-.56.933-1.167V6.354c0-.606-.233-.933-.746-.886l-15.177.887c-.56.046-.747.326-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.746 0-.933-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.14c-.093-.514.28-.886.747-.933l3.222-.186zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.933.653.933 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.448-1.632z" />
447
+ </svg>
448
+ ),
449
+ };
450
+
451
+ return iconMap[name] || (
452
+ <svg className="w-6 h-6 text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
453
+ <path
454
+ strokeLinecap="round"
455
+ strokeLinejoin="round"
456
+ strokeWidth={2}
457
+ d="M13 10V3L4 14h7v7l9-11h-7z"
458
+ />
459
+ </svg>
460
+ );
461
+ }