codebakers 2.1.0 → 2.2.0

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 CHANGED
@@ -11,8 +11,8 @@ import {
11
11
 
12
12
  // src/index.ts
13
13
  import { Command } from "commander";
14
- import * as p20 from "@clack/prompts";
15
- import chalk20 from "chalk";
14
+ import * as p21 from "@clack/prompts";
15
+ import chalk21 from "chalk";
16
16
  import boxen from "boxen";
17
17
  import gradient from "gradient-string";
18
18
 
@@ -43,6 +43,159 @@ async function checkForUpdates() {
43
43
  import * as p from "@clack/prompts";
44
44
  import chalk2 from "chalk";
45
45
  import open from "open";
46
+ var SERVICES = {
47
+ anthropic: {
48
+ key: "anthropic",
49
+ name: "Anthropic (Claude)",
50
+ description: "Powers the AI coding agent",
51
+ whyNeeded: "Without this, CodeBakers cannot generate any code. This is the brain of the system.",
52
+ url: "https://console.anthropic.com/settings/keys",
53
+ urlDescription: "Create an API key",
54
+ fields: [{
55
+ key: "apiKey",
56
+ label: "Anthropic API Key",
57
+ placeholder: "sk-ant-...",
58
+ validate: (v) => {
59
+ if (!v) return "API key is required";
60
+ if (!v.startsWith("sk-ant-")) return "Should start with sk-ant-";
61
+ return void 0;
62
+ }
63
+ }]
64
+ },
65
+ github: {
66
+ key: "github",
67
+ name: "GitHub",
68
+ description: "Create repos and push code",
69
+ whyNeeded: "Without this, CodeBakers cannot create repositories or save your code to GitHub.",
70
+ url: "https://github.com/settings/tokens/new?scopes=repo,user&description=CodeBakers",
71
+ urlDescription: 'Create a token with "repo" and "user" scopes checked',
72
+ fields: [{
73
+ key: "token",
74
+ label: "GitHub Token",
75
+ placeholder: "ghp_... or github_pat_...",
76
+ validate: (v) => {
77
+ if (!v) return "Token is required";
78
+ if (!v.startsWith("ghp_") && !v.startsWith("github_pat_")) {
79
+ return "Should start with ghp_ or github_pat_";
80
+ }
81
+ return void 0;
82
+ }
83
+ }]
84
+ },
85
+ vercel: {
86
+ key: "vercel",
87
+ name: "Vercel",
88
+ description: "Deploy your apps to production",
89
+ whyNeeded: "Without this, CodeBakers cannot deploy your apps. You'll have to deploy manually.",
90
+ url: "https://vercel.com/account/tokens",
91
+ urlDescription: 'Click "Create" to generate a new token',
92
+ fields: [{
93
+ key: "token",
94
+ label: "Vercel Token",
95
+ placeholder: "vercel_..."
96
+ }]
97
+ },
98
+ supabase: {
99
+ key: "supabase",
100
+ name: "Supabase",
101
+ description: "Database and authentication",
102
+ whyNeeded: "Without this, CodeBakers cannot create databases for your apps. You'll need to set up databases manually.",
103
+ url: "https://supabase.com/dashboard/account/tokens",
104
+ urlDescription: "Generate a new access token",
105
+ fields: [{
106
+ key: "accessToken",
107
+ label: "Supabase Access Token",
108
+ placeholder: "sbp_..."
109
+ }]
110
+ },
111
+ openai: {
112
+ key: "openai",
113
+ name: "OpenAI",
114
+ description: "GPT models, embeddings, DALL-E",
115
+ whyNeeded: "Optional. Only needed if you want to use OpenAI models instead of or alongside Claude.",
116
+ url: "https://platform.openai.com/api-keys",
117
+ urlDescription: "Create a new API key",
118
+ fields: [{
119
+ key: "apiKey",
120
+ label: "OpenAI API Key",
121
+ placeholder: "sk-..."
122
+ }]
123
+ },
124
+ stripe: {
125
+ key: "stripe",
126
+ name: "Stripe",
127
+ description: "Payment processing",
128
+ whyNeeded: "Optional. Only needed if your app accepts payments.",
129
+ url: "https://dashboard.stripe.com/apikeys",
130
+ urlDescription: "Copy your Secret key (sk_live or sk_test)",
131
+ fields: [{
132
+ key: "secretKey",
133
+ label: "Stripe Secret Key",
134
+ placeholder: "sk_live_... or sk_test_..."
135
+ }]
136
+ },
137
+ twilio: {
138
+ key: "twilio",
139
+ name: "Twilio",
140
+ description: "SMS, voice calls, WhatsApp",
141
+ whyNeeded: "Optional. Only needed if your app sends SMS or makes calls.",
142
+ url: "https://console.twilio.com/",
143
+ urlDescription: "Find Account SID and Auth Token on the dashboard",
144
+ fields: [
145
+ {
146
+ key: "accountSid",
147
+ label: "Account SID",
148
+ placeholder: "AC..."
149
+ },
150
+ {
151
+ key: "authToken",
152
+ label: "Auth Token",
153
+ placeholder: "..."
154
+ }
155
+ ]
156
+ },
157
+ resend: {
158
+ key: "resend",
159
+ name: "Resend",
160
+ description: "Email sending",
161
+ whyNeeded: "Optional. Only needed if your app sends emails.",
162
+ url: "https://resend.com/api-keys",
163
+ urlDescription: "Create an API key",
164
+ fields: [{
165
+ key: "apiKey",
166
+ label: "Resend API Key",
167
+ placeholder: "re_..."
168
+ }]
169
+ },
170
+ vapi: {
171
+ key: "vapi",
172
+ name: "VAPI",
173
+ description: "Voice AI agents",
174
+ whyNeeded: "Optional. Only needed if you're building voice agents.",
175
+ url: "https://dashboard.vapi.ai/",
176
+ urlDescription: "Copy your API key from the dashboard",
177
+ fields: [{
178
+ key: "apiKey",
179
+ label: "VAPI API Key",
180
+ placeholder: "..."
181
+ }]
182
+ },
183
+ elevenlabs: {
184
+ key: "elevenlabs",
185
+ name: "ElevenLabs",
186
+ description: "AI voice generation",
187
+ whyNeeded: "Optional. Only needed if you want AI-generated voices.",
188
+ url: "https://elevenlabs.io/app/settings/api-keys",
189
+ urlDescription: "Create an API key",
190
+ fields: [{
191
+ key: "apiKey",
192
+ label: "ElevenLabs API Key",
193
+ placeholder: "..."
194
+ }]
195
+ }
196
+ };
197
+ var CORE_SERVICES = ["anthropic", "github", "vercel", "supabase"];
198
+ var OPTIONAL_SERVICES = ["openai", "stripe", "twilio", "resend", "vapi", "elevenlabs"];
46
199
  async function setupCommand() {
47
200
  const config = new Config();
48
201
  p.intro(chalk2.bgCyan.black(" CodeBakers Setup "));
@@ -52,7 +205,7 @@ async function setupCommand() {
52
205
  options: [
53
206
  { value: "view", label: "\u{1F440} View connected services" },
54
207
  { value: "add", label: "\u2795 Add another service" },
55
- { value: "update", label: "\u{1F504} Update credentials" },
208
+ { value: "update", label: "\u{1F504} Update a service" },
56
209
  { value: "reset", label: "\u{1F5D1}\uFE0F Reset all configuration" },
57
210
  { value: "back", label: "\u2190 Back" }
58
211
  ]
@@ -60,341 +213,198 @@ async function setupCommand() {
60
213
  if (p.isCancel(action) || action === "back") {
61
214
  return;
62
215
  }
63
- if (action === "view") {
64
- showConnectedServices(config);
65
- return;
66
- }
67
- if (action === "reset") {
68
- const confirm11 = await p.confirm({
69
- message: "Are you sure? This will remove all credentials and settings."
70
- });
71
- if (confirm11) {
72
- p.outro(chalk2.yellow("Configuration reset. Run `codebakers setup` again."));
73
- }
74
- return;
216
+ switch (action) {
217
+ case "view":
218
+ showConnectedServices(config);
219
+ return;
220
+ case "add":
221
+ await addService(config);
222
+ return;
223
+ case "update":
224
+ await updateService(config);
225
+ return;
226
+ case "reset":
227
+ await resetConfig(config);
228
+ return;
75
229
  }
76
- if (action === "add") {
77
- await addService(config);
78
- return;
230
+ }
231
+ console.log(chalk2.cyan(`
232
+ Welcome to CodeBakers! Let's connect your services.
233
+
234
+ ${chalk2.dim("Your credentials are stored locally in ~/.codebakers/")}
235
+ ${chalk2.dim("and are never sent to our servers.")}
236
+
237
+ ${chalk2.bold("Core Services (Recommended):")}
238
+ ${chalk2.dim("These power the main CodeBakers features.")}
239
+
240
+ `));
241
+ let skippedCore = [];
242
+ for (const serviceKey of CORE_SERVICES) {
243
+ const service = SERVICES[serviceKey];
244
+ const connected = await connectService(config, service);
245
+ if (!connected) {
246
+ skippedCore.push(service.name);
79
247
  }
80
248
  }
81
- p.note(
82
- `Let's connect your accounts. This only takes a few minutes.
83
-
84
- ${chalk2.dim("Your credentials are stored locally in ~/.codebakers/")}
85
- ${chalk2.dim("and are never sent to our servers.")}`,
86
- "Welcome to CodeBakers!"
87
- );
88
- const requiredServices = [
89
- { name: "GitHub", key: "github", required: true },
90
- { name: "Vercel", key: "vercel", required: true },
91
- { name: "Supabase", key: "supabase", required: true },
92
- { name: "Anthropic (Claude)", key: "anthropic", required: true }
93
- ];
94
- const optionalServices = [
95
- { name: "OpenAI", key: "openai" },
96
- { name: "Stripe", key: "stripe" },
97
- { name: "Twilio", key: "twilio" },
98
- { name: "VAPI", key: "vapi" },
99
- { name: "Resend", key: "resend" },
100
- { name: "ElevenLabs", key: "elevenLabs" },
101
- { name: "Microsoft Graph", key: "microsoft" },
102
- { name: "Google APIs", key: "google" }
103
- ];
104
- p.log.step("Connecting required services...");
105
- for (const service of requiredServices) {
106
- await connectService(config, service.key, service.name, true);
249
+ if (skippedCore.length > 0) {
250
+ console.log(chalk2.yellow(`
251
+ \u26A0\uFE0F You skipped: ${skippedCore.join(", ")}
252
+
253
+ Some features won't work without these services.
254
+ Run ${chalk2.bold("codebakers setup")} anytime to add them.
255
+ `));
107
256
  }
108
257
  const addOptional = await p.confirm({
109
- message: "Do you want to connect optional services? (Stripe, Twilio, etc.)",
258
+ message: "Add optional services? (Stripe, Twilio, Resend, etc.)",
110
259
  initialValue: false
111
260
  });
112
261
  if (addOptional && !p.isCancel(addOptional)) {
113
262
  const selected = await p.multiselect({
114
- message: "Select services to connect:",
115
- options: optionalServices.map((s) => ({
116
- value: s.key,
117
- label: s.name
263
+ message: "Select services to add:",
264
+ options: OPTIONAL_SERVICES.map((key) => ({
265
+ value: key,
266
+ label: `${SERVICES[key].name} - ${SERVICES[key].description}`
118
267
  })),
119
268
  required: false
120
269
  });
121
- if (!p.isCancel(selected)) {
270
+ if (!p.isCancel(selected) && Array.isArray(selected)) {
122
271
  for (const serviceKey of selected) {
123
- const service = optionalServices.find((s) => s.key === serviceKey);
124
- if (service) {
125
- await connectService(config, service.key, service.name, false);
126
- }
272
+ await connectService(config, SERVICES[serviceKey]);
127
273
  }
128
274
  }
129
275
  }
130
- p.log.step("Installing CodeBakers patterns...");
131
- await installPatterns(config);
132
- p.outro(chalk2.green("\u2713 Setup complete! Run `codebakers init` to create your first project."));
276
+ const connectedCount = [...CORE_SERVICES, ...OPTIONAL_SERVICES].filter(
277
+ (key) => config.getCredentials(key)
278
+ ).length;
279
+ p.outro(chalk2.green(`
280
+ \u2713 Setup complete! ${connectedCount} services connected.
281
+
282
+ Run ${chalk2.bold("codebakers")} to get started.
283
+ `));
133
284
  }
134
- async function connectService(config, serviceKey, serviceName, required) {
135
- const spinner16 = p.spinner();
136
- switch (serviceKey) {
137
- case "github": {
138
- p.log.info(`${chalk2.bold("GitHub")} - Opens browser for OAuth authorization`);
139
- const proceed = await p.confirm({
140
- message: "Open browser to authorize GitHub?",
141
- initialValue: true
142
- });
143
- if (p.isCancel(proceed) || !proceed) {
144
- if (required) {
145
- p.log.warn("GitHub is required. Skipping for now.");
146
- }
147
- return false;
148
- }
149
- p.log.info(chalk2.dim("Opening browser..."));
150
- await open("https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&scope=repo,user");
151
- const token = await p.text({
152
- message: "Paste your GitHub token (or press Enter if OAuth completed):",
153
- placeholder: "ghp_..."
154
- });
155
- if (!p.isCancel(token) && token) {
156
- config.setCredentials("github", { token });
157
- p.log.success("GitHub connected!");
158
- return true;
159
- }
160
- break;
161
- }
162
- case "vercel": {
163
- p.log.info(`${chalk2.bold("Vercel")} - Opens browser for OAuth authorization`);
164
- const proceed = await p.confirm({
165
- message: "Open browser to authorize Vercel?",
166
- initialValue: true
167
- });
168
- if (p.isCancel(proceed) || !proceed) {
169
- if (required) {
170
- p.log.warn("Vercel is required. Skipping for now.");
171
- }
172
- return false;
173
- }
174
- p.log.info(chalk2.dim("Opening browser..."));
175
- await open("https://vercel.com/account/tokens");
176
- const token = await p.text({
177
- message: "Paste your Vercel token:",
178
- placeholder: "vercel_..."
179
- });
180
- if (!p.isCancel(token) && token) {
181
- config.setCredentials("vercel", { token });
182
- p.log.success("Vercel connected!");
183
- return true;
184
- }
185
- break;
186
- }
187
- case "supabase": {
188
- p.log.info(`${chalk2.bold("Supabase")} - Opens browser for OAuth authorization`);
189
- const proceed = await p.confirm({
190
- message: "Open browser to authorize Supabase?",
191
- initialValue: true
192
- });
193
- if (p.isCancel(proceed) || !proceed) {
194
- if (required) {
195
- p.log.warn("Supabase is required. Skipping for now.");
196
- }
197
- return false;
198
- }
199
- p.log.info(chalk2.dim("Opening browser..."));
200
- await open("https://supabase.com/dashboard/account/tokens");
201
- const token = await p.text({
202
- message: "Paste your Supabase access token:",
203
- placeholder: "sbp_..."
204
- });
205
- if (!p.isCancel(token) && token) {
206
- config.setCredentials("supabase", { accessToken: token });
207
- p.log.success("Supabase connected!");
208
- return true;
209
- }
210
- break;
211
- }
212
- case "anthropic": {
213
- p.log.info(`${chalk2.bold("Anthropic (Claude)")} - Powers the AI coding agent`);
214
- const openBrowser = await p.confirm({
215
- message: "Open browser to get API key?",
216
- initialValue: true
217
- });
218
- if (openBrowser && !p.isCancel(openBrowser)) {
219
- await open("https://console.anthropic.com/settings/keys");
220
- }
221
- const apiKey = await p.text({
222
- message: "Paste your Anthropic API key:",
223
- placeholder: "sk-ant-...",
224
- validate: (value) => {
225
- if (!value && required) return "API key is required";
226
- if (value && !value.startsWith("sk-ant-")) return "Invalid API key format";
227
- return void 0;
228
- }
229
- });
230
- if (!p.isCancel(apiKey) && apiKey) {
231
- config.setCredentials("anthropic", { apiKey });
232
- p.log.success("Anthropic connected!");
233
- return true;
234
- }
235
- break;
236
- }
237
- case "openai": {
238
- const openBrowser = await p.confirm({
239
- message: "Open browser to get OpenAI API key?",
240
- initialValue: true
241
- });
242
- if (openBrowser && !p.isCancel(openBrowser)) {
243
- await open("https://platform.openai.com/api-keys");
244
- }
245
- const apiKey = await p.text({
246
- message: "Paste your OpenAI API key:",
247
- placeholder: "sk-..."
248
- });
249
- if (!p.isCancel(apiKey) && apiKey) {
250
- config.setCredentials("openai", { apiKey });
251
- p.log.success("OpenAI connected!");
252
- return true;
253
- }
254
- break;
255
- }
256
- case "stripe": {
257
- const openBrowser = await p.confirm({
258
- message: "Open browser to get Stripe API keys?",
259
- initialValue: true
260
- });
261
- if (openBrowser && !p.isCancel(openBrowser)) {
262
- await open("https://dashboard.stripe.com/apikeys");
263
- }
264
- const secretKey = await p.text({
265
- message: "Paste your Stripe secret key:",
266
- placeholder: "sk_live_... or sk_test_..."
267
- });
268
- if (!p.isCancel(secretKey) && secretKey) {
269
- config.setCredentials("stripe", { secretKey });
270
- p.log.success("Stripe connected!");
271
- return true;
272
- }
273
- break;
274
- }
275
- case "twilio": {
276
- const openBrowser = await p.confirm({
277
- message: "Open browser to get Twilio credentials?",
278
- initialValue: true
279
- });
280
- if (openBrowser && !p.isCancel(openBrowser)) {
281
- await open("https://console.twilio.com/");
282
- }
283
- const accountSid = await p.text({
284
- message: "Paste your Twilio Account SID:",
285
- placeholder: "AC..."
286
- });
287
- const authToken = await p.text({
288
- message: "Paste your Twilio Auth Token:",
289
- placeholder: "..."
290
- });
291
- if (!p.isCancel(accountSid) && !p.isCancel(authToken) && accountSid && authToken) {
292
- config.setCredentials("twilio", {
293
- accountSid,
294
- authToken
295
- });
296
- p.log.success("Twilio connected!");
297
- return true;
298
- }
299
- break;
285
+ async function connectService(config, service) {
286
+ console.log("");
287
+ console.log(chalk2.bold(` ${service.name}`));
288
+ console.log(chalk2.dim(` ${service.description}`));
289
+ console.log("");
290
+ const action = await p.select({
291
+ message: `Connect ${service.name}?`,
292
+ options: [
293
+ { value: "connect", label: "\u2713 Yes, connect now" },
294
+ { value: "skip", label: "\u2192 Skip for now" },
295
+ { value: "why", label: "? Why do I need this?" }
296
+ ]
297
+ });
298
+ if (p.isCancel(action)) return false;
299
+ if (action === "why") {
300
+ console.log(chalk2.yellow(`
301
+ ${service.whyNeeded}
302
+ `));
303
+ const proceed = await p.confirm({
304
+ message: `Connect ${service.name}?`,
305
+ initialValue: true
306
+ });
307
+ if (!proceed || p.isCancel(proceed)) {
308
+ console.log(chalk2.dim(` Skipped ${service.name}`));
309
+ return false;
300
310
  }
301
- case "vapi": {
302
- const openBrowser = await p.confirm({
303
- message: "Open browser to get VAPI API key?",
304
- initialValue: true
305
- });
306
- if (openBrowser && !p.isCancel(openBrowser)) {
307
- await open("https://dashboard.vapi.ai/");
308
- }
309
- const apiKey = await p.text({
310
- message: "Paste your VAPI API key:",
311
- placeholder: "..."
312
- });
313
- if (!p.isCancel(apiKey) && apiKey) {
314
- config.setCredentials("vapi", { apiKey });
315
- p.log.success("VAPI connected!");
316
- return true;
317
- }
318
- break;
311
+ } else if (action === "skip") {
312
+ console.log(chalk2.dim(` Skipped ${service.name}`));
313
+ console.log(chalk2.dim(` ${service.whyNeeded}`));
314
+ return false;
315
+ }
316
+ const openBrowser = await p.confirm({
317
+ message: "Open browser to get credentials?",
318
+ initialValue: true
319
+ });
320
+ if (openBrowser && !p.isCancel(openBrowser)) {
321
+ console.log(chalk2.dim(`
322
+ Opening: ${service.url}`));
323
+ console.log(chalk2.dim(` ${service.urlDescription}
324
+ `));
325
+ await open(service.url);
326
+ }
327
+ const credentials = {};
328
+ for (const field of service.fields) {
329
+ const value = await p.text({
330
+ message: `${field.label}:`,
331
+ placeholder: field.placeholder,
332
+ validate: field.validate
333
+ });
334
+ if (p.isCancel(value)) return false;
335
+ if (!value) {
336
+ console.log(chalk2.dim(` Skipped ${service.name} (no ${field.label} provided)`));
337
+ return false;
319
338
  }
320
- case "resend": {
321
- const openBrowser = await p.confirm({
322
- message: "Open browser to get Resend API key?",
323
- initialValue: true
324
- });
325
- if (openBrowser && !p.isCancel(openBrowser)) {
326
- await open("https://resend.com/api-keys");
327
- }
328
- const apiKey = await p.text({
329
- message: "Paste your Resend API key:",
330
- placeholder: "re_..."
331
- });
332
- if (!p.isCancel(apiKey) && apiKey) {
333
- config.setCredentials("resend", { apiKey });
334
- p.log.success("Resend connected!");
335
- return true;
336
- }
337
- break;
339
+ credentials[field.key] = value;
340
+ }
341
+ config.setCredentials(service.key, credentials);
342
+ console.log(chalk2.green(` \u2713 ${service.name} connected!`));
343
+ return true;
344
+ }
345
+ function showConnectedServices(config) {
346
+ console.log(chalk2.bold("\n Connected Services:\n"));
347
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
348
+ let connectedCount = 0;
349
+ for (const key of allServices) {
350
+ const service = SERVICES[key];
351
+ const creds = config.getCredentials(key);
352
+ if (creds) {
353
+ console.log(chalk2.green(` \u2713 ${service.name}`));
354
+ connectedCount++;
355
+ } else {
356
+ console.log(chalk2.dim(` \u25CB ${service.name} (not connected)`));
338
357
  }
339
- default:
340
- p.log.warn(`Service ${serviceName} not yet implemented`);
341
- return false;
342
358
  }
343
- return false;
359
+ console.log(chalk2.dim(`
360
+ ${connectedCount}/${allServices.length} services connected
361
+ `));
344
362
  }
345
363
  async function addService(config) {
346
- const services = [
347
- { value: "github", label: "GitHub" },
348
- { value: "vercel", label: "Vercel" },
349
- { value: "supabase", label: "Supabase" },
350
- { value: "anthropic", label: "Anthropic (Claude)" },
351
- { value: "openai", label: "OpenAI" },
352
- { value: "stripe", label: "Stripe" },
353
- { value: "twilio", label: "Twilio" },
354
- { value: "vapi", label: "VAPI" },
355
- { value: "resend", label: "Resend" },
356
- { value: "elevenLabs", label: "ElevenLabs" },
357
- { value: "microsoft", label: "Microsoft Graph" },
358
- { value: "google", label: "Google APIs" }
359
- ];
360
- const service = await p.select({
361
- message: "Which service do you want to connect?",
362
- options: services
364
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
365
+ const unconnected = allServices.filter((key) => !config.getCredentials(key));
366
+ if (unconnected.length === 0) {
367
+ p.log.info("All services are already connected!");
368
+ return;
369
+ }
370
+ const selected = await p.select({
371
+ message: "Select service to add:",
372
+ options: unconnected.map((key) => ({
373
+ value: key,
374
+ label: `${SERVICES[key].name} - ${SERVICES[key].description}`
375
+ }))
363
376
  });
364
- if (!p.isCancel(service)) {
365
- const serviceInfo = services.find((s) => s.value === service);
366
- if (serviceInfo) {
367
- await connectService(config, serviceInfo.value, serviceInfo.label, false);
368
- }
377
+ if (p.isCancel(selected)) return;
378
+ await connectService(config, SERVICES[selected]);
379
+ }
380
+ async function updateService(config) {
381
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
382
+ const connected = allServices.filter((key) => config.getCredentials(key));
383
+ if (connected.length === 0) {
384
+ p.log.info("No services connected yet. Run setup first.");
385
+ return;
369
386
  }
387
+ const selected = await p.select({
388
+ message: "Select service to update:",
389
+ options: connected.map((key) => ({
390
+ value: key,
391
+ label: SERVICES[key].name
392
+ }))
393
+ });
394
+ if (p.isCancel(selected)) return;
395
+ await connectService(config, SERVICES[selected]);
370
396
  }
371
- function showConnectedServices(config) {
372
- const services = [
373
- { key: "github", name: "GitHub" },
374
- { key: "vercel", name: "Vercel" },
375
- { key: "supabase", name: "Supabase" },
376
- { key: "anthropic", name: "Anthropic" },
377
- { key: "openai", name: "OpenAI" },
378
- { key: "stripe", name: "Stripe" },
379
- { key: "twilio", name: "Twilio" },
380
- { key: "vapi", name: "VAPI" },
381
- { key: "resend", name: "Resend" },
382
- { key: "elevenLabs", name: "ElevenLabs" },
383
- { key: "microsoft", name: "Microsoft" },
384
- { key: "google", name: "Google" }
385
- ];
386
- console.log("\n" + chalk2.bold("Connected Services:") + "\n");
387
- for (const service of services) {
388
- const creds = config.getCredentials(service.key);
389
- const isConnected = creds && Object.values(creds).some((v) => v);
390
- const status = isConnected ? chalk2.green("\u2713 Connected") : chalk2.dim("\u25CB Not connected");
391
- console.log(` ${service.name.padEnd(15)} ${status}`);
397
+ async function resetConfig(config) {
398
+ const confirm13 = await p.confirm({
399
+ message: "Are you sure? This will remove ALL credentials.",
400
+ initialValue: false
401
+ });
402
+ if (!confirm13 || p.isCancel(confirm13)) return;
403
+ const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
404
+ for (const key of allServices) {
405
+ config.setCredentials(key, null);
392
406
  }
393
- console.log("");
394
- }
395
- async function installPatterns(config) {
396
- const patternsDir = config.getPatternsDir();
397
- p.log.info(chalk2.dim(`Patterns installed to ${patternsDir}`));
407
+ p.log.success("Configuration reset. Run `codebakers setup` to reconfigure.");
398
408
  }
399
409
 
400
410
  // src/commands/init.ts
@@ -606,10 +616,10 @@ var SupabaseService = class {
606
616
  throw new Error("Failed to list projects");
607
617
  }
608
618
  const projects = await response.json();
609
- return projects.map((p21) => ({
610
- id: p21.id,
611
- name: p21.name,
612
- region: p21.region
619
+ return projects.map((p22) => ({
620
+ id: p22.id,
621
+ name: p22.name,
622
+ region: p22.region
613
623
  }));
614
624
  }
615
625
  };
@@ -1036,7 +1046,15 @@ Domain: ${domain || "Vercel default"}`,
1036
1046
  await createLocalProject(projectPath, projectConfig);
1037
1047
  spinner16.stop("Local project created");
1038
1048
  spinner16.start("Installing dependencies...");
1039
- await execa3("pnpm", ["install"], { cwd: projectPath });
1049
+ try {
1050
+ await execa3("npm", ["install"], { cwd: projectPath });
1051
+ } catch {
1052
+ try {
1053
+ await execa3("pnpm", ["install"], { cwd: projectPath });
1054
+ } catch {
1055
+ await execa3("yarn", ["install"], { cwd: projectPath });
1056
+ }
1057
+ }
1040
1058
  spinner16.stop("Dependencies installed");
1041
1059
  if (services.includes("github")) {
1042
1060
  spinner16.start("Creating GitHub repository...");
@@ -2056,11 +2074,11 @@ async function getVoiceInput(prompt) {
2056
2074
  console.log(chalk5.green(`
2057
2075
  \u2713 Heard: "${transcription}"
2058
2076
  `));
2059
- const confirm11 = await p4.confirm({
2077
+ const confirm13 = await p4.confirm({
2060
2078
  message: "Is this correct?",
2061
2079
  initialValue: true
2062
2080
  });
2063
- if (confirm11 && !p4.isCancel(confirm11)) {
2081
+ if (confirm13 && !p4.isCancel(confirm13)) {
2064
2082
  return transcription;
2065
2083
  } else {
2066
2084
  const action = await p4.select({
@@ -2075,20 +2093,20 @@ async function getVoiceInput(prompt) {
2075
2093
  if (action === "retry") {
2076
2094
  return await getVoiceInput(prompt);
2077
2095
  } else {
2078
- const text15 = await p4.text({ message: "Type your response:" });
2079
- return p4.isCancel(text15) ? null : text15;
2096
+ const text16 = await p4.text({ message: "Type your response:" });
2097
+ return p4.isCancel(text16) ? null : text16;
2080
2098
  }
2081
2099
  }
2082
2100
  } else {
2083
2101
  console.log(chalk5.yellow("\n No speech detected. Try again or type your response.\n"));
2084
- const text15 = await p4.text({ message: "Type instead:" });
2085
- return p4.isCancel(text15) ? null : text15;
2102
+ const text16 = await p4.text({ message: "Type instead:" });
2103
+ return p4.isCancel(text16) ? null : text16;
2086
2104
  }
2087
2105
  } catch (error) {
2088
2106
  spinner16.stop("Recording failed");
2089
2107
  console.log(chalk5.yellow("Voice input failed. Please type instead."));
2090
- const text15 = await p4.text({ message: prompt });
2091
- return p4.isCancel(text15) ? null : text15;
2108
+ const text16 = await p4.text({ message: prompt });
2109
+ return p4.isCancel(text16) ? null : text16;
2092
2110
  }
2093
2111
  }
2094
2112
  async function playBeep() {
@@ -2223,10 +2241,10 @@ async function transcribeWithWhisper(audioFile) {
2223
2241
  ], { timeout: 6e4 });
2224
2242
  const txtFile = outputBase + ".txt";
2225
2243
  if (await fs4.pathExists(txtFile)) {
2226
- const text15 = await fs4.readFile(txtFile, "utf-8");
2244
+ const text16 = await fs4.readFile(txtFile, "utf-8");
2227
2245
  await fs4.remove(txtFile).catch(() => {
2228
2246
  });
2229
- return text15.trim();
2247
+ return text16.trim();
2230
2248
  }
2231
2249
  } catch {
2232
2250
  }
@@ -2333,9 +2351,9 @@ async function readFile5(filePath) {
2333
2351
  name
2334
2352
  };
2335
2353
  } else if (pdfExtensions.includes(ext)) {
2336
- const text15 = await extractPdfText(cleanPath);
2337
- if (text15) {
2338
- return { content: text15, type: "pdf", name };
2354
+ const text16 = await extractPdfText(cleanPath);
2355
+ if (text16) {
2356
+ return { content: text16, type: "pdf", name };
2339
2357
  }
2340
2358
  return {
2341
2359
  content: `[PDF file: ${name} - text extraction not available]`,
@@ -2407,19 +2425,19 @@ function formatFilesForContext(files) {
2407
2425
  context += "--- END FILES ---\n\n";
2408
2426
  return context;
2409
2427
  }
2410
- function looksLikePaste(text15) {
2411
- if (text15.includes("\n") && text15.split("\n").length > 3) return true;
2412
- if (text15.includes("function ") || text15.includes("const ") || text15.includes("import ") || text15.includes("export ") || text15.includes("class ") || text15.includes("def ") || text15.includes("public ") || text15.includes("private ")) return true;
2413
- if (text15.startsWith("{") && text15.endsWith("}") || text15.startsWith("[") && text15.endsWith("]")) return true;
2414
- if (text15.length > 200 && !text15.includes("\n")) return true;
2428
+ function looksLikePaste(text16) {
2429
+ if (text16.includes("\n") && text16.split("\n").length > 3) return true;
2430
+ if (text16.includes("function ") || text16.includes("const ") || text16.includes("import ") || text16.includes("export ") || text16.includes("class ") || text16.includes("def ") || text16.includes("public ") || text16.includes("private ")) return true;
2431
+ if (text16.startsWith("{") && text16.endsWith("}") || text16.startsWith("[") && text16.endsWith("]")) return true;
2432
+ if (text16.length > 200 && !text16.includes("\n")) return true;
2415
2433
  return false;
2416
2434
  }
2417
- async function handlePastedContent(text15) {
2418
- if (!looksLikePaste(text15)) {
2435
+ async function handlePastedContent(text16) {
2436
+ if (!looksLikePaste(text16)) {
2419
2437
  return null;
2420
2438
  }
2421
2439
  console.log(chalk6.cyan("\n\u{1F4CB} Detected pasted content!\n"));
2422
- const preview = text15.length > 200 ? text15.slice(0, 200) + "..." : text15;
2440
+ const preview = text16.length > 200 ? text16.slice(0, 200) + "..." : text16;
2423
2441
  console.log(chalk6.dim(preview));
2424
2442
  console.log("");
2425
2443
  const action = await p5.select({
@@ -2435,7 +2453,7 @@ async function handlePastedContent(text15) {
2435
2453
  });
2436
2454
  if (p5.isCancel(action)) return null;
2437
2455
  if (action === "literal") {
2438
- return { prompt: text15, context: "" };
2456
+ return { prompt: text16, context: "" };
2439
2457
  }
2440
2458
  if (action === "custom") {
2441
2459
  const instruction = await p5.text({
@@ -2449,7 +2467,7 @@ async function handlePastedContent(text15) {
2449
2467
 
2450
2468
  --- PASTED CODE ---
2451
2469
  \`\`\`
2452
- ${text15}
2470
+ ${text16}
2453
2471
  \`\`\`
2454
2472
  --- END ---
2455
2473
 
@@ -2468,7 +2486,7 @@ ${text15}
2468
2486
 
2469
2487
  --- PASTED CODE ---
2470
2488
  \`\`\`
2471
- ${text15}
2489
+ ${text16}
2472
2490
  \`\`\`
2473
2491
  --- END ---
2474
2492
 
@@ -2480,16 +2498,41 @@ ${text15}
2480
2498
  async function codeCommand(prompt, options = {}) {
2481
2499
  const config = new Config();
2482
2500
  if (!config.isConfigured()) {
2483
- p6.log.error("Please run `codebakers setup` first.");
2501
+ console.log(chalk7.yellow(`
2502
+ \u26A0\uFE0F CodeBakers isn't set up yet.
2503
+
2504
+ Run this first:
2505
+ ${chalk7.cyan("codebakers setup")}
2506
+
2507
+ This connects your API keys (Anthropic, GitHub, etc.)
2508
+ so CodeBakers can generate code and deploy for you.
2509
+ `));
2484
2510
  return;
2485
2511
  }
2486
2512
  if (!config.isInProject()) {
2487
- p6.log.error("Not in a CodeBakers project. Run `codebakers init` first.");
2513
+ console.log(chalk7.yellow(`
2514
+ \u26A0\uFE0F You're not in a CodeBakers project.
2515
+
2516
+ Options:
2517
+ ${chalk7.cyan("codebakers init")} Create a new project here
2518
+ ${chalk7.cyan("codebakers website")} Build a website by describing it
2519
+ ${chalk7.cyan("cd my-project")} Navigate to an existing project
2520
+ `));
2488
2521
  return;
2489
2522
  }
2490
2523
  const anthropicCreds = config.getCredentials("anthropic");
2491
2524
  if (!anthropicCreds?.apiKey) {
2492
- p6.log.error("Anthropic API key not configured. Run `codebakers setup`.");
2525
+ console.log(chalk7.yellow(`
2526
+ \u26A0\uFE0F Anthropic API key not configured.
2527
+
2528
+ The AI coding agent needs Claude to work.
2529
+
2530
+ Run this to add your API key:
2531
+ ${chalk7.cyan("codebakers setup")}
2532
+
2533
+ Get an API key at:
2534
+ ${chalk7.dim("https://console.anthropic.com/settings/keys")}
2535
+ `));
2493
2536
  return;
2494
2537
  }
2495
2538
  const anthropic = new Anthropic({
@@ -3077,7 +3120,30 @@ import Anthropic2 from "@anthropic-ai/sdk";
3077
3120
  async function deployCommand(options = {}) {
3078
3121
  const config = new Config();
3079
3122
  if (!config.isInProject()) {
3080
- p7.log.error("Not in a CodeBakers project.");
3123
+ console.log(chalk8.yellow(`
3124
+ \u26A0\uFE0F You're not in a CodeBakers project.
3125
+
3126
+ Navigate to your project first:
3127
+ ${chalk8.cyan("cd my-project")}
3128
+
3129
+ Or create a new one:
3130
+ ${chalk8.cyan("codebakers init")}
3131
+ `));
3132
+ return;
3133
+ }
3134
+ const vercelCreds = config.getCredentials("vercel");
3135
+ if (!vercelCreds?.token) {
3136
+ console.log(chalk8.yellow(`
3137
+ \u26A0\uFE0F Vercel isn't connected.
3138
+
3139
+ CodeBakers deploys to Vercel by default.
3140
+
3141
+ Run this to connect:
3142
+ ${chalk8.cyan("codebakers setup")}
3143
+
3144
+ Or deploy manually:
3145
+ ${chalk8.cyan("npx vercel")}
3146
+ `));
3081
3147
  return;
3082
3148
  }
3083
3149
  p7.intro(chalk8.bgCyan.black(" Deploy to Production "));
@@ -3146,7 +3212,15 @@ async function deployCommand(options = {}) {
3146
3212
  }
3147
3213
  spinner16.start("Building project...");
3148
3214
  try {
3149
- await execa7("pnpm", ["build"], { cwd: process.cwd() });
3215
+ try {
3216
+ await execa7("npm", ["run", "build"], { cwd: process.cwd() });
3217
+ } catch {
3218
+ try {
3219
+ await execa7("pnpm", ["build"], { cwd: process.cwd() });
3220
+ } catch {
3221
+ await execa7("yarn", ["build"], { cwd: process.cwd() });
3222
+ }
3223
+ }
3150
3224
  spinner16.stop("Build successful");
3151
3225
  } catch (error) {
3152
3226
  spinner16.stop("");
@@ -4770,21 +4844,21 @@ async function extractMigrationSQL(tool, errorOutput) {
4770
4844
  }
4771
4845
  return null;
4772
4846
  }
4773
- async function copyToClipboard(text15) {
4847
+ async function copyToClipboard(text16) {
4774
4848
  try {
4775
4849
  const platform = process.platform;
4776
4850
  if (platform === "win32") {
4777
- const proc = await execa8("clip", { input: text15, reject: false });
4851
+ const proc = await execa8("clip", { input: text16, reject: false });
4778
4852
  return proc.exitCode === 0;
4779
4853
  } else if (platform === "darwin") {
4780
- const proc = await execa8("pbcopy", { input: text15, reject: false });
4854
+ const proc = await execa8("pbcopy", { input: text16, reject: false });
4781
4855
  return proc.exitCode === 0;
4782
4856
  } else {
4783
4857
  try {
4784
- const proc = await execa8("xclip", ["-selection", "clipboard"], { input: text15, reject: false });
4858
+ const proc = await execa8("xclip", ["-selection", "clipboard"], { input: text16, reject: false });
4785
4859
  return proc.exitCode === 0;
4786
4860
  } catch {
4787
- const proc = await execa8("xsel", ["--clipboard", "--input"], { input: text15, reject: false });
4861
+ const proc = await execa8("xsel", ["--clipboard", "--input"], { input: text16, reject: false });
4788
4862
  return proc.exitCode === 0;
4789
4863
  }
4790
4864
  }
@@ -5053,8 +5127,8 @@ async function getVoiceInput2(prompt) {
5053
5127
  initialValue: true
5054
5128
  });
5055
5129
  if (!ready || p16.isCancel(ready)) {
5056
- const text16 = await p16.text({ message: "Type instead:" });
5057
- return p16.isCancel(text16) ? null : text16;
5130
+ const text17 = await p16.text({ message: "Type instead:" });
5131
+ return p16.isCancel(text17) ? null : text17;
5058
5132
  }
5059
5133
  const spinner16 = p16.spinner();
5060
5134
  spinner16.start("\u{1F534} Recording... (press Ctrl+C to stop)");
@@ -5072,11 +5146,11 @@ async function getVoiceInput2(prompt) {
5072
5146
  console.log(chalk17.green(`
5073
5147
  Heard: "${transcription}"
5074
5148
  `));
5075
- const confirm11 = await p16.confirm({
5149
+ const confirm13 = await p16.confirm({
5076
5150
  message: "Is this correct?",
5077
5151
  initialValue: true
5078
5152
  });
5079
- if (confirm11 && !p16.isCancel(confirm11)) {
5153
+ if (confirm13 && !p16.isCancel(confirm13)) {
5080
5154
  return transcription;
5081
5155
  } else {
5082
5156
  const retry = await p16.select({
@@ -5090,8 +5164,8 @@ async function getVoiceInput2(prompt) {
5090
5164
  if (retry === "retry") {
5091
5165
  return await getVoiceInput2(prompt);
5092
5166
  } else {
5093
- const text16 = await p16.text({ message: "Type your response:" });
5094
- return p16.isCancel(text16) ? null : text16;
5167
+ const text17 = await p16.text({ message: "Type your response:" });
5168
+ return p16.isCancel(text17) ? null : text17;
5095
5169
  }
5096
5170
  }
5097
5171
  }
@@ -5099,8 +5173,8 @@ async function getVoiceInput2(prompt) {
5099
5173
  spinner16.stop("Recording failed");
5100
5174
  console.log(chalk17.yellow("Voice input failed. Falling back to text."));
5101
5175
  }
5102
- const text15 = await p16.text({ message: prompt });
5103
- return p16.isCancel(text15) ? null : text15;
5176
+ const text16 = await p16.text({ message: prompt });
5177
+ return p16.isCancel(text16) ? null : text16;
5104
5178
  }
5105
5179
  async function recordWithWindowsSpeech2() {
5106
5180
  const psScript = `
@@ -5155,9 +5229,9 @@ async function recordWithMacOS2() {
5155
5229
  });
5156
5230
  const txtFile = tempFile.replace(".wav", ".txt");
5157
5231
  if (await fs13.pathExists(txtFile)) {
5158
- const text15 = await fs13.readFile(txtFile, "utf-8");
5232
+ const text16 = await fs13.readFile(txtFile, "utf-8");
5159
5233
  await fs13.remove(txtFile);
5160
- return text15.trim();
5234
+ return text16.trim();
5161
5235
  }
5162
5236
  } catch {
5163
5237
  }
@@ -5205,9 +5279,9 @@ async function recordWithLinux2() {
5205
5279
  });
5206
5280
  const txtFile = tempFile.replace(".wav", ".txt");
5207
5281
  if (await fs13.pathExists(txtFile)) {
5208
- const text15 = await fs13.readFile(txtFile, "utf-8");
5282
+ const text16 = await fs13.readFile(txtFile, "utf-8");
5209
5283
  await fs13.remove(txtFile);
5210
- return text15.trim();
5284
+ return text16.trim();
5211
5285
  }
5212
5286
  } catch {
5213
5287
  }
@@ -5308,16 +5382,34 @@ var displayPaused = false;
5308
5382
  async function buildCommand(prdPath, options = {}) {
5309
5383
  const config = new Config();
5310
5384
  if (!config.isConfigured()) {
5311
- p17.log.error("Please run `codebakers setup` first.");
5385
+ console.log(chalk18.yellow(`
5386
+ \u26A0\uFE0F CodeBakers isn't set up yet.
5387
+
5388
+ Run this first:
5389
+ ${chalk18.cyan("codebakers setup")}
5390
+ `));
5312
5391
  return;
5313
5392
  }
5314
5393
  const anthropicCreds = config.getCredentials("anthropic");
5315
5394
  if (!anthropicCreds?.apiKey) {
5316
- p17.log.error("Anthropic API key not configured.");
5395
+ console.log(chalk18.yellow(`
5396
+ \u26A0\uFE0F Anthropic API key not configured.
5397
+
5398
+ The parallel build needs Claude AI to work.
5399
+
5400
+ Run this to add your API key:
5401
+ ${chalk18.cyan("codebakers setup")}
5402
+ `));
5317
5403
  return;
5318
5404
  }
5319
5405
  let prdFile = prdPath;
5320
5406
  if (!prdFile) {
5407
+ console.log(chalk18.dim(`
5408
+ A PRD (Product Requirements Document) describes what you want to build.
5409
+
5410
+ Don't have one? Create one with:
5411
+ ${chalk18.cyan("codebakers prd-maker")}
5412
+ `));
5321
5413
  const file = await p17.text({
5322
5414
  message: "Path to PRD file:",
5323
5415
  placeholder: "./my-app-prd.md",
@@ -5327,13 +5419,25 @@ async function buildCommand(prdPath, options = {}) {
5327
5419
  prdFile = file;
5328
5420
  }
5329
5421
  if (!await fs14.pathExists(prdFile)) {
5330
- p17.log.error(`PRD file not found: ${prdFile}`);
5422
+ console.log(chalk18.red(`
5423
+ \u274C PRD file not found: ${prdFile}
5424
+
5425
+ Make sure the file exists, or create one:
5426
+ ${chalk18.cyan("codebakers prd-maker")}
5427
+ `));
5331
5428
  return;
5332
5429
  }
5333
5430
  const prdContent = await fs14.readFile(prdFile, "utf-8");
5334
5431
  console.log(chalk18.cyan(`
5335
5432
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
5336
5433
  \u2551 \u{1F680} CODEBAKERS PARALLEL BUILD \u2551
5434
+ \u2551 \u2551
5435
+ \u2551 This will: \u2551
5436
+ \u2551 1. Analyze your PRD \u2551
5437
+ \u2551 2. Break it into features \u2551
5438
+ \u2551 3. Run up to 3 AI agents in parallel \u2551
5439
+ \u2551 4. Auto-fix any errors \u2551
5440
+ \u2551 5. Merge everything together \u2551
5337
5441
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
5338
5442
  `));
5339
5443
  const anthropic = new Anthropic4({ apiKey: anthropicCreds.apiKey });
@@ -5450,8 +5554,8 @@ Think about:
5450
5554
  - What needs data from other features (dashboards, reports)?`
5451
5555
  }]
5452
5556
  });
5453
- const text15 = response.content[0].type === "text" ? response.content[0].text : "";
5454
- const jsonMatch = text15.match(/\{[\s\S]*\}/);
5557
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5558
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
5455
5559
  if (!jsonMatch) {
5456
5560
  throw new Error("Failed to parse PRD analysis");
5457
5561
  }
@@ -5695,9 +5799,9 @@ Start with the index.ts that exports everything.`
5695
5799
  max_tokens: 8192,
5696
5800
  messages
5697
5801
  });
5698
- const text15 = response.content[0].type === "text" ? response.content[0].text : "";
5699
- messages.push({ role: "assistant", content: text15 });
5700
- const questionMatch = text15.match(/<<<ASK_USER>>>([\s\S]*?)<<<END_ASK>>>/);
5802
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5803
+ messages.push({ role: "assistant", content: text16 });
5804
+ const questionMatch = text16.match(/<<<ASK_USER>>>([\s\S]*?)<<<END_ASK>>>/);
5701
5805
  if (questionMatch) {
5702
5806
  const questionBlock = questionMatch[1];
5703
5807
  const questionLine = questionBlock.match(/question:\s*(.+)/);
@@ -5726,7 +5830,7 @@ Now continue building the feature with this choice. Generate the code files.`
5726
5830
  }
5727
5831
  }
5728
5832
  if (onProgress) onProgress(60, "Writing files...");
5729
- const files = await writeFilesFromResponse(text15, projectPath);
5833
+ const files = await writeFilesFromResponse(text16, projectPath);
5730
5834
  agent.files = files;
5731
5835
  if (onProgress) onProgress(80, "Validating...");
5732
5836
  if (files.length === 0) {
@@ -5809,8 +5913,8 @@ Common fixes:
5809
5913
  - Timeout \u2192 retry with simpler approach`
5810
5914
  }]
5811
5915
  });
5812
- const text15 = response.content[0].type === "text" ? response.content[0].text : "";
5813
- const jsonMatch = text15.match(/\{[\s\S]*\}/);
5916
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5917
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
5814
5918
  if (!jsonMatch) {
5815
5919
  return {
5816
5920
  canFix: false,
@@ -5850,8 +5954,8 @@ content
5850
5954
  Make sure all features are accessible and properly connected.`
5851
5955
  }]
5852
5956
  });
5853
- const text15 = response.content[0].type === "text" ? response.content[0].text : "";
5854
- await writeFilesFromResponse(text15, projectPath);
5957
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5958
+ await writeFilesFromResponse(text16, projectPath);
5855
5959
  await execa9("git", ["add", "."], { cwd: projectPath });
5856
5960
  await execa9("git", ["commit", "-m", "Integration: wire up all features"], { cwd: projectPath, reject: false });
5857
5961
  spinner16.stop("Integration complete");
@@ -5943,11 +6047,11 @@ var ProgressDisplay = class {
5943
6047
  return chalk18.green("\u2588".repeat(filled)) + chalk18.gray("\u2591".repeat(empty));
5944
6048
  }
5945
6049
  };
5946
- async function writeFilesFromResponse(text15, projectPath) {
6050
+ async function writeFilesFromResponse(text16, projectPath) {
5947
6051
  const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
5948
6052
  const files = [];
5949
6053
  let match;
5950
- while ((match = fileRegex.exec(text15)) !== null) {
6054
+ while ((match = fileRegex.exec(text16)) !== null) {
5951
6055
  const filePath = path13.join(projectPath, match[1].trim());
5952
6056
  const content = match[2].trim();
5953
6057
  await fs14.ensureDir(path13.dirname(filePath));
@@ -5969,93 +6073,94 @@ import open2 from "open";
5969
6073
  import { execa as execa10 } from "execa";
5970
6074
  var INTEGRATIONS = [
5971
6075
  // ═══════════════════════════════════════════════════════════════════════════
5972
- // AUTH PROVIDERS
6076
+ // AUTH
5973
6077
  // ═══════════════════════════════════════════════════════════════════════════
5974
6078
  {
5975
6079
  id: "clerk",
5976
6080
  name: "Clerk",
5977
- description: "Complete user management & authentication",
6081
+ description: "User management & authentication",
5978
6082
  category: "auth",
5979
6083
  icon: "\u{1F510}",
5980
- authType: "oauth",
5981
- oauthUrl: "https://dashboard.clerk.com/apps/new",
6084
+ dashboardUrl: "https://dashboard.clerk.com",
5982
6085
  envVars: ["NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", "CLERK_SECRET_KEY"],
6086
+ envVarHints: {
6087
+ "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY": "pk_test_... or pk_live_...",
6088
+ "CLERK_SECRET_KEY": "sk_test_... or sk_live_..."
6089
+ },
5983
6090
  packages: ["@clerk/nextjs"],
5984
- docs: "https://clerk.com/docs"
5985
- },
5986
- {
5987
- id: "auth0",
5988
- name: "Auth0",
5989
- description: "Enterprise authentication platform",
5990
- category: "auth",
5991
- icon: "\u{1F512}",
5992
- authType: "oauth",
5993
- oauthUrl: "https://manage.auth0.com/dashboard",
5994
- envVars: ["AUTH0_SECRET", "AUTH0_BASE_URL", "AUTH0_ISSUER_BASE_URL", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET"],
5995
- packages: ["@auth0/nextjs-auth0"],
5996
- docs: "https://auth0.com/docs"
5997
- },
5998
- {
5999
- id: "nextauth",
6000
- name: "NextAuth.js",
6001
- description: "Open source authentication for Next.js",
6002
- category: "auth",
6003
- icon: "\u{1F511}",
6004
- authType: "npm",
6005
- envVars: ["NEXTAUTH_SECRET", "NEXTAUTH_URL"],
6006
- packages: ["next-auth"],
6007
- docs: "https://next-auth.js.org"
6091
+ docs: "https://clerk.com/docs",
6092
+ steps: [
6093
+ "Sign up or log in at dashboard.clerk.com",
6094
+ "Create a new application",
6095
+ "Go to API Keys in the sidebar",
6096
+ "Copy Publishable Key and Secret Key"
6097
+ ]
6008
6098
  },
6009
6099
  {
6010
6100
  id: "supabase-auth",
6011
6101
  name: "Supabase Auth",
6012
- description: "Authentication with Supabase",
6102
+ description: "Auth with email, social, magic links",
6013
6103
  category: "auth",
6014
6104
  icon: "\u26A1",
6015
- authType: "oauth",
6016
- oauthUrl: "https://supabase.com/dashboard/projects",
6105
+ dashboardUrl: "https://supabase.com/dashboard",
6017
6106
  envVars: ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY"],
6107
+ envVarHints: {
6108
+ "NEXT_PUBLIC_SUPABASE_URL": "https://xxx.supabase.co",
6109
+ "NEXT_PUBLIC_SUPABASE_ANON_KEY": "eyJ... (anon public key)"
6110
+ },
6018
6111
  packages: ["@supabase/supabase-js", "@supabase/auth-helpers-nextjs"],
6019
- docs: "https://supabase.com/docs/guides/auth"
6112
+ docs: "https://supabase.com/docs/guides/auth",
6113
+ steps: [
6114
+ "Sign in at supabase.com/dashboard",
6115
+ "Create or select a project",
6116
+ "Go to Settings > API",
6117
+ "Copy Project URL and anon/public key"
6118
+ ]
6020
6119
  },
6021
6120
  {
6022
- id: "firebase-auth",
6023
- name: "Firebase Auth",
6024
- description: "Google Firebase authentication",
6121
+ id: "nextauth",
6122
+ name: "NextAuth.js",
6123
+ description: "Open source auth for Next.js",
6025
6124
  category: "auth",
6026
- icon: "\u{1F525}",
6027
- authType: "oauth",
6028
- oauthUrl: "https://console.firebase.google.com",
6029
- envVars: ["NEXT_PUBLIC_FIREBASE_API_KEY", "NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN", "NEXT_PUBLIC_FIREBASE_PROJECT_ID"],
6030
- packages: ["firebase"],
6031
- docs: "https://firebase.google.com/docs/auth"
6125
+ icon: "\u{1F511}",
6126
+ dashboardUrl: "https://next-auth.js.org",
6127
+ envVars: ["NEXTAUTH_SECRET", "NEXTAUTH_URL"],
6128
+ envVarHints: {
6129
+ "NEXTAUTH_SECRET": "Run: openssl rand -base64 32",
6130
+ "NEXTAUTH_URL": "http://localhost:3000"
6131
+ },
6132
+ packages: ["next-auth"],
6133
+ docs: "https://next-auth.js.org",
6134
+ steps: [
6135
+ "No signup needed - open source!",
6136
+ "Generate secret: openssl rand -base64 32",
6137
+ "Set URL to your app (localhost for dev)"
6138
+ ]
6032
6139
  },
6033
6140
  // ═══════════════════════════════════════════════════════════════════════════
6034
- // DATABASES
6141
+ // DATABASE
6035
6142
  // ═══════════════════════════════════════════════════════════════════════════
6036
6143
  {
6037
6144
  id: "supabase",
6038
6145
  name: "Supabase",
6039
- description: "Postgres database with realtime & auth",
6146
+ description: "Postgres + Realtime + Auth + Storage",
6040
6147
  category: "database",
6041
6148
  icon: "\u26A1",
6042
- authType: "oauth",
6043
- oauthUrl: "https://supabase.com/dashboard/projects",
6149
+ dashboardUrl: "https://supabase.com/dashboard",
6044
6150
  envVars: ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY"],
6151
+ envVarHints: {
6152
+ "NEXT_PUBLIC_SUPABASE_URL": "https://xxx.supabase.co",
6153
+ "NEXT_PUBLIC_SUPABASE_ANON_KEY": "eyJ... (anon key)",
6154
+ "SUPABASE_SERVICE_ROLE_KEY": "eyJ... (service_role key - keep secret!)"
6155
+ },
6045
6156
  packages: ["@supabase/supabase-js"],
6046
- docs: "https://supabase.com/docs"
6047
- },
6048
- {
6049
- id: "planetscale",
6050
- name: "PlanetScale",
6051
- description: "Serverless MySQL database",
6052
- category: "database",
6053
- icon: "\u{1FA90}",
6054
- authType: "oauth",
6055
- oauthUrl: "https://app.planetscale.com",
6056
- envVars: ["DATABASE_URL"],
6057
- packages: ["@planetscale/database"],
6058
- docs: "https://planetscale.com/docs"
6157
+ docs: "https://supabase.com/docs",
6158
+ steps: [
6159
+ "Sign in at supabase.com/dashboard",
6160
+ "Create a new project",
6161
+ "Go to Settings > API",
6162
+ "Copy URL, anon key, and service_role key"
6163
+ ]
6059
6164
  },
6060
6165
  {
6061
6166
  id: "neon",
@@ -6063,57 +6168,58 @@ var INTEGRATIONS = [
6063
6168
  description: "Serverless Postgres",
6064
6169
  category: "database",
6065
6170
  icon: "\u{1F418}",
6066
- authType: "oauth",
6067
- oauthUrl: "https://console.neon.tech",
6171
+ dashboardUrl: "https://console.neon.tech",
6068
6172
  envVars: ["DATABASE_URL"],
6173
+ envVarHints: {
6174
+ "DATABASE_URL": "postgres://user:pass@xxx.neon.tech/db?sslmode=require"
6175
+ },
6069
6176
  packages: ["@neondatabase/serverless"],
6070
- docs: "https://neon.tech/docs"
6071
- },
6072
- {
6073
- id: "turso",
6074
- name: "Turso",
6075
- description: "Edge SQLite database",
6076
- category: "database",
6077
- icon: "\u{1F422}",
6078
- authType: "oauth",
6079
- oauthUrl: "https://turso.tech/app",
6080
- envVars: ["TURSO_DATABASE_URL", "TURSO_AUTH_TOKEN"],
6081
- packages: ["@libsql/client"],
6082
- docs: "https://docs.turso.tech"
6177
+ docs: "https://neon.tech/docs",
6178
+ steps: [
6179
+ "Sign in at console.neon.tech",
6180
+ "Create a project",
6181
+ "Go to Dashboard > Connection Details",
6182
+ "Copy the connection string"
6183
+ ]
6083
6184
  },
6084
6185
  {
6085
- id: "mongodb",
6086
- name: "MongoDB Atlas",
6087
- description: "NoSQL document database",
6186
+ id: "planetscale",
6187
+ name: "PlanetScale",
6188
+ description: "Serverless MySQL",
6088
6189
  category: "database",
6089
- icon: "\u{1F343}",
6090
- authType: "oauth",
6091
- oauthUrl: "https://cloud.mongodb.com",
6092
- envVars: ["MONGODB_URI"],
6093
- packages: ["mongodb"],
6094
- docs: "https://www.mongodb.com/docs"
6190
+ icon: "\u{1FA90}",
6191
+ dashboardUrl: "https://app.planetscale.com",
6192
+ envVars: ["DATABASE_URL"],
6193
+ envVarHints: {
6194
+ "DATABASE_URL": "mysql://user:pass@xxx.psdb.cloud/db?sslaccept=strict"
6195
+ },
6196
+ packages: ["@planetscale/database"],
6197
+ docs: "https://planetscale.com/docs",
6198
+ steps: [
6199
+ "Sign in at app.planetscale.com",
6200
+ "Create a database",
6201
+ "Go to Connect > Create password",
6202
+ "Copy the connection string"
6203
+ ]
6095
6204
  },
6096
6205
  {
6097
6206
  id: "prisma",
6098
6207
  name: "Prisma",
6099
- description: "Type-safe ORM for any database",
6208
+ description: "Type-safe ORM",
6100
6209
  category: "database",
6101
6210
  icon: "\u{1F537}",
6102
- authType: "npm",
6211
+ dashboardUrl: "https://prisma.io",
6103
6212
  envVars: ["DATABASE_URL"],
6213
+ envVarHints: {
6214
+ "DATABASE_URL": "Your database connection string"
6215
+ },
6104
6216
  packages: ["prisma", "@prisma/client"],
6105
- docs: "https://www.prisma.io/docs"
6106
- },
6107
- {
6108
- id: "drizzle",
6109
- name: "Drizzle ORM",
6110
- description: "Lightweight TypeScript ORM",
6111
- category: "database",
6112
- icon: "\u{1F4A7}",
6113
- authType: "npm",
6114
- envVars: ["DATABASE_URL"],
6115
- packages: ["drizzle-orm", "drizzle-kit"],
6116
- docs: "https://orm.drizzle.team"
6217
+ docs: "https://prisma.io/docs",
6218
+ steps: [
6219
+ "No account needed - Prisma is open source!",
6220
+ "Use your existing database URL",
6221
+ "Run: npx prisma init"
6222
+ ]
6117
6223
  },
6118
6224
  // ═══════════════════════════════════════════════════════════════════════════
6119
6225
  // PAYMENTS
@@ -6121,38 +6227,46 @@ var INTEGRATIONS = [
6121
6227
  {
6122
6228
  id: "stripe",
6123
6229
  name: "Stripe",
6124
- description: "Payment processing & subscriptions",
6230
+ description: "Payments & subscriptions",
6125
6231
  category: "payments",
6126
6232
  icon: "\u{1F4B3}",
6127
- authType: "oauth",
6128
- oauthUrl: "https://dashboard.stripe.com/apikeys",
6233
+ dashboardUrl: "https://dashboard.stripe.com/apikeys",
6129
6234
  envVars: ["STRIPE_SECRET_KEY", "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY", "STRIPE_WEBHOOK_SECRET"],
6235
+ envVarHints: {
6236
+ "STRIPE_SECRET_KEY": "sk_test_... or sk_live_...",
6237
+ "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY": "pk_test_... or pk_live_...",
6238
+ "STRIPE_WEBHOOK_SECRET": "whsec_... (from Webhooks page)"
6239
+ },
6130
6240
  packages: ["stripe", "@stripe/stripe-js"],
6131
- docs: "https://stripe.com/docs"
6241
+ docs: "https://stripe.com/docs",
6242
+ steps: [
6243
+ "Sign in at dashboard.stripe.com",
6244
+ "Go to Developers > API keys",
6245
+ "Copy Publishable and Secret keys",
6246
+ "For webhooks: Developers > Webhooks > Add endpoint"
6247
+ ]
6132
6248
  },
6133
6249
  {
6134
6250
  id: "lemonsqueezy",
6135
6251
  name: "Lemon Squeezy",
6136
- description: "Merchant of record for SaaS",
6252
+ description: "Payments with tax handling (MoR)",
6137
6253
  category: "payments",
6138
6254
  icon: "\u{1F34B}",
6139
- authType: "oauth",
6140
- oauthUrl: "https://app.lemonsqueezy.com/settings/api",
6255
+ dashboardUrl: "https://app.lemonsqueezy.com/settings/api",
6141
6256
  envVars: ["LEMONSQUEEZY_API_KEY", "LEMONSQUEEZY_STORE_ID", "LEMONSQUEEZY_WEBHOOK_SECRET"],
6257
+ envVarHints: {
6258
+ "LEMONSQUEEZY_API_KEY": "From Settings > API",
6259
+ "LEMONSQUEEZY_STORE_ID": "Your store ID number",
6260
+ "LEMONSQUEEZY_WEBHOOK_SECRET": "From Settings > Webhooks"
6261
+ },
6142
6262
  packages: ["@lemonsqueezy/lemonsqueezy.js"],
6143
- docs: "https://docs.lemonsqueezy.com"
6144
- },
6145
- {
6146
- id: "paddle",
6147
- name: "Paddle",
6148
- description: "Payment infrastructure for SaaS",
6149
- category: "payments",
6150
- icon: "\u{1F3D3}",
6151
- authType: "oauth",
6152
- oauthUrl: "https://vendors.paddle.com/authentication",
6153
- envVars: ["PADDLE_VENDOR_ID", "PADDLE_API_KEY", "PADDLE_PUBLIC_KEY"],
6154
- packages: ["@paddle/paddle-js"],
6155
- docs: "https://developer.paddle.com"
6263
+ docs: "https://docs.lemonsqueezy.com",
6264
+ steps: [
6265
+ "Sign in at app.lemonsqueezy.com",
6266
+ "Go to Settings > API",
6267
+ "Create an API key",
6268
+ "Note your Store ID from the URL"
6269
+ ]
6156
6270
  },
6157
6271
  // ═══════════════════════════════════════════════════════════════════════════
6158
6272
  // EMAIL
@@ -6160,14 +6274,22 @@ var INTEGRATIONS = [
6160
6274
  {
6161
6275
  id: "resend",
6162
6276
  name: "Resend",
6163
- description: "Modern email API for developers",
6277
+ description: "Modern email API",
6164
6278
  category: "email",
6165
6279
  icon: "\u{1F4E7}",
6166
- authType: "oauth",
6167
- oauthUrl: "https://resend.com/api-keys",
6280
+ dashboardUrl: "https://resend.com/api-keys",
6168
6281
  envVars: ["RESEND_API_KEY"],
6282
+ envVarHints: {
6283
+ "RESEND_API_KEY": "re_..."
6284
+ },
6169
6285
  packages: ["resend"],
6170
- docs: "https://resend.com/docs"
6286
+ docs: "https://resend.com/docs",
6287
+ steps: [
6288
+ "Sign up at resend.com",
6289
+ "Go to API Keys",
6290
+ "Create a new API key",
6291
+ "Copy the key (only shown once!)"
6292
+ ]
6171
6293
  },
6172
6294
  {
6173
6295
  id: "sendgrid",
@@ -6175,46 +6297,19 @@ var INTEGRATIONS = [
6175
6297
  description: "Email delivery service",
6176
6298
  category: "email",
6177
6299
  icon: "\u{1F4E8}",
6178
- authType: "oauth",
6179
- oauthUrl: "https://app.sendgrid.com/settings/api_keys",
6300
+ dashboardUrl: "https://app.sendgrid.com/settings/api_keys",
6180
6301
  envVars: ["SENDGRID_API_KEY"],
6302
+ envVarHints: {
6303
+ "SENDGRID_API_KEY": "SG.xxx..."
6304
+ },
6181
6305
  packages: ["@sendgrid/mail"],
6182
- docs: "https://docs.sendgrid.com"
6183
- },
6184
- {
6185
- id: "postmark",
6186
- name: "Postmark",
6187
- description: "Transactional email service",
6188
- category: "email",
6189
- icon: "\u{1F4EC}",
6190
- authType: "oauth",
6191
- oauthUrl: "https://account.postmarkapp.com/servers",
6192
- envVars: ["POSTMARK_API_KEY"],
6193
- packages: ["postmark"],
6194
- docs: "https://postmarkapp.com/developer"
6195
- },
6196
- {
6197
- id: "mailgun",
6198
- name: "Mailgun",
6199
- description: "Email API service",
6200
- category: "email",
6201
- icon: "\u{1F4EE}",
6202
- authType: "oauth",
6203
- oauthUrl: "https://app.mailgun.com/app/account/security/api_keys",
6204
- envVars: ["MAILGUN_API_KEY", "MAILGUN_DOMAIN"],
6205
- packages: ["mailgun.js"],
6206
- docs: "https://documentation.mailgun.com"
6207
- },
6208
- {
6209
- id: "react-email",
6210
- name: "React Email",
6211
- description: "Build emails with React components",
6212
- category: "email",
6213
- icon: "\u269B\uFE0F",
6214
- authType: "npm",
6215
- envVars: [],
6216
- packages: ["react-email", "@react-email/components"],
6217
- docs: "https://react.email/docs"
6306
+ docs: "https://docs.sendgrid.com",
6307
+ steps: [
6308
+ "Sign in at app.sendgrid.com",
6309
+ "Go to Settings > API Keys",
6310
+ "Create an API key with Mail Send permission",
6311
+ "Copy the key (only shown once!)"
6312
+ ]
6218
6313
  },
6219
6314
  // ═══════════════════════════════════════════════════════════════════════════
6220
6315
  // STORAGE
@@ -6225,11 +6320,20 @@ var INTEGRATIONS = [
6225
6320
  description: "File uploads for Next.js",
6226
6321
  category: "storage",
6227
6322
  icon: "\u{1F4E4}",
6228
- authType: "oauth",
6229
- oauthUrl: "https://uploadthing.com/dashboard",
6323
+ dashboardUrl: "https://uploadthing.com/dashboard",
6230
6324
  envVars: ["UPLOADTHING_SECRET", "UPLOADTHING_APP_ID"],
6325
+ envVarHints: {
6326
+ "UPLOADTHING_SECRET": "sk_live_...",
6327
+ "UPLOADTHING_APP_ID": "Your app ID"
6328
+ },
6231
6329
  packages: ["uploadthing", "@uploadthing/react"],
6232
- docs: "https://docs.uploadthing.com"
6330
+ docs: "https://docs.uploadthing.com",
6331
+ steps: [
6332
+ "Sign in at uploadthing.com",
6333
+ "Create or select an app",
6334
+ "Go to API Keys",
6335
+ "Copy Secret and App ID"
6336
+ ]
6233
6337
  },
6234
6338
  {
6235
6339
  id: "cloudinary",
@@ -6237,85 +6341,20 @@ var INTEGRATIONS = [
6237
6341
  description: "Image & video management",
6238
6342
  category: "storage",
6239
6343
  icon: "\u2601\uFE0F",
6240
- authType: "oauth",
6241
- oauthUrl: "https://console.cloudinary.com/settings/api-keys",
6344
+ dashboardUrl: "https://console.cloudinary.com/settings/api-keys",
6242
6345
  envVars: ["CLOUDINARY_CLOUD_NAME", "CLOUDINARY_API_KEY", "CLOUDINARY_API_SECRET"],
6346
+ envVarHints: {
6347
+ "CLOUDINARY_CLOUD_NAME": "Your cloud name",
6348
+ "CLOUDINARY_API_KEY": "Numeric API key",
6349
+ "CLOUDINARY_API_SECRET": "API Secret"
6350
+ },
6243
6351
  packages: ["cloudinary"],
6244
- docs: "https://cloudinary.com/documentation"
6245
- },
6246
- {
6247
- id: "aws-s3",
6248
- name: "AWS S3",
6249
- description: "Amazon cloud storage",
6250
- category: "storage",
6251
- icon: "\u{1FAA3}",
6252
- authType: "oauth",
6253
- oauthUrl: "https://console.aws.amazon.com/iam/home#/security_credentials",
6254
- envVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION", "AWS_S3_BUCKET"],
6255
- packages: ["@aws-sdk/client-s3"],
6256
- docs: "https://docs.aws.amazon.com/s3"
6257
- },
6258
- {
6259
- id: "vercel-blob",
6260
- name: "Vercel Blob",
6261
- description: "File storage by Vercel",
6262
- category: "storage",
6263
- icon: "\u25B2",
6264
- authType: "oauth",
6265
- oauthUrl: "https://vercel.com/dashboard/stores",
6266
- envVars: ["BLOB_READ_WRITE_TOKEN"],
6267
- packages: ["@vercel/blob"],
6268
- docs: "https://vercel.com/docs/storage/vercel-blob"
6269
- },
6270
- // ═══════════════════════════════════════════════════════════════════════════
6271
- // ANALYTICS
6272
- // ═══════════════════════════════════════════════════════════════════════════
6273
- {
6274
- id: "vercel-analytics",
6275
- name: "Vercel Analytics",
6276
- description: "Web analytics by Vercel",
6277
- category: "analytics",
6278
- icon: "\u{1F4CA}",
6279
- authType: "npm",
6280
- envVars: [],
6281
- packages: ["@vercel/analytics"],
6282
- docs: "https://vercel.com/docs/analytics"
6283
- },
6284
- {
6285
- id: "posthog",
6286
- name: "PostHog",
6287
- description: "Product analytics & feature flags",
6288
- category: "analytics",
6289
- icon: "\u{1F994}",
6290
- authType: "oauth",
6291
- oauthUrl: "https://app.posthog.com/project/settings",
6292
- envVars: ["NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST"],
6293
- packages: ["posthog-js"],
6294
- docs: "https://posthog.com/docs"
6295
- },
6296
- {
6297
- id: "mixpanel",
6298
- name: "Mixpanel",
6299
- description: "Event-based analytics",
6300
- category: "analytics",
6301
- icon: "\u{1F4C8}",
6302
- authType: "oauth",
6303
- oauthUrl: "https://mixpanel.com/settings/project",
6304
- envVars: ["NEXT_PUBLIC_MIXPANEL_TOKEN"],
6305
- packages: ["mixpanel-browser"],
6306
- docs: "https://docs.mixpanel.com"
6307
- },
6308
- {
6309
- id: "plausible",
6310
- name: "Plausible",
6311
- description: "Privacy-friendly analytics",
6312
- category: "analytics",
6313
- icon: "\u{1F4C9}",
6314
- authType: "oauth",
6315
- oauthUrl: "https://plausible.io/sites",
6316
- envVars: ["NEXT_PUBLIC_PLAUSIBLE_DOMAIN"],
6317
- packages: ["next-plausible"],
6318
- docs: "https://plausible.io/docs"
6352
+ docs: "https://cloudinary.com/documentation",
6353
+ steps: [
6354
+ "Sign in at cloudinary.com",
6355
+ "Go to Settings > API Keys",
6356
+ "Copy Cloud Name, API Key, API Secret"
6357
+ ]
6319
6358
  },
6320
6359
  // ═══════════════════════════════════════════════════════════════════════════
6321
6360
  // AI
@@ -6323,14 +6362,22 @@ var INTEGRATIONS = [
6323
6362
  {
6324
6363
  id: "openai",
6325
6364
  name: "OpenAI",
6326
- description: "GPT models & DALL-E",
6365
+ description: "GPT-4, DALL-E, Whisper",
6327
6366
  category: "ai",
6328
6367
  icon: "\u{1F916}",
6329
- authType: "oauth",
6330
- oauthUrl: "https://platform.openai.com/api-keys",
6368
+ dashboardUrl: "https://platform.openai.com/api-keys",
6331
6369
  envVars: ["OPENAI_API_KEY"],
6370
+ envVarHints: {
6371
+ "OPENAI_API_KEY": "sk-..."
6372
+ },
6332
6373
  packages: ["openai"],
6333
- docs: "https://platform.openai.com/docs"
6374
+ docs: "https://platform.openai.com/docs",
6375
+ steps: [
6376
+ "Sign in at platform.openai.com",
6377
+ "Go to API Keys",
6378
+ "Create new secret key",
6379
+ "Copy it (only shown once!)"
6380
+ ]
6334
6381
  },
6335
6382
  {
6336
6383
  id: "anthropic",
@@ -6338,11 +6385,19 @@ var INTEGRATIONS = [
6338
6385
  description: "Claude AI models",
6339
6386
  category: "ai",
6340
6387
  icon: "\u{1F9E0}",
6341
- authType: "oauth",
6342
- oauthUrl: "https://console.anthropic.com/settings/keys",
6388
+ dashboardUrl: "https://console.anthropic.com/settings/keys",
6343
6389
  envVars: ["ANTHROPIC_API_KEY"],
6390
+ envVarHints: {
6391
+ "ANTHROPIC_API_KEY": "sk-ant-..."
6392
+ },
6344
6393
  packages: ["@anthropic-ai/sdk"],
6345
- docs: "https://docs.anthropic.com"
6394
+ docs: "https://docs.anthropic.com",
6395
+ steps: [
6396
+ "Sign in at console.anthropic.com",
6397
+ "Go to API Keys",
6398
+ "Create a new key",
6399
+ "Copy it (only shown once!)"
6400
+ ]
6346
6401
  },
6347
6402
  {
6348
6403
  id: "replicate",
@@ -6350,72 +6405,83 @@ var INTEGRATIONS = [
6350
6405
  description: "Run ML models in the cloud",
6351
6406
  category: "ai",
6352
6407
  icon: "\u{1F504}",
6353
- authType: "oauth",
6354
- oauthUrl: "https://replicate.com/account/api-tokens",
6408
+ dashboardUrl: "https://replicate.com/account/api-tokens",
6355
6409
  envVars: ["REPLICATE_API_TOKEN"],
6410
+ envVarHints: {
6411
+ "REPLICATE_API_TOKEN": "r8_..."
6412
+ },
6356
6413
  packages: ["replicate"],
6357
- docs: "https://replicate.com/docs"
6358
- },
6359
- {
6360
- id: "vercel-ai",
6361
- name: "Vercel AI SDK",
6362
- description: "Build AI-powered apps",
6363
- category: "ai",
6364
- icon: "\u2728",
6365
- authType: "npm",
6366
- envVars: [],
6367
- packages: ["ai"],
6368
- docs: "https://sdk.vercel.ai/docs"
6369
- },
6370
- {
6371
- id: "elevenlabs",
6372
- name: "ElevenLabs",
6373
- description: "AI voice generation",
6374
- category: "ai",
6375
- icon: "\u{1F399}\uFE0F",
6376
- authType: "oauth",
6377
- oauthUrl: "https://elevenlabs.io/app/settings/api-keys",
6378
- envVars: ["ELEVENLABS_API_KEY"],
6379
- packages: ["elevenlabs"],
6380
- docs: "https://elevenlabs.io/docs"
6414
+ docs: "https://replicate.com/docs",
6415
+ steps: [
6416
+ "Sign in at replicate.com",
6417
+ "Go to Account > API Tokens",
6418
+ "Create a token"
6419
+ ]
6381
6420
  },
6382
6421
  // ═══════════════════════════════════════════════════════════════════════════
6383
- // CMS
6422
+ // ANALYTICS
6384
6423
  // ═══════════════════════════════════════════════════════════════════════════
6385
6424
  {
6386
- id: "sanity",
6387
- name: "Sanity",
6388
- description: "Headless CMS with real-time collaboration",
6389
- category: "cms",
6390
- icon: "\u{1F4DD}",
6391
- authType: "oauth",
6392
- oauthUrl: "https://www.sanity.io/manage",
6393
- envVars: ["NEXT_PUBLIC_SANITY_PROJECT_ID", "NEXT_PUBLIC_SANITY_DATASET", "SANITY_API_TOKEN"],
6394
- packages: ["@sanity/client", "next-sanity"],
6395
- docs: "https://www.sanity.io/docs"
6425
+ id: "posthog",
6426
+ name: "PostHog",
6427
+ description: "Product analytics & feature flags",
6428
+ category: "analytics",
6429
+ icon: "\u{1F994}",
6430
+ dashboardUrl: "https://app.posthog.com/project/settings",
6431
+ envVars: ["NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST"],
6432
+ envVarHints: {
6433
+ "NEXT_PUBLIC_POSTHOG_KEY": "phc_...",
6434
+ "NEXT_PUBLIC_POSTHOG_HOST": "https://app.posthog.com (or your self-hosted URL)"
6435
+ },
6436
+ packages: ["posthog-js"],
6437
+ docs: "https://posthog.com/docs",
6438
+ steps: [
6439
+ "Sign up at posthog.com",
6440
+ "Create or select a project",
6441
+ "Go to Project Settings",
6442
+ "Copy Project API Key"
6443
+ ]
6396
6444
  },
6397
6445
  {
6398
- id: "contentful",
6399
- name: "Contentful",
6400
- description: "Enterprise headless CMS",
6401
- category: "cms",
6402
- icon: "\u{1F4C4}",
6403
- authType: "oauth",
6404
- oauthUrl: "https://app.contentful.com/account/profile/cma_tokens",
6405
- envVars: ["CONTENTFUL_SPACE_ID", "CONTENTFUL_ACCESS_TOKEN"],
6406
- packages: ["contentful"],
6407
- docs: "https://www.contentful.com/developers/docs"
6446
+ id: "vercel-analytics",
6447
+ name: "Vercel Analytics",
6448
+ description: "Web analytics by Vercel",
6449
+ category: "analytics",
6450
+ icon: "\u25B2",
6451
+ dashboardUrl: "https://vercel.com/dashboard",
6452
+ envVars: [],
6453
+ packages: ["@vercel/analytics"],
6454
+ docs: "https://vercel.com/docs/analytics",
6455
+ steps: [
6456
+ "No API key needed!",
6457
+ "Just install the package",
6458
+ "Add <Analytics /> to your layout",
6459
+ "Deploy to Vercel to see data"
6460
+ ]
6408
6461
  },
6462
+ // ═══════════════════════════════════════════════════════════════════════════
6463
+ // MONITORING
6464
+ // ═══════════════════════════════════════════════════════════════════════════
6409
6465
  {
6410
- id: "strapi",
6411
- name: "Strapi",
6412
- description: "Open-source headless CMS",
6413
- category: "cms",
6414
- icon: "\u{1F680}",
6415
- authType: "npm",
6416
- envVars: ["STRAPI_URL", "STRAPI_API_TOKEN"],
6417
- packages: [],
6418
- docs: "https://docs.strapi.io"
6466
+ id: "sentry",
6467
+ name: "Sentry",
6468
+ description: "Error tracking & performance",
6469
+ category: "monitoring",
6470
+ icon: "\u{1F41B}",
6471
+ dashboardUrl: "https://sentry.io/settings/account/api/auth-tokens/",
6472
+ envVars: ["SENTRY_DSN", "SENTRY_AUTH_TOKEN"],
6473
+ envVarHints: {
6474
+ "SENTRY_DSN": "https://xxx@xxx.ingest.sentry.io/xxx",
6475
+ "SENTRY_AUTH_TOKEN": "From Organization Auth Tokens"
6476
+ },
6477
+ packages: ["@sentry/nextjs"],
6478
+ docs: "https://docs.sentry.io",
6479
+ steps: [
6480
+ "Sign up at sentry.io",
6481
+ "Create a project (Next.js)",
6482
+ "Copy DSN from Project Settings > Client Keys",
6483
+ "Create auth token in Settings > Auth Tokens"
6484
+ ]
6419
6485
  },
6420
6486
  // ═══════════════════════════════════════════════════════════════════════════
6421
6487
  // MESSAGING
@@ -6426,11 +6492,21 @@ var INTEGRATIONS = [
6426
6492
  description: "SMS, voice & WhatsApp",
6427
6493
  category: "messaging",
6428
6494
  icon: "\u{1F4F1}",
6429
- authType: "oauth",
6430
- oauthUrl: "https://console.twilio.com/us1/account/keys-credentials/api-keys",
6495
+ dashboardUrl: "https://console.twilio.com",
6431
6496
  envVars: ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN", "TWILIO_PHONE_NUMBER"],
6497
+ envVarHints: {
6498
+ "TWILIO_ACCOUNT_SID": "AC... (from Console Dashboard)",
6499
+ "TWILIO_AUTH_TOKEN": "From Console Dashboard (click to reveal)",
6500
+ "TWILIO_PHONE_NUMBER": "+1234567890 (buy in Phone Numbers)"
6501
+ },
6432
6502
  packages: ["twilio"],
6433
- docs: "https://www.twilio.com/docs"
6503
+ docs: "https://www.twilio.com/docs",
6504
+ steps: [
6505
+ "Sign up at twilio.com",
6506
+ "Go to Console Dashboard",
6507
+ "Copy Account SID and Auth Token",
6508
+ "Buy a phone number in Phone Numbers > Manage"
6509
+ ]
6434
6510
  },
6435
6511
  {
6436
6512
  id: "pusher",
@@ -6438,62 +6514,23 @@ var INTEGRATIONS = [
6438
6514
  description: "Realtime websockets",
6439
6515
  category: "messaging",
6440
6516
  icon: "\u{1F514}",
6441
- authType: "oauth",
6442
- oauthUrl: "https://dashboard.pusher.com",
6443
- envVars: ["PUSHER_APP_ID", "PUSHER_KEY", "PUSHER_SECRET", "NEXT_PUBLIC_PUSHER_KEY"],
6517
+ dashboardUrl: "https://dashboard.pusher.com",
6518
+ envVars: ["PUSHER_APP_ID", "PUSHER_KEY", "PUSHER_SECRET", "NEXT_PUBLIC_PUSHER_KEY", "PUSHER_CLUSTER"],
6519
+ envVarHints: {
6520
+ "PUSHER_APP_ID": "App ID from App Keys",
6521
+ "PUSHER_KEY": "Key from App Keys",
6522
+ "PUSHER_SECRET": "Secret from App Keys",
6523
+ "NEXT_PUBLIC_PUSHER_KEY": "Same as PUSHER_KEY",
6524
+ "PUSHER_CLUSTER": "e.g., us2, eu, ap1"
6525
+ },
6444
6526
  packages: ["pusher", "pusher-js"],
6445
- docs: "https://pusher.com/docs"
6446
- },
6447
- {
6448
- id: "knock",
6449
- name: "Knock",
6450
- description: "Notification infrastructure",
6451
- category: "messaging",
6452
- icon: "\u{1F514}",
6453
- authType: "oauth",
6454
- oauthUrl: "https://dashboard.knock.app",
6455
- envVars: ["KNOCK_API_KEY", "NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY"],
6456
- packages: ["@knocklabs/node", "@knocklabs/react"],
6457
- docs: "https://docs.knock.app"
6458
- },
6459
- {
6460
- id: "stream",
6461
- name: "Stream",
6462
- description: "Chat & activity feeds",
6463
- category: "messaging",
6464
- icon: "\u{1F4AC}",
6465
- authType: "oauth",
6466
- oauthUrl: "https://dashboard.getstream.io",
6467
- envVars: ["STREAM_API_KEY", "STREAM_API_SECRET"],
6468
- packages: ["stream-chat", "stream-chat-react"],
6469
- docs: "https://getstream.io/docs"
6470
- },
6471
- // ═══════════════════════════════════════════════════════════════════════════
6472
- // MONITORING
6473
- // ═══════════════════════════════════════════════════════════════════════════
6474
- {
6475
- id: "sentry",
6476
- name: "Sentry",
6477
- description: "Error tracking & performance",
6478
- category: "monitoring",
6479
- icon: "\u{1F41B}",
6480
- authType: "oauth",
6481
- oauthUrl: "https://sentry.io/settings/account/api/auth-tokens/",
6482
- envVars: ["SENTRY_DSN", "SENTRY_AUTH_TOKEN"],
6483
- packages: ["@sentry/nextjs"],
6484
- docs: "https://docs.sentry.io"
6485
- },
6486
- {
6487
- id: "logrocket",
6488
- name: "LogRocket",
6489
- description: "Session replay & monitoring",
6490
- category: "monitoring",
6491
- icon: "\u{1F680}",
6492
- authType: "oauth",
6493
- oauthUrl: "https://app.logrocket.com/settings/setup",
6494
- envVars: ["NEXT_PUBLIC_LOGROCKET_APP_ID"],
6495
- packages: ["logrocket"],
6496
- docs: "https://docs.logrocket.com"
6527
+ docs: "https://pusher.com/docs",
6528
+ steps: [
6529
+ "Sign up at pusher.com",
6530
+ "Create a Channels app",
6531
+ "Go to App Keys",
6532
+ "Copy all credentials"
6533
+ ]
6497
6534
  },
6498
6535
  // ═══════════════════════════════════════════════════════════════════════════
6499
6536
  // DEPLOYMENT
@@ -6504,11 +6541,19 @@ var INTEGRATIONS = [
6504
6541
  description: "Deploy Next.js apps",
6505
6542
  category: "deployment",
6506
6543
  icon: "\u25B2",
6507
- authType: "oauth",
6508
- oauthUrl: "https://vercel.com/account/tokens",
6544
+ dashboardUrl: "https://vercel.com/account/tokens",
6509
6545
  envVars: ["VERCEL_TOKEN"],
6546
+ envVarHints: {
6547
+ "VERCEL_TOKEN": "From Account Settings > Tokens"
6548
+ },
6510
6549
  packages: ["vercel"],
6511
- docs: "https://vercel.com/docs"
6550
+ docs: "https://vercel.com/docs",
6551
+ steps: [
6552
+ "Sign in at vercel.com",
6553
+ "Go to Account Settings > Tokens",
6554
+ "Create a new token",
6555
+ "Copy it"
6556
+ ]
6512
6557
  },
6513
6558
  {
6514
6559
  id: "github",
@@ -6516,73 +6561,80 @@ var INTEGRATIONS = [
6516
6561
  description: "Code hosting & CI/CD",
6517
6562
  category: "deployment",
6518
6563
  icon: "\u{1F419}",
6519
- authType: "oauth",
6520
- oauthUrl: "https://github.com/settings/tokens",
6564
+ dashboardUrl: "https://github.com/settings/tokens",
6521
6565
  envVars: ["GITHUB_TOKEN"],
6566
+ envVarHints: {
6567
+ "GITHUB_TOKEN": "ghp_... or github_pat_..."
6568
+ },
6522
6569
  packages: ["octokit"],
6523
- docs: "https://docs.github.com"
6570
+ docs: "https://docs.github.com",
6571
+ steps: [
6572
+ "Sign in at github.com",
6573
+ "Go to Settings > Developer settings > Personal access tokens",
6574
+ "Generate new token (classic)",
6575
+ "Select scopes: repo, user",
6576
+ "Copy the token"
6577
+ ]
6524
6578
  },
6525
6579
  // ═══════════════════════════════════════════════════════════════════════════
6526
- // OTHER
6580
+ // CMS
6527
6581
  // ═══════════════════════════════════════════════════════════════════════════
6528
6582
  {
6529
- id: "crisp",
6530
- name: "Crisp",
6531
- description: "Customer support chat",
6532
- category: "other",
6533
- icon: "\u{1F4AC}",
6534
- authType: "oauth",
6535
- oauthUrl: "https://app.crisp.chat/settings/website",
6536
- envVars: ["NEXT_PUBLIC_CRISP_WEBSITE_ID"],
6537
- packages: ["crisp-sdk-web"],
6538
- docs: "https://docs.crisp.chat"
6539
- },
6540
- {
6541
- id: "intercom",
6542
- name: "Intercom",
6543
- description: "Customer messaging platform",
6544
- category: "other",
6545
- icon: "\u{1F4AC}",
6546
- authType: "oauth",
6547
- oauthUrl: "https://app.intercom.com/a/apps/_/developer-hub",
6548
- envVars: ["NEXT_PUBLIC_INTERCOM_APP_ID"],
6549
- packages: ["@intercom/messenger-js-sdk"],
6550
- docs: "https://developers.intercom.com"
6551
- },
6552
- {
6553
- id: "cal",
6554
- name: "Cal.com",
6555
- description: "Scheduling infrastructure",
6556
- category: "other",
6557
- icon: "\u{1F4C5}",
6558
- authType: "oauth",
6559
- oauthUrl: "https://app.cal.com/settings/developer/api-keys",
6560
- envVars: ["CAL_API_KEY"],
6561
- packages: ["@calcom/embed-react"],
6562
- docs: "https://cal.com/docs"
6583
+ id: "sanity",
6584
+ name: "Sanity",
6585
+ description: "Headless CMS",
6586
+ category: "cms",
6587
+ icon: "\u{1F4DD}",
6588
+ dashboardUrl: "https://www.sanity.io/manage",
6589
+ envVars: ["NEXT_PUBLIC_SANITY_PROJECT_ID", "NEXT_PUBLIC_SANITY_DATASET", "SANITY_API_TOKEN"],
6590
+ envVarHints: {
6591
+ "NEXT_PUBLIC_SANITY_PROJECT_ID": "From project settings",
6592
+ "NEXT_PUBLIC_SANITY_DATASET": 'Usually "production"',
6593
+ "SANITY_API_TOKEN": "From API > Tokens"
6594
+ },
6595
+ packages: ["@sanity/client", "next-sanity"],
6596
+ docs: "https://www.sanity.io/docs",
6597
+ steps: [
6598
+ "Sign in at sanity.io/manage",
6599
+ "Create or select a project",
6600
+ "Copy Project ID from settings",
6601
+ "Create API token in API > Tokens"
6602
+ ]
6563
6603
  }
6564
6604
  ];
6565
6605
  async function integrateCommand(integrationId) {
6566
- const config = new Config();
6567
6606
  console.log(chalk19.cyan(`
6568
6607
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
6569
6608
  \u2551 \u{1F50C} ONE-CLICK INTEGRATIONS \u2551
6570
6609
  \u2551 \u2551
6571
- \u2551 ${INTEGRATIONS.length} integrations available \u2551
6572
- \u2551 Browser-based auth \u2022 Auto-install \u2022 Ready in seconds \u2551
6610
+ \u2551 ${INTEGRATIONS.length} services \u2022 Step-by-step guidance \u2022 Auto-setup \u2551
6573
6611
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
6574
6612
  `));
6575
6613
  if (integrationId) {
6576
6614
  const integration2 = INTEGRATIONS.find((i) => i.id === integrationId);
6577
6615
  if (!integration2) {
6578
6616
  p18.log.error(`Unknown integration: ${integrationId}`);
6579
- console.log(chalk19.dim(`Run 'codebakers integrate' to see all available integrations`));
6617
+ console.log(chalk19.dim(`
6618
+ Available: ${INTEGRATIONS.map((i) => i.id).join(", ")}`));
6580
6619
  return;
6581
6620
  }
6582
- await installIntegration(integration2, config);
6621
+ await installIntegration(integration2);
6583
6622
  return;
6584
6623
  }
6585
6624
  const categories = [...new Set(INTEGRATIONS.map((i) => i.category))];
6625
+ const categoryLabels = {
6626
+ auth: "\u{1F510} Authentication",
6627
+ database: "\u{1F5C4}\uFE0F Database",
6628
+ payments: "\u{1F4B3} Payments",
6629
+ email: "\u{1F4E7} Email",
6630
+ storage: "\u{1F4E6} Storage",
6631
+ ai: "\u{1F916} AI & ML",
6632
+ analytics: "\u{1F4CA} Analytics",
6633
+ monitoring: "\u{1F41B} Monitoring",
6634
+ messaging: "\u{1F4AC} Messaging",
6635
+ deployment: "\u{1F680} Deployment",
6636
+ cms: "\u{1F4DD} CMS"
6637
+ };
6586
6638
  const category = await p18.select({
6587
6639
  message: "Choose a category:",
6588
6640
  options: [
@@ -6590,33 +6642,33 @@ async function integrateCommand(integrationId) {
6590
6642
  { value: "search", label: "\u{1F50D} Search by name" },
6591
6643
  ...categories.map((c) => ({
6592
6644
  value: c,
6593
- label: getCategoryLabel(c),
6594
- hint: `${INTEGRATIONS.filter((i) => i.category === c).length} integrations`
6645
+ label: categoryLabels[c] || c,
6646
+ hint: `${INTEGRATIONS.filter((i) => i.category === c).length} services`
6595
6647
  }))
6596
6648
  ]
6597
6649
  });
6598
6650
  if (p18.isCancel(category)) return;
6599
- let filteredIntegrations = INTEGRATIONS;
6651
+ let filtered = INTEGRATIONS;
6600
6652
  if (category === "search") {
6601
6653
  const query = await p18.text({
6602
- message: "Search integrations:",
6603
- placeholder: "stripe, auth, email..."
6654
+ message: "Search:",
6655
+ placeholder: "stripe, supabase, openai..."
6604
6656
  });
6605
6657
  if (p18.isCancel(query)) return;
6606
6658
  const q = query.toLowerCase();
6607
- filteredIntegrations = INTEGRATIONS.filter(
6608
- (i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q) || i.id.toLowerCase().includes(q)
6659
+ filtered = INTEGRATIONS.filter(
6660
+ (i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q) || i.id.includes(q)
6609
6661
  );
6662
+ if (filtered.length === 0) {
6663
+ p18.log.warn("No integrations found");
6664
+ return;
6665
+ }
6610
6666
  } else if (category !== "all") {
6611
- filteredIntegrations = INTEGRATIONS.filter((i) => i.category === category);
6612
- }
6613
- if (filteredIntegrations.length === 0) {
6614
- p18.log.warn("No integrations found");
6615
- return;
6667
+ filtered = INTEGRATIONS.filter((i) => i.category === category);
6616
6668
  }
6617
6669
  const selected = await p18.select({
6618
- message: "Select an integration to install:",
6619
- options: filteredIntegrations.map((i) => ({
6670
+ message: "Select integration:",
6671
+ options: filtered.map((i) => ({
6620
6672
  value: i.id,
6621
6673
  label: `${i.icon} ${i.name}`,
6622
6674
  hint: i.description
@@ -6624,112 +6676,97 @@ async function integrateCommand(integrationId) {
6624
6676
  });
6625
6677
  if (p18.isCancel(selected)) return;
6626
6678
  const integration = INTEGRATIONS.find((i) => i.id === selected);
6627
- await installIntegration(integration, config);
6679
+ await installIntegration(integration);
6628
6680
  }
6629
- async function installIntegration(integration, config) {
6681
+ async function installIntegration(integration) {
6630
6682
  console.log(chalk19.cyan(`
6631
- Installing ${integration.icon} ${integration.name}...
6683
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6684
+ \u2502 ${integration.icon} Installing ${integration.name}
6685
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6632
6686
  `));
6633
- const steps = [];
6634
6687
  if (integration.packages && integration.packages.length > 0) {
6635
- steps.push({ name: "Install packages", done: false });
6636
- }
6637
- if (integration.envVars.length > 0) {
6638
- steps.push({ name: "Configure credentials", done: false });
6639
- }
6640
- steps.push({ name: "Setup integration", done: false });
6641
- const spinner16 = p18.spinner();
6642
- if (integration.packages && integration.packages.length > 0) {
6643
- spinner16.start(`Installing ${integration.packages.join(", ")}...`);
6688
+ const spinner17 = p18.spinner();
6689
+ spinner17.start(`Installing ${integration.packages.join(", ")}...`);
6644
6690
  try {
6645
6691
  await execa10("npm", ["install", ...integration.packages], {
6646
6692
  cwd: process.cwd(),
6647
6693
  reject: false
6648
6694
  });
6649
- spinner16.stop(`\u2713 Packages installed`);
6695
+ spinner17.stop(chalk19.green("\u2713 Packages installed"));
6650
6696
  } catch {
6651
- spinner16.stop(`\u2713 Packages installed (or already present)`);
6697
+ spinner17.stop(chalk19.yellow("\u26A0 Package install had issues (may already be installed)"));
6652
6698
  }
6653
6699
  }
6654
6700
  if (integration.envVars.length > 0) {
6655
- if (integration.authType === "oauth" && integration.oauthUrl) {
6656
- console.log(chalk19.cyan(`
6657
- Opening ${integration.name} in your browser...`));
6658
- console.log(chalk19.dim(` Get your API keys and paste them below.
6701
+ console.log(chalk19.cyan(`
6702
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6703
+ \u2502 \u{1F4CB} HOW TO GET YOUR API KEYS
6704
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6659
6705
  `));
6660
- await open2(integration.oauthUrl);
6661
- await sleep2(1500);
6706
+ integration.steps.forEach((step, i) => {
6707
+ console.log(chalk19.white(` ${i + 1}. ${step}`));
6708
+ });
6709
+ console.log(chalk19.dim(`
6710
+ Dashboard: ${integration.dashboardUrl}
6711
+ Docs: ${integration.docs}
6712
+ `));
6713
+ const openDashboard = await p18.confirm({
6714
+ message: `Open ${integration.name} dashboard in browser?`,
6715
+ initialValue: true
6716
+ });
6717
+ if (openDashboard && !p18.isCancel(openDashboard)) {
6718
+ await open2(integration.dashboardUrl);
6719
+ console.log(chalk19.dim(`
6720
+ \u2713 Browser opened! Get your keys and come back.
6721
+ `));
6722
+ await sleep2(2e3);
6662
6723
  }
6724
+ console.log(chalk19.cyan(`
6725
+ Enter your credentials:
6726
+ `));
6663
6727
  const credentials = {};
6664
6728
  for (const envVar of integration.envVars) {
6729
+ const hint = integration.envVarHints?.[envVar] || "Paste here...";
6665
6730
  const isPublic = envVar.startsWith("NEXT_PUBLIC_");
6666
- const hint = isPublic ? "(public, will be in client bundle)" : "(secret, server-only)";
6731
+ const label = isPublic ? chalk19.yellow("(public)") : chalk19.green("(secret)");
6667
6732
  const value = await p18.text({
6668
- message: `${envVar} ${chalk19.dim(hint)}:`,
6669
- placeholder: "Paste your key here...",
6733
+ message: `${envVar} ${label}`,
6734
+ placeholder: hint,
6670
6735
  validate: (v) => !v ? "Required" : void 0
6671
6736
  });
6672
6737
  if (p18.isCancel(value)) return;
6673
6738
  credentials[envVar] = value;
6674
6739
  }
6675
6740
  await saveEnvVars(credentials);
6676
- console.log(chalk19.green(` \u2713 Credentials saved to .env.local
6741
+ console.log(chalk19.green(`
6742
+ \u2713 Saved to .env.local
6677
6743
  `));
6678
6744
  }
6679
- spinner16.start("Generating setup code...");
6680
- const setupCode = await generateSetupCode(integration);
6681
- if (setupCode) {
6682
- for (const file of setupCode) {
6683
- await fs15.ensureDir(path14.dirname(file.path));
6684
- await fs15.writeFile(file.path, file.content);
6685
- }
6686
- }
6687
- spinner16.stop("\u2713 Setup complete");
6745
+ const spinner16 = p18.spinner();
6746
+ spinner16.start("Creating setup file...");
6747
+ await generateSetupCode(integration);
6748
+ spinner16.stop(chalk19.green("\u2713 Setup complete"));
6688
6749
  console.log(chalk19.green(`
6689
6750
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
6690
- \u2551 \u2713 ${integration.name} installed successfully!
6751
+ \u2551 \u2705 ${integration.name} installed!
6691
6752
  \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
6692
- `));
6693
- if (integration.packages && integration.packages.length > 0) {
6694
- console.log(chalk19.dim(` Packages: ${integration.packages.join(", ")}`));
6695
- }
6696
- if (integration.envVars.length > 0) {
6697
- console.log(chalk19.dim(` Env vars: ${integration.envVars.join(", ")}`));
6698
- }
6699
- console.log(`
6700
- \u2551 \u{1F4DA} Docs: ${integration.docs}
6753
+ \u2551 \u2551
6754
+ \u2551 Import: import { ... } from '@/lib/${integration.id}'
6755
+ \u2551 Docs: ${integration.docs.substring(0, 45).padEnd(45)}\u2551
6756
+ \u2551 \u2551
6701
6757
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
6702
- `);
6703
- }
6704
- function getCategoryLabel(category) {
6705
- const labels = {
6706
- auth: "\u{1F510} Authentication",
6707
- database: "\u{1F5C4}\uFE0F Databases",
6708
- payments: "\u{1F4B3} Payments",
6709
- email: "\u{1F4E7} Email",
6710
- storage: "\u{1F4E6} Storage",
6711
- analytics: "\u{1F4CA} Analytics",
6712
- ai: "\u{1F916} AI & ML",
6713
- cms: "\u{1F4DD} CMS",
6714
- messaging: "\u{1F4AC} Messaging",
6715
- monitoring: "\u{1F50D} Monitoring",
6716
- deployment: "\u{1F680} Deployment",
6717
- other: "\u{1F527} Other"
6718
- };
6719
- return labels[category] || category;
6758
+ `));
6720
6759
  }
6721
6760
  async function saveEnvVars(vars) {
6722
6761
  const envPath = path14.join(process.cwd(), ".env.local");
6723
6762
  let content = "";
6724
6763
  if (await fs15.pathExists(envPath)) {
6725
6764
  content = await fs15.readFile(envPath, "utf-8");
6726
- if (!content.endsWith("\n")) {
6727
- content += "\n";
6728
- }
6729
- content += "\n# Added by CodeBakers\n";
6765
+ if (!content.endsWith("\n")) content += "\n";
6730
6766
  }
6767
+ content += "\n# Added by CodeBakers\n";
6731
6768
  for (const [key, value] of Object.entries(vars)) {
6732
- const regex = new RegExp(`^${key}=`, "m");
6769
+ const regex = new RegExp(`^${key}=.*$`, "m");
6733
6770
  if (regex.test(content)) {
6734
6771
  content = content.replace(regex, `${key}=${value}`);
6735
6772
  } else {
@@ -6740,98 +6777,604 @@ async function saveEnvVars(vars) {
6740
6777
  await fs15.writeFile(envPath, content);
6741
6778
  }
6742
6779
  async function generateSetupCode(integration) {
6743
- const files = [];
6780
+ const libDir = path14.join(process.cwd(), "src", "lib");
6781
+ await fs15.ensureDir(libDir);
6782
+ let code = "";
6744
6783
  switch (integration.id) {
6745
- case "clerk":
6746
- files.push({
6747
- path: "src/middleware.ts",
6748
- content: `import { clerkMiddleware } from '@clerk/nextjs/server';
6784
+ case "supabase":
6785
+ case "supabase-auth":
6786
+ code = `import { createClient } from '@supabase/supabase-js';
6749
6787
 
6750
- export default clerkMiddleware();
6788
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
6789
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
6751
6790
 
6752
- export const config = {
6753
- matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
6754
- };
6755
- `
6756
- });
6791
+ export const supabase = createClient(supabaseUrl, supabaseAnonKey);
6792
+ `;
6757
6793
  break;
6758
6794
  case "stripe":
6759
- files.push({
6760
- path: "src/lib/stripe.ts",
6761
- content: `import Stripe from 'stripe';
6795
+ code = `import Stripe from 'stripe';
6762
6796
 
6763
6797
  export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
6764
6798
  apiVersion: '2023-10-16',
6765
- typescript: true,
6766
6799
  });
6767
- `
6768
- });
6769
- break;
6770
- case "supabase":
6771
- files.push({
6772
- path: "src/lib/supabase.ts",
6773
- content: `import { createClient } from '@supabase/supabase-js';
6774
-
6775
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
6776
- const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
6777
-
6778
- export const supabase = createClient(supabaseUrl, supabaseAnonKey);
6779
- `
6780
- });
6800
+ `;
6781
6801
  break;
6782
6802
  case "resend":
6783
- files.push({
6784
- path: "src/lib/resend.ts",
6785
- content: `import { Resend } from 'resend';
6803
+ code = `import { Resend } from 'resend';
6786
6804
 
6787
6805
  export const resend = new Resend(process.env.RESEND_API_KEY);
6788
- `
6789
- });
6806
+ `;
6790
6807
  break;
6791
6808
  case "openai":
6792
- files.push({
6793
- path: "src/lib/openai.ts",
6794
- content: `import OpenAI from 'openai';
6809
+ code = `import OpenAI from 'openai';
6795
6810
 
6796
6811
  export const openai = new OpenAI({
6797
6812
  apiKey: process.env.OPENAI_API_KEY,
6798
6813
  });
6799
- `
6800
- });
6814
+ `;
6801
6815
  break;
6802
6816
  case "anthropic":
6803
- files.push({
6804
- path: "src/lib/anthropic.ts",
6805
- content: `import Anthropic from '@anthropic-ai/sdk';
6817
+ code = `import Anthropic from '@anthropic-ai/sdk';
6806
6818
 
6807
6819
  export const anthropic = new Anthropic({
6808
6820
  apiKey: process.env.ANTHROPIC_API_KEY,
6809
6821
  });
6810
- `
6811
- });
6822
+ `;
6812
6823
  break;
6813
- case "sentry":
6814
- files.push({
6815
- path: "sentry.client.config.ts",
6816
- content: `import * as Sentry from '@sentry/nextjs';
6817
-
6818
- Sentry.init({
6819
- dsn: process.env.SENTRY_DSN,
6820
- tracesSampleRate: 1.0,
6821
- });
6822
- `
6823
- });
6824
+ case "clerk":
6825
+ code = `// Clerk is configured via middleware
6826
+ // See: https://clerk.com/docs/quickstarts/nextjs
6827
+
6828
+ // In middleware.ts:
6829
+ // import { clerkMiddleware } from '@clerk/nextjs/server';
6830
+ // export default clerkMiddleware();
6831
+
6832
+ // In layout.tsx:
6833
+ // import { ClerkProvider } from '@clerk/nextjs';
6834
+ // <ClerkProvider>{children}</ClerkProvider>
6835
+
6836
+ export {};
6837
+ `;
6824
6838
  break;
6839
+ default:
6840
+ code = `// ${integration.name}
6841
+ // Docs: ${integration.docs}
6842
+ //
6843
+ // Environment variables:
6844
+ ${integration.envVars.map((v) => `// - ${v}`).join("\n")}
6845
+
6846
+ export {};
6847
+ `;
6825
6848
  }
6826
- return files.length > 0 ? files : null;
6849
+ await fs15.writeFile(path14.join(libDir, `${integration.id}.ts`), code);
6827
6850
  }
6828
6851
  function sleep2(ms) {
6829
6852
  return new Promise((resolve) => setTimeout(resolve, ms));
6830
6853
  }
6831
6854
 
6832
- // src/utils/nlp.ts
6855
+ // src/commands/website.ts
6833
6856
  import * as p19 from "@clack/prompts";
6857
+ import chalk20 from "chalk";
6858
+ import * as fs16 from "fs-extra";
6859
+ import * as path15 from "path";
6834
6860
  import Anthropic5 from "@anthropic-ai/sdk";
6861
+ import { execa as execa11 } from "execa";
6862
+ var TEMPLATES = [
6863
+ {
6864
+ id: "landing",
6865
+ name: "Landing Page",
6866
+ description: "Convert visitors into customers",
6867
+ icon: "\u{1F680}",
6868
+ sections: ["hero", "features", "testimonials", "pricing", "cta", "footer"],
6869
+ style: "bold"
6870
+ },
6871
+ {
6872
+ id: "saas",
6873
+ name: "SaaS Website",
6874
+ description: "Software product marketing site",
6875
+ icon: "\u{1F4BB}",
6876
+ sections: ["hero", "features", "how-it-works", "pricing", "faq", "testimonials", "cta", "footer"],
6877
+ style: "minimal"
6878
+ },
6879
+ {
6880
+ id: "portfolio",
6881
+ name: "Portfolio",
6882
+ description: "Showcase your work",
6883
+ icon: "\u{1F3A8}",
6884
+ sections: ["hero", "about", "projects", "skills", "contact", "footer"],
6885
+ style: "elegant"
6886
+ },
6887
+ {
6888
+ id: "agency",
6889
+ name: "Agency Website",
6890
+ description: "Professional services company",
6891
+ icon: "\u{1F3E2}",
6892
+ sections: ["hero", "services", "portfolio", "team", "testimonials", "contact", "footer"],
6893
+ style: "corporate"
6894
+ },
6895
+ {
6896
+ id: "startup",
6897
+ name: "Startup Page",
6898
+ description: "Early stage company",
6899
+ icon: "\u26A1",
6900
+ sections: ["hero", "problem", "solution", "features", "team", "waitlist", "footer"],
6901
+ style: "bold"
6902
+ },
6903
+ {
6904
+ id: "restaurant",
6905
+ name: "Restaurant",
6906
+ description: "Food & dining establishment",
6907
+ icon: "\u{1F37D}\uFE0F",
6908
+ sections: ["hero", "menu", "about", "gallery", "reservations", "location", "footer"],
6909
+ style: "elegant"
6910
+ },
6911
+ {
6912
+ id: "ecommerce",
6913
+ name: "E-commerce",
6914
+ description: "Online store",
6915
+ icon: "\u{1F6D2}",
6916
+ sections: ["hero", "featured-products", "categories", "bestsellers", "newsletter", "footer"],
6917
+ style: "minimal"
6918
+ },
6919
+ {
6920
+ id: "blog",
6921
+ name: "Blog",
6922
+ description: "Content & articles",
6923
+ icon: "\u{1F4DD}",
6924
+ sections: ["hero", "featured-posts", "categories", "newsletter", "about", "footer"],
6925
+ style: "minimal"
6926
+ },
6927
+ {
6928
+ id: "event",
6929
+ name: "Event Page",
6930
+ description: "Conference or meetup",
6931
+ icon: "\u{1F389}",
6932
+ sections: ["hero", "speakers", "schedule", "venue", "sponsors", "tickets", "footer"],
6933
+ style: "bold"
6934
+ },
6935
+ {
6936
+ id: "nonprofit",
6937
+ name: "Nonprofit",
6938
+ description: "Charity or cause",
6939
+ icon: "\u{1F49A}",
6940
+ sections: ["hero", "mission", "impact", "programs", "donate", "volunteer", "footer"],
6941
+ style: "elegant"
6942
+ },
6943
+ {
6944
+ id: "personal",
6945
+ name: "Personal Website",
6946
+ description: "Your online presence",
6947
+ icon: "\u{1F464}",
6948
+ sections: ["hero", "about", "experience", "projects", "blog", "contact", "footer"],
6949
+ style: "minimal"
6950
+ },
6951
+ {
6952
+ id: "coming-soon",
6953
+ name: "Coming Soon",
6954
+ description: "Pre-launch teaser",
6955
+ icon: "\u23F3",
6956
+ sections: ["hero", "countdown", "features-preview", "waitlist", "footer"],
6957
+ style: "bold"
6958
+ },
6959
+ {
6960
+ id: "custom",
6961
+ name: "Custom",
6962
+ description: "Build from scratch with AI",
6963
+ icon: "\u2728",
6964
+ sections: [],
6965
+ style: "minimal"
6966
+ }
6967
+ ];
6968
+ async function websiteCommand() {
6969
+ const config = new Config();
6970
+ if (!config.isConfigured()) {
6971
+ console.log(chalk20.yellow(`
6972
+ \u26A0\uFE0F CodeBakers isn't set up yet.
6973
+
6974
+ Run this first:
6975
+ ${chalk20.cyan("codebakers setup")}
6976
+ `));
6977
+ return;
6978
+ }
6979
+ const anthropicCreds = config.getCredentials("anthropic");
6980
+ if (!anthropicCreds?.apiKey) {
6981
+ console.log(chalk20.yellow(`
6982
+ \u26A0\uFE0F Anthropic API key not configured.
6983
+
6984
+ The website builder needs Claude AI to generate code.
6985
+
6986
+ Run this to add your API key:
6987
+ ${chalk20.cyan("codebakers setup")}
6988
+
6989
+ Get an API key at:
6990
+ ${chalk20.dim("https://console.anthropic.com/settings/keys")}
6991
+ `));
6992
+ return;
6993
+ }
6994
+ console.log(chalk20.cyan(`
6995
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
6996
+ \u2551 \u{1F310} WEBSITE BUILDER \u2551
6997
+ \u2551 \u2551
6998
+ \u2551 Describe your website in plain English. \u2551
6999
+ \u2551 AI builds it in minutes. \u2551
7000
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
7001
+ `));
7002
+ const approach = await p19.select({
7003
+ message: "How would you like to build your website?",
7004
+ options: [
7005
+ { value: "describe", label: "\u{1F4AC} Describe it", hint: "Tell me what you want in plain English" },
7006
+ { value: "template", label: "\u{1F4CB} Start from template", hint: "Choose a pre-made structure" },
7007
+ { value: "url", label: "\u{1F517} Clone a design", hint: "Describe a site you like" }
7008
+ ]
7009
+ });
7010
+ if (p19.isCancel(approach)) return;
7011
+ const anthropic = new Anthropic5({ apiKey: anthropicCreds.apiKey });
7012
+ let websiteSpec;
7013
+ if (approach === "describe") {
7014
+ websiteSpec = await describeWebsite(anthropic);
7015
+ } else if (approach === "template") {
7016
+ websiteSpec = await templateWebsite(anthropic);
7017
+ } else {
7018
+ websiteSpec = await cloneDesign(anthropic);
7019
+ }
7020
+ if (!websiteSpec) return;
7021
+ console.log(chalk20.cyan(`
7022
+ \u{1F4CB} Website Plan:
7023
+ `));
7024
+ console.log(chalk20.bold(` ${websiteSpec.name}`));
7025
+ console.log(chalk20.dim(` ${websiteSpec.description}
7026
+ `));
7027
+ console.log(chalk20.dim(` Style: ${websiteSpec.style}`));
7028
+ console.log(chalk20.dim(` Sections: ${websiteSpec.sections.join(", ")}
7029
+ `));
7030
+ const confirm13 = await p19.confirm({
7031
+ message: "Build this website?",
7032
+ initialValue: true
7033
+ });
7034
+ if (!confirm13 || p19.isCancel(confirm13)) return;
7035
+ await buildWebsite(anthropic, websiteSpec, config);
7036
+ }
7037
+ async function describeWebsite(anthropic) {
7038
+ console.log(chalk20.dim("\n Describe your website. Be as detailed as you want.\n"));
7039
+ console.log(chalk20.dim(" Examples:"));
7040
+ console.log(chalk20.dim(' \u2022 "A landing page for my AI writing tool called WriteBot"'));
7041
+ console.log(chalk20.dim(' \u2022 "Portfolio site for a photographer, dark theme, minimal"'));
7042
+ console.log(chalk20.dim(' \u2022 "Coffee shop website with menu, location, and online ordering"\n'));
7043
+ const description = await textWithVoice({
7044
+ message: "Describe your website:",
7045
+ placeholder: "A landing page for..."
7046
+ });
7047
+ if (p19.isCancel(description)) return null;
7048
+ const spinner16 = p19.spinner();
7049
+ spinner16.start("Understanding your vision...");
7050
+ const response = await anthropic.messages.create({
7051
+ model: "claude-sonnet-4-20250514",
7052
+ max_tokens: 2048,
7053
+ messages: [{
7054
+ role: "user",
7055
+ content: `Based on this website description, create a detailed specification.
7056
+
7057
+ Description: "${description}"
7058
+
7059
+ Return JSON only:
7060
+ {
7061
+ "name": "Project name (kebab-case)",
7062
+ "description": "One line description",
7063
+ "style": "minimal | bold | elegant | playful | corporate",
7064
+ "colorScheme": {
7065
+ "primary": "#hex",
7066
+ "secondary": "#hex",
7067
+ "accent": "#hex",
7068
+ "background": "#hex",
7069
+ "text": "#hex"
7070
+ },
7071
+ "sections": ["hero", "features", ...],
7072
+ "content": {
7073
+ "hero": {
7074
+ "headline": "...",
7075
+ "subheadline": "...",
7076
+ "cta": "..."
7077
+ },
7078
+ "features": [
7079
+ { "title": "...", "description": "...", "icon": "..." }
7080
+ ]
7081
+ // etc for each section
7082
+ },
7083
+ "features": ["responsive", "dark-mode", "animations", etc]
7084
+ }
7085
+
7086
+ Make the content compelling and professional. Use appropriate sections for the type of website.`
7087
+ }]
7088
+ });
7089
+ spinner16.stop("Got it!");
7090
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7091
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
7092
+ if (!jsonMatch) {
7093
+ p19.log.error("Failed to understand website description");
7094
+ return null;
7095
+ }
7096
+ return JSON.parse(jsonMatch[0]);
7097
+ }
7098
+ async function templateWebsite(anthropic) {
7099
+ const template = await p19.select({
7100
+ message: "Choose a template:",
7101
+ options: TEMPLATES.map((t) => ({
7102
+ value: t.id,
7103
+ label: `${t.icon} ${t.name}`,
7104
+ hint: t.description
7105
+ }))
7106
+ });
7107
+ if (p19.isCancel(template)) return null;
7108
+ const selectedTemplate = TEMPLATES.find((t) => t.id === template);
7109
+ const name = await p19.text({
7110
+ message: "What is your business/project name?",
7111
+ placeholder: "My Awesome Project"
7112
+ });
7113
+ if (p19.isCancel(name)) return null;
7114
+ const tagline = await p19.text({
7115
+ message: "Tagline or one-line description:",
7116
+ placeholder: "The best solution for..."
7117
+ });
7118
+ if (p19.isCancel(tagline)) return null;
7119
+ const details = await textWithVoice({
7120
+ message: "Any other details? (or press enter to skip)",
7121
+ placeholder: "We help startups with..., Our colors are blue and white..."
7122
+ });
7123
+ if (p19.isCancel(details)) return null;
7124
+ const spinner16 = p19.spinner();
7125
+ spinner16.start("Customizing template...");
7126
+ const response = await anthropic.messages.create({
7127
+ model: "claude-sonnet-4-20250514",
7128
+ max_tokens: 2048,
7129
+ messages: [{
7130
+ role: "user",
7131
+ content: `Create a website spec based on this template and customization.
7132
+
7133
+ Template: ${selectedTemplate.name}
7134
+ Sections: ${selectedTemplate.sections.join(", ")}
7135
+ Default Style: ${selectedTemplate.style}
7136
+
7137
+ Business Name: ${name}
7138
+ Tagline: ${tagline}
7139
+ Additional Details: ${details || "None provided"}
7140
+
7141
+ Return JSON only:
7142
+ {
7143
+ "name": "project-name-kebab",
7144
+ "description": "${tagline}",
7145
+ "style": "${selectedTemplate.style}",
7146
+ "colorScheme": {
7147
+ "primary": "#hex",
7148
+ "secondary": "#hex",
7149
+ "accent": "#hex",
7150
+ "background": "#hex",
7151
+ "text": "#hex"
7152
+ },
7153
+ "sections": ${JSON.stringify(selectedTemplate.sections)},
7154
+ "content": {
7155
+ // Content for each section, tailored to the business
7156
+ },
7157
+ "features": ["responsive", "dark-mode", etc]
7158
+ }
7159
+
7160
+ Make the content specific and compelling for this business.`
7161
+ }]
7162
+ });
7163
+ spinner16.stop("Template customized!");
7164
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7165
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
7166
+ if (!jsonMatch) {
7167
+ p19.log.error("Failed to customize template");
7168
+ return null;
7169
+ }
7170
+ return JSON.parse(jsonMatch[0]);
7171
+ }
7172
+ async function cloneDesign(anthropic) {
7173
+ console.log(chalk20.dim("\n Describe a website design you like.\n"));
7174
+ console.log(chalk20.dim(" Examples:"));
7175
+ console.log(chalk20.dim(' \u2022 "Like Linear.app - minimal, clean, dark mode"'));
7176
+ console.log(chalk20.dim(' \u2022 "Like Stripe - professional, lots of gradients"'));
7177
+ console.log(chalk20.dim(' \u2022 "Like Notion - simple, friendly, illustrated"\n'));
7178
+ const inspiration = await textWithVoice({
7179
+ message: "What site do you want to be inspired by?",
7180
+ placeholder: "Like Linear.app but for..."
7181
+ });
7182
+ if (p19.isCancel(inspiration)) return null;
7183
+ const purpose = await p19.text({
7184
+ message: "What is YOUR website for?",
7185
+ placeholder: "A project management tool for designers"
7186
+ });
7187
+ if (p19.isCancel(purpose)) return null;
7188
+ const spinner16 = p19.spinner();
7189
+ spinner16.start("Analyzing design inspiration...");
7190
+ const response = await anthropic.messages.create({
7191
+ model: "claude-sonnet-4-20250514",
7192
+ max_tokens: 2048,
7193
+ messages: [{
7194
+ role: "user",
7195
+ content: `Create a website spec inspired by another site's design.
7196
+
7197
+ Inspiration: "${inspiration}"
7198
+ Purpose: "${purpose}"
7199
+
7200
+ Analyze the design style of the inspiration (based on your knowledge of popular websites) and create a spec that captures that aesthetic for this new purpose.
7201
+
7202
+ Return JSON only:
7203
+ {
7204
+ "name": "project-name-kebab",
7205
+ "description": "One line description",
7206
+ "style": "minimal | bold | elegant | playful | corporate",
7207
+ "colorScheme": {
7208
+ "primary": "#hex - inspired by the reference",
7209
+ "secondary": "#hex",
7210
+ "accent": "#hex",
7211
+ "background": "#hex",
7212
+ "text": "#hex"
7213
+ },
7214
+ "sections": ["appropriate sections for the purpose"],
7215
+ "content": {
7216
+ // Compelling content for each section
7217
+ },
7218
+ "features": ["responsive", "dark-mode", "animations", "gradients", etc - based on inspiration]
7219
+ }
7220
+
7221
+ Capture the FEEL of the inspiration but make it original.`
7222
+ }]
7223
+ });
7224
+ spinner16.stop("Design analyzed!");
7225
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7226
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
7227
+ if (!jsonMatch) {
7228
+ p19.log.error("Failed to analyze design");
7229
+ return null;
7230
+ }
7231
+ return JSON.parse(jsonMatch[0]);
7232
+ }
7233
+ async function buildWebsite(anthropic, spec, config) {
7234
+ const projectPath = path15.join(process.cwd(), spec.name);
7235
+ if (await fs16.pathExists(projectPath)) {
7236
+ const overwrite = await p19.confirm({
7237
+ message: `${spec.name} already exists. Overwrite?`,
7238
+ initialValue: false
7239
+ });
7240
+ if (!overwrite || p19.isCancel(overwrite)) return;
7241
+ await fs16.remove(projectPath);
7242
+ }
7243
+ console.log(chalk20.cyan(`
7244
+ \u{1F3D7}\uFE0F Building ${spec.name}...
7245
+ `));
7246
+ const spinner16 = p19.spinner();
7247
+ spinner16.start("Creating Next.js project...");
7248
+ await execa11("npx", [
7249
+ "create-next-app@latest",
7250
+ spec.name,
7251
+ "--typescript",
7252
+ "--tailwind",
7253
+ "--eslint",
7254
+ "--app",
7255
+ "--src-dir",
7256
+ "--import-alias",
7257
+ "@/*",
7258
+ "--no-git"
7259
+ ], { cwd: process.cwd(), reject: false });
7260
+ spinner16.stop("Project created");
7261
+ spinner16.start("Setting up shadcn/ui...");
7262
+ await execa11("npx", ["shadcn@latest", "init", "-y", "-d"], {
7263
+ cwd: projectPath,
7264
+ reject: false
7265
+ });
7266
+ await execa11("npx", ["shadcn@latest", "add", "button", "card", "input", "badge", "-y"], {
7267
+ cwd: projectPath,
7268
+ reject: false
7269
+ });
7270
+ spinner16.stop("UI components ready");
7271
+ spinner16.start("Generating website code...");
7272
+ const response = await anthropic.messages.create({
7273
+ model: "claude-sonnet-4-20250514",
7274
+ max_tokens: 16e3,
7275
+ messages: [{
7276
+ role: "user",
7277
+ content: `Generate a complete Next.js website based on this specification.
7278
+
7279
+ Website Spec:
7280
+ ${JSON.stringify(spec, null, 2)}
7281
+
7282
+ Generate these files:
7283
+
7284
+ 1. src/app/page.tsx - Main landing page with ALL sections
7285
+ 2. src/app/layout.tsx - Root layout with fonts, metadata
7286
+ 3. src/app/globals.css - Tailwind config with custom colors
7287
+ 4. src/components/sections/Hero.tsx
7288
+ 5. src/components/sections/[OtherSections].tsx - One for each section
7289
+ 6. src/components/ui/Navbar.tsx
7290
+ 7. src/components/ui/Footer.tsx
7291
+ 8. tailwind.config.ts - With custom colors from colorScheme
7292
+
7293
+ Requirements:
7294
+ - Use TypeScript
7295
+ - Use Tailwind CSS for styling
7296
+ - Use shadcn/ui components (Button, Card, Input, Badge)
7297
+ - Make it responsive (mobile-first)
7298
+ - Add smooth scroll behavior
7299
+ - Use modern design patterns
7300
+ - Include hover effects and transitions
7301
+ - Use Lucide icons where appropriate
7302
+
7303
+ Color scheme to use:
7304
+ - Primary: ${spec.colorScheme.primary}
7305
+ - Secondary: ${spec.colorScheme.secondary}
7306
+ - Accent: ${spec.colorScheme.accent}
7307
+ - Background: ${spec.colorScheme.background}
7308
+ - Text: ${spec.colorScheme.text}
7309
+
7310
+ Style: ${spec.style}
7311
+ ${spec.style === "minimal" ? "Clean, lots of whitespace, simple" : ""}
7312
+ ${spec.style === "bold" ? "Strong colors, big typography, impactful" : ""}
7313
+ ${spec.style === "elegant" ? "Refined, sophisticated, subtle animations" : ""}
7314
+ ${spec.style === "playful" ? "Fun, colorful, friendly illustrations" : ""}
7315
+ ${spec.style === "corporate" ? "Professional, trustworthy, structured" : ""}
7316
+
7317
+ Output format:
7318
+ <<<FILE: path/to/file.tsx>>>
7319
+ content
7320
+ <<<END_FILE>>>
7321
+
7322
+ Make it production-quality and visually impressive.`
7323
+ }]
7324
+ });
7325
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7326
+ const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
7327
+ let match;
7328
+ let fileCount = 0;
7329
+ while ((match = fileRegex.exec(text16)) !== null) {
7330
+ const filePath = path15.join(projectPath, match[1].trim());
7331
+ const content = match[2].trim();
7332
+ await fs16.ensureDir(path15.dirname(filePath));
7333
+ await fs16.writeFile(filePath, content);
7334
+ fileCount++;
7335
+ }
7336
+ spinner16.stop(`Generated ${fileCount} files`);
7337
+ spinner16.start("Initializing git...");
7338
+ await execa11("git", ["init"], { cwd: projectPath, reject: false });
7339
+ await execa11("git", ["add", "."], { cwd: projectPath, reject: false });
7340
+ await execa11("git", ["commit", "-m", "Initial website build by CodeBakers"], { cwd: projectPath, reject: false });
7341
+ spinner16.stop("Git initialized");
7342
+ console.log(chalk20.green(`
7343
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
7344
+ \u2551 \u2705 Website built successfully! \u2551
7345
+ \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
7346
+ \u2551 \u2551
7347
+ \u2551 ${spec.name.padEnd(55)}\u2551
7348
+ \u2551 ${spec.description.substring(0, 55).padEnd(55)}\u2551
7349
+ \u2551 \u2551
7350
+ \u2551 Next steps: \u2551
7351
+ \u2551 cd ${spec.name.padEnd(52)}\u2551
7352
+ \u2551 npm run dev \u2551
7353
+ \u2551 \u2551
7354
+ \u2551 Then open http://localhost:3000 \u2551
7355
+ \u2551 \u2551
7356
+ \u2551 Ready to deploy? \u2551
7357
+ \u2551 codebakers deploy \u2551
7358
+ \u2551 \u2551
7359
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
7360
+ `));
7361
+ const openDev = await p19.confirm({
7362
+ message: "Start development server now?",
7363
+ initialValue: true
7364
+ });
7365
+ if (openDev && !p19.isCancel(openDev)) {
7366
+ console.log(chalk20.dim("\n Starting dev server...\n"));
7367
+ process.chdir(projectPath);
7368
+ await execa11("npm", ["run", "dev"], {
7369
+ stdio: "inherit",
7370
+ reject: false
7371
+ });
7372
+ }
7373
+ }
7374
+
7375
+ // src/utils/nlp.ts
7376
+ import * as p20 from "@clack/prompts";
7377
+ import Anthropic6 from "@anthropic-ai/sdk";
6835
7378
  var COMMAND_PATTERNS = {
6836
7379
  init: {
6837
7380
  patterns: ["new project", "create app", "start project", "initialize", "new app", "start building", "create new"],
@@ -6900,7 +7443,7 @@ async function parseNaturalLanguage(input, config) {
6900
7443
  if (!anthropicCreds?.apiKey) {
6901
7444
  return quickMatch;
6902
7445
  }
6903
- const anthropic = new Anthropic5({ apiKey: anthropicCreds.apiKey });
7446
+ const anthropic = new Anthropic6({ apiKey: anthropicCreds.apiKey });
6904
7447
  try {
6905
7448
  const result = await parseWithAI(anthropic, input);
6906
7449
  return result;
@@ -6952,7 +7495,7 @@ function extractArgs(input, pattern) {
6952
7495
  const patternWords = pattern.toLowerCase().split(/\s+/);
6953
7496
  const inputWords = input.split(/\s+/);
6954
7497
  const args2 = inputWords.filter(
6955
- (word) => !patternWords.some((p21) => word.toLowerCase().includes(p21) || p21.includes(word.toLowerCase()))
7498
+ (word) => !patternWords.some((p22) => word.toLowerCase().includes(p22) || p22.includes(word.toLowerCase()))
6956
7499
  );
6957
7500
  return args2.filter((a) => a.length > 2);
6958
7501
  }
@@ -6983,8 +7526,8 @@ If the input is asking to build/create/add a specific feature, use "code" and pu
6983
7526
  If unclear between multiple commands, use the most likely one with lower confidence.`
6984
7527
  }]
6985
7528
  });
6986
- const text15 = response.content[0].type === "text" ? response.content[0].text : "";
6987
- const jsonMatch = text15.match(/\{[\s\S]*?\}/);
7529
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7530
+ const jsonMatch = text16.match(/\{[\s\S]*?\}/);
6988
7531
  if (!jsonMatch) {
6989
7532
  return {
6990
7533
  command: "code",
@@ -7000,11 +7543,11 @@ async function clarifyCommand(parsed) {
7000
7543
  return parsed;
7001
7544
  }
7002
7545
  if (parsed.confidence >= 0.5) {
7003
- const confirm11 = await p19.confirm({
7546
+ const confirm13 = await p20.confirm({
7004
7547
  message: `Did you mean: ${parsed.interpretation}?`,
7005
7548
  initialValue: true
7006
7549
  });
7007
- if (confirm11 && !p19.isCancel(confirm11)) {
7550
+ if (confirm13 && !p20.isCancel(confirm13)) {
7008
7551
  return { ...parsed, confidence: 1 };
7009
7552
  }
7010
7553
  }
@@ -7019,19 +7562,19 @@ async function clarifyCommand(parsed) {
7019
7562
  { value: "prd-maker", label: "\u{1F4DD} Create PRD", description: "Document your idea" },
7020
7563
  { value: "other", label: "\u2753 Something else", description: "Describe what you need" }
7021
7564
  ];
7022
- const selection = await p19.select({
7565
+ const selection = await p20.select({
7023
7566
  message: "What would you like to do?",
7024
7567
  options: options.map((o) => ({ value: o.value, label: o.label, hint: o.description }))
7025
7568
  });
7026
- if (p19.isCancel(selection)) {
7569
+ if (p20.isCancel(selection)) {
7027
7570
  return { ...parsed, command: "cancel", confidence: 1 };
7028
7571
  }
7029
7572
  if (selection === "other") {
7030
- const description = await p19.text({
7573
+ const description = await p20.text({
7031
7574
  message: "Describe what you want to do:",
7032
7575
  placeholder: "I want to..."
7033
7576
  });
7034
- if (p19.isCancel(description)) {
7577
+ if (p20.isCancel(description)) {
7035
7578
  return { ...parsed, command: "cancel", confidence: 1 };
7036
7579
  }
7037
7580
  return {
@@ -7049,19 +7592,19 @@ async function clarifyCommand(parsed) {
7049
7592
  };
7050
7593
  }
7051
7594
  async function clarifyDeployTarget() {
7052
- const target = await p19.select({
7595
+ const target = await p20.select({
7053
7596
  message: "Where do you want to deploy?",
7054
7597
  options: [
7055
7598
  { value: "preview", label: "\u{1F50D} Preview", hint: "Test URL to review changes" },
7056
7599
  { value: "production", label: "\u{1F680} Production", hint: "Live site for users" }
7057
7600
  ]
7058
7601
  });
7059
- if (p19.isCancel(target)) return null;
7602
+ if (p20.isCancel(target)) return null;
7060
7603
  return target;
7061
7604
  }
7062
7605
 
7063
7606
  // src/index.ts
7064
- var VERSION2 = "2.1.0";
7607
+ var VERSION2 = "2.2.0";
7065
7608
  var logo = `
7066
7609
  \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
7067
7610
  \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
@@ -7074,18 +7617,18 @@ async function showMainMenu() {
7074
7617
  const config = new Config();
7075
7618
  const isSetup = config.isConfigured();
7076
7619
  console.log(gradient.pastel.multiline(logo));
7077
- console.log(chalk20.dim(` v${VERSION2} \u2014 AI dev team that follows the rules
7620
+ console.log(chalk21.dim(` v${VERSION2} \u2014 AI dev team that follows the rules
7078
7621
  `));
7079
7622
  if (!isSetup) {
7080
7623
  console.log(boxen(
7081
- chalk20.yellow("Welcome to CodeBakers! Let's get you set up."),
7624
+ chalk21.yellow("Welcome to CodeBakers! Let's get you set up."),
7082
7625
  { padding: 1, borderColor: "yellow", borderStyle: "round" }
7083
7626
  ));
7084
7627
  await setupCommand();
7085
7628
  return;
7086
7629
  }
7087
7630
  const inProject = config.isInProject();
7088
- const action = await p20.select({
7631
+ const action = await p21.select({
7089
7632
  message: "What do you want to do? (or type naturally)",
7090
7633
  options: inProject ? [
7091
7634
  { value: "code", label: "\u{1F4AC} Code with AI", hint: "build features, fix bugs" },
@@ -7104,6 +7647,7 @@ async function showMainMenu() {
7104
7647
  { value: "design", label: "\u{1F3A8} Design system", hint: "set profile, colors" },
7105
7648
  { value: "separator2", label: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" },
7106
7649
  { value: "new", label: "\u{1F195} Create new project" },
7650
+ { value: "website", label: "\u{1F310} Website Builder", hint: "Describe \u2192 AI builds it" },
7107
7651
  { value: "build", label: "\u{1F3D7}\uFE0F Parallel Build", hint: "3 agents from PRD (swarm)" },
7108
7652
  { value: "prd", label: "\u{1F4C4} Build from PRD", hint: "sequential build" },
7109
7653
  { value: "prd-maker", label: "\u270F\uFE0F Create PRD", hint: "interview \u2192 generate PRD" },
@@ -7112,6 +7656,7 @@ async function showMainMenu() {
7112
7656
  { value: "help", label: "\u2753 Help" }
7113
7657
  ] : [
7114
7658
  { value: "new", label: "\u{1F195} Create new project" },
7659
+ { value: "website", label: "\u{1F310} Website Builder", hint: "Describe \u2192 AI builds it" },
7115
7660
  { value: "build", label: "\u{1F3D7}\uFE0F Parallel Build", hint: "3 agents from PRD (swarm)" },
7116
7661
  { value: "prd", label: "\u{1F4C4} Build from PRD", hint: "sequential build" },
7117
7662
  { value: "prd-maker", label: "\u270F\uFE0F Create PRD", hint: "interview \u2192 generate PRD" },
@@ -7126,8 +7671,8 @@ async function showMainMenu() {
7126
7671
  { value: "help", label: "\u2753 Help" }
7127
7672
  ]
7128
7673
  });
7129
- if (p20.isCancel(action)) {
7130
- p20.cancel("Goodbye!");
7674
+ if (p21.isCancel(action)) {
7675
+ p21.cancel("Goodbye!");
7131
7676
  process.exit(0);
7132
7677
  }
7133
7678
  switch (action) {
@@ -7173,6 +7718,9 @@ async function showMainMenu() {
7173
7718
  case "new":
7174
7719
  await initCommand();
7175
7720
  break;
7721
+ case "website":
7722
+ await websiteCommand();
7723
+ break;
7176
7724
  case "build":
7177
7725
  await buildCommand();
7178
7726
  break;
@@ -7197,27 +7745,34 @@ async function showMainMenu() {
7197
7745
  }
7198
7746
  function showHelp2() {
7199
7747
  console.log(boxen(`
7200
- ${chalk20.bold("CodeBakers CLI")} \u2014 AI dev team that follows the rules
7201
-
7202
- ${chalk20.bold("Commands:")}
7203
- ${chalk20.cyan("codebakers")} Interactive menu (or just run with no args)
7204
- ${chalk20.cyan("codebakers init")} Create a new project
7205
- ${chalk20.cyan("codebakers code")} Start AI coding session
7206
- ${chalk20.cyan("codebakers check")} Run pattern enforcement
7207
- ${chalk20.cyan("codebakers deploy")} Deploy to production
7208
- ${chalk20.cyan("codebakers fix")} Auto-fix errors
7209
- ${chalk20.cyan("codebakers generate")} Generate components/pages
7210
- ${chalk20.cyan("codebakers connect")} Connect external services
7211
- ${chalk20.cyan("codebakers gateway")} Manage messaging channels
7212
- ${chalk20.cyan("codebakers status")} View project status
7213
- ${chalk20.cyan("codebakers security")} Run security audit
7214
- ${chalk20.cyan("codebakers learn")} View/manage learning
7215
-
7216
- ${chalk20.bold("Help at any time:")}
7217
- Press ${chalk20.yellow("?")} during any command to get contextual help
7218
-
7219
- ${chalk20.bold("Documentation:")}
7220
- ${chalk20.dim("https://codebakers.dev/docs")}
7748
+ ${chalk21.bold("CodeBakers CLI v" + VERSION2)} \u2014 AI dev team that follows the rules
7749
+
7750
+ ${chalk21.bold.cyan("Getting Started:")}
7751
+ ${chalk21.cyan("codebakers setup")} Connect your accounts (GitHub, Vercel, etc.)
7752
+ ${chalk21.cyan("codebakers init")} Create a new project from scratch
7753
+ ${chalk21.cyan("codebakers website")} Build a website by describing it
7754
+ ${chalk21.cyan("codebakers build")} Build from PRD with parallel AI agents
7755
+
7756
+ ${chalk21.bold.cyan("Daily Development:")}
7757
+ ${chalk21.cyan("codebakers code")} Chat with AI to build features
7758
+ ${chalk21.cyan("codebakers check")} Check code quality & patterns
7759
+ ${chalk21.cyan("codebakers fix")} Auto-fix errors with AI
7760
+ ${chalk21.cyan("codebakers deploy")} Deploy to Vercel
7761
+
7762
+ ${chalk21.bold.cyan("Integrations:")}
7763
+ ${chalk21.cyan("codebakers integrate")} 50+ one-click integrations (Stripe, Supabase, etc.)
7764
+ ${chalk21.cyan("codebakers gateway")} Connect WhatsApp, Telegram, Discord, etc.
7765
+
7766
+ ${chalk21.bold.cyan("Planning:")}
7767
+ ${chalk21.cyan("codebakers prd-maker")} Create PRD through interview (voice supported)
7768
+ ${chalk21.cyan("codebakers advisors")} Consult AI experts (CEO, CTO, Designer, etc.)
7769
+
7770
+ ${chalk21.bold.cyan("Tips:")}
7771
+ \u2022 Type naturally: ${chalk21.dim('codebakers "add a contact form"')}
7772
+ \u2022 Voice input: Type ${chalk21.yellow('"v"')} at any prompt
7773
+ \u2022 Clipboard: Type ${chalk21.yellow('"clip"')} to paste from clipboard
7774
+
7775
+ ${chalk21.bold("Docs:")} ${chalk21.dim("https://codebakers.dev/docs")}
7221
7776
  `, { padding: 1, borderColor: "cyan", borderStyle: "round" }));
7222
7777
  }
7223
7778
  var program = new Command();
@@ -7241,9 +7796,10 @@ program.command("migrate").alias("db").description("Database migrations (push, g
7241
7796
  program.command("prd-maker").alias("create-prd").description("Create a PRD through guided interview (supports voice input)").action(prdMakerCommand);
7242
7797
  program.command("build [prd-file]").alias("swarm").description("Parallel build with 3 AI agents (self-healing)").option("--sequential", "Disable parallel execution").action(buildCommand);
7243
7798
  program.command("integrate [integration]").alias("add").description("One-click integrations (50+ services with browser auth)").action(integrateCommand);
7799
+ program.command("website").alias("site").description("Build a website by describing it in plain English").action(websiteCommand);
7244
7800
  async function handleNaturalLanguage(input) {
7245
7801
  const config = new Config();
7246
- console.log(chalk20.dim("\n Parsing your request...\n"));
7802
+ console.log(chalk21.dim("\n Parsing your request...\n"));
7247
7803
  const parsed = await parseNaturalLanguage(input, config);
7248
7804
  if (!parsed) {
7249
7805
  await codeCommand(input);
@@ -7331,6 +7887,8 @@ if (args.length === 0) {
7331
7887
  "swarm",
7332
7888
  "integrate",
7333
7889
  "add",
7890
+ "website",
7891
+ "site",
7334
7892
  "help"
7335
7893
  ];
7336
7894
  const firstArg = args[0];