codebakers 2.2.2 → 2.3.4

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,160 +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
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
- p.outro(chalk.green(`
285
- Setup complete! ${connectedCount} services connected.
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);
286
365
 
287
- Run ${chalk.bold('codebakers')} to get started.
288
- `));
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
+ ));
289
381
  }
290
382
 
291
383
  // ============================================================================
292
- // CONNECT SERVICE
384
+ // CONNECT SERVICE WITH DETAILED INSTRUCTIONS
293
385
  // ============================================================================
294
386
 
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('');
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
+ }
300
400
 
301
- // Ask to proceed or skip
401
+ // Ask if they want to connect or skip
302
402
  const action = await p.select({
303
403
  message: `Connect ${service.name}?`,
304
- options: [
305
- { 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' },
306
408
  { value: 'skip', label: '→ Skip for now' },
307
- { value: 'why', label: '? Why do I need this?' },
308
409
  ],
309
410
  });
310
411
 
311
- if (p.isCancel(action)) return false;
312
-
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;
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`));
324
415
  }
325
- } else if (action === 'skip') {
326
- console.log(chalk.dim(` Skipped ${service.name}`));
327
- console.log(chalk.dim(` ${service.whyNeeded}`));
328
416
  return false;
329
417
  }
330
418
 
331
- // 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
332
427
  const openBrowser = await p.confirm({
333
- message: 'Open browser to get credentials?',
428
+ message: 'Open the website in your browser?',
334
429
  initialValue: true,
335
430
  });
336
431
 
337
432
  if (openBrowser && !p.isCancel(openBrowser)) {
338
- console.log(chalk.dim(`\n Opening: ${service.url}`));
339
- console.log(chalk.dim(` ${service.urlDescription}\n`));
433
+ console.log(chalk.dim(`\n Opening: ${service.url}\n`));
340
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'));
341
438
  }
342
439
 
343
440
  // Collect credentials
@@ -345,7 +442,7 @@ async function connectService(config: Config, service: ServiceDef): Promise<bool
345
442
 
346
443
  for (const field of service.fields) {
347
444
  const value = await p.text({
348
- message: `${field.label}:`,
445
+ message: field.label + (field.hint ? chalk.dim(` (${field.hint})`) : '') + ':',
349
446
  placeholder: field.placeholder,
350
447
  validate: field.validate,
351
448
  });
@@ -353,7 +450,11 @@ async function connectService(config: Config, service: ServiceDef): Promise<bool
353
450
  if (p.isCancel(value)) return false;
354
451
 
355
452
  if (!value) {
356
- 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`));
357
458
  return false;
358
459
  }
359
460
 
@@ -362,34 +463,61 @@ async function connectService(config: Config, service: ServiceDef): Promise<bool
362
463
 
363
464
  // Save credentials
364
465
  config.setCredentials(service.key, credentials);
365
- console.log(chalk.green(` ✓ ${service.name} connected!`));
466
+ console.log(chalk.green(`\n ✓ ${service.name} connected successfully!\n`));
366
467
 
367
468
  return true;
368
469
  }
369
470
 
370
471
  // ============================================================================
371
- // VIEW CONNECTED SERVICES
472
+ // MANAGEMENT MENU (for already-configured users)
372
473
  // ============================================================================
373
474
 
374
- function showConnectedServices(config: Config): void {
375
- console.log(chalk.bold('\n Connected Services:\n'));
376
-
475
+ async function showManagementMenu(config: Config): Promise<void> {
476
+ // Show current status
477
+ console.log(chalk.bold.cyan('\n CodeBakers Settings\n'));
478
+
377
479
  const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
378
- 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('');
379
495
 
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)`));
389
- }
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;
390
508
  }
391
509
 
392
- 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
+ }
393
521
  }
394
522
 
395
523
  // ============================================================================
@@ -401,7 +529,7 @@ async function addService(config: Config): Promise<void> {
401
529
  const unconnected = allServices.filter(key => !config.getCredentials(key));
402
530
 
403
531
  if (unconnected.length === 0) {
404
- p.log.info('All services are already connected!');
532
+ console.log(chalk.green('\n All services are already connected!\n'));
405
533
  return;
406
534
  }
407
535
 
@@ -415,7 +543,7 @@ async function addService(config: Config): Promise<void> {
415
543
 
416
544
  if (p.isCancel(selected)) return;
417
545
 
418
- await connectService(config, SERVICES[selected as string]);
546
+ await connectServiceWithInstructions(config, SERVICES[selected as string], false);
419
547
  }
420
548
 
421
549
  // ============================================================================
@@ -427,7 +555,7 @@ async function updateService(config: Config): Promise<void> {
427
555
  const connected = allServices.filter(key => config.getCredentials(key));
428
556
 
429
557
  if (connected.length === 0) {
430
- p.log.info('No services connected yet. Run setup first.');
558
+ console.log(chalk.yellow('\n No services connected yet.\n'));
431
559
  return;
432
560
  }
433
561
 
@@ -441,7 +569,7 @@ async function updateService(config: Config): Promise<void> {
441
569
 
442
570
  if (p.isCancel(selected)) return;
443
571
 
444
- await connectService(config, SERVICES[selected as string]);
572
+ await connectServiceWithInstructions(config, SERVICES[selected as string], false);
445
573
  }
446
574
 
447
575
  // ============================================================================
@@ -449,12 +577,17 @@ async function updateService(config: Config): Promise<void> {
449
577
  // ============================================================================
450
578
 
451
579
  async function resetConfig(config: Config): Promise<void> {
580
+ console.log(chalk.yellow('\n ⚠️ This will remove ALL your saved API keys.\n'));
581
+
452
582
  const confirm = await p.confirm({
453
- message: 'Are you sure? This will remove ALL credentials.',
583
+ message: 'Are you sure?',
454
584
  initialValue: false,
455
585
  });
456
586
 
457
- if (!confirm || p.isCancel(confirm)) return;
587
+ if (!confirm || p.isCancel(confirm)) {
588
+ console.log(chalk.dim('\n Cancelled.\n'));
589
+ return;
590
+ }
458
591
 
459
592
  // Clear all credentials
460
593
  const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
@@ -462,5 +595,6 @@ async function resetConfig(config: Config): Promise<void> {
462
595
  config.setCredentials(key, null as any);
463
596
  }
464
597
 
465
- 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'));
466
600
  }