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