codebakers 2.1.2 → 2.2.1

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.
@@ -3,6 +3,184 @@ import chalk from 'chalk';
3
3
  import open from 'open';
4
4
  import { Config } from '../utils/config.js';
5
5
 
6
+ // ============================================================================
7
+ // SERVICE DEFINITIONS
8
+ // ============================================================================
9
+
10
+ interface ServiceDef {
11
+ key: string;
12
+ name: string;
13
+ description: string;
14
+ whyNeeded: string;
15
+ url: string;
16
+ urlDescription: string;
17
+ fields: Array<{
18
+ key: string;
19
+ label: string;
20
+ placeholder: string;
21
+ validate?: (v: string) => string | undefined;
22
+ }>;
23
+ }
24
+
25
+ const SERVICES: Record<string, ServiceDef> = {
26
+ anthropic: {
27
+ key: 'anthropic',
28
+ name: 'Anthropic (Claude)',
29
+ description: 'Powers the AI coding agent',
30
+ whyNeeded: 'Without this, CodeBakers cannot generate any code. This is the brain of the system.',
31
+ url: 'https://console.anthropic.com/settings/keys',
32
+ urlDescription: 'Create an API key',
33
+ fields: [{
34
+ key: 'apiKey',
35
+ label: 'Anthropic API Key',
36
+ placeholder: 'sk-ant-...',
37
+ validate: (v) => {
38
+ if (!v) return 'API key is required';
39
+ if (!v.startsWith('sk-ant-')) return 'Should start with sk-ant-';
40
+ return undefined;
41
+ },
42
+ }],
43
+ },
44
+ github: {
45
+ key: 'github',
46
+ name: 'GitHub',
47
+ description: 'Create repos and push code',
48
+ whyNeeded: 'Without this, CodeBakers cannot create repositories or save your code to GitHub.',
49
+ url: 'https://github.com/settings/tokens/new?scopes=repo,user&description=CodeBakers',
50
+ urlDescription: 'Create a token with "repo" and "user" scopes checked',
51
+ fields: [{
52
+ key: 'token',
53
+ label: 'GitHub Token',
54
+ placeholder: 'ghp_... or github_pat_...',
55
+ validate: (v) => {
56
+ if (!v) return 'Token is required';
57
+ if (!v.startsWith('ghp_') && !v.startsWith('github_pat_')) {
58
+ return 'Should start with ghp_ or github_pat_';
59
+ }
60
+ return undefined;
61
+ },
62
+ }],
63
+ },
64
+ vercel: {
65
+ key: 'vercel',
66
+ name: 'Vercel',
67
+ description: 'Deploy your apps to production',
68
+ whyNeeded: 'Without this, CodeBakers cannot deploy your apps. You\'ll have to deploy manually.',
69
+ url: 'https://vercel.com/account/tokens',
70
+ urlDescription: 'Click "Create" to generate a new token',
71
+ fields: [{
72
+ key: 'token',
73
+ label: 'Vercel Token',
74
+ placeholder: 'vercel_...',
75
+ }],
76
+ },
77
+ supabase: {
78
+ key: 'supabase',
79
+ name: 'Supabase',
80
+ description: 'Database and authentication',
81
+ whyNeeded: 'Without this, CodeBakers cannot create databases for your apps. You\'ll need to set up databases manually.',
82
+ url: 'https://supabase.com/dashboard/account/tokens',
83
+ urlDescription: 'Generate a new access token',
84
+ fields: [{
85
+ key: 'accessToken',
86
+ label: 'Supabase Access Token',
87
+ placeholder: 'sbp_...',
88
+ }],
89
+ },
90
+ openai: {
91
+ key: 'openai',
92
+ name: 'OpenAI',
93
+ description: 'GPT models, embeddings, DALL-E',
94
+ whyNeeded: 'Optional. Only needed if you want to use OpenAI models instead of or alongside Claude.',
95
+ url: 'https://platform.openai.com/api-keys',
96
+ urlDescription: 'Create a new API key',
97
+ fields: [{
98
+ key: 'apiKey',
99
+ label: 'OpenAI API Key',
100
+ placeholder: 'sk-...',
101
+ }],
102
+ },
103
+ stripe: {
104
+ key: 'stripe',
105
+ name: 'Stripe',
106
+ description: 'Payment processing',
107
+ whyNeeded: 'Optional. Only needed if your app accepts payments.',
108
+ url: 'https://dashboard.stripe.com/apikeys',
109
+ urlDescription: 'Copy your Secret key (sk_live or sk_test)',
110
+ fields: [{
111
+ key: 'secretKey',
112
+ label: 'Stripe Secret Key',
113
+ placeholder: 'sk_live_... or sk_test_...',
114
+ }],
115
+ },
116
+ twilio: {
117
+ key: 'twilio',
118
+ name: 'Twilio',
119
+ description: 'SMS, voice calls, WhatsApp',
120
+ whyNeeded: 'Optional. Only needed if your app sends SMS or makes calls.',
121
+ url: 'https://console.twilio.com/',
122
+ urlDescription: 'Find Account SID and Auth Token on the dashboard',
123
+ fields: [
124
+ {
125
+ key: 'accountSid',
126
+ label: 'Account SID',
127
+ placeholder: 'AC...',
128
+ },
129
+ {
130
+ key: 'authToken',
131
+ label: 'Auth Token',
132
+ placeholder: '...',
133
+ },
134
+ ],
135
+ },
136
+ resend: {
137
+ key: 'resend',
138
+ name: 'Resend',
139
+ description: 'Email sending',
140
+ whyNeeded: 'Optional. Only needed if your app sends emails.',
141
+ url: 'https://resend.com/api-keys',
142
+ urlDescription: 'Create an API key',
143
+ fields: [{
144
+ key: 'apiKey',
145
+ label: 'Resend API Key',
146
+ placeholder: 're_...',
147
+ }],
148
+ },
149
+ vapi: {
150
+ key: 'vapi',
151
+ name: 'VAPI',
152
+ description: 'Voice AI agents',
153
+ whyNeeded: 'Optional. Only needed if you\'re building voice agents.',
154
+ url: 'https://dashboard.vapi.ai/',
155
+ urlDescription: 'Copy your API key from the dashboard',
156
+ fields: [{
157
+ key: 'apiKey',
158
+ label: 'VAPI API Key',
159
+ placeholder: '...',
160
+ }],
161
+ },
162
+ elevenlabs: {
163
+ key: 'elevenlabs',
164
+ name: 'ElevenLabs',
165
+ description: 'AI voice generation',
166
+ whyNeeded: 'Optional. Only needed if you want AI-generated voices.',
167
+ url: 'https://elevenlabs.io/app/settings/api-keys',
168
+ urlDescription: 'Create an API key',
169
+ fields: [{
170
+ key: 'apiKey',
171
+ label: 'ElevenLabs API Key',
172
+ placeholder: '...',
173
+ }],
174
+ },
175
+ };
176
+
177
+ const CORE_SERVICES = ['anthropic', 'github', 'vercel', 'supabase'];
178
+ const OPTIONAL_SERVICES = ['openai', 'stripe', 'twilio', 'resend', 'vapi', 'elevenlabs'];
179
+
180
+ // ============================================================================
181
+ // MAIN SETUP COMMAND
182
+ // ============================================================================
183
+
6
184
  export async function setupCommand(): Promise<void> {
7
185
  const config = new Config();
8
186
 
@@ -15,7 +193,7 @@ export async function setupCommand(): Promise<void> {
15
193
  options: [
16
194
  { value: 'view', label: '👀 View connected services' },
17
195
  { value: 'add', label: '➕ Add another service' },
18
- { value: 'update', label: '🔄 Update credentials' },
196
+ { value: 'update', label: '🔄 Update a service' },
19
197
  { value: 'reset', label: '🗑️ Reset all configuration' },
20
198
  { value: 'back', label: '← Back' },
21
199
  ],
@@ -25,424 +203,264 @@ export async function setupCommand(): Promise<void> {
25
203
  return;
26
204
  }
27
205
 
28
- if (action === 'view') {
29
- showConnectedServices(config);
30
- return;
31
- }
32
-
33
- if (action === 'reset') {
34
- const confirm = await p.confirm({
35
- message: 'Are you sure? This will remove all credentials and settings.',
36
- });
37
- if (confirm) {
38
- // Reset config - in real implementation would clear the config
39
- p.outro(chalk.yellow('Configuration reset. Run `codebakers setup` again.'));
40
- }
41
- return;
42
- }
43
-
44
- if (action === 'add') {
45
- await addService(config);
46
- return;
206
+ switch (action) {
207
+ case 'view':
208
+ showConnectedServices(config);
209
+ return;
210
+ case 'add':
211
+ await addService(config);
212
+ return;
213
+ case 'update':
214
+ await updateService(config);
215
+ return;
216
+ case 'reset':
217
+ await resetConfig(config);
218
+ return;
47
219
  }
48
220
  }
49
221
 
50
222
  // First-time setup
51
- p.note(
52
- `Let's connect your accounts. This only takes a few minutes.
223
+ console.log(chalk.cyan(`
224
+ Welcome to CodeBakers! Let's connect your services.
225
+
226
+ ${chalk.dim('Your credentials are stored locally in ~/.codebakers/')}
227
+ ${chalk.dim('and are never sent to our servers.')}
228
+
229
+ ${chalk.bold('Core Services (Recommended):')}
230
+ ${chalk.dim('These power the main CodeBakers features.')}
231
+
232
+ `));
233
+
234
+ // Setup core services
235
+ let skippedCore: string[] = [];
236
+
237
+ for (const serviceKey of CORE_SERVICES) {
238
+ const service = SERVICES[serviceKey];
239
+ const connected = await connectService(config, service);
53
240
 
54
- ${chalk.dim('Your credentials are stored locally in ~/.codebakers/')}
55
- ${chalk.dim('and are never sent to our servers.')}`,
56
- 'Welcome to CodeBakers!'
57
- );
58
-
59
- // Required services
60
- const requiredServices = [
61
- { name: 'GitHub', key: 'github', required: true },
62
- { name: 'Vercel', key: 'vercel', required: true },
63
- { name: 'Supabase', key: 'supabase', required: true },
64
- { name: 'Anthropic (Claude)', key: 'anthropic', required: true },
65
- ];
66
-
67
- // Optional services
68
- const optionalServices = [
69
- { name: 'OpenAI', key: 'openai' },
70
- { name: 'Stripe', key: 'stripe' },
71
- { name: 'Twilio', key: 'twilio' },
72
- { name: 'VAPI', key: 'vapi' },
73
- { name: 'Resend', key: 'resend' },
74
- { name: 'ElevenLabs', key: 'elevenLabs' },
75
- { name: 'Microsoft Graph', key: 'microsoft' },
76
- { name: 'Google APIs', key: 'google' },
77
- ];
78
-
79
- p.log.step('Connecting required services...');
80
-
81
- // Connect required services
82
- for (const service of requiredServices) {
83
- await connectService(config, service.key, service.name, true);
241
+ if (!connected) {
242
+ skippedCore.push(service.name);
243
+ }
244
+ }
245
+
246
+ // Show warning if core services skipped
247
+ if (skippedCore.length > 0) {
248
+ console.log(chalk.yellow(`
249
+ ⚠️ You skipped: ${skippedCore.join(', ')}
250
+
251
+ Some features won't work without these services.
252
+ Run ${chalk.bold('codebakers setup')} anytime to add them.
253
+ `));
84
254
  }
85
255
 
86
256
  // Ask about optional services
87
257
  const addOptional = await p.confirm({
88
- message: 'Do you want to connect optional services? (Stripe, Twilio, etc.)',
258
+ message: 'Add optional services? (Stripe, Twilio, Resend, etc.)',
89
259
  initialValue: false,
90
260
  });
91
261
 
92
262
  if (addOptional && !p.isCancel(addOptional)) {
93
263
  const selected = await p.multiselect({
94
- message: 'Select services to connect:',
95
- options: optionalServices.map(s => ({
96
- value: s.key,
97
- label: s.name,
264
+ message: 'Select services to add:',
265
+ options: OPTIONAL_SERVICES.map(key => ({
266
+ value: key,
267
+ label: `${SERVICES[key].name} - ${SERVICES[key].description}`,
98
268
  })),
99
269
  required: false,
100
270
  });
101
271
 
102
- if (!p.isCancel(selected)) {
272
+ if (!p.isCancel(selected) && Array.isArray(selected)) {
103
273
  for (const serviceKey of selected) {
104
- const service = optionalServices.find(s => s.key === serviceKey);
105
- if (service) {
106
- await connectService(config, service.key, service.name, false);
107
- }
274
+ await connectService(config, SERVICES[serviceKey as string]);
108
275
  }
109
276
  }
110
277
  }
111
278
 
112
- // Copy patterns
113
- p.log.step('Installing CodeBakers patterns...');
114
- await installPatterns(config);
279
+ // Done
280
+ const connectedCount = [...CORE_SERVICES, ...OPTIONAL_SERVICES].filter(
281
+ key => config.getCredentials(key)
282
+ ).length;
115
283
 
116
- p.outro(chalk.green('✓ Setup complete! Run `codebakers init` to create your first project.'));
284
+ p.outro(chalk.green(`
285
+ ✓ Setup complete! ${connectedCount} services connected.
286
+
287
+ Run ${chalk.bold('codebakers')} to get started.
288
+ `));
117
289
  }
118
290
 
119
- async function connectService(
120
- config: Config,
121
- serviceKey: string,
122
- serviceName: string,
123
- required: boolean
124
- ): Promise<boolean> {
125
- const spinner = p.spinner();
126
-
127
- switch (serviceKey) {
128
- case 'github': {
129
- p.log.info(`${chalk.bold('GitHub')} - Opens browser for OAuth authorization`);
130
-
131
- const proceed = await p.confirm({
132
- message: 'Open browser to authorize GitHub?',
133
- initialValue: true,
134
- });
135
-
136
- if (p.isCancel(proceed) || !proceed) {
137
- if (required) {
138
- p.log.warn('GitHub is required. Skipping for now.');
139
- }
140
- return false;
141
- }
142
-
143
- // In real implementation, this would:
144
- // 1. Start local server for OAuth callback
145
- // 2. Open GitHub OAuth URL
146
- // 3. Handle callback and save token
147
- p.log.info(chalk.dim('Opening browser...'));
148
- await open('https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&scope=repo,user');
149
-
150
- const token = await p.text({
151
- message: 'Paste your GitHub token (or press Enter if OAuth completed):',
152
- placeholder: 'ghp_...',
153
- });
154
-
155
- if (!p.isCancel(token) && token) {
156
- config.setCredentials('github', { token: token as string });
157
- p.log.success('GitHub connected!');
158
- return true;
159
- }
160
- break;
161
- }
291
+ // ============================================================================
292
+ // CONNECT SERVICE
293
+ // ============================================================================
162
294
 
163
- case 'vercel': {
164
- p.log.info(`${chalk.bold('Vercel')} - Opens browser for OAuth authorization`);
165
-
166
- const proceed = await p.confirm({
167
- message: 'Open browser to authorize Vercel?',
168
- initialValue: true,
169
- });
170
-
171
- if (p.isCancel(proceed) || !proceed) {
172
- if (required) {
173
- p.log.warn('Vercel is required. Skipping for now.');
174
- }
175
- return false;
176
- }
295
+ async function connectService(config: Config, service: ServiceDef): Promise<boolean> {
296
+ console.log('');
297
+ console.log(chalk.bold(` ${service.name}`));
298
+ console.log(chalk.dim(` ${service.description}`));
299
+ console.log('');
177
300
 
178
- p.log.info(chalk.dim('Opening browser...'));
179
- await open('https://vercel.com/account/tokens');
301
+ // Ask to proceed or skip
302
+ const action = await p.select({
303
+ message: `Connect ${service.name}?`,
304
+ options: [
305
+ { value: 'connect', label: '✓ Yes, connect now' },
306
+ { value: 'skip', label: '→ Skip for now' },
307
+ { value: 'why', label: '? Why do I need this?' },
308
+ ],
309
+ });
180
310
 
181
- const token = await p.text({
182
- message: 'Paste your Vercel token:',
183
- placeholder: 'vercel_...',
184
- });
311
+ if (p.isCancel(action)) return false;
185
312
 
186
- if (!p.isCancel(token) && token) {
187
- config.setCredentials('vercel', { token: token as string });
188
- p.log.success('Vercel connected!');
189
- return true;
190
- }
191
- break;
313
+ if (action === 'why') {
314
+ console.log(chalk.yellow(`\n ${service.whyNeeded}\n`));
315
+
316
+ const proceed = await p.confirm({
317
+ message: `Connect ${service.name}?`,
318
+ initialValue: true,
319
+ });
320
+
321
+ if (!proceed || p.isCancel(proceed)) {
322
+ console.log(chalk.dim(` Skipped ${service.name}`));
323
+ return false;
192
324
  }
325
+ } else if (action === 'skip') {
326
+ console.log(chalk.dim(` Skipped ${service.name}`));
327
+ console.log(chalk.dim(` ${service.whyNeeded}`));
328
+ return false;
329
+ }
193
330
 
194
- case 'supabase': {
195
- p.log.info(`${chalk.bold('Supabase')} - Opens browser for OAuth authorization`);
196
-
197
- const proceed = await p.confirm({
198
- message: 'Open browser to authorize Supabase?',
199
- initialValue: true,
200
- });
201
-
202
- if (p.isCancel(proceed) || !proceed) {
203
- if (required) {
204
- p.log.warn('Supabase is required. Skipping for now.');
205
- }
206
- return false;
207
- }
208
-
209
- p.log.info(chalk.dim('Opening browser...'));
210
- await open('https://supabase.com/dashboard/account/tokens');
211
-
212
- const token = await p.text({
213
- message: 'Paste your Supabase access token:',
214
- placeholder: 'sbp_...',
215
- });
331
+ // Open browser
332
+ const openBrowser = await p.confirm({
333
+ message: 'Open browser to get credentials?',
334
+ initialValue: true,
335
+ });
216
336
 
217
- if (!p.isCancel(token) && token) {
218
- config.setCredentials('supabase', { accessToken: token as string });
219
- p.log.success('Supabase connected!');
220
- return true;
221
- }
222
- break;
223
- }
337
+ if (openBrowser && !p.isCancel(openBrowser)) {
338
+ console.log(chalk.dim(`\n Opening: ${service.url}`));
339
+ console.log(chalk.dim(` ${service.urlDescription}\n`));
340
+ await open(service.url);
341
+ }
224
342
 
225
- case 'anthropic': {
226
- p.log.info(`${chalk.bold('Anthropic (Claude)')} - Powers the AI coding agent`);
227
-
228
- const openBrowser = await p.confirm({
229
- message: 'Open browser to get API key?',
230
- initialValue: true,
231
- });
343
+ // Collect credentials
344
+ const credentials: Record<string, string> = {};
232
345
 
233
- if (openBrowser && !p.isCancel(openBrowser)) {
234
- await open('https://console.anthropic.com/settings/keys');
235
- }
346
+ for (const field of service.fields) {
347
+ const value = await p.text({
348
+ message: `${field.label}:`,
349
+ placeholder: field.placeholder,
350
+ validate: field.validate,
351
+ });
236
352
 
237
- const apiKey = await p.text({
238
- message: 'Paste your Anthropic API key:',
239
- placeholder: 'sk-ant-...',
240
- validate: (value) => {
241
- if (!value && required) return 'API key is required';
242
- if (value && !value.startsWith('sk-ant-')) return 'Invalid API key format';
243
- return undefined;
244
- },
245
- });
246
-
247
- if (!p.isCancel(apiKey) && apiKey) {
248
- config.setCredentials('anthropic', { apiKey: apiKey as string });
249
- p.log.success('Anthropic connected!');
250
- return true;
251
- }
252
- break;
353
+ if (p.isCancel(value)) return false;
354
+
355
+ if (!value) {
356
+ console.log(chalk.dim(` Skipped ${service.name} (no ${field.label} provided)`));
357
+ return false;
253
358
  }
254
359
 
255
- case 'openai': {
256
- const openBrowser = await p.confirm({
257
- message: 'Open browser to get OpenAI API key?',
258
- initialValue: true,
259
- });
260
-
261
- if (openBrowser && !p.isCancel(openBrowser)) {
262
- await open('https://platform.openai.com/api-keys');
263
- }
264
-
265
- const apiKey = await p.text({
266
- message: 'Paste your OpenAI API key:',
267
- placeholder: 'sk-...',
268
- });
360
+ credentials[field.key] = value as string;
361
+ }
269
362
 
270
- if (!p.isCancel(apiKey) && apiKey) {
271
- config.setCredentials('openai', { apiKey: apiKey as string });
272
- p.log.success('OpenAI connected!');
273
- return true;
274
- }
275
- break;
276
- }
363
+ // Save credentials
364
+ config.setCredentials(service.key, credentials);
365
+ console.log(chalk.green(` ✓ ${service.name} connected!`));
366
+
367
+ return true;
368
+ }
277
369
 
278
- case 'stripe': {
279
- const openBrowser = await p.confirm({
280
- message: 'Open browser to get Stripe API keys?',
281
- initialValue: true,
282
- });
370
+ // ============================================================================
371
+ // VIEW CONNECTED SERVICES
372
+ // ============================================================================
283
373
 
284
- if (openBrowser && !p.isCancel(openBrowser)) {
285
- await open('https://dashboard.stripe.com/apikeys');
286
- }
374
+ function showConnectedServices(config: Config): void {
375
+ console.log(chalk.bold('\n Connected Services:\n'));
287
376
 
288
- const secretKey = await p.text({
289
- message: 'Paste your Stripe secret key:',
290
- placeholder: 'sk_live_... or sk_test_...',
291
- });
377
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
378
+ let connectedCount = 0;
292
379
 
293
- if (!p.isCancel(secretKey) && secretKey) {
294
- config.setCredentials('stripe', { secretKey: secretKey as string });
295
- p.log.success('Stripe connected!');
296
- return true;
297
- }
298
- break;
380
+ for (const key of allServices) {
381
+ const service = SERVICES[key];
382
+ const creds = config.getCredentials(key);
383
+
384
+ if (creds) {
385
+ console.log(chalk.green(` ✓ ${service.name}`));
386
+ connectedCount++;
387
+ } else {
388
+ console.log(chalk.dim(` ○ ${service.name} (not connected)`));
299
389
  }
390
+ }
300
391
 
301
- case 'twilio': {
302
- const openBrowser = await p.confirm({
303
- message: 'Open browser to get Twilio credentials?',
304
- initialValue: true,
305
- });
306
-
307
- if (openBrowser && !p.isCancel(openBrowser)) {
308
- await open('https://console.twilio.com/');
309
- }
392
+ console.log(chalk.dim(`\n ${connectedCount}/${allServices.length} services connected\n`));
393
+ }
310
394
 
311
- const accountSid = await p.text({
312
- message: 'Paste your Twilio Account SID:',
313
- placeholder: 'AC...',
314
- });
395
+ // ============================================================================
396
+ // ADD SERVICE
397
+ // ============================================================================
315
398
 
316
- const authToken = await p.text({
317
- message: 'Paste your Twilio Auth Token:',
318
- placeholder: '...',
319
- });
320
-
321
- if (!p.isCancel(accountSid) && !p.isCancel(authToken) && accountSid && authToken) {
322
- config.setCredentials('twilio', {
323
- accountSid: accountSid as string,
324
- authToken: authToken as string
325
- });
326
- p.log.success('Twilio connected!');
327
- return true;
328
- }
329
- break;
330
- }
399
+ async function addService(config: Config): Promise<void> {
400
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
401
+ const unconnected = allServices.filter(key => !config.getCredentials(key));
331
402
 
332
- case 'vapi': {
333
- const openBrowser = await p.confirm({
334
- message: 'Open browser to get VAPI API key?',
335
- initialValue: true,
336
- });
403
+ if (unconnected.length === 0) {
404
+ p.log.info('All services are already connected!');
405
+ return;
406
+ }
337
407
 
338
- if (openBrowser && !p.isCancel(openBrowser)) {
339
- await open('https://dashboard.vapi.ai/');
340
- }
408
+ const selected = await p.select({
409
+ message: 'Select service to add:',
410
+ options: unconnected.map(key => ({
411
+ value: key,
412
+ label: `${SERVICES[key].name} - ${SERVICES[key].description}`,
413
+ })),
414
+ });
341
415
 
342
- const apiKey = await p.text({
343
- message: 'Paste your VAPI API key:',
344
- placeholder: '...',
345
- });
416
+ if (p.isCancel(selected)) return;
346
417
 
347
- if (!p.isCancel(apiKey) && apiKey) {
348
- config.setCredentials('vapi', { apiKey: apiKey as string });
349
- p.log.success('VAPI connected!');
350
- return true;
351
- }
352
- break;
353
- }
418
+ await connectService(config, SERVICES[selected as string]);
419
+ }
354
420
 
355
- case 'resend': {
356
- const openBrowser = await p.confirm({
357
- message: 'Open browser to get Resend API key?',
358
- initialValue: true,
359
- });
421
+ // ============================================================================
422
+ // UPDATE SERVICE
423
+ // ============================================================================
360
424
 
361
- if (openBrowser && !p.isCancel(openBrowser)) {
362
- await open('https://resend.com/api-keys');
363
- }
425
+ async function updateService(config: Config): Promise<void> {
426
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
427
+ const connected = allServices.filter(key => config.getCredentials(key));
364
428
 
365
- const apiKey = await p.text({
366
- message: 'Paste your Resend API key:',
367
- placeholder: 're_...',
368
- });
429
+ if (connected.length === 0) {
430
+ p.log.info('No services connected yet. Run setup first.');
431
+ return;
432
+ }
369
433
 
370
- if (!p.isCancel(apiKey) && apiKey) {
371
- config.setCredentials('resend', { apiKey: apiKey as string });
372
- p.log.success('Resend connected!');
373
- return true;
374
- }
375
- break;
376
- }
434
+ const selected = await p.select({
435
+ message: 'Select service to update:',
436
+ options: connected.map(key => ({
437
+ value: key,
438
+ label: SERVICES[key].name,
439
+ })),
440
+ });
377
441
 
378
- default:
379
- p.log.warn(`Service ${serviceName} not yet implemented`);
380
- return false;
381
- }
442
+ if (p.isCancel(selected)) return;
382
443
 
383
- return false;
444
+ await connectService(config, SERVICES[selected as string]);
384
445
  }
385
446
 
386
- async function addService(config: Config): Promise<void> {
387
- const services = [
388
- { value: 'github', label: 'GitHub' },
389
- { value: 'vercel', label: 'Vercel' },
390
- { value: 'supabase', label: 'Supabase' },
391
- { value: 'anthropic', label: 'Anthropic (Claude)' },
392
- { value: 'openai', label: 'OpenAI' },
393
- { value: 'stripe', label: 'Stripe' },
394
- { value: 'twilio', label: 'Twilio' },
395
- { value: 'vapi', label: 'VAPI' },
396
- { value: 'resend', label: 'Resend' },
397
- { value: 'elevenLabs', label: 'ElevenLabs' },
398
- { value: 'microsoft', label: 'Microsoft Graph' },
399
- { value: 'google', label: 'Google APIs' },
400
- ];
401
-
402
- const service = await p.select({
403
- message: 'Which service do you want to connect?',
404
- options: services,
447
+ // ============================================================================
448
+ // RESET CONFIG
449
+ // ============================================================================
450
+
451
+ async function resetConfig(config: Config): Promise<void> {
452
+ const confirm = await p.confirm({
453
+ message: 'Are you sure? This will remove ALL credentials.',
454
+ initialValue: false,
405
455
  });
406
456
 
407
- if (!p.isCancel(service)) {
408
- const serviceInfo = services.find(s => s.value === service);
409
- if (serviceInfo) {
410
- await connectService(config, serviceInfo.value, serviceInfo.label, false);
411
- }
412
- }
413
- }
457
+ if (!confirm || p.isCancel(confirm)) return;
414
458
 
415
- function showConnectedServices(config: Config): void {
416
- const services = [
417
- { key: 'github', name: 'GitHub' },
418
- { key: 'vercel', name: 'Vercel' },
419
- { key: 'supabase', name: 'Supabase' },
420
- { key: 'anthropic', name: 'Anthropic' },
421
- { key: 'openai', name: 'OpenAI' },
422
- { key: 'stripe', name: 'Stripe' },
423
- { key: 'twilio', name: 'Twilio' },
424
- { key: 'vapi', name: 'VAPI' },
425
- { key: 'resend', name: 'Resend' },
426
- { key: 'elevenLabs', name: 'ElevenLabs' },
427
- { key: 'microsoft', name: 'Microsoft' },
428
- { key: 'google', name: 'Google' },
429
- ];
430
-
431
- console.log('\n' + chalk.bold('Connected Services:') + '\n');
432
-
433
- for (const service of services) {
434
- const creds = config.getCredentials(service.key);
435
- const isConnected = creds && Object.values(creds).some(v => v);
436
- const status = isConnected ? chalk.green('✓ Connected') : chalk.dim('○ Not connected');
437
- console.log(` ${service.name.padEnd(15)} ${status}`);
459
+ // Clear all credentials
460
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
461
+ for (const key of allServices) {
462
+ config.setCredentials(key, null as any);
438
463
  }
439
464
 
440
- console.log('');
441
- }
442
-
443
- async function installPatterns(config: Config): Promise<void> {
444
- // In real implementation, this would copy the pattern files
445
- // from the bundled templates to ~/.codebakers/patterns/
446
- const patternsDir = config.getPatternsDir();
447
- p.log.info(chalk.dim(`Patterns installed to ${patternsDir}`));
465
+ p.log.success('Configuration reset. Run `codebakers setup` to reconfigure.');
448
466
  }