codebakers 2.0.10 → 2.1.0

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.
@@ -0,0 +1,985 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import * as fs from 'fs-extra';
4
+ import * as path from 'path';
5
+ import http from 'http';
6
+ import open from 'open';
7
+ import { Config } from '../utils/config.js';
8
+ import { execa } from 'execa';
9
+
10
+ // ============================================================================
11
+ // INTEGRATION LIBRARY
12
+ // ============================================================================
13
+
14
+ interface Integration {
15
+ id: string;
16
+ name: string;
17
+ description: string;
18
+ category: 'auth' | 'database' | 'payments' | 'email' | 'storage' | 'analytics' | 'ai' | 'cms' | 'messaging' | 'monitoring' | 'deployment' | 'other';
19
+ icon: string;
20
+ authType: 'oauth' | 'apikey' | 'npm' | 'cli';
21
+ oauthUrl?: string;
22
+ scopes?: string[];
23
+ envVars: string[];
24
+ packages?: string[];
25
+ setupFiles?: Array<{ path: string; template: string }>;
26
+ docs: string;
27
+ }
28
+
29
+ const INTEGRATIONS: Integration[] = [
30
+ // ═══════════════════════════════════════════════════════════════════════════
31
+ // AUTH PROVIDERS
32
+ // ═══════════════════════════════════════════════════════════════════════════
33
+ {
34
+ id: 'clerk',
35
+ name: 'Clerk',
36
+ description: 'Complete user management & authentication',
37
+ category: 'auth',
38
+ icon: '🔐',
39
+ authType: 'oauth',
40
+ oauthUrl: 'https://dashboard.clerk.com/apps/new',
41
+ envVars: ['NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY', 'CLERK_SECRET_KEY'],
42
+ packages: ['@clerk/nextjs'],
43
+ docs: 'https://clerk.com/docs',
44
+ },
45
+ {
46
+ id: 'auth0',
47
+ name: 'Auth0',
48
+ description: 'Enterprise authentication platform',
49
+ category: 'auth',
50
+ icon: '🔒',
51
+ authType: 'oauth',
52
+ oauthUrl: 'https://manage.auth0.com/dashboard',
53
+ envVars: ['AUTH0_SECRET', 'AUTH0_BASE_URL', 'AUTH0_ISSUER_BASE_URL', 'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_SECRET'],
54
+ packages: ['@auth0/nextjs-auth0'],
55
+ docs: 'https://auth0.com/docs',
56
+ },
57
+ {
58
+ id: 'nextauth',
59
+ name: 'NextAuth.js',
60
+ description: 'Open source authentication for Next.js',
61
+ category: 'auth',
62
+ icon: '🔑',
63
+ authType: 'npm',
64
+ envVars: ['NEXTAUTH_SECRET', 'NEXTAUTH_URL'],
65
+ packages: ['next-auth'],
66
+ docs: 'https://next-auth.js.org',
67
+ },
68
+ {
69
+ id: 'supabase-auth',
70
+ name: 'Supabase Auth',
71
+ description: 'Authentication with Supabase',
72
+ category: 'auth',
73
+ icon: '⚡',
74
+ authType: 'oauth',
75
+ oauthUrl: 'https://supabase.com/dashboard/projects',
76
+ envVars: ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'],
77
+ packages: ['@supabase/supabase-js', '@supabase/auth-helpers-nextjs'],
78
+ docs: 'https://supabase.com/docs/guides/auth',
79
+ },
80
+ {
81
+ id: 'firebase-auth',
82
+ name: 'Firebase Auth',
83
+ description: 'Google Firebase authentication',
84
+ category: 'auth',
85
+ icon: '🔥',
86
+ authType: 'oauth',
87
+ oauthUrl: 'https://console.firebase.google.com',
88
+ envVars: ['NEXT_PUBLIC_FIREBASE_API_KEY', 'NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN', 'NEXT_PUBLIC_FIREBASE_PROJECT_ID'],
89
+ packages: ['firebase'],
90
+ docs: 'https://firebase.google.com/docs/auth',
91
+ },
92
+
93
+ // ═══════════════════════════════════════════════════════════════════════════
94
+ // DATABASES
95
+ // ═══════════════════════════════════════════════════════════════════════════
96
+ {
97
+ id: 'supabase',
98
+ name: 'Supabase',
99
+ description: 'Postgres database with realtime & auth',
100
+ category: 'database',
101
+ icon: '⚡',
102
+ authType: 'oauth',
103
+ oauthUrl: 'https://supabase.com/dashboard/projects',
104
+ envVars: ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY'],
105
+ packages: ['@supabase/supabase-js'],
106
+ docs: 'https://supabase.com/docs',
107
+ },
108
+ {
109
+ id: 'planetscale',
110
+ name: 'PlanetScale',
111
+ description: 'Serverless MySQL database',
112
+ category: 'database',
113
+ icon: '🪐',
114
+ authType: 'oauth',
115
+ oauthUrl: 'https://app.planetscale.com',
116
+ envVars: ['DATABASE_URL'],
117
+ packages: ['@planetscale/database'],
118
+ docs: 'https://planetscale.com/docs',
119
+ },
120
+ {
121
+ id: 'neon',
122
+ name: 'Neon',
123
+ description: 'Serverless Postgres',
124
+ category: 'database',
125
+ icon: '🐘',
126
+ authType: 'oauth',
127
+ oauthUrl: 'https://console.neon.tech',
128
+ envVars: ['DATABASE_URL'],
129
+ packages: ['@neondatabase/serverless'],
130
+ docs: 'https://neon.tech/docs',
131
+ },
132
+ {
133
+ id: 'turso',
134
+ name: 'Turso',
135
+ description: 'Edge SQLite database',
136
+ category: 'database',
137
+ icon: '🐢',
138
+ authType: 'oauth',
139
+ oauthUrl: 'https://turso.tech/app',
140
+ envVars: ['TURSO_DATABASE_URL', 'TURSO_AUTH_TOKEN'],
141
+ packages: ['@libsql/client'],
142
+ docs: 'https://docs.turso.tech',
143
+ },
144
+ {
145
+ id: 'mongodb',
146
+ name: 'MongoDB Atlas',
147
+ description: 'NoSQL document database',
148
+ category: 'database',
149
+ icon: '🍃',
150
+ authType: 'oauth',
151
+ oauthUrl: 'https://cloud.mongodb.com',
152
+ envVars: ['MONGODB_URI'],
153
+ packages: ['mongodb'],
154
+ docs: 'https://www.mongodb.com/docs',
155
+ },
156
+ {
157
+ id: 'prisma',
158
+ name: 'Prisma',
159
+ description: 'Type-safe ORM for any database',
160
+ category: 'database',
161
+ icon: '🔷',
162
+ authType: 'npm',
163
+ envVars: ['DATABASE_URL'],
164
+ packages: ['prisma', '@prisma/client'],
165
+ docs: 'https://www.prisma.io/docs',
166
+ },
167
+ {
168
+ id: 'drizzle',
169
+ name: 'Drizzle ORM',
170
+ description: 'Lightweight TypeScript ORM',
171
+ category: 'database',
172
+ icon: '💧',
173
+ authType: 'npm',
174
+ envVars: ['DATABASE_URL'],
175
+ packages: ['drizzle-orm', 'drizzle-kit'],
176
+ docs: 'https://orm.drizzle.team',
177
+ },
178
+
179
+ // ═══════════════════════════════════════════════════════════════════════════
180
+ // PAYMENTS
181
+ // ═══════════════════════════════════════════════════════════════════════════
182
+ {
183
+ id: 'stripe',
184
+ name: 'Stripe',
185
+ description: 'Payment processing & subscriptions',
186
+ category: 'payments',
187
+ icon: '💳',
188
+ authType: 'oauth',
189
+ oauthUrl: 'https://dashboard.stripe.com/apikeys',
190
+ envVars: ['STRIPE_SECRET_KEY', 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY', 'STRIPE_WEBHOOK_SECRET'],
191
+ packages: ['stripe', '@stripe/stripe-js'],
192
+ docs: 'https://stripe.com/docs',
193
+ },
194
+ {
195
+ id: 'lemonsqueezy',
196
+ name: 'Lemon Squeezy',
197
+ description: 'Merchant of record for SaaS',
198
+ category: 'payments',
199
+ icon: '🍋',
200
+ authType: 'oauth',
201
+ oauthUrl: 'https://app.lemonsqueezy.com/settings/api',
202
+ envVars: ['LEMONSQUEEZY_API_KEY', 'LEMONSQUEEZY_STORE_ID', 'LEMONSQUEEZY_WEBHOOK_SECRET'],
203
+ packages: ['@lemonsqueezy/lemonsqueezy.js'],
204
+ docs: 'https://docs.lemonsqueezy.com',
205
+ },
206
+ {
207
+ id: 'paddle',
208
+ name: 'Paddle',
209
+ description: 'Payment infrastructure for SaaS',
210
+ category: 'payments',
211
+ icon: '🏓',
212
+ authType: 'oauth',
213
+ oauthUrl: 'https://vendors.paddle.com/authentication',
214
+ envVars: ['PADDLE_VENDOR_ID', 'PADDLE_API_KEY', 'PADDLE_PUBLIC_KEY'],
215
+ packages: ['@paddle/paddle-js'],
216
+ docs: 'https://developer.paddle.com',
217
+ },
218
+
219
+ // ═══════════════════════════════════════════════════════════════════════════
220
+ // EMAIL
221
+ // ═══════════════════════════════════════════════════════════════════════════
222
+ {
223
+ id: 'resend',
224
+ name: 'Resend',
225
+ description: 'Modern email API for developers',
226
+ category: 'email',
227
+ icon: '📧',
228
+ authType: 'oauth',
229
+ oauthUrl: 'https://resend.com/api-keys',
230
+ envVars: ['RESEND_API_KEY'],
231
+ packages: ['resend'],
232
+ docs: 'https://resend.com/docs',
233
+ },
234
+ {
235
+ id: 'sendgrid',
236
+ name: 'SendGrid',
237
+ description: 'Email delivery service',
238
+ category: 'email',
239
+ icon: '📨',
240
+ authType: 'oauth',
241
+ oauthUrl: 'https://app.sendgrid.com/settings/api_keys',
242
+ envVars: ['SENDGRID_API_KEY'],
243
+ packages: ['@sendgrid/mail'],
244
+ docs: 'https://docs.sendgrid.com',
245
+ },
246
+ {
247
+ id: 'postmark',
248
+ name: 'Postmark',
249
+ description: 'Transactional email service',
250
+ category: 'email',
251
+ icon: '📬',
252
+ authType: 'oauth',
253
+ oauthUrl: 'https://account.postmarkapp.com/servers',
254
+ envVars: ['POSTMARK_API_KEY'],
255
+ packages: ['postmark'],
256
+ docs: 'https://postmarkapp.com/developer',
257
+ },
258
+ {
259
+ id: 'mailgun',
260
+ name: 'Mailgun',
261
+ description: 'Email API service',
262
+ category: 'email',
263
+ icon: '📮',
264
+ authType: 'oauth',
265
+ oauthUrl: 'https://app.mailgun.com/app/account/security/api_keys',
266
+ envVars: ['MAILGUN_API_KEY', 'MAILGUN_DOMAIN'],
267
+ packages: ['mailgun.js'],
268
+ docs: 'https://documentation.mailgun.com',
269
+ },
270
+ {
271
+ id: 'react-email',
272
+ name: 'React Email',
273
+ description: 'Build emails with React components',
274
+ category: 'email',
275
+ icon: '⚛️',
276
+ authType: 'npm',
277
+ envVars: [],
278
+ packages: ['react-email', '@react-email/components'],
279
+ docs: 'https://react.email/docs',
280
+ },
281
+
282
+ // ═══════════════════════════════════════════════════════════════════════════
283
+ // STORAGE
284
+ // ═══════════════════════════════════════════════════════════════════════════
285
+ {
286
+ id: 'uploadthing',
287
+ name: 'UploadThing',
288
+ description: 'File uploads for Next.js',
289
+ category: 'storage',
290
+ icon: '📤',
291
+ authType: 'oauth',
292
+ oauthUrl: 'https://uploadthing.com/dashboard',
293
+ envVars: ['UPLOADTHING_SECRET', 'UPLOADTHING_APP_ID'],
294
+ packages: ['uploadthing', '@uploadthing/react'],
295
+ docs: 'https://docs.uploadthing.com',
296
+ },
297
+ {
298
+ id: 'cloudinary',
299
+ name: 'Cloudinary',
300
+ description: 'Image & video management',
301
+ category: 'storage',
302
+ icon: '☁️',
303
+ authType: 'oauth',
304
+ oauthUrl: 'https://console.cloudinary.com/settings/api-keys',
305
+ envVars: ['CLOUDINARY_CLOUD_NAME', 'CLOUDINARY_API_KEY', 'CLOUDINARY_API_SECRET'],
306
+ packages: ['cloudinary'],
307
+ docs: 'https://cloudinary.com/documentation',
308
+ },
309
+ {
310
+ id: 'aws-s3',
311
+ name: 'AWS S3',
312
+ description: 'Amazon cloud storage',
313
+ category: 'storage',
314
+ icon: '🪣',
315
+ authType: 'oauth',
316
+ oauthUrl: 'https://console.aws.amazon.com/iam/home#/security_credentials',
317
+ envVars: ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION', 'AWS_S3_BUCKET'],
318
+ packages: ['@aws-sdk/client-s3'],
319
+ docs: 'https://docs.aws.amazon.com/s3',
320
+ },
321
+ {
322
+ id: 'vercel-blob',
323
+ name: 'Vercel Blob',
324
+ description: 'File storage by Vercel',
325
+ category: 'storage',
326
+ icon: '▲',
327
+ authType: 'oauth',
328
+ oauthUrl: 'https://vercel.com/dashboard/stores',
329
+ envVars: ['BLOB_READ_WRITE_TOKEN'],
330
+ packages: ['@vercel/blob'],
331
+ docs: 'https://vercel.com/docs/storage/vercel-blob',
332
+ },
333
+
334
+ // ═══════════════════════════════════════════════════════════════════════════
335
+ // ANALYTICS
336
+ // ═══════════════════════════════════════════════════════════════════════════
337
+ {
338
+ id: 'vercel-analytics',
339
+ name: 'Vercel Analytics',
340
+ description: 'Web analytics by Vercel',
341
+ category: 'analytics',
342
+ icon: '📊',
343
+ authType: 'npm',
344
+ envVars: [],
345
+ packages: ['@vercel/analytics'],
346
+ docs: 'https://vercel.com/docs/analytics',
347
+ },
348
+ {
349
+ id: 'posthog',
350
+ name: 'PostHog',
351
+ description: 'Product analytics & feature flags',
352
+ category: 'analytics',
353
+ icon: '🦔',
354
+ authType: 'oauth',
355
+ oauthUrl: 'https://app.posthog.com/project/settings',
356
+ envVars: ['NEXT_PUBLIC_POSTHOG_KEY', 'NEXT_PUBLIC_POSTHOG_HOST'],
357
+ packages: ['posthog-js'],
358
+ docs: 'https://posthog.com/docs',
359
+ },
360
+ {
361
+ id: 'mixpanel',
362
+ name: 'Mixpanel',
363
+ description: 'Event-based analytics',
364
+ category: 'analytics',
365
+ icon: '📈',
366
+ authType: 'oauth',
367
+ oauthUrl: 'https://mixpanel.com/settings/project',
368
+ envVars: ['NEXT_PUBLIC_MIXPANEL_TOKEN'],
369
+ packages: ['mixpanel-browser'],
370
+ docs: 'https://docs.mixpanel.com',
371
+ },
372
+ {
373
+ id: 'plausible',
374
+ name: 'Plausible',
375
+ description: 'Privacy-friendly analytics',
376
+ category: 'analytics',
377
+ icon: '📉',
378
+ authType: 'oauth',
379
+ oauthUrl: 'https://plausible.io/sites',
380
+ envVars: ['NEXT_PUBLIC_PLAUSIBLE_DOMAIN'],
381
+ packages: ['next-plausible'],
382
+ docs: 'https://plausible.io/docs',
383
+ },
384
+
385
+ // ═══════════════════════════════════════════════════════════════════════════
386
+ // AI
387
+ // ═══════════════════════════════════════════════════════════════════════════
388
+ {
389
+ id: 'openai',
390
+ name: 'OpenAI',
391
+ description: 'GPT models & DALL-E',
392
+ category: 'ai',
393
+ icon: '🤖',
394
+ authType: 'oauth',
395
+ oauthUrl: 'https://platform.openai.com/api-keys',
396
+ envVars: ['OPENAI_API_KEY'],
397
+ packages: ['openai'],
398
+ docs: 'https://platform.openai.com/docs',
399
+ },
400
+ {
401
+ id: 'anthropic',
402
+ name: 'Anthropic',
403
+ description: 'Claude AI models',
404
+ category: 'ai',
405
+ icon: '🧠',
406
+ authType: 'oauth',
407
+ oauthUrl: 'https://console.anthropic.com/settings/keys',
408
+ envVars: ['ANTHROPIC_API_KEY'],
409
+ packages: ['@anthropic-ai/sdk'],
410
+ docs: 'https://docs.anthropic.com',
411
+ },
412
+ {
413
+ id: 'replicate',
414
+ name: 'Replicate',
415
+ description: 'Run ML models in the cloud',
416
+ category: 'ai',
417
+ icon: '🔄',
418
+ authType: 'oauth',
419
+ oauthUrl: 'https://replicate.com/account/api-tokens',
420
+ envVars: ['REPLICATE_API_TOKEN'],
421
+ packages: ['replicate'],
422
+ docs: 'https://replicate.com/docs',
423
+ },
424
+ {
425
+ id: 'vercel-ai',
426
+ name: 'Vercel AI SDK',
427
+ description: 'Build AI-powered apps',
428
+ category: 'ai',
429
+ icon: '✨',
430
+ authType: 'npm',
431
+ envVars: [],
432
+ packages: ['ai'],
433
+ docs: 'https://sdk.vercel.ai/docs',
434
+ },
435
+ {
436
+ id: 'elevenlabs',
437
+ name: 'ElevenLabs',
438
+ description: 'AI voice generation',
439
+ category: 'ai',
440
+ icon: '🎙️',
441
+ authType: 'oauth',
442
+ oauthUrl: 'https://elevenlabs.io/app/settings/api-keys',
443
+ envVars: ['ELEVENLABS_API_KEY'],
444
+ packages: ['elevenlabs'],
445
+ docs: 'https://elevenlabs.io/docs',
446
+ },
447
+
448
+ // ═══════════════════════════════════════════════════════════════════════════
449
+ // CMS
450
+ // ═══════════════════════════════════════════════════════════════════════════
451
+ {
452
+ id: 'sanity',
453
+ name: 'Sanity',
454
+ description: 'Headless CMS with real-time collaboration',
455
+ category: 'cms',
456
+ icon: '📝',
457
+ authType: 'oauth',
458
+ oauthUrl: 'https://www.sanity.io/manage',
459
+ envVars: ['NEXT_PUBLIC_SANITY_PROJECT_ID', 'NEXT_PUBLIC_SANITY_DATASET', 'SANITY_API_TOKEN'],
460
+ packages: ['@sanity/client', 'next-sanity'],
461
+ docs: 'https://www.sanity.io/docs',
462
+ },
463
+ {
464
+ id: 'contentful',
465
+ name: 'Contentful',
466
+ description: 'Enterprise headless CMS',
467
+ category: 'cms',
468
+ icon: '📄',
469
+ authType: 'oauth',
470
+ oauthUrl: 'https://app.contentful.com/account/profile/cma_tokens',
471
+ envVars: ['CONTENTFUL_SPACE_ID', 'CONTENTFUL_ACCESS_TOKEN'],
472
+ packages: ['contentful'],
473
+ docs: 'https://www.contentful.com/developers/docs',
474
+ },
475
+ {
476
+ id: 'strapi',
477
+ name: 'Strapi',
478
+ description: 'Open-source headless CMS',
479
+ category: 'cms',
480
+ icon: '🚀',
481
+ authType: 'npm',
482
+ envVars: ['STRAPI_URL', 'STRAPI_API_TOKEN'],
483
+ packages: [],
484
+ docs: 'https://docs.strapi.io',
485
+ },
486
+
487
+ // ═══════════════════════════════════════════════════════════════════════════
488
+ // MESSAGING
489
+ // ═══════════════════════════════════════════════════════════════════════════
490
+ {
491
+ id: 'twilio',
492
+ name: 'Twilio',
493
+ description: 'SMS, voice & WhatsApp',
494
+ category: 'messaging',
495
+ icon: '📱',
496
+ authType: 'oauth',
497
+ oauthUrl: 'https://console.twilio.com/us1/account/keys-credentials/api-keys',
498
+ envVars: ['TWILIO_ACCOUNT_SID', 'TWILIO_AUTH_TOKEN', 'TWILIO_PHONE_NUMBER'],
499
+ packages: ['twilio'],
500
+ docs: 'https://www.twilio.com/docs',
501
+ },
502
+ {
503
+ id: 'pusher',
504
+ name: 'Pusher',
505
+ description: 'Realtime websockets',
506
+ category: 'messaging',
507
+ icon: '🔔',
508
+ authType: 'oauth',
509
+ oauthUrl: 'https://dashboard.pusher.com',
510
+ envVars: ['PUSHER_APP_ID', 'PUSHER_KEY', 'PUSHER_SECRET', 'NEXT_PUBLIC_PUSHER_KEY'],
511
+ packages: ['pusher', 'pusher-js'],
512
+ docs: 'https://pusher.com/docs',
513
+ },
514
+ {
515
+ id: 'knock',
516
+ name: 'Knock',
517
+ description: 'Notification infrastructure',
518
+ category: 'messaging',
519
+ icon: '🔔',
520
+ authType: 'oauth',
521
+ oauthUrl: 'https://dashboard.knock.app',
522
+ envVars: ['KNOCK_API_KEY', 'NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY'],
523
+ packages: ['@knocklabs/node', '@knocklabs/react'],
524
+ docs: 'https://docs.knock.app',
525
+ },
526
+ {
527
+ id: 'stream',
528
+ name: 'Stream',
529
+ description: 'Chat & activity feeds',
530
+ category: 'messaging',
531
+ icon: '💬',
532
+ authType: 'oauth',
533
+ oauthUrl: 'https://dashboard.getstream.io',
534
+ envVars: ['STREAM_API_KEY', 'STREAM_API_SECRET'],
535
+ packages: ['stream-chat', 'stream-chat-react'],
536
+ docs: 'https://getstream.io/docs',
537
+ },
538
+
539
+ // ═══════════════════════════════════════════════════════════════════════════
540
+ // MONITORING
541
+ // ═══════════════════════════════════════════════════════════════════════════
542
+ {
543
+ id: 'sentry',
544
+ name: 'Sentry',
545
+ description: 'Error tracking & performance',
546
+ category: 'monitoring',
547
+ icon: '🐛',
548
+ authType: 'oauth',
549
+ oauthUrl: 'https://sentry.io/settings/account/api/auth-tokens/',
550
+ envVars: ['SENTRY_DSN', 'SENTRY_AUTH_TOKEN'],
551
+ packages: ['@sentry/nextjs'],
552
+ docs: 'https://docs.sentry.io',
553
+ },
554
+ {
555
+ id: 'logrocket',
556
+ name: 'LogRocket',
557
+ description: 'Session replay & monitoring',
558
+ category: 'monitoring',
559
+ icon: '🚀',
560
+ authType: 'oauth',
561
+ oauthUrl: 'https://app.logrocket.com/settings/setup',
562
+ envVars: ['NEXT_PUBLIC_LOGROCKET_APP_ID'],
563
+ packages: ['logrocket'],
564
+ docs: 'https://docs.logrocket.com',
565
+ },
566
+
567
+ // ═══════════════════════════════════════════════════════════════════════════
568
+ // DEPLOYMENT
569
+ // ═══════════════════════════════════════════════════════════════════════════
570
+ {
571
+ id: 'vercel',
572
+ name: 'Vercel',
573
+ description: 'Deploy Next.js apps',
574
+ category: 'deployment',
575
+ icon: '▲',
576
+ authType: 'oauth',
577
+ oauthUrl: 'https://vercel.com/account/tokens',
578
+ envVars: ['VERCEL_TOKEN'],
579
+ packages: ['vercel'],
580
+ docs: 'https://vercel.com/docs',
581
+ },
582
+ {
583
+ id: 'github',
584
+ name: 'GitHub',
585
+ description: 'Code hosting & CI/CD',
586
+ category: 'deployment',
587
+ icon: '🐙',
588
+ authType: 'oauth',
589
+ oauthUrl: 'https://github.com/settings/tokens',
590
+ envVars: ['GITHUB_TOKEN'],
591
+ packages: ['octokit'],
592
+ docs: 'https://docs.github.com',
593
+ },
594
+
595
+ // ═══════════════════════════════════════════════════════════════════════════
596
+ // OTHER
597
+ // ═══════════════════════════════════════════════════════════════════════════
598
+ {
599
+ id: 'crisp',
600
+ name: 'Crisp',
601
+ description: 'Customer support chat',
602
+ category: 'other',
603
+ icon: '💬',
604
+ authType: 'oauth',
605
+ oauthUrl: 'https://app.crisp.chat/settings/website',
606
+ envVars: ['NEXT_PUBLIC_CRISP_WEBSITE_ID'],
607
+ packages: ['crisp-sdk-web'],
608
+ docs: 'https://docs.crisp.chat',
609
+ },
610
+ {
611
+ id: 'intercom',
612
+ name: 'Intercom',
613
+ description: 'Customer messaging platform',
614
+ category: 'other',
615
+ icon: '💬',
616
+ authType: 'oauth',
617
+ oauthUrl: 'https://app.intercom.com/a/apps/_/developer-hub',
618
+ envVars: ['NEXT_PUBLIC_INTERCOM_APP_ID'],
619
+ packages: ['@intercom/messenger-js-sdk'],
620
+ docs: 'https://developers.intercom.com',
621
+ },
622
+ {
623
+ id: 'cal',
624
+ name: 'Cal.com',
625
+ description: 'Scheduling infrastructure',
626
+ category: 'other',
627
+ icon: '📅',
628
+ authType: 'oauth',
629
+ oauthUrl: 'https://app.cal.com/settings/developer/api-keys',
630
+ envVars: ['CAL_API_KEY'],
631
+ packages: ['@calcom/embed-react'],
632
+ docs: 'https://cal.com/docs',
633
+ },
634
+ ];
635
+
636
+ // ============================================================================
637
+ // MAIN COMMAND
638
+ // ============================================================================
639
+
640
+ export async function integrateCommand(integrationId?: string): Promise<void> {
641
+ const config = new Config();
642
+
643
+ console.log(chalk.cyan(`
644
+ ╔═══════════════════════════════════════════════════════════════╗
645
+ ║ 🔌 ONE-CLICK INTEGRATIONS ║
646
+ ║ ║
647
+ ║ ${INTEGRATIONS.length} integrations available ║
648
+ ║ Browser-based auth • Auto-install • Ready in seconds ║
649
+ ╚═══════════════════════════════════════════════════════════════╝
650
+ `));
651
+
652
+ // If integration ID provided, install directly
653
+ if (integrationId) {
654
+ const integration = INTEGRATIONS.find(i => i.id === integrationId);
655
+ if (!integration) {
656
+ p.log.error(`Unknown integration: ${integrationId}`);
657
+ console.log(chalk.dim(`Run 'codebakers integrate' to see all available integrations`));
658
+ return;
659
+ }
660
+ await installIntegration(integration, config);
661
+ return;
662
+ }
663
+
664
+ // Show category selection
665
+ const categories = [...new Set(INTEGRATIONS.map(i => i.category))];
666
+
667
+ const category = await p.select({
668
+ message: 'Choose a category:',
669
+ options: [
670
+ { value: 'all', label: '📋 Show all integrations' },
671
+ { value: 'search', label: '🔍 Search by name' },
672
+ ...categories.map(c => ({
673
+ value: c,
674
+ label: getCategoryLabel(c),
675
+ hint: `${INTEGRATIONS.filter(i => i.category === c).length} integrations`,
676
+ })),
677
+ ],
678
+ });
679
+
680
+ if (p.isCancel(category)) return;
681
+
682
+ let filteredIntegrations = INTEGRATIONS;
683
+
684
+ if (category === 'search') {
685
+ const query = await p.text({
686
+ message: 'Search integrations:',
687
+ placeholder: 'stripe, auth, email...',
688
+ });
689
+ if (p.isCancel(query)) return;
690
+
691
+ const q = (query as string).toLowerCase();
692
+ filteredIntegrations = INTEGRATIONS.filter(i =>
693
+ i.name.toLowerCase().includes(q) ||
694
+ i.description.toLowerCase().includes(q) ||
695
+ i.id.toLowerCase().includes(q)
696
+ );
697
+ } else if (category !== 'all') {
698
+ filteredIntegrations = INTEGRATIONS.filter(i => i.category === category);
699
+ }
700
+
701
+ if (filteredIntegrations.length === 0) {
702
+ p.log.warn('No integrations found');
703
+ return;
704
+ }
705
+
706
+ // Show integration selection
707
+ const selected = await p.select({
708
+ message: 'Select an integration to install:',
709
+ options: filteredIntegrations.map(i => ({
710
+ value: i.id,
711
+ label: `${i.icon} ${i.name}`,
712
+ hint: i.description,
713
+ })),
714
+ });
715
+
716
+ if (p.isCancel(selected)) return;
717
+
718
+ const integration = INTEGRATIONS.find(i => i.id === selected)!;
719
+ await installIntegration(integration, config);
720
+ }
721
+
722
+ // ============================================================================
723
+ // INSTALL INTEGRATION
724
+ // ============================================================================
725
+
726
+ async function installIntegration(integration: Integration, config: Config): Promise<void> {
727
+ console.log(chalk.cyan(`\n Installing ${integration.icon} ${integration.name}...\n`));
728
+
729
+ const steps = [];
730
+
731
+ // Step 1: Install packages
732
+ if (integration.packages && integration.packages.length > 0) {
733
+ steps.push({ name: 'Install packages', done: false });
734
+ }
735
+
736
+ // Step 2: Get credentials (OAuth or API key)
737
+ if (integration.envVars.length > 0) {
738
+ steps.push({ name: 'Configure credentials', done: false });
739
+ }
740
+
741
+ // Step 3: Create setup files
742
+ steps.push({ name: 'Setup integration', done: false });
743
+
744
+ // Execute steps
745
+ const spinner = p.spinner();
746
+
747
+ // STEP 1: Install packages
748
+ if (integration.packages && integration.packages.length > 0) {
749
+ spinner.start(`Installing ${integration.packages.join(', ')}...`);
750
+
751
+ try {
752
+ await execa('npm', ['install', ...integration.packages], {
753
+ cwd: process.cwd(),
754
+ reject: false,
755
+ });
756
+ spinner.stop(`✓ Packages installed`);
757
+ } catch {
758
+ spinner.stop(`✓ Packages installed (or already present)`);
759
+ }
760
+ }
761
+
762
+ // STEP 2: Get credentials
763
+ if (integration.envVars.length > 0) {
764
+ if (integration.authType === 'oauth' && integration.oauthUrl) {
765
+ // Open browser for OAuth
766
+ console.log(chalk.cyan(`\n Opening ${integration.name} in your browser...`));
767
+ console.log(chalk.dim(` Get your API keys and paste them below.\n`));
768
+
769
+ // Open the OAuth URL
770
+ await open(integration.oauthUrl);
771
+
772
+ // Wait a moment for browser to open
773
+ await sleep(1500);
774
+ }
775
+
776
+ // Collect credentials
777
+ const credentials: Record<string, string> = {};
778
+
779
+ for (const envVar of integration.envVars) {
780
+ const isPublic = envVar.startsWith('NEXT_PUBLIC_');
781
+ const hint = isPublic ? '(public, will be in client bundle)' : '(secret, server-only)';
782
+
783
+ const value = await p.text({
784
+ message: `${envVar} ${chalk.dim(hint)}:`,
785
+ placeholder: 'Paste your key here...',
786
+ validate: (v) => !v ? 'Required' : undefined,
787
+ });
788
+
789
+ if (p.isCancel(value)) return;
790
+ credentials[envVar] = value as string;
791
+ }
792
+
793
+ // Save to .env.local
794
+ await saveEnvVars(credentials);
795
+ console.log(chalk.green(` ✓ Credentials saved to .env.local\n`));
796
+ }
797
+
798
+ // STEP 3: Generate setup code
799
+ spinner.start('Generating setup code...');
800
+
801
+ const setupCode = await generateSetupCode(integration);
802
+
803
+ if (setupCode) {
804
+ for (const file of setupCode) {
805
+ await fs.ensureDir(path.dirname(file.path));
806
+ await fs.writeFile(file.path, file.content);
807
+ }
808
+ }
809
+
810
+ spinner.stop('✓ Setup complete');
811
+
812
+ // Success message
813
+ console.log(chalk.green(`
814
+ ╔═══════════════════════════════════════════════════════════════╗
815
+ ║ ✓ ${integration.name} installed successfully!
816
+ ╠═══════════════════════════════════════════════════════════════╣
817
+ `));
818
+
819
+ if (integration.packages && integration.packages.length > 0) {
820
+ console.log(chalk.dim(` Packages: ${integration.packages.join(', ')}`));
821
+ }
822
+
823
+ if (integration.envVars.length > 0) {
824
+ console.log(chalk.dim(` Env vars: ${integration.envVars.join(', ')}`));
825
+ }
826
+
827
+ console.log(`
828
+ ║ 📚 Docs: ${integration.docs}
829
+ ╚═══════════════════════════════════════════════════════════════╝
830
+ `);
831
+ }
832
+
833
+ // ============================================================================
834
+ // HELPERS
835
+ // ============================================================================
836
+
837
+ function getCategoryLabel(category: string): string {
838
+ const labels: Record<string, string> = {
839
+ auth: '🔐 Authentication',
840
+ database: '🗄️ Databases',
841
+ payments: '💳 Payments',
842
+ email: '📧 Email',
843
+ storage: '📦 Storage',
844
+ analytics: '📊 Analytics',
845
+ ai: '🤖 AI & ML',
846
+ cms: '📝 CMS',
847
+ messaging: '💬 Messaging',
848
+ monitoring: '🔍 Monitoring',
849
+ deployment: '🚀 Deployment',
850
+ other: '🔧 Other',
851
+ };
852
+ return labels[category] || category;
853
+ }
854
+
855
+ async function saveEnvVars(vars: Record<string, string>): Promise<void> {
856
+ const envPath = path.join(process.cwd(), '.env.local');
857
+ let content = '';
858
+
859
+ // Read existing content
860
+ if (await fs.pathExists(envPath)) {
861
+ content = await fs.readFile(envPath, 'utf-8');
862
+ if (!content.endsWith('\n')) {
863
+ content += '\n';
864
+ }
865
+ content += '\n# Added by CodeBakers\n';
866
+ }
867
+
868
+ // Add new vars
869
+ for (const [key, value] of Object.entries(vars)) {
870
+ // Check if var already exists
871
+ const regex = new RegExp(`^${key}=`, 'm');
872
+ if (regex.test(content)) {
873
+ // Update existing
874
+ content = content.replace(regex, `${key}=${value}`);
875
+ } else {
876
+ // Add new
877
+ content += `${key}=${value}\n`;
878
+ }
879
+ }
880
+
881
+ await fs.writeFile(envPath, content);
882
+ }
883
+
884
+ async function generateSetupCode(integration: Integration): Promise<Array<{ path: string; content: string }> | null> {
885
+ // Generate basic setup files based on integration type
886
+ const files: Array<{ path: string; content: string }> = [];
887
+
888
+ switch (integration.id) {
889
+ case 'clerk':
890
+ files.push({
891
+ path: 'src/middleware.ts',
892
+ content: `import { clerkMiddleware } from '@clerk/nextjs/server';
893
+
894
+ export default clerkMiddleware();
895
+
896
+ export const config = {
897
+ matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
898
+ };
899
+ `,
900
+ });
901
+ break;
902
+
903
+ case 'stripe':
904
+ files.push({
905
+ path: 'src/lib/stripe.ts',
906
+ content: `import Stripe from 'stripe';
907
+
908
+ export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
909
+ apiVersion: '2023-10-16',
910
+ typescript: true,
911
+ });
912
+ `,
913
+ });
914
+ break;
915
+
916
+ case 'supabase':
917
+ files.push({
918
+ path: 'src/lib/supabase.ts',
919
+ content: `import { createClient } from '@supabase/supabase-js';
920
+
921
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
922
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
923
+
924
+ export const supabase = createClient(supabaseUrl, supabaseAnonKey);
925
+ `,
926
+ });
927
+ break;
928
+
929
+ case 'resend':
930
+ files.push({
931
+ path: 'src/lib/resend.ts',
932
+ content: `import { Resend } from 'resend';
933
+
934
+ export const resend = new Resend(process.env.RESEND_API_KEY);
935
+ `,
936
+ });
937
+ break;
938
+
939
+ case 'openai':
940
+ files.push({
941
+ path: 'src/lib/openai.ts',
942
+ content: `import OpenAI from 'openai';
943
+
944
+ export const openai = new OpenAI({
945
+ apiKey: process.env.OPENAI_API_KEY,
946
+ });
947
+ `,
948
+ });
949
+ break;
950
+
951
+ case 'anthropic':
952
+ files.push({
953
+ path: 'src/lib/anthropic.ts',
954
+ content: `import Anthropic from '@anthropic-ai/sdk';
955
+
956
+ export const anthropic = new Anthropic({
957
+ apiKey: process.env.ANTHROPIC_API_KEY,
958
+ });
959
+ `,
960
+ });
961
+ break;
962
+
963
+ case 'sentry':
964
+ files.push({
965
+ path: 'sentry.client.config.ts',
966
+ content: `import * as Sentry from '@sentry/nextjs';
967
+
968
+ Sentry.init({
969
+ dsn: process.env.SENTRY_DSN,
970
+ tracesSampleRate: 1.0,
971
+ });
972
+ `,
973
+ });
974
+ break;
975
+ }
976
+
977
+ return files.length > 0 ? files : null;
978
+ }
979
+
980
+ function sleep(ms: number): Promise<void> {
981
+ return new Promise(resolve => setTimeout(resolve, ms));
982
+ }
983
+
984
+ // Export for use elsewhere
985
+ export { INTEGRATIONS, Integration };