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.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +441 -366
- 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 +306 -172
- package/src/index.ts +295 -198
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,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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
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
|
|
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))
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
//
|
|
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
|
|
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:
|
|
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
|
-
|
|
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(
|
|
466
|
+
console.log(chalk.green(`\n ✓ ${service.name} connected successfully!\n`));
|
|
366
467
|
|
|
367
468
|
return true;
|
|
368
469
|
}
|
|
369
470
|
|
|
370
471
|
// ============================================================================
|
|
371
|
-
//
|
|
472
|
+
// MANAGEMENT MENU (for already-configured users)
|
|
372
473
|
// ============================================================================
|
|
373
474
|
|
|
374
|
-
function
|
|
375
|
-
|
|
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
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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?
|
|
583
|
+
message: 'Are you sure?',
|
|
454
584
|
initialValue: false,
|
|
455
585
|
});
|
|
456
586
|
|
|
457
|
-
if (!confirm || p.isCancel(confirm))
|
|
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
|
-
|
|
598
|
+
console.log(chalk.green('\n ✓ All settings reset.\n'));
|
|
599
|
+
console.log(chalk.dim(' Run `codebakers setup` to configure again.\n'));
|
|
466
600
|
}
|