codebakers 2.3.0 → 2.3.5

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.
@@ -1,5 +1,6 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import chalk from 'chalk';
3
+ import boxen from 'boxen';
3
4
  import open from 'open';
4
5
  import { Config } from '../utils/config.js';
5
6
 
@@ -13,11 +14,12 @@ interface ServiceDef {
13
14
  description: string;
14
15
  whyNeeded: string;
15
16
  url: string;
16
- urlDescription: string;
17
+ steps: string[]; // Step-by-step instructions
17
18
  fields: Array<{
18
19
  key: string;
19
20
  label: string;
20
21
  placeholder: string;
22
+ hint?: string;
21
23
  validate?: (v: string) => string | undefined;
22
24
  }>;
23
25
  }
@@ -27,13 +29,19 @@ const SERVICES: Record<string, ServiceDef> = {
27
29
  key: 'anthropic',
28
30
  name: 'Anthropic (Claude)',
29
31
  description: 'Powers the AI coding agent',
30
- whyNeeded: 'Without this, CodeBakers cannot generate any code. This is the brain of the system.',
32
+ whyNeeded: 'This is REQUIRED. Without it, CodeBakers cannot generate any code.',
31
33
  url: 'https://console.anthropic.com/settings/keys',
32
- urlDescription: 'Create an API key',
34
+ steps: [
35
+ 'Sign up or log in to Anthropic Console',
36
+ 'Click "Create Key"',
37
+ 'Give it a name like "CodeBakers"',
38
+ 'Copy the key (starts with sk-ant-)',
39
+ ],
33
40
  fields: [{
34
41
  key: 'apiKey',
35
- label: 'Anthropic API Key',
42
+ label: 'Paste your Anthropic API Key',
36
43
  placeholder: 'sk-ant-...',
44
+ hint: 'Starts with sk-ant-',
37
45
  validate: (v) => {
38
46
  if (!v) return 'API key is required';
39
47
  if (!v.startsWith('sk-ant-')) return 'Should start with sk-ant-';
@@ -45,13 +53,19 @@ const SERVICES: Record<string, ServiceDef> = {
45
53
  key: 'github',
46
54
  name: 'GitHub',
47
55
  description: 'Create repos and push code',
48
- whyNeeded: 'Without this, CodeBakers cannot create repositories or save your code to GitHub.',
56
+ whyNeeded: 'Needed to create repositories and save your code. Skip if you\'ll manage git manually.',
49
57
  url: 'https://github.com/settings/tokens/new?scopes=repo,user&description=CodeBakers',
50
- urlDescription: 'Create a token with "repo" and "user" scopes checked',
58
+ steps: [
59
+ 'Log in to GitHub',
60
+ 'The "repo" and "user" boxes should already be checked',
61
+ 'Scroll down and click "Generate token"',
62
+ 'Copy the token (starts with ghp_)',
63
+ ],
51
64
  fields: [{
52
65
  key: 'token',
53
- label: 'GitHub Token',
54
- placeholder: 'ghp_... or github_pat_...',
66
+ label: 'Paste your GitHub Token',
67
+ placeholder: 'ghp_...',
68
+ hint: 'Starts with ghp_ or github_pat_',
55
69
  validate: (v) => {
56
70
  if (!v) return 'Token is required';
57
71
  if (!v.startsWith('ghp_') && !v.startsWith('github_pat_')) {
@@ -65,38 +79,55 @@ const SERVICES: Record<string, ServiceDef> = {
65
79
  key: 'vercel',
66
80
  name: 'Vercel',
67
81
  description: 'Deploy your apps to production',
68
- whyNeeded: 'Without this, CodeBakers cannot deploy your apps. You\'ll have to deploy manually.',
82
+ whyNeeded: 'Needed to deploy apps automatically. Skip if you\'ll deploy manually.',
69
83
  url: 'https://vercel.com/account/tokens',
70
- urlDescription: 'Click "Create" to generate a new token',
84
+ steps: [
85
+ 'Log in to Vercel',
86
+ 'Click "Create" button',
87
+ 'Name it "CodeBakers"',
88
+ 'Click "Create Token"',
89
+ 'Copy the token',
90
+ ],
71
91
  fields: [{
72
92
  key: 'token',
73
- label: 'Vercel Token',
93
+ label: 'Paste your Vercel Token',
74
94
  placeholder: 'vercel_...',
95
+ hint: 'The token you just created',
75
96
  }],
76
97
  },
77
98
  supabase: {
78
99
  key: 'supabase',
79
100
  name: 'Supabase',
80
101
  description: 'Database and authentication',
81
- whyNeeded: 'Without this, CodeBakers cannot create databases for your apps. You\'ll need to set up databases manually.',
102
+ whyNeeded: 'Needed for database features. Skip if you\'ll set up databases manually.',
82
103
  url: 'https://supabase.com/dashboard/account/tokens',
83
- urlDescription: 'Generate a new access token',
104
+ steps: [
105
+ 'Log in to Supabase',
106
+ 'Click "Generate new token"',
107
+ 'Name it "CodeBakers"',
108
+ 'Copy the token',
109
+ ],
84
110
  fields: [{
85
111
  key: 'accessToken',
86
- label: 'Supabase Access Token',
112
+ label: 'Paste your Supabase Token',
87
113
  placeholder: 'sbp_...',
114
+ hint: 'Starts with sbp_',
88
115
  }],
89
116
  },
90
117
  openai: {
91
118
  key: 'openai',
92
119
  name: 'OpenAI',
93
120
  description: 'GPT models, embeddings, DALL-E',
94
- whyNeeded: 'Optional. Only needed if you want to use OpenAI models instead of or alongside Claude.',
121
+ whyNeeded: 'Optional. Only if you want to use OpenAI alongside Claude.',
95
122
  url: 'https://platform.openai.com/api-keys',
96
- urlDescription: 'Create a new API key',
123
+ steps: [
124
+ 'Log in to OpenAI',
125
+ 'Click "Create new secret key"',
126
+ 'Copy the key',
127
+ ],
97
128
  fields: [{
98
129
  key: 'apiKey',
99
- label: 'OpenAI API Key',
130
+ label: 'Paste your OpenAI API Key',
100
131
  placeholder: 'sk-...',
101
132
  }],
102
133
  },
@@ -104,12 +135,15 @@ const SERVICES: Record<string, ServiceDef> = {
104
135
  key: 'stripe',
105
136
  name: 'Stripe',
106
137
  description: 'Payment processing',
107
- whyNeeded: 'Optional. Only needed if your app accepts payments.',
138
+ whyNeeded: 'Optional. Only if your app accepts payments.',
108
139
  url: 'https://dashboard.stripe.com/apikeys',
109
- urlDescription: 'Copy your Secret key (sk_live or sk_test)',
140
+ steps: [
141
+ 'Log in to Stripe',
142
+ 'Copy your Secret key (starts with sk_)',
143
+ ],
110
144
  fields: [{
111
145
  key: 'secretKey',
112
- label: 'Stripe Secret Key',
146
+ label: 'Paste your Stripe Secret Key',
113
147
  placeholder: 'sk_live_... or sk_test_...',
114
148
  }],
115
149
  },
@@ -117,9 +151,13 @@ const SERVICES: Record<string, ServiceDef> = {
117
151
  key: 'twilio',
118
152
  name: 'Twilio',
119
153
  description: 'SMS, voice calls, WhatsApp',
120
- whyNeeded: 'Optional. Only needed if your app sends SMS or makes calls.',
154
+ whyNeeded: 'Optional. Only if your app sends SMS or makes calls.',
121
155
  url: 'https://console.twilio.com/',
122
- urlDescription: 'Find Account SID and Auth Token on the dashboard',
156
+ steps: [
157
+ 'Log in to Twilio',
158
+ 'Find Account SID on the dashboard',
159
+ 'Find Auth Token on the dashboard',
160
+ ],
123
161
  fields: [
124
162
  {
125
163
  key: 'accountSid',
@@ -137,12 +175,16 @@ const SERVICES: Record<string, ServiceDef> = {
137
175
  key: 'resend',
138
176
  name: 'Resend',
139
177
  description: 'Email sending',
140
- whyNeeded: 'Optional. Only needed if your app sends emails.',
178
+ whyNeeded: 'Optional. Only if your app sends emails.',
141
179
  url: 'https://resend.com/api-keys',
142
- urlDescription: 'Create an API key',
180
+ steps: [
181
+ 'Log in to Resend',
182
+ 'Click "Create API Key"',
183
+ 'Copy the key',
184
+ ],
143
185
  fields: [{
144
186
  key: 'apiKey',
145
- label: 'Resend API Key',
187
+ label: 'Paste your Resend API Key',
146
188
  placeholder: 're_...',
147
189
  }],
148
190
  },
@@ -150,12 +192,16 @@ const SERVICES: Record<string, ServiceDef> = {
150
192
  key: 'vapi',
151
193
  name: 'VAPI',
152
194
  description: 'Voice AI agents',
153
- whyNeeded: 'Optional. Only needed if you\'re building voice agents.',
195
+ whyNeeded: 'Optional. Only if you\'re building voice agents.',
154
196
  url: 'https://dashboard.vapi.ai/',
155
- urlDescription: 'Copy your API key from the dashboard',
197
+ steps: [
198
+ 'Log in to VAPI',
199
+ 'Go to Settings > API Keys',
200
+ 'Copy your API key',
201
+ ],
156
202
  fields: [{
157
203
  key: 'apiKey',
158
- label: 'VAPI API Key',
204
+ label: 'Paste your VAPI API Key',
159
205
  placeholder: '...',
160
206
  }],
161
207
  },
@@ -163,12 +209,16 @@ const SERVICES: Record<string, ServiceDef> = {
163
209
  key: 'elevenlabs',
164
210
  name: 'ElevenLabs',
165
211
  description: 'AI voice generation',
166
- whyNeeded: 'Optional. Only needed if you want AI-generated voices.',
212
+ whyNeeded: 'Optional. Only if you want AI-generated voices.',
167
213
  url: 'https://elevenlabs.io/app/settings/api-keys',
168
- urlDescription: 'Create an API key',
214
+ steps: [
215
+ 'Log in to ElevenLabs',
216
+ 'Click "Create API Key"',
217
+ 'Copy the key',
218
+ ],
169
219
  fields: [{
170
220
  key: 'apiKey',
171
- label: 'ElevenLabs API Key',
221
+ label: 'Paste your ElevenLabs API Key',
172
222
  placeholder: '...',
173
223
  }],
174
224
  },
@@ -184,156 +234,207 @@ const OPTIONAL_SERVICES = ['openai', 'stripe', 'twilio', 'resend', 'vapi', 'elev
184
234
  export async function setupCommand(): Promise<void> {
185
235
  const config = new Config();
186
236
 
187
- p.intro(chalk.bgCyan.black(' CodeBakers Setup '));
188
-
189
237
  // Check if already configured
190
- if (config.isConfigured()) {
191
- const action = await p.select({
192
- message: 'CodeBakers is already configured. What do you want to do?',
193
- options: [
194
- { value: 'view', label: '👀 View connected services' },
195
- { value: 'add', label: '➕ Add another service' },
196
- { value: 'update', label: '🔄 Update a service' },
197
- { value: 'reset', label: '🗑️ Reset all configuration' },
198
- { value: 'back', label: '← Back' },
199
- ],
200
- });
201
-
202
- if (p.isCancel(action) || action === 'back') {
203
- return;
204
- }
205
-
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;
219
- }
238
+ const hasAnthropic = !!config.getCredentials('anthropic')?.apiKey;
239
+
240
+ if (hasAnthropic) {
241
+ // Already has minimum setup - show management menu
242
+ await showManagementMenu(config);
243
+ return;
220
244
  }
221
245
 
222
- // First-time setup
223
- console.log(chalk.cyan(`
224
- Welcome to CodeBakers! Let's connect your services.
246
+ // =========================================================================
247
+ // FIRST TIME SETUP - Step by step with clear instructions
248
+ // =========================================================================
225
249
 
226
- ${chalk.dim('Your credentials are stored locally in ~/.codebakers/')}
227
- ${chalk.dim('and are never sent to our servers.')}
250
+ console.log(boxen(
251
+ chalk.bold.cyan('Welcome to CodeBakers Setup!\n\n') +
252
+ chalk.white('We need to connect a few services to get started.\n\n') +
253
+ chalk.dim('Your credentials are stored locally on your computer\n') +
254
+ chalk.dim('in ~/.codebakers/ and never sent to our servers.\n\n') +
255
+ chalk.yellow('Required: ') + chalk.white('Anthropic API key (for AI)\n') +
256
+ chalk.dim('Optional: GitHub, Vercel, Supabase (for full features)'),
257
+ { padding: 1, borderColor: 'cyan', borderStyle: 'round' }
258
+ ));
259
+
260
+ // STEP 1: Anthropic (REQUIRED)
261
+ console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
262
+ console.log(chalk.bold.cyan(' STEP 1 of 4: Anthropic API Key (Required)'));
263
+ console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
228
264
 
229
- ${chalk.bold('Core Services (Recommended):')}
230
- ${chalk.dim('These power the main CodeBakers features.')}
265
+ console.log(chalk.white(' Anthropic (Claude)'));
266
+ console.log(chalk.dim(' Powers the AI coding agent\n'));
231
267
 
232
- `));
233
-
234
- // Setup core services
235
- let skippedCore: string[] = [];
268
+ // Check if they have an account
269
+ const hasAccount = await p.select({
270
+ message: 'Do you have an Anthropic account?',
271
+ options: [
272
+ { value: 'yes', label: '✓ Yes, I have an API key ready' },
273
+ { value: 'no', label: '✗ No, I need to sign up (free)' },
274
+ ],
275
+ });
236
276
 
237
- for (const serviceKey of CORE_SERVICES) {
238
- const service = SERVICES[serviceKey];
239
- const connected = await connectService(config, service);
277
+ if (p.isCancel(hasAccount)) {
278
+ return;
279
+ }
280
+
281
+ if (hasAccount === 'no') {
282
+ console.log(chalk.yellow('\n No problem! Let\'s get you signed up.\n'));
283
+ console.log(chalk.white(' Steps:'));
284
+ console.log(chalk.cyan(' 1. ') + 'Click "Sign Up" on the page that opens');
285
+ console.log(chalk.cyan(' 2. ') + 'Create your account (email + password)');
286
+ console.log(chalk.cyan(' 3. ') + 'Once logged in, go to Settings → API Keys');
287
+ console.log(chalk.cyan(' 4. ') + 'Click "Create Key"');
288
+ console.log(chalk.cyan(' 5. ') + 'Copy the key and paste it here\n');
289
+
290
+ const openSignup = await p.confirm({
291
+ message: 'Open Anthropic signup page?',
292
+ initialValue: true,
293
+ });
240
294
 
241
- if (!connected) {
242
- skippedCore.push(service.name);
295
+ if (openSignup && !p.isCancel(openSignup)) {
296
+ await open('https://console.anthropic.com/');
297
+ console.log(chalk.dim('\n Take your time - come back when you have your API key.\n'));
298
+ }
299
+ } else {
300
+ // They have an account, show regular instructions
301
+ console.log(chalk.bold.white('\n Here\'s what to do:\n'));
302
+ console.log(chalk.cyan(' 1. ') + 'Log in to Anthropic Console');
303
+ console.log(chalk.cyan(' 2. ') + 'Go to Settings → API Keys');
304
+ console.log(chalk.cyan(' 3. ') + 'Click "Create Key"');
305
+ console.log(chalk.cyan(' 4. ') + 'Copy the key (starts with sk-ant-)');
306
+ console.log('');
307
+
308
+ const openBrowser = await p.confirm({
309
+ message: 'Open Anthropic Console?',
310
+ initialValue: true,
311
+ });
312
+
313
+ if (openBrowser && !p.isCancel(openBrowser)) {
314
+ await open('https://console.anthropic.com/settings/keys');
243
315
  }
244
316
  }
245
-
246
- // Show warning if core services skipped
247
- if (skippedCore.length > 0) {
248
- console.log(chalk.yellow(`
249
- ⚠️ You skipped: ${skippedCore.join(', ')}
250
317
 
251
- Some features won't work without these services.
252
- Run ${chalk.bold('codebakers setup')} anytime to add them.
253
- `));
254
- }
255
-
256
- // Ask about optional services
257
- const addOptional = await p.confirm({
258
- message: 'Add optional services? (Stripe, Twilio, Resend, etc.)',
259
- initialValue: false,
318
+ // Now collect the API key
319
+ console.log('');
320
+ const apiKey = await p.text({
321
+ message: 'Paste your Anthropic API Key:',
322
+ placeholder: 'sk-ant-...',
323
+ validate: (v) => {
324
+ if (!v) return 'API key is required';
325
+ if (!v.startsWith('sk-ant-')) return 'Should start with sk-ant-';
326
+ return undefined;
327
+ },
260
328
  });
329
+
330
+ if (p.isCancel(apiKey) || !apiKey) {
331
+ console.log(chalk.red('\n ❌ Anthropic API key is required to use CodeBakers.'));
332
+ console.log(chalk.dim(' Run `codebakers setup` when you\'re ready to try again.\n'));
333
+ return;
334
+ }
335
+
336
+ config.setCredentials('anthropic', { apiKey: apiKey as string });
337
+ console.log(chalk.green('\n ✓ Anthropic connected successfully!\n'));
261
338
 
262
- if (addOptional && !p.isCancel(addOptional)) {
263
- const selected = await p.multiselect({
264
- message: 'Select services to add:',
265
- options: OPTIONAL_SERVICES.map(key => ({
266
- value: key,
267
- label: `${SERVICES[key].name} - ${SERVICES[key].description}`,
268
- })),
269
- required: false,
270
- });
339
+ // STEP 2: GitHub (Optional but recommended)
340
+ console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
341
+ console.log(chalk.bold.cyan(' STEP 2 of 4: GitHub Token (Recommended)'));
342
+ console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
343
+
344
+ await connectServiceWithInstructions(config, SERVICES.github, false);
271
345
 
272
- if (!p.isCancel(selected) && Array.isArray(selected)) {
273
- for (const serviceKey of selected) {
274
- await connectService(config, SERVICES[serviceKey as string]);
275
- }
276
- }
277
- }
346
+ // STEP 3: Vercel (Optional but recommended)
347
+ console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
348
+ console.log(chalk.bold.cyan(' STEP 3 of 4: Vercel Token (Recommended)'));
349
+ console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
350
+
351
+ await connectServiceWithInstructions(config, SERVICES.vercel, false);
278
352
 
279
- // Done - just show count, index.ts will show detailed next steps
280
- const connectedCount = [...CORE_SERVICES, ...OPTIONAL_SERVICES].filter(
281
- key => config.getCredentials(key)
282
- ).length;
353
+ // STEP 4: Supabase (Optional)
354
+ console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
355
+ console.log(chalk.bold.cyan(' STEP 4 of 4: Supabase Token (Optional)'));
356
+ console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
357
+
358
+ await connectServiceWithInstructions(config, SERVICES.supabase, false);
283
359
 
284
- console.log(chalk.green(`\n ✓ ${connectedCount} services connected!\n`));
360
+ // =========================================================================
361
+ // SETUP COMPLETE - Show what to do next
362
+ // =========================================================================
363
+
364
+ const connectedServices = CORE_SERVICES.filter(key => config.getCredentials(key)).map(key => SERVICES[key].name);
365
+
366
+ console.log(boxen(
367
+ chalk.bold.green('✓ Setup Complete!\n\n') +
368
+ chalk.white('Connected: ') + chalk.cyan(connectedServices.join(', ')) + '\n\n' +
369
+ chalk.bold.white('What to do next:\n\n') +
370
+ chalk.cyan('1. ') + chalk.white('Open a terminal and navigate to where you want to build:\n') +
371
+ chalk.dim(' cd C:\\dev\\my-project\n') +
372
+ chalk.dim(' (or any folder where you want to create your project)\n\n') +
373
+ chalk.cyan('2. ') + chalk.white('Run CodeBakers:\n') +
374
+ chalk.dim(' codebakers\n\n') +
375
+ chalk.bold.white('Or try these commands directly:\n\n') +
376
+ chalk.dim(' codebakers website ') + chalk.white('Build a website by describing it\n') +
377
+ chalk.dim(' codebakers init ') + chalk.white('Create a new project from scratch\n') +
378
+ chalk.dim(' codebakers help ') + chalk.white('See all available commands'),
379
+ { padding: 1, borderColor: 'green', borderStyle: 'round' }
380
+ ));
285
381
  }
286
382
 
287
383
  // ============================================================================
288
- // CONNECT SERVICE
384
+ // CONNECT SERVICE WITH DETAILED INSTRUCTIONS
289
385
  // ============================================================================
290
386
 
291
- async function connectService(config: Config, service: ServiceDef): Promise<boolean> {
292
- console.log('');
293
- console.log(chalk.bold(` ${service.name}`));
294
- console.log(chalk.dim(` ${service.description}`));
295
- console.log('');
387
+ async function connectServiceWithInstructions(
388
+ config: Config,
389
+ service: ServiceDef,
390
+ required: boolean
391
+ ): Promise<boolean> {
392
+
393
+ // Show what this service does
394
+ console.log(chalk.white(` ${service.name}`));
395
+ console.log(chalk.dim(` ${service.description}\n`));
396
+
397
+ if (!required) {
398
+ console.log(chalk.yellow(` ${service.whyNeeded}\n`));
399
+ }
296
400
 
297
- // Ask to proceed or skip
401
+ // Ask if they want to connect or skip
298
402
  const action = await p.select({
299
403
  message: `Connect ${service.name}?`,
300
- options: [
301
- { value: 'connect', label: '✓ Yes, connect now' },
404
+ options: required ? [
405
+ { value: 'connect', label: '✓ Yes, let\'s connect it' },
406
+ ] : [
407
+ { value: 'connect', label: '✓ Yes, connect it' },
302
408
  { value: 'skip', label: '→ Skip for now' },
303
- { value: 'why', label: '? Why do I need this?' },
304
409
  ],
305
410
  });
306
411
 
307
- if (p.isCancel(action)) return false;
308
-
309
- if (action === 'why') {
310
- console.log(chalk.yellow(`\n ${service.whyNeeded}\n`));
311
-
312
- const proceed = await p.confirm({
313
- message: `Connect ${service.name}?`,
314
- initialValue: true,
315
- });
316
-
317
- if (!proceed || p.isCancel(proceed)) {
318
- console.log(chalk.dim(` Skipped ${service.name}`));
319
- return false;
412
+ if (p.isCancel(action) || action === 'skip') {
413
+ if (!required) {
414
+ console.log(chalk.dim(`\n Skipped ${service.name}. You can add it later with: codebakers setup\n`));
320
415
  }
321
- } else if (action === 'skip') {
322
- console.log(chalk.dim(` Skipped ${service.name}`));
323
- console.log(chalk.dim(` ${service.whyNeeded}`));
324
416
  return false;
325
417
  }
326
418
 
327
- // Open browser
419
+ // Show step-by-step instructions
420
+ console.log(chalk.bold.white('\n Here\'s what to do:\n'));
421
+ service.steps.forEach((step, i) => {
422
+ console.log(chalk.cyan(` ${i + 1}. `) + chalk.white(step));
423
+ });
424
+ console.log('');
425
+
426
+ // Ask to open browser
328
427
  const openBrowser = await p.confirm({
329
- message: 'Open browser to get credentials?',
428
+ message: 'Open the website in your browser?',
330
429
  initialValue: true,
331
430
  });
332
431
 
333
432
  if (openBrowser && !p.isCancel(openBrowser)) {
334
- console.log(chalk.dim(`\n Opening: ${service.url}`));
335
- console.log(chalk.dim(` ${service.urlDescription}\n`));
433
+ console.log(chalk.dim(`\n Opening: ${service.url}\n`));
336
434
  await open(service.url);
435
+
436
+ // Give user time to get the key
437
+ console.log(chalk.dim(' Complete the steps above, then paste your key below.\n'));
337
438
  }
338
439
 
339
440
  // Collect credentials
@@ -341,7 +442,7 @@ async function connectService(config: Config, service: ServiceDef): Promise<bool
341
442
 
342
443
  for (const field of service.fields) {
343
444
  const value = await p.text({
344
- message: `${field.label}:`,
445
+ message: field.label + (field.hint ? chalk.dim(` (${field.hint})`) : '') + ':',
345
446
  placeholder: field.placeholder,
346
447
  validate: field.validate,
347
448
  });
@@ -349,7 +450,11 @@ async function connectService(config: Config, service: ServiceDef): Promise<bool
349
450
  if (p.isCancel(value)) return false;
350
451
 
351
452
  if (!value) {
352
- console.log(chalk.dim(` Skipped ${service.name} (no ${field.label} provided)`));
453
+ if (required) {
454
+ console.log(chalk.red(`\n ${field.label} is required.\n`));
455
+ return false;
456
+ }
457
+ console.log(chalk.dim(`\n Skipped ${service.name}.\n`));
353
458
  return false;
354
459
  }
355
460
 
@@ -358,34 +463,61 @@ async function connectService(config: Config, service: ServiceDef): Promise<bool
358
463
 
359
464
  // Save credentials
360
465
  config.setCredentials(service.key, credentials);
361
- console.log(chalk.green(` ✓ ${service.name} connected!`));
466
+ console.log(chalk.green(`\n ✓ ${service.name} connected successfully!\n`));
362
467
 
363
468
  return true;
364
469
  }
365
470
 
366
471
  // ============================================================================
367
- // VIEW CONNECTED SERVICES
472
+ // MANAGEMENT MENU (for already-configured users)
368
473
  // ============================================================================
369
474
 
370
- function showConnectedServices(config: Config): void {
371
- console.log(chalk.bold('\n Connected Services:\n'));
372
-
475
+ async function showManagementMenu(config: Config): Promise<void> {
476
+ // Show current status
477
+ console.log(chalk.bold.cyan('\n CodeBakers Settings\n'));
478
+
373
479
  const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
374
- let connectedCount = 0;
480
+ const connected = allServices.filter(key => config.getCredentials(key));
481
+ const notConnected = allServices.filter(key => !config.getCredentials(key));
482
+
483
+ console.log(chalk.green(' Connected:'));
484
+ connected.forEach(key => {
485
+ console.log(chalk.green(` ✓ ${SERVICES[key].name}`));
486
+ });
487
+
488
+ if (notConnected.length > 0) {
489
+ console.log(chalk.dim('\n Not connected:'));
490
+ notConnected.forEach(key => {
491
+ console.log(chalk.dim(` ○ ${SERVICES[key].name}`));
492
+ });
493
+ }
494
+ console.log('');
375
495
 
376
- for (const key of allServices) {
377
- const service = SERVICES[key];
378
- const creds = config.getCredentials(key);
379
-
380
- if (creds) {
381
- console.log(chalk.green(` ${service.name}`));
382
- connectedCount++;
383
- } else {
384
- console.log(chalk.dim(` ○ ${service.name} (not connected)`));
385
- }
496
+ const action = await p.select({
497
+ message: 'What would you like to do?',
498
+ options: [
499
+ { value: 'add', label: '➕ Add a service' },
500
+ { value: 'update', label: '🔄 Update a service' },
501
+ { value: 'reset', label: '🗑️ Reset all settings' },
502
+ { value: 'back', label: '← Back' },
503
+ ],
504
+ });
505
+
506
+ if (p.isCancel(action) || action === 'back') {
507
+ return;
386
508
  }
387
509
 
388
- console.log(chalk.dim(`\n ${connectedCount}/${allServices.length} services connected\n`));
510
+ switch (action) {
511
+ case 'add':
512
+ await addService(config);
513
+ break;
514
+ case 'update':
515
+ await updateService(config);
516
+ break;
517
+ case 'reset':
518
+ await resetConfig(config);
519
+ break;
520
+ }
389
521
  }
390
522
 
391
523
  // ============================================================================
@@ -397,7 +529,7 @@ async function addService(config: Config): Promise<void> {
397
529
  const unconnected = allServices.filter(key => !config.getCredentials(key));
398
530
 
399
531
  if (unconnected.length === 0) {
400
- p.log.info('All services are already connected!');
532
+ console.log(chalk.green('\n All services are already connected!\n'));
401
533
  return;
402
534
  }
403
535
 
@@ -411,7 +543,7 @@ async function addService(config: Config): Promise<void> {
411
543
 
412
544
  if (p.isCancel(selected)) return;
413
545
 
414
- await connectService(config, SERVICES[selected as string]);
546
+ await connectServiceWithInstructions(config, SERVICES[selected as string], false);
415
547
  }
416
548
 
417
549
  // ============================================================================
@@ -423,7 +555,7 @@ async function updateService(config: Config): Promise<void> {
423
555
  const connected = allServices.filter(key => config.getCredentials(key));
424
556
 
425
557
  if (connected.length === 0) {
426
- p.log.info('No services connected yet. Run setup first.');
558
+ console.log(chalk.yellow('\n No services connected yet.\n'));
427
559
  return;
428
560
  }
429
561
 
@@ -437,7 +569,7 @@ async function updateService(config: Config): Promise<void> {
437
569
 
438
570
  if (p.isCancel(selected)) return;
439
571
 
440
- await connectService(config, SERVICES[selected as string]);
572
+ await connectServiceWithInstructions(config, SERVICES[selected as string], false);
441
573
  }
442
574
 
443
575
  // ============================================================================
@@ -445,12 +577,17 @@ async function updateService(config: Config): Promise<void> {
445
577
  // ============================================================================
446
578
 
447
579
  async function resetConfig(config: Config): Promise<void> {
580
+ console.log(chalk.yellow('\n ⚠️ This will remove ALL your saved API keys.\n'));
581
+
448
582
  const confirm = await p.confirm({
449
- message: 'Are you sure? This will remove ALL credentials.',
583
+ message: 'Are you sure?',
450
584
  initialValue: false,
451
585
  });
452
586
 
453
- if (!confirm || p.isCancel(confirm)) return;
587
+ if (!confirm || p.isCancel(confirm)) {
588
+ console.log(chalk.dim('\n Cancelled.\n'));
589
+ return;
590
+ }
454
591
 
455
592
  // Clear all credentials
456
593
  const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
@@ -458,5 +595,6 @@ async function resetConfig(config: Config): Promise<void> {
458
595
  config.setCredentials(key, null as any);
459
596
  }
460
597
 
461
- p.log.success('Configuration reset. Run `codebakers setup` to reconfigure.');
598
+ console.log(chalk.green('\n ✓ All settings reset.\n'));
599
+ console.log(chalk.dim(' Run `codebakers setup` to configure again.\n'));
462
600
  }