codebakers 2.3.0 → 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.
- package/dist/index.js +331 -208
- package/installers/CodeBakers-Install.bat +207 -0
- package/installers/CodeBakers-Install.command +232 -0
- package/installers/README.md +157 -0
- package/installers/mac/assets/README.txt +31 -0
- package/installers/mac/build-mac-installer.sh +240 -0
- package/installers/windows/CodeBakers.iss +256 -0
- package/installers/windows/assets/README.txt +16 -0
- package/installers/windows/scripts/post-install.bat +15 -0
- package/package.json +1 -1
- package/src/commands/setup.ts +307 -169
- package/src/index.ts +92 -54
package/src/commands/setup.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
32
|
+
whyNeeded: 'This is REQUIRED. Without it, CodeBakers cannot generate any code.',
|
|
31
33
|
url: 'https://console.anthropic.com/settings/keys',
|
|
32
|
-
|
|
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: '
|
|
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
|
-
|
|
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_...
|
|
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: '
|
|
82
|
+
whyNeeded: 'Needed to deploy apps automatically. Skip if you\'ll deploy manually.',
|
|
69
83
|
url: 'https://vercel.com/account/tokens',
|
|
70
|
-
|
|
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: '
|
|
102
|
+
whyNeeded: 'Needed for database features. Skip if you\'ll set up databases manually.',
|
|
82
103
|
url: 'https://supabase.com/dashboard/account/tokens',
|
|
83
|
-
|
|
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
|
|
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
|
|
121
|
+
whyNeeded: 'Optional. Only if you want to use OpenAI alongside Claude.',
|
|
95
122
|
url: 'https://platform.openai.com/api-keys',
|
|
96
|
-
|
|
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
|
|
138
|
+
whyNeeded: 'Optional. Only if your app accepts payments.',
|
|
108
139
|
url: 'https://dashboard.stripe.com/apikeys',
|
|
109
|
-
|
|
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
|
|
154
|
+
whyNeeded: 'Optional. Only if your app sends SMS or makes calls.',
|
|
121
155
|
url: 'https://console.twilio.com/',
|
|
122
|
-
|
|
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
|
|
178
|
+
whyNeeded: 'Optional. Only if your app sends emails.',
|
|
141
179
|
url: 'https://resend.com/api-keys',
|
|
142
|
-
|
|
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
|
|
195
|
+
whyNeeded: 'Optional. Only if you\'re building voice agents.',
|
|
154
196
|
url: 'https://dashboard.vapi.ai/',
|
|
155
|
-
|
|
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
|
|
212
|
+
whyNeeded: 'Optional. Only if you want AI-generated voices.',
|
|
167
213
|
url: 'https://elevenlabs.io/app/settings/api-keys',
|
|
168
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
246
|
+
// =========================================================================
|
|
247
|
+
// FIRST TIME SETUP - Step by step with clear instructions
|
|
248
|
+
// =========================================================================
|
|
225
249
|
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
265
|
+
console.log(chalk.white(' Anthropic (Claude)'));
|
|
266
|
+
console.log(chalk.dim(' Powers the AI coding agent\n'));
|
|
231
267
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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 (!
|
|
242
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
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
|
-
|
|
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
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
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
|
|
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))
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
//
|
|
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
|
|
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:
|
|
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
|
-
|
|
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(
|
|
466
|
+
console.log(chalk.green(`\n ✓ ${service.name} connected successfully!\n`));
|
|
362
467
|
|
|
363
468
|
return true;
|
|
364
469
|
}
|
|
365
470
|
|
|
366
471
|
// ============================================================================
|
|
367
|
-
//
|
|
472
|
+
// MANAGEMENT MENU (for already-configured users)
|
|
368
473
|
// ============================================================================
|
|
369
474
|
|
|
370
|
-
function
|
|
371
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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?
|
|
583
|
+
message: 'Are you sure?',
|
|
450
584
|
initialValue: false,
|
|
451
585
|
});
|
|
452
586
|
|
|
453
|
-
if (!confirm || p.isCancel(confirm))
|
|
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
|
-
|
|
598
|
+
console.log(chalk.green('\n ✓ All settings reset.\n'));
|
|
599
|
+
console.log(chalk.dim(' Run `codebakers setup` to configure again.\n'));
|
|
462
600
|
}
|