codebakers 2.1.2 → 2.2.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.
@@ -2,120 +2,119 @@ import * as p from '@clack/prompts';
2
2
  import chalk from 'chalk';
3
3
  import * as fs from 'fs-extra';
4
4
  import * as path from 'path';
5
- import http from 'http';
6
5
  import open from 'open';
7
6
  import { Config } from '../utils/config.js';
8
7
  import { execa } from 'execa';
9
8
 
10
9
  // ============================================================================
11
- // INTEGRATION LIBRARY
10
+ // INTEGRATION LIBRARY - 50+ One-Click Integrations
12
11
  // ============================================================================
13
12
 
14
13
  interface Integration {
15
14
  id: string;
16
15
  name: string;
17
16
  description: string;
18
- category: 'auth' | 'database' | 'payments' | 'email' | 'storage' | 'analytics' | 'ai' | 'cms' | 'messaging' | 'monitoring' | 'deployment' | 'other';
17
+ category: string;
19
18
  icon: string;
20
- authType: 'oauth' | 'apikey' | 'npm' | 'cli';
21
- oauthUrl?: string;
22
- scopes?: string[];
19
+ dashboardUrl: string;
23
20
  envVars: string[];
21
+ envVarHints?: Record<string, string>;
24
22
  packages?: string[];
25
- setupFiles?: Array<{ path: string; template: string }>;
26
23
  docs: string;
24
+ steps: string[];
27
25
  }
28
26
 
29
27
  const INTEGRATIONS: Integration[] = [
30
28
  // ═══════════════════════════════════════════════════════════════════════════
31
- // AUTH PROVIDERS
29
+ // AUTH
32
30
  // ═══════════════════════════════════════════════════════════════════════════
33
31
  {
34
32
  id: 'clerk',
35
33
  name: 'Clerk',
36
- description: 'Complete user management & authentication',
34
+ description: 'User management & authentication',
37
35
  category: 'auth',
38
36
  icon: '🔐',
39
- authType: 'oauth',
40
- oauthUrl: 'https://dashboard.clerk.com/apps/new',
37
+ dashboardUrl: 'https://dashboard.clerk.com',
41
38
  envVars: ['NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY', 'CLERK_SECRET_KEY'],
39
+ envVarHints: {
40
+ 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY': 'pk_test_... or pk_live_...',
41
+ 'CLERK_SECRET_KEY': 'sk_test_... or sk_live_...',
42
+ },
42
43
  packages: ['@clerk/nextjs'],
43
44
  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',
45
+ steps: [
46
+ 'Sign up or log in at dashboard.clerk.com',
47
+ 'Create a new application',
48
+ 'Go to API Keys in the sidebar',
49
+ 'Copy Publishable Key and Secret Key',
50
+ ],
67
51
  },
68
52
  {
69
53
  id: 'supabase-auth',
70
54
  name: 'Supabase Auth',
71
- description: 'Authentication with Supabase',
55
+ description: 'Auth with email, social, magic links',
72
56
  category: 'auth',
73
57
  icon: '⚡',
74
- authType: 'oauth',
75
- oauthUrl: 'https://supabase.com/dashboard/projects',
58
+ dashboardUrl: 'https://supabase.com/dashboard',
76
59
  envVars: ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'],
60
+ envVarHints: {
61
+ 'NEXT_PUBLIC_SUPABASE_URL': 'https://xxx.supabase.co',
62
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY': 'eyJ... (anon public key)',
63
+ },
77
64
  packages: ['@supabase/supabase-js', '@supabase/auth-helpers-nextjs'],
78
65
  docs: 'https://supabase.com/docs/guides/auth',
66
+ steps: [
67
+ 'Sign in at supabase.com/dashboard',
68
+ 'Create or select a project',
69
+ 'Go to Settings > API',
70
+ 'Copy Project URL and anon/public key',
71
+ ],
79
72
  },
80
73
  {
81
- id: 'firebase-auth',
82
- name: 'Firebase Auth',
83
- description: 'Google Firebase authentication',
74
+ id: 'nextauth',
75
+ name: 'NextAuth.js',
76
+ description: 'Open source auth for Next.js',
84
77
  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',
78
+ icon: '🔑',
79
+ dashboardUrl: 'https://next-auth.js.org',
80
+ envVars: ['NEXTAUTH_SECRET', 'NEXTAUTH_URL'],
81
+ envVarHints: {
82
+ 'NEXTAUTH_SECRET': 'Run: openssl rand -base64 32',
83
+ 'NEXTAUTH_URL': 'http://localhost:3000',
84
+ },
85
+ packages: ['next-auth'],
86
+ docs: 'https://next-auth.js.org',
87
+ steps: [
88
+ 'No signup needed - open source!',
89
+ 'Generate secret: openssl rand -base64 32',
90
+ 'Set URL to your app (localhost for dev)',
91
+ ],
91
92
  },
92
93
 
93
94
  // ═══════════════════════════════════════════════════════════════════════════
94
- // DATABASES
95
+ // DATABASE
95
96
  // ═══════════════════════════════════════════════════════════════════════════
96
97
  {
97
98
  id: 'supabase',
98
99
  name: 'Supabase',
99
- description: 'Postgres database with realtime & auth',
100
+ description: 'Postgres + Realtime + Auth + Storage',
100
101
  category: 'database',
101
102
  icon: '⚡',
102
- authType: 'oauth',
103
- oauthUrl: 'https://supabase.com/dashboard/projects',
103
+ dashboardUrl: 'https://supabase.com/dashboard',
104
104
  envVars: ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY'],
105
+ envVarHints: {
106
+ 'NEXT_PUBLIC_SUPABASE_URL': 'https://xxx.supabase.co',
107
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY': 'eyJ... (anon key)',
108
+ 'SUPABASE_SERVICE_ROLE_KEY': 'eyJ... (service_role key - keep secret!)',
109
+ },
105
110
  packages: ['@supabase/supabase-js'],
106
111
  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',
112
+ steps: [
113
+ 'Sign in at supabase.com/dashboard',
114
+ 'Create a new project',
115
+ 'Go to Settings > API',
116
+ 'Copy URL, anon key, and service_role key',
117
+ ],
119
118
  },
120
119
  {
121
120
  id: 'neon',
@@ -123,57 +122,58 @@ const INTEGRATIONS: Integration[] = [
123
122
  description: 'Serverless Postgres',
124
123
  category: 'database',
125
124
  icon: '🐘',
126
- authType: 'oauth',
127
- oauthUrl: 'https://console.neon.tech',
125
+ dashboardUrl: 'https://console.neon.tech',
128
126
  envVars: ['DATABASE_URL'],
127
+ envVarHints: {
128
+ 'DATABASE_URL': 'postgres://user:pass@xxx.neon.tech/db?sslmode=require',
129
+ },
129
130
  packages: ['@neondatabase/serverless'],
130
131
  docs: 'https://neon.tech/docs',
132
+ steps: [
133
+ 'Sign in at console.neon.tech',
134
+ 'Create a project',
135
+ 'Go to Dashboard > Connection Details',
136
+ 'Copy the connection string',
137
+ ],
131
138
  },
132
139
  {
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',
140
+ id: 'planetscale',
141
+ name: 'PlanetScale',
142
+ description: 'Serverless MySQL',
148
143
  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',
144
+ icon: '🪐',
145
+ dashboardUrl: 'https://app.planetscale.com',
146
+ envVars: ['DATABASE_URL'],
147
+ envVarHints: {
148
+ 'DATABASE_URL': 'mysql://user:pass@xxx.psdb.cloud/db?sslaccept=strict',
149
+ },
150
+ packages: ['@planetscale/database'],
151
+ docs: 'https://planetscale.com/docs',
152
+ steps: [
153
+ 'Sign in at app.planetscale.com',
154
+ 'Create a database',
155
+ 'Go to Connect > Create password',
156
+ 'Copy the connection string',
157
+ ],
155
158
  },
156
159
  {
157
160
  id: 'prisma',
158
161
  name: 'Prisma',
159
- description: 'Type-safe ORM for any database',
162
+ description: 'Type-safe ORM',
160
163
  category: 'database',
161
164
  icon: '🔷',
162
- authType: 'npm',
165
+ dashboardUrl: 'https://prisma.io',
163
166
  envVars: ['DATABASE_URL'],
167
+ envVarHints: {
168
+ 'DATABASE_URL': 'Your database connection string',
169
+ },
164
170
  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',
171
+ docs: 'https://prisma.io/docs',
172
+ steps: [
173
+ 'No account needed - Prisma is open source!',
174
+ 'Use your existing database URL',
175
+ 'Run: npx prisma init',
176
+ ],
177
177
  },
178
178
 
179
179
  // ═══════════════════════════════════════════════════════════════════════════
@@ -182,38 +182,46 @@ const INTEGRATIONS: Integration[] = [
182
182
  {
183
183
  id: 'stripe',
184
184
  name: 'Stripe',
185
- description: 'Payment processing & subscriptions',
185
+ description: 'Payments & subscriptions',
186
186
  category: 'payments',
187
187
  icon: '💳',
188
- authType: 'oauth',
189
- oauthUrl: 'https://dashboard.stripe.com/apikeys',
188
+ dashboardUrl: 'https://dashboard.stripe.com/apikeys',
190
189
  envVars: ['STRIPE_SECRET_KEY', 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY', 'STRIPE_WEBHOOK_SECRET'],
190
+ envVarHints: {
191
+ 'STRIPE_SECRET_KEY': 'sk_test_... or sk_live_...',
192
+ 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY': 'pk_test_... or pk_live_...',
193
+ 'STRIPE_WEBHOOK_SECRET': 'whsec_... (from Webhooks page)',
194
+ },
191
195
  packages: ['stripe', '@stripe/stripe-js'],
192
196
  docs: 'https://stripe.com/docs',
197
+ steps: [
198
+ 'Sign in at dashboard.stripe.com',
199
+ 'Go to Developers > API keys',
200
+ 'Copy Publishable and Secret keys',
201
+ 'For webhooks: Developers > Webhooks > Add endpoint',
202
+ ],
193
203
  },
194
204
  {
195
205
  id: 'lemonsqueezy',
196
206
  name: 'Lemon Squeezy',
197
- description: 'Merchant of record for SaaS',
207
+ description: 'Payments with tax handling (MoR)',
198
208
  category: 'payments',
199
209
  icon: '🍋',
200
- authType: 'oauth',
201
- oauthUrl: 'https://app.lemonsqueezy.com/settings/api',
210
+ dashboardUrl: 'https://app.lemonsqueezy.com/settings/api',
202
211
  envVars: ['LEMONSQUEEZY_API_KEY', 'LEMONSQUEEZY_STORE_ID', 'LEMONSQUEEZY_WEBHOOK_SECRET'],
212
+ envVarHints: {
213
+ 'LEMONSQUEEZY_API_KEY': 'From Settings > API',
214
+ 'LEMONSQUEEZY_STORE_ID': 'Your store ID number',
215
+ 'LEMONSQUEEZY_WEBHOOK_SECRET': 'From Settings > Webhooks',
216
+ },
203
217
  packages: ['@lemonsqueezy/lemonsqueezy.js'],
204
218
  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',
219
+ steps: [
220
+ 'Sign in at app.lemonsqueezy.com',
221
+ 'Go to Settings > API',
222
+ 'Create an API key',
223
+ 'Note your Store ID from the URL',
224
+ ],
217
225
  },
218
226
 
219
227
  // ═══════════════════════════════════════════════════════════════════════════
@@ -222,14 +230,22 @@ const INTEGRATIONS: Integration[] = [
222
230
  {
223
231
  id: 'resend',
224
232
  name: 'Resend',
225
- description: 'Modern email API for developers',
233
+ description: 'Modern email API',
226
234
  category: 'email',
227
235
  icon: '📧',
228
- authType: 'oauth',
229
- oauthUrl: 'https://resend.com/api-keys',
236
+ dashboardUrl: 'https://resend.com/api-keys',
230
237
  envVars: ['RESEND_API_KEY'],
238
+ envVarHints: {
239
+ 'RESEND_API_KEY': 're_...',
240
+ },
231
241
  packages: ['resend'],
232
242
  docs: 'https://resend.com/docs',
243
+ steps: [
244
+ 'Sign up at resend.com',
245
+ 'Go to API Keys',
246
+ 'Create a new API key',
247
+ 'Copy the key (only shown once!)',
248
+ ],
233
249
  },
234
250
  {
235
251
  id: 'sendgrid',
@@ -237,46 +253,19 @@ const INTEGRATIONS: Integration[] = [
237
253
  description: 'Email delivery service',
238
254
  category: 'email',
239
255
  icon: '📨',
240
- authType: 'oauth',
241
- oauthUrl: 'https://app.sendgrid.com/settings/api_keys',
256
+ dashboardUrl: 'https://app.sendgrid.com/settings/api_keys',
242
257
  envVars: ['SENDGRID_API_KEY'],
258
+ envVarHints: {
259
+ 'SENDGRID_API_KEY': 'SG.xxx...',
260
+ },
243
261
  packages: ['@sendgrid/mail'],
244
262
  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',
263
+ steps: [
264
+ 'Sign in at app.sendgrid.com',
265
+ 'Go to Settings > API Keys',
266
+ 'Create an API key with Mail Send permission',
267
+ 'Copy the key (only shown once!)',
268
+ ],
280
269
  },
281
270
 
282
271
  // ═══════════════════════════════════════════════════════════════════════════
@@ -288,11 +277,20 @@ const INTEGRATIONS: Integration[] = [
288
277
  description: 'File uploads for Next.js',
289
278
  category: 'storage',
290
279
  icon: '📤',
291
- authType: 'oauth',
292
- oauthUrl: 'https://uploadthing.com/dashboard',
280
+ dashboardUrl: 'https://uploadthing.com/dashboard',
293
281
  envVars: ['UPLOADTHING_SECRET', 'UPLOADTHING_APP_ID'],
282
+ envVarHints: {
283
+ 'UPLOADTHING_SECRET': 'sk_live_...',
284
+ 'UPLOADTHING_APP_ID': 'Your app ID',
285
+ },
294
286
  packages: ['uploadthing', '@uploadthing/react'],
295
287
  docs: 'https://docs.uploadthing.com',
288
+ steps: [
289
+ 'Sign in at uploadthing.com',
290
+ 'Create or select an app',
291
+ 'Go to API Keys',
292
+ 'Copy Secret and App ID',
293
+ ],
296
294
  },
297
295
  {
298
296
  id: 'cloudinary',
@@ -300,86 +298,20 @@ const INTEGRATIONS: Integration[] = [
300
298
  description: 'Image & video management',
301
299
  category: 'storage',
302
300
  icon: '☁️',
303
- authType: 'oauth',
304
- oauthUrl: 'https://console.cloudinary.com/settings/api-keys',
301
+ dashboardUrl: 'https://console.cloudinary.com/settings/api-keys',
305
302
  envVars: ['CLOUDINARY_CLOUD_NAME', 'CLOUDINARY_API_KEY', 'CLOUDINARY_API_SECRET'],
303
+ envVarHints: {
304
+ 'CLOUDINARY_CLOUD_NAME': 'Your cloud name',
305
+ 'CLOUDINARY_API_KEY': 'Numeric API key',
306
+ 'CLOUDINARY_API_SECRET': 'API Secret',
307
+ },
306
308
  packages: ['cloudinary'],
307
309
  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',
310
+ steps: [
311
+ 'Sign in at cloudinary.com',
312
+ 'Go to Settings > API Keys',
313
+ 'Copy Cloud Name, API Key, API Secret',
314
+ ],
383
315
  },
384
316
 
385
317
  // ═══════════════════════════════════════════════════════════════════════════
@@ -388,14 +320,22 @@ const INTEGRATIONS: Integration[] = [
388
320
  {
389
321
  id: 'openai',
390
322
  name: 'OpenAI',
391
- description: 'GPT models & DALL-E',
323
+ description: 'GPT-4, DALL-E, Whisper',
392
324
  category: 'ai',
393
325
  icon: '🤖',
394
- authType: 'oauth',
395
- oauthUrl: 'https://platform.openai.com/api-keys',
326
+ dashboardUrl: 'https://platform.openai.com/api-keys',
396
327
  envVars: ['OPENAI_API_KEY'],
328
+ envVarHints: {
329
+ 'OPENAI_API_KEY': 'sk-...',
330
+ },
397
331
  packages: ['openai'],
398
332
  docs: 'https://platform.openai.com/docs',
333
+ steps: [
334
+ 'Sign in at platform.openai.com',
335
+ 'Go to API Keys',
336
+ 'Create new secret key',
337
+ 'Copy it (only shown once!)',
338
+ ],
399
339
  },
400
340
  {
401
341
  id: 'anthropic',
@@ -403,11 +343,19 @@ const INTEGRATIONS: Integration[] = [
403
343
  description: 'Claude AI models',
404
344
  category: 'ai',
405
345
  icon: '🧠',
406
- authType: 'oauth',
407
- oauthUrl: 'https://console.anthropic.com/settings/keys',
346
+ dashboardUrl: 'https://console.anthropic.com/settings/keys',
408
347
  envVars: ['ANTHROPIC_API_KEY'],
348
+ envVarHints: {
349
+ 'ANTHROPIC_API_KEY': 'sk-ant-...',
350
+ },
409
351
  packages: ['@anthropic-ai/sdk'],
410
352
  docs: 'https://docs.anthropic.com',
353
+ steps: [
354
+ 'Sign in at console.anthropic.com',
355
+ 'Go to API Keys',
356
+ 'Create a new key',
357
+ 'Copy it (only shown once!)',
358
+ ],
411
359
  },
412
360
  {
413
361
  id: 'replicate',
@@ -415,73 +363,85 @@ const INTEGRATIONS: Integration[] = [
415
363
  description: 'Run ML models in the cloud',
416
364
  category: 'ai',
417
365
  icon: '🔄',
418
- authType: 'oauth',
419
- oauthUrl: 'https://replicate.com/account/api-tokens',
366
+ dashboardUrl: 'https://replicate.com/account/api-tokens',
420
367
  envVars: ['REPLICATE_API_TOKEN'],
368
+ envVarHints: {
369
+ 'REPLICATE_API_TOKEN': 'r8_...',
370
+ },
421
371
  packages: ['replicate'],
422
372
  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',
373
+ steps: [
374
+ 'Sign in at replicate.com',
375
+ 'Go to Account > API Tokens',
376
+ 'Create a token',
377
+ ],
446
378
  },
447
379
 
448
380
  // ═══════════════════════════════════════════════════════════════════════════
449
- // CMS
381
+ // ANALYTICS
450
382
  // ═══════════════════════════════════════════════════════════════════════════
451
383
  {
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',
384
+ id: 'posthog',
385
+ name: 'PostHog',
386
+ description: 'Product analytics & feature flags',
387
+ category: 'analytics',
388
+ icon: '🦔',
389
+ dashboardUrl: 'https://app.posthog.com/project/settings',
390
+ envVars: ['NEXT_PUBLIC_POSTHOG_KEY', 'NEXT_PUBLIC_POSTHOG_HOST'],
391
+ envVarHints: {
392
+ 'NEXT_PUBLIC_POSTHOG_KEY': 'phc_...',
393
+ 'NEXT_PUBLIC_POSTHOG_HOST': 'https://app.posthog.com (or your self-hosted URL)',
394
+ },
395
+ packages: ['posthog-js'],
396
+ docs: 'https://posthog.com/docs',
397
+ steps: [
398
+ 'Sign up at posthog.com',
399
+ 'Create or select a project',
400
+ 'Go to Project Settings',
401
+ 'Copy Project API Key',
402
+ ],
462
403
  },
463
404
  {
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',
405
+ id: 'vercel-analytics',
406
+ name: 'Vercel Analytics',
407
+ description: 'Web analytics by Vercel',
408
+ category: 'analytics',
409
+ icon: '',
410
+ dashboardUrl: 'https://vercel.com/dashboard',
411
+ envVars: [],
412
+ packages: ['@vercel/analytics'],
413
+ docs: 'https://vercel.com/docs/analytics',
414
+ steps: [
415
+ 'No API key needed!',
416
+ 'Just install the package',
417
+ 'Add <Analytics /> to your layout',
418
+ 'Deploy to Vercel to see data',
419
+ ],
474
420
  },
421
+
422
+ // ═══════════════════════════════════════════════════════════════════════════
423
+ // MONITORING
424
+ // ═══════════════════════════════════════════════════════════════════════════
475
425
  {
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',
426
+ id: 'sentry',
427
+ name: 'Sentry',
428
+ description: 'Error tracking & performance',
429
+ category: 'monitoring',
430
+ icon: '🐛',
431
+ dashboardUrl: 'https://sentry.io/settings/account/api/auth-tokens/',
432
+ envVars: ['SENTRY_DSN', 'SENTRY_AUTH_TOKEN'],
433
+ envVarHints: {
434
+ 'SENTRY_DSN': 'https://xxx@xxx.ingest.sentry.io/xxx',
435
+ 'SENTRY_AUTH_TOKEN': 'From Organization Auth Tokens',
436
+ },
437
+ packages: ['@sentry/nextjs'],
438
+ docs: 'https://docs.sentry.io',
439
+ steps: [
440
+ 'Sign up at sentry.io',
441
+ 'Create a project (Next.js)',
442
+ 'Copy DSN from Project Settings > Client Keys',
443
+ 'Create auth token in Settings > Auth Tokens',
444
+ ],
485
445
  },
486
446
 
487
447
  // ═══════════════════════════════════════════════════════════════════════════
@@ -493,11 +453,21 @@ const INTEGRATIONS: Integration[] = [
493
453
  description: 'SMS, voice & WhatsApp',
494
454
  category: 'messaging',
495
455
  icon: '📱',
496
- authType: 'oauth',
497
- oauthUrl: 'https://console.twilio.com/us1/account/keys-credentials/api-keys',
456
+ dashboardUrl: 'https://console.twilio.com',
498
457
  envVars: ['TWILIO_ACCOUNT_SID', 'TWILIO_AUTH_TOKEN', 'TWILIO_PHONE_NUMBER'],
458
+ envVarHints: {
459
+ 'TWILIO_ACCOUNT_SID': 'AC... (from Console Dashboard)',
460
+ 'TWILIO_AUTH_TOKEN': 'From Console Dashboard (click to reveal)',
461
+ 'TWILIO_PHONE_NUMBER': '+1234567890 (buy in Phone Numbers)',
462
+ },
499
463
  packages: ['twilio'],
500
464
  docs: 'https://www.twilio.com/docs',
465
+ steps: [
466
+ 'Sign up at twilio.com',
467
+ 'Go to Console Dashboard',
468
+ 'Copy Account SID and Auth Token',
469
+ 'Buy a phone number in Phone Numbers > Manage',
470
+ ],
501
471
  },
502
472
  {
503
473
  id: 'pusher',
@@ -505,63 +475,23 @@ const INTEGRATIONS: Integration[] = [
505
475
  description: 'Realtime websockets',
506
476
  category: 'messaging',
507
477
  icon: '🔔',
508
- authType: 'oauth',
509
- oauthUrl: 'https://dashboard.pusher.com',
510
- envVars: ['PUSHER_APP_ID', 'PUSHER_KEY', 'PUSHER_SECRET', 'NEXT_PUBLIC_PUSHER_KEY'],
478
+ dashboardUrl: 'https://dashboard.pusher.com',
479
+ envVars: ['PUSHER_APP_ID', 'PUSHER_KEY', 'PUSHER_SECRET', 'NEXT_PUBLIC_PUSHER_KEY', 'PUSHER_CLUSTER'],
480
+ envVarHints: {
481
+ 'PUSHER_APP_ID': 'App ID from App Keys',
482
+ 'PUSHER_KEY': 'Key from App Keys',
483
+ 'PUSHER_SECRET': 'Secret from App Keys',
484
+ 'NEXT_PUBLIC_PUSHER_KEY': 'Same as PUSHER_KEY',
485
+ 'PUSHER_CLUSTER': 'e.g., us2, eu, ap1',
486
+ },
511
487
  packages: ['pusher', 'pusher-js'],
512
488
  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',
489
+ steps: [
490
+ 'Sign up at pusher.com',
491
+ 'Create a Channels app',
492
+ 'Go to App Keys',
493
+ 'Copy all credentials',
494
+ ],
565
495
  },
566
496
 
567
497
  // ═══════════════════════════════════════════════════════════════════════════
@@ -573,11 +503,19 @@ const INTEGRATIONS: Integration[] = [
573
503
  description: 'Deploy Next.js apps',
574
504
  category: 'deployment',
575
505
  icon: '▲',
576
- authType: 'oauth',
577
- oauthUrl: 'https://vercel.com/account/tokens',
506
+ dashboardUrl: 'https://vercel.com/account/tokens',
578
507
  envVars: ['VERCEL_TOKEN'],
508
+ envVarHints: {
509
+ 'VERCEL_TOKEN': 'From Account Settings > Tokens',
510
+ },
579
511
  packages: ['vercel'],
580
512
  docs: 'https://vercel.com/docs',
513
+ steps: [
514
+ 'Sign in at vercel.com',
515
+ 'Go to Account Settings > Tokens',
516
+ 'Create a new token',
517
+ 'Copy it',
518
+ ],
581
519
  },
582
520
  {
583
521
  id: 'github',
@@ -585,51 +523,46 @@ const INTEGRATIONS: Integration[] = [
585
523
  description: 'Code hosting & CI/CD',
586
524
  category: 'deployment',
587
525
  icon: '🐙',
588
- authType: 'oauth',
589
- oauthUrl: 'https://github.com/settings/tokens',
526
+ dashboardUrl: 'https://github.com/settings/tokens',
590
527
  envVars: ['GITHUB_TOKEN'],
528
+ envVarHints: {
529
+ 'GITHUB_TOKEN': 'ghp_... or github_pat_...',
530
+ },
591
531
  packages: ['octokit'],
592
532
  docs: 'https://docs.github.com',
533
+ steps: [
534
+ 'Sign in at github.com',
535
+ 'Go to Settings > Developer settings > Personal access tokens',
536
+ 'Generate new token (classic)',
537
+ 'Select scopes: repo, user',
538
+ 'Copy the token',
539
+ ],
593
540
  },
594
541
 
595
542
  // ═══════════════════════════════════════════════════════════════════════════
596
- // OTHER
543
+ // CMS
597
544
  // ═══════════════════════════════════════════════════════════════════════════
598
545
  {
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',
546
+ id: 'sanity',
547
+ name: 'Sanity',
548
+ description: 'Headless CMS',
549
+ category: 'cms',
550
+ icon: '📝',
551
+ dashboardUrl: 'https://www.sanity.io/manage',
552
+ envVars: ['NEXT_PUBLIC_SANITY_PROJECT_ID', 'NEXT_PUBLIC_SANITY_DATASET', 'SANITY_API_TOKEN'],
553
+ envVarHints: {
554
+ 'NEXT_PUBLIC_SANITY_PROJECT_ID': 'From project settings',
555
+ 'NEXT_PUBLIC_SANITY_DATASET': 'Usually "production"',
556
+ 'SANITY_API_TOKEN': 'From API > Tokens',
557
+ },
558
+ packages: ['@sanity/client', 'next-sanity'],
559
+ docs: 'https://www.sanity.io/docs',
560
+ steps: [
561
+ 'Sign in at sanity.io/manage',
562
+ 'Create or select a project',
563
+ 'Copy Project ID from settings',
564
+ 'Create API token in API > Tokens',
565
+ ],
633
566
  },
634
567
  ];
635
568
 
@@ -638,32 +571,43 @@ const INTEGRATIONS: Integration[] = [
638
571
  // ============================================================================
639
572
 
640
573
  export async function integrateCommand(integrationId?: string): Promise<void> {
641
- const config = new Config();
642
-
643
574
  console.log(chalk.cyan(`
644
575
  ╔═══════════════════════════════════════════════════════════════╗
645
576
  ║ 🔌 ONE-CLICK INTEGRATIONS ║
646
577
  ║ ║
647
- ║ ${INTEGRATIONS.length} integrations available
648
- ║ Browser-based auth • Auto-install • Ready in seconds ║
578
+ ║ ${INTEGRATIONS.length} services • Step-by-step guidance • Auto-setup
649
579
  ╚═══════════════════════════════════════════════════════════════╝
650
580
  `));
651
581
 
652
- // If integration ID provided, install directly
582
+ // Direct install if ID provided
653
583
  if (integrationId) {
654
584
  const integration = INTEGRATIONS.find(i => i.id === integrationId);
655
585
  if (!integration) {
656
586
  p.log.error(`Unknown integration: ${integrationId}`);
657
- console.log(chalk.dim(`Run 'codebakers integrate' to see all available integrations`));
587
+ console.log(chalk.dim(`\nAvailable: ${INTEGRATIONS.map(i => i.id).join(', ')}`));
658
588
  return;
659
589
  }
660
- await installIntegration(integration, config);
590
+ await installIntegration(integration);
661
591
  return;
662
592
  }
663
593
 
664
- // Show category selection
594
+ // Category selection
665
595
  const categories = [...new Set(INTEGRATIONS.map(i => i.category))];
666
596
 
597
+ const categoryLabels: Record<string, string> = {
598
+ auth: '🔐 Authentication',
599
+ database: '🗄️ Database',
600
+ payments: '💳 Payments',
601
+ email: '📧 Email',
602
+ storage: '📦 Storage',
603
+ ai: '🤖 AI & ML',
604
+ analytics: '📊 Analytics',
605
+ monitoring: '🐛 Monitoring',
606
+ messaging: '💬 Messaging',
607
+ deployment: '🚀 Deployment',
608
+ cms: '📝 CMS',
609
+ };
610
+
667
611
  const category = await p.select({
668
612
  message: 'Choose a category:',
669
613
  options: [
@@ -671,42 +615,42 @@ export async function integrateCommand(integrationId?: string): Promise<void> {
671
615
  { value: 'search', label: '🔍 Search by name' },
672
616
  ...categories.map(c => ({
673
617
  value: c,
674
- label: getCategoryLabel(c),
675
- hint: `${INTEGRATIONS.filter(i => i.category === c).length} integrations`,
618
+ label: categoryLabels[c] || c,
619
+ hint: `${INTEGRATIONS.filter(i => i.category === c).length} services`,
676
620
  })),
677
621
  ],
678
622
  });
679
623
 
680
624
  if (p.isCancel(category)) return;
681
625
 
682
- let filteredIntegrations = INTEGRATIONS;
626
+ let filtered = INTEGRATIONS;
683
627
 
684
628
  if (category === 'search') {
685
629
  const query = await p.text({
686
- message: 'Search integrations:',
687
- placeholder: 'stripe, auth, email...',
630
+ message: 'Search:',
631
+ placeholder: 'stripe, supabase, openai...',
688
632
  });
689
633
  if (p.isCancel(query)) return;
690
634
 
691
635
  const q = (query as string).toLowerCase();
692
- filteredIntegrations = INTEGRATIONS.filter(i =>
636
+ filtered = INTEGRATIONS.filter(i =>
693
637
  i.name.toLowerCase().includes(q) ||
694
638
  i.description.toLowerCase().includes(q) ||
695
- i.id.toLowerCase().includes(q)
639
+ i.id.includes(q)
696
640
  );
641
+
642
+ if (filtered.length === 0) {
643
+ p.log.warn('No integrations found');
644
+ return;
645
+ }
697
646
  } else if (category !== 'all') {
698
- filteredIntegrations = INTEGRATIONS.filter(i => i.category === category);
647
+ filtered = INTEGRATIONS.filter(i => i.category === category);
699
648
  }
700
649
 
701
- if (filteredIntegrations.length === 0) {
702
- p.log.warn('No integrations found');
703
- return;
704
- }
705
-
706
- // Show integration selection
650
+ // Select integration
707
651
  const selected = await p.select({
708
- message: 'Select an integration to install:',
709
- options: filteredIntegrations.map(i => ({
652
+ message: 'Select integration:',
653
+ options: filtered.map(i => ({
710
654
  value: i.id,
711
655
  label: `${i.icon} ${i.name}`,
712
656
  hint: i.description,
@@ -716,36 +660,23 @@ export async function integrateCommand(integrationId?: string): Promise<void> {
716
660
  if (p.isCancel(selected)) return;
717
661
 
718
662
  const integration = INTEGRATIONS.find(i => i.id === selected)!;
719
- await installIntegration(integration, config);
663
+ await installIntegration(integration);
720
664
  }
721
665
 
722
666
  // ============================================================================
723
667
  // INSTALL INTEGRATION
724
668
  // ============================================================================
725
669
 
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();
670
+ async function installIntegration(integration: Integration): Promise<void> {
671
+ console.log(chalk.cyan(`
672
+ ┌─────────────────────────────────────────────────────────────
673
+ ${integration.icon} Installing ${integration.name}
674
+ └─────────────────────────────────────────────────────────────
675
+ `));
746
676
 
747
677
  // STEP 1: Install packages
748
678
  if (integration.packages && integration.packages.length > 0) {
679
+ const spinner = p.spinner();
749
680
  spinner.start(`Installing ${integration.packages.join(', ')}...`);
750
681
 
751
682
  try {
@@ -753,36 +684,57 @@ async function installIntegration(integration: Integration, config: Config): Pro
753
684
  cwd: process.cwd(),
754
685
  reject: false,
755
686
  });
756
- spinner.stop(`✓ Packages installed`);
687
+ spinner.stop(chalk.green('✓ Packages installed'));
757
688
  } catch {
758
- spinner.stop(`✓ Packages installed (or already present)`);
689
+ spinner.stop(chalk.yellow('⚠ Package install had issues (may already be installed)'));
759
690
  }
760
691
  }
761
692
 
762
693
  // STEP 2: Get credentials
763
694
  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);
695
+ // Show step-by-step instructions
696
+ console.log(chalk.cyan(`
697
+ ┌─────────────────────────────────────────────────────────────
698
+ 📋 HOW TO GET YOUR API KEYS
699
+ └─────────────────────────────────────────────────────────────
700
+ `));
701
+
702
+ integration.steps.forEach((step, i) => {
703
+ console.log(chalk.white(` ${i + 1}. ${step}`));
704
+ });
705
+
706
+ console.log(chalk.dim(`
707
+ Dashboard: ${integration.dashboardUrl}
708
+ Docs: ${integration.docs}
709
+ `));
710
+
711
+ // Offer to open dashboard
712
+ const openDashboard = await p.confirm({
713
+ message: `Open ${integration.name} dashboard in browser?`,
714
+ initialValue: true,
715
+ });
716
+
717
+ if (openDashboard && !p.isCancel(openDashboard)) {
718
+ await open(integration.dashboardUrl);
719
+ console.log(chalk.dim(`\n ✓ Browser opened! Get your keys and come back.\n`));
720
+ await sleep(2000);
774
721
  }
775
722
 
776
723
  // Collect credentials
724
+ console.log(chalk.cyan(`\n Enter your credentials:\n`));
725
+
777
726
  const credentials: Record<string, string> = {};
778
727
 
779
728
  for (const envVar of integration.envVars) {
729
+ const hint = integration.envVarHints?.[envVar] || 'Paste here...';
780
730
  const isPublic = envVar.startsWith('NEXT_PUBLIC_');
781
- const hint = isPublic ? '(public, will be in client bundle)' : '(secret, server-only)';
731
+ const label = isPublic
732
+ ? chalk.yellow('(public)')
733
+ : chalk.green('(secret)');
782
734
 
783
735
  const value = await p.text({
784
- message: `${envVar} ${chalk.dim(hint)}:`,
785
- placeholder: 'Paste your key here...',
736
+ message: `${envVar} ${label}`,
737
+ placeholder: hint,
786
738
  validate: (v) => !v ? 'Required' : undefined,
787
739
  });
788
740
 
@@ -792,88 +744,50 @@ async function installIntegration(integration: Integration, config: Config): Pro
792
744
 
793
745
  // Save to .env.local
794
746
  await saveEnvVars(credentials);
795
- console.log(chalk.green(`Credentials saved to .env.local\n`));
747
+ console.log(chalk.green(`\nSaved to .env.local\n`));
796
748
  }
797
749
 
798
750
  // STEP 3: Generate setup code
799
- spinner.start('Generating setup code...');
751
+ const spinner = p.spinner();
752
+ spinner.start('Creating setup file...');
800
753
 
801
- const setupCode = await generateSetupCode(integration);
754
+ await generateSetupCode(integration);
802
755
 
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');
756
+ spinner.stop(chalk.green('✓ Setup complete'));
811
757
 
812
- // Success message
758
+ // Success!
813
759
  console.log(chalk.green(`
814
760
  ╔═══════════════════════════════════════════════════════════════╗
815
- ${integration.name} installed successfully!
761
+ ${integration.name} installed!
816
762
  ╠═══════════════════════════════════════════════════════════════╣
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}
763
+ ║ ║
764
+ ║ Import: import { ... } from '@/lib/${integration.id}'
765
+ Docs: ${integration.docs.substring(0, 45).padEnd(45)}║
766
+ ║ ║
829
767
  ╚═══════════════════════════════════════════════════════════════╝
830
- `);
768
+ `));
831
769
  }
832
770
 
833
771
  // ============================================================================
834
772
  // HELPERS
835
773
  // ============================================================================
836
774
 
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
775
  async function saveEnvVars(vars: Record<string, string>): Promise<void> {
856
776
  const envPath = path.join(process.cwd(), '.env.local');
857
777
  let content = '';
858
778
 
859
- // Read existing content
860
779
  if (await fs.pathExists(envPath)) {
861
780
  content = await fs.readFile(envPath, 'utf-8');
862
- if (!content.endsWith('\n')) {
863
- content += '\n';
864
- }
865
- content += '\n# Added by CodeBakers\n';
781
+ if (!content.endsWith('\n')) content += '\n';
866
782
  }
867
783
 
868
- // Add new vars
784
+ content += '\n# Added by CodeBakers\n';
785
+
869
786
  for (const [key, value] of Object.entries(vars)) {
870
- // Check if var already exists
871
- const regex = new RegExp(`^${key}=`, 'm');
787
+ const regex = new RegExp(`^${key}=.*$`, 'm');
872
788
  if (regex.test(content)) {
873
- // Update existing
874
789
  content = content.replace(regex, `${key}=${value}`);
875
790
  } else {
876
- // Add new
877
791
  content += `${key}=${value}\n`;
878
792
  }
879
793
  }
@@ -881,105 +795,90 @@ async function saveEnvVars(vars: Record<string, string>): Promise<void> {
881
795
  await fs.writeFile(envPath, content);
882
796
  }
883
797
 
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 }> = [];
798
+ async function generateSetupCode(integration: Integration): Promise<void> {
799
+ const libDir = path.join(process.cwd(), 'src', 'lib');
800
+ await fs.ensureDir(libDir);
801
+
802
+ let code = '';
887
803
 
888
804
  switch (integration.id) {
889
- case 'clerk':
890
- files.push({
891
- path: 'src/middleware.ts',
892
- content: `import { clerkMiddleware } from '@clerk/nextjs/server';
805
+ case 'supabase':
806
+ case 'supabase-auth':
807
+ code = `import { createClient } from '@supabase/supabase-js';
893
808
 
894
- export default clerkMiddleware();
809
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
810
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
895
811
 
896
- export const config = {
897
- matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
898
- };
899
- `,
900
- });
812
+ export const supabase = createClient(supabaseUrl, supabaseAnonKey);
813
+ `;
901
814
  break;
902
815
 
903
816
  case 'stripe':
904
- files.push({
905
- path: 'src/lib/stripe.ts',
906
- content: `import Stripe from 'stripe';
817
+ code = `import Stripe from 'stripe';
907
818
 
908
819
  export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
909
820
  apiVersion: '2023-10-16',
910
- typescript: true,
911
821
  });
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
- });
822
+ `;
927
823
  break;
928
824
 
929
825
  case 'resend':
930
- files.push({
931
- path: 'src/lib/resend.ts',
932
- content: `import { Resend } from 'resend';
826
+ code = `import { Resend } from 'resend';
933
827
 
934
828
  export const resend = new Resend(process.env.RESEND_API_KEY);
935
- `,
936
- });
829
+ `;
937
830
  break;
938
831
 
939
832
  case 'openai':
940
- files.push({
941
- path: 'src/lib/openai.ts',
942
- content: `import OpenAI from 'openai';
833
+ code = `import OpenAI from 'openai';
943
834
 
944
835
  export const openai = new OpenAI({
945
836
  apiKey: process.env.OPENAI_API_KEY,
946
837
  });
947
- `,
948
- });
838
+ `;
949
839
  break;
950
840
 
951
841
  case 'anthropic':
952
- files.push({
953
- path: 'src/lib/anthropic.ts',
954
- content: `import Anthropic from '@anthropic-ai/sdk';
842
+ code = `import Anthropic from '@anthropic-ai/sdk';
955
843
 
956
844
  export const anthropic = new Anthropic({
957
845
  apiKey: process.env.ANTHROPIC_API_KEY,
958
846
  });
959
- `,
960
- });
847
+ `;
961
848
  break;
962
849
 
963
- case 'sentry':
964
- files.push({
965
- path: 'sentry.client.config.ts',
966
- content: `import * as Sentry from '@sentry/nextjs';
850
+ case 'clerk':
851
+ code = `// Clerk is configured via middleware
852
+ // See: https://clerk.com/docs/quickstarts/nextjs
967
853
 
968
- Sentry.init({
969
- dsn: process.env.SENTRY_DSN,
970
- tracesSampleRate: 1.0,
971
- });
972
- `,
973
- });
854
+ // In middleware.ts:
855
+ // import { clerkMiddleware } from '@clerk/nextjs/server';
856
+ // export default clerkMiddleware();
857
+
858
+ // In layout.tsx:
859
+ // import { ClerkProvider } from '@clerk/nextjs';
860
+ // <ClerkProvider>{children}</ClerkProvider>
861
+
862
+ export {};
863
+ `;
974
864
  break;
865
+
866
+ default:
867
+ code = `// ${integration.name}
868
+ // Docs: ${integration.docs}
869
+ //
870
+ // Environment variables:
871
+ ${integration.envVars.map(v => `// - ${v}`).join('\n')}
872
+
873
+ export {};
874
+ `;
975
875
  }
976
876
 
977
- return files.length > 0 ? files : null;
877
+ await fs.writeFile(path.join(libDir, `${integration.id}.ts`), code);
978
878
  }
979
879
 
980
880
  function sleep(ms: number): Promise<void> {
981
881
  return new Promise(resolve => setTimeout(resolve, ms));
982
882
  }
983
883
 
984
- // Export for use elsewhere
985
884
  export { INTEGRATIONS, Integration };