codebakers 2.1.2 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  prdCommand
4
- } from "./chunk-YGVDLNXY.js";
4
+ } from "./chunk-ND6T4UDY.js";
5
5
  import {
6
6
  advisorsCommand
7
- } from "./chunk-FWQNLNTI.js";
7
+ } from "./chunk-YUSDTJD6.js";
8
8
  import {
9
9
  Config
10
- } from "./chunk-RCC7FYEU.js";
10
+ } from "./chunk-ASIJIQYC.js";
11
11
 
12
12
  // src/index.ts
13
13
  import { Command } from "commander";
@@ -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 confirm12 = await p.confirm({
69
- message: "Are you sure? This will remove all credentials and settings."
70
- });
71
- if (confirm12) {
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 spinner17 = 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
@@ -1029,20 +1039,28 @@ Domain: ${domain || "Vercel default"}`,
1029
1039
  p2.cancel("Project creation cancelled.");
1030
1040
  return;
1031
1041
  }
1032
- const spinner17 = p2.spinner();
1042
+ const spinner16 = p2.spinner();
1033
1043
  const projectPath = path.join(process.cwd(), projectName);
1034
1044
  try {
1035
- spinner17.start("Creating local project...");
1045
+ spinner16.start("Creating local project...");
1036
1046
  await createLocalProject(projectPath, projectConfig);
1037
- spinner17.stop("Local project created");
1038
- spinner17.start("Installing dependencies...");
1039
- await execa3("pnpm", ["install"], { cwd: projectPath });
1040
- spinner17.stop("Dependencies installed");
1047
+ spinner16.stop("Local project created");
1048
+ spinner16.start("Installing dependencies...");
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
+ }
1058
+ spinner16.stop("Dependencies installed");
1041
1059
  if (services.includes("github")) {
1042
- spinner17.start("Creating GitHub repository...");
1060
+ spinner16.start("Creating GitHub repository...");
1043
1061
  const github = new GitHubService(config);
1044
1062
  const repo = await github.createRepo(projectName, { private: true });
1045
- spinner17.stop(`GitHub repo created: ${repo.html_url}`);
1063
+ spinner16.stop(`GitHub repo created: ${repo.html_url}`);
1046
1064
  await execa3("git", ["init"], { cwd: projectPath });
1047
1065
  await execa3("git", ["add", "."], { cwd: projectPath });
1048
1066
  await execa3("git", ["commit", "-m", "Initial commit by CodeBakers"], { cwd: projectPath });
@@ -1050,10 +1068,10 @@ Domain: ${domain || "Vercel default"}`,
1050
1068
  await execa3("git", ["push", "-u", "origin", "main"], { cwd: projectPath });
1051
1069
  }
1052
1070
  if (services.includes("supabase")) {
1053
- spinner17.start("Creating Supabase project...");
1071
+ spinner16.start("Creating Supabase project...");
1054
1072
  const supabase = new SupabaseService(config);
1055
1073
  const project = await supabase.createProject(projectName);
1056
- spinner17.stop(`Supabase project created: ${project.name}`);
1074
+ spinner16.stop(`Supabase project created: ${project.name}`);
1057
1075
  await fs.writeJson(
1058
1076
  path.join(projectPath, ".codebakers", "supabase.json"),
1059
1077
  { projectId: project.id, projectUrl: project.api_url },
@@ -1061,26 +1079,26 @@ Domain: ${domain || "Vercel default"}`,
1061
1079
  );
1062
1080
  }
1063
1081
  if (services.includes("vercel")) {
1064
- spinner17.start("Creating Vercel project...");
1082
+ spinner16.start("Creating Vercel project...");
1065
1083
  const vercel = new VercelService(config);
1066
1084
  const project = await vercel.createProject(projectName);
1067
- spinner17.stop(`Vercel project created`);
1085
+ spinner16.stop(`Vercel project created`);
1068
1086
  if (domain) {
1069
- spinner17.start(`Configuring domain: ${domain}...`);
1087
+ spinner16.start(`Configuring domain: ${domain}...`);
1070
1088
  await vercel.addDomain(projectName, domain);
1071
- spinner17.stop("Domain configured");
1089
+ spinner16.stop("Domain configured");
1072
1090
  }
1073
- spinner17.start("Deploying to Vercel...");
1091
+ spinner16.start("Deploying to Vercel...");
1074
1092
  const deployment = await vercel.deploy(projectPath);
1075
- spinner17.stop(`Deployed: ${deployment.url}`);
1093
+ spinner16.stop(`Deployed: ${deployment.url}`);
1076
1094
  }
1077
- spinner17.start("Generating CLAUDE.md...");
1095
+ spinner16.start("Generating CLAUDE.md...");
1078
1096
  const claudeMd = generateClaudeMd(projectConfig);
1079
1097
  await fs.writeFile(path.join(projectPath, "CLAUDE.md"), claudeMd);
1080
- spinner17.stop("CLAUDE.md generated");
1081
- spinner17.start("Setting up CodeBakers enforcement...");
1098
+ spinner16.stop("CLAUDE.md generated");
1099
+ spinner16.start("Setting up CodeBakers enforcement...");
1082
1100
  await setupGitHooks(projectPath);
1083
- spinner17.stop("CodeBakers enforcement configured");
1101
+ spinner16.stop("CodeBakers enforcement configured");
1084
1102
  config.addProject({
1085
1103
  name: projectName,
1086
1104
  path: projectPath,
@@ -1100,7 +1118,7 @@ ${chalk3.dim("Shortcuts:")}
1100
1118
  ${chalk3.cyan("codebakers deploy")} \u2014 Deploy to production
1101
1119
  `));
1102
1120
  } catch (error) {
1103
- spinner17.stop("Error occurred");
1121
+ spinner16.stop("Error occurred");
1104
1122
  p2.log.error(`Failed to create project: ${error instanceof Error ? error.message : "Unknown error"}`);
1105
1123
  const cleanup = await p2.confirm({
1106
1124
  message: "Clean up partially created project?"
@@ -1826,17 +1844,17 @@ async function checkCommand(options = {}) {
1826
1844
  return;
1827
1845
  }
1828
1846
  p3.intro(chalk4.bgCyan.black(" CodeBakers Pattern Check "));
1829
- const spinner17 = p3.spinner();
1830
- spinner17.start("Analyzing code...");
1847
+ const spinner16 = p3.spinner();
1848
+ spinner16.start("Analyzing code...");
1831
1849
  const result = await runPatternCheck(options.fix || false);
1832
- spinner17.stop("Analysis complete");
1850
+ spinner16.stop("Analysis complete");
1833
1851
  displayResults(result);
1834
1852
  if (result.violations.length > 0 && options.fix) {
1835
1853
  const fixable = result.violations.filter((v) => v.autoFixable);
1836
1854
  if (fixable.length > 0) {
1837
- spinner17.start(`Auto-fixing ${fixable.length} violations...`);
1855
+ spinner16.start(`Auto-fixing ${fixable.length} violations...`);
1838
1856
  await autoFix(fixable);
1839
- spinner17.stop("Auto-fix complete");
1857
+ spinner16.stop("Auto-fix complete");
1840
1858
  }
1841
1859
  }
1842
1860
  const errors = result.violations.filter((v) => v.severity === "error");
@@ -2040,8 +2058,8 @@ async function getVoiceInput(prompt) {
2040
2058
  return null;
2041
2059
  }
2042
2060
  await playBeep();
2043
- const spinner17 = p4.spinner();
2044
- spinner17.start("\u{1F534} Recording...");
2061
+ const spinner16 = p4.spinner();
2062
+ spinner16.start("\u{1F534} Recording...");
2045
2063
  try {
2046
2064
  let transcription = "";
2047
2065
  if (process.platform === "win32") {
@@ -2051,16 +2069,16 @@ async function getVoiceInput(prompt) {
2051
2069
  } else {
2052
2070
  transcription = await recordWithLinux();
2053
2071
  }
2054
- spinner17.stop("Recording complete");
2072
+ spinner16.stop("Recording complete");
2055
2073
  if (transcription) {
2056
2074
  console.log(chalk5.green(`
2057
2075
  \u2713 Heard: "${transcription}"
2058
2076
  `));
2059
- const confirm12 = await p4.confirm({
2077
+ const confirm13 = await p4.confirm({
2060
2078
  message: "Is this correct?",
2061
2079
  initialValue: true
2062
2080
  });
2063
- if (confirm12 && !p4.isCancel(confirm12)) {
2081
+ if (confirm13 && !p4.isCancel(confirm13)) {
2064
2082
  return transcription;
2065
2083
  } else {
2066
2084
  const action = await p4.select({
@@ -2085,7 +2103,7 @@ async function getVoiceInput(prompt) {
2085
2103
  return p4.isCancel(text16) ? null : text16;
2086
2104
  }
2087
2105
  } catch (error) {
2088
- spinner17.stop("Recording failed");
2106
+ spinner16.stop("Recording failed");
2089
2107
  console.log(chalk5.yellow("Voice input failed. Please type instead."));
2090
2108
  const text16 = await p4.text({ message: prompt });
2091
2109
  return p4.isCancel(text16) ? null : text16;
@@ -2480,16 +2498,41 @@ ${text16}
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({
@@ -2629,14 +2672,14 @@ ${clipContent}
2629
2672
  }
2630
2673
  }
2631
2674
  async function processUserInput(userInput, messages, anthropic, systemPrompt, projectContext, config) {
2632
- const spinner17 = p6.spinner();
2675
+ const spinner16 = p6.spinner();
2633
2676
  messages.push({ role: "user", content: userInput });
2634
2677
  const wizardResult = await checkForWizard(userInput);
2635
2678
  if (wizardResult) {
2636
2679
  messages[messages.length - 1].content = wizardResult;
2637
2680
  }
2638
2681
  try {
2639
- spinner17.start("Thinking...");
2682
+ spinner16.start("Thinking...");
2640
2683
  const response = await anthropic.messages.create({
2641
2684
  model: "claude-sonnet-4-20250514",
2642
2685
  max_tokens: 8192,
@@ -2646,7 +2689,7 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2646
2689
  content: m.content
2647
2690
  }))
2648
2691
  });
2649
- spinner17.stop("");
2692
+ spinner16.stop("");
2650
2693
  const assistantMessage = response.content[0].type === "text" ? response.content[0].text : "";
2651
2694
  messages.push({ role: "assistant", content: assistantMessage });
2652
2695
  const actions = parseActions(assistantMessage);
@@ -2661,11 +2704,11 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2661
2704
  initialValue: true
2662
2705
  });
2663
2706
  if (proceed && !p6.isCancel(proceed)) {
2664
- spinner17.start("Building...");
2707
+ spinner16.start("Building...");
2665
2708
  for (const action of actions) {
2666
- await executeAction(action, spinner17);
2709
+ await executeAction(action, spinner16);
2667
2710
  }
2668
- spinner17.stop("Build complete");
2711
+ spinner16.stop("Build complete");
2669
2712
  console.log(chalk7.dim("\n\u{1F50D} Running CodeBakers check..."));
2670
2713
  const checkResult = await runPatternCheck(false);
2671
2714
  if (checkResult.violations.length > 0) {
@@ -2676,9 +2719,9 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2676
2719
  initialValue: true
2677
2720
  });
2678
2721
  if (autoFix2 && !p6.isCancel(autoFix2)) {
2679
- spinner17.start("Auto-fixing...");
2722
+ spinner16.start("Auto-fixing...");
2680
2723
  await autoFixViolations(checkResult.violations, anthropic, systemPrompt);
2681
- spinner17.stop("Violations fixed");
2724
+ spinner16.stop("Violations fixed");
2682
2725
  }
2683
2726
  } else {
2684
2727
  console.log(chalk7.green("\u2713 All patterns satisfied"));
@@ -2689,7 +2732,7 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2689
2732
  console.log("\n" + assistantMessage + "\n");
2690
2733
  }
2691
2734
  } catch (error) {
2692
- spinner17.stop("Error");
2735
+ spinner16.stop("Error");
2693
2736
  console.log(chalk7.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2694
2737
  }
2695
2738
  }
@@ -2801,14 +2844,14 @@ function parseActions(response) {
2801
2844
  }
2802
2845
  return actions;
2803
2846
  }
2804
- async function executeAction(action, spinner17) {
2847
+ async function executeAction(action, spinner16) {
2805
2848
  const cwd = process.cwd();
2806
2849
  switch (action.type) {
2807
2850
  case "CREATE_FILE": {
2808
2851
  const filePath = path5.join(cwd, action.path);
2809
2852
  await fs6.ensureDir(path5.dirname(filePath));
2810
2853
  await fs6.writeFile(filePath, action.content);
2811
- spinner17.message(`Created ${action.path}`);
2854
+ spinner16.message(`Created ${action.path}`);
2812
2855
  break;
2813
2856
  }
2814
2857
  case "EDIT_FILE": {
@@ -2818,13 +2861,13 @@ async function executeAction(action, spinner17) {
2818
2861
  if (action.find && content.includes(action.find)) {
2819
2862
  content = content.replace(action.find, action.replace || "");
2820
2863
  await fs6.writeFile(filePath, content);
2821
- spinner17.message(`Edited ${action.path}`);
2864
+ spinner16.message(`Edited ${action.path}`);
2822
2865
  }
2823
2866
  }
2824
2867
  break;
2825
2868
  }
2826
2869
  case "RUN_COMMAND": {
2827
- spinner17.message(`Running: ${action.command}`);
2870
+ spinner16.message(`Running: ${action.command}`);
2828
2871
  const [cmd, ...args2] = action.command.split(" ");
2829
2872
  await execa6(cmd, args2, { cwd, stdio: "pipe" });
2830
2873
  break;
@@ -2833,7 +2876,7 @@ async function executeAction(action, spinner17) {
2833
2876
  const filePath = path5.join(cwd, action.path);
2834
2877
  if (await fs6.pathExists(filePath)) {
2835
2878
  await fs6.remove(filePath);
2836
- spinner17.message(`Deleted ${action.path}`);
2879
+ spinner16.message(`Deleted ${action.path}`);
2837
2880
  }
2838
2881
  break;
2839
2882
  }
@@ -3077,16 +3120,39 @@ 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 "));
3084
- const spinner17 = p7.spinner();
3150
+ const spinner16 = p7.spinner();
3085
3151
  if (options.check !== false) {
3086
- spinner17.start("Running CodeBakers check...");
3152
+ spinner16.start("Running CodeBakers check...");
3087
3153
  const checkResult = await runPatternCheck(false);
3088
3154
  if (!checkResult.passed) {
3089
- spinner17.stop("");
3155
+ spinner16.stop("");
3090
3156
  const errors = checkResult.violations.filter((v) => v.severity === "error");
3091
3157
  if (errors.length > 0) {
3092
3158
  p7.log.error(`${errors.length} pattern violations found. Fix before deploying.`);
@@ -3107,9 +3173,9 @@ async function deployCommand(options = {}) {
3107
3173
  p7.outro(chalk8.red("Deploy cancelled."));
3108
3174
  return;
3109
3175
  }
3110
- spinner17.start("Auto-fixing with AI...");
3176
+ spinner16.start("Auto-fixing with AI...");
3111
3177
  await autoFixWithAI(config, errors);
3112
- spinner17.stop("Auto-fix complete");
3178
+ spinner16.stop("Auto-fix complete");
3113
3179
  const recheck = await runPatternCheck(false);
3114
3180
  if (!recheck.passed) {
3115
3181
  p7.log.error("Some violations remain. Manual fix required.");
@@ -3118,23 +3184,23 @@ async function deployCommand(options = {}) {
3118
3184
  }
3119
3185
  }
3120
3186
  }
3121
- spinner17.stop("Pattern check passed");
3187
+ spinner16.stop("Pattern check passed");
3122
3188
  }
3123
- spinner17.start("Running TypeScript check...");
3189
+ spinner16.start("Running TypeScript check...");
3124
3190
  try {
3125
3191
  await execa7("npx", ["tsc", "--noEmit"], { cwd: process.cwd() });
3126
- spinner17.stop("TypeScript check passed");
3192
+ spinner16.stop("TypeScript check passed");
3127
3193
  } catch (error) {
3128
- spinner17.stop("");
3194
+ spinner16.stop("");
3129
3195
  p7.log.error("TypeScript errors found.");
3130
3196
  const fix = await p7.confirm({
3131
3197
  message: "Attempt to fix TypeScript errors?",
3132
3198
  initialValue: true
3133
3199
  });
3134
3200
  if (fix && !p7.isCancel(fix)) {
3135
- spinner17.start("Fixing TypeScript errors...");
3201
+ spinner16.start("Fixing TypeScript errors...");
3136
3202
  const fixed = await fixTypeScriptErrors(config);
3137
- spinner17.stop(fixed ? "TypeScript errors fixed" : "Could not auto-fix all errors");
3203
+ spinner16.stop(fixed ? "TypeScript errors fixed" : "Could not auto-fix all errors");
3138
3204
  if (!fixed) {
3139
3205
  p7.outro(chalk8.red("Deploy cancelled."));
3140
3206
  return;
@@ -3144,12 +3210,20 @@ async function deployCommand(options = {}) {
3144
3210
  return;
3145
3211
  }
3146
3212
  }
3147
- spinner17.start("Building project...");
3213
+ spinner16.start("Building project...");
3148
3214
  try {
3149
- await execa7("pnpm", ["build"], { cwd: process.cwd() });
3150
- spinner17.stop("Build successful");
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
+ }
3224
+ spinner16.stop("Build successful");
3151
3225
  } catch (error) {
3152
- spinner17.stop("");
3226
+ spinner16.stop("");
3153
3227
  p7.log.error("Build failed.");
3154
3228
  const errorOutput = error instanceof Error ? error.message : "Unknown error";
3155
3229
  console.log(chalk8.dim(errorOutput));
@@ -3158,16 +3232,16 @@ async function deployCommand(options = {}) {
3158
3232
  initialValue: true
3159
3233
  });
3160
3234
  if (fix && !p7.isCancel(fix)) {
3161
- spinner17.start("Fixing build errors...");
3235
+ spinner16.start("Fixing build errors...");
3162
3236
  const fixed = await fixBuildErrors(config, errorOutput);
3163
- spinner17.stop(fixed ? "Build errors fixed" : "Could not auto-fix");
3237
+ spinner16.stop(fixed ? "Build errors fixed" : "Could not auto-fix");
3164
3238
  if (fixed) {
3165
- spinner17.start("Retrying build...");
3239
+ spinner16.start("Retrying build...");
3166
3240
  try {
3167
3241
  await execa7("pnpm", ["build"], { cwd: process.cwd() });
3168
- spinner17.stop("Build successful");
3242
+ spinner16.stop("Build successful");
3169
3243
  } catch {
3170
- spinner17.stop("Build still failing");
3244
+ spinner16.stop("Build still failing");
3171
3245
  p7.outro(chalk8.red("Deploy cancelled."));
3172
3246
  return;
3173
3247
  }
@@ -3180,10 +3254,10 @@ async function deployCommand(options = {}) {
3180
3254
  return;
3181
3255
  }
3182
3256
  }
3183
- spinner17.start("Checking for uncommitted changes...");
3257
+ spinner16.start("Checking for uncommitted changes...");
3184
3258
  const { stdout: gitStatus } = await execa7("git", ["status", "--porcelain"], { cwd: process.cwd() });
3185
3259
  if (gitStatus.trim()) {
3186
- spinner17.stop("");
3260
+ spinner16.stop("");
3187
3261
  const commit = await p7.confirm({
3188
3262
  message: "You have uncommitted changes. Commit before deploying?",
3189
3263
  initialValue: true
@@ -3197,20 +3271,20 @@ async function deployCommand(options = {}) {
3197
3271
  if (!p7.isCancel(message)) {
3198
3272
  await execa7("git", ["add", "."], { cwd: process.cwd() });
3199
3273
  await execa7("git", ["commit", "-m", message], { cwd: process.cwd() });
3200
- spinner17.start("Pushing to GitHub...");
3274
+ spinner16.start("Pushing to GitHub...");
3201
3275
  await execa7("git", ["push"], { cwd: process.cwd() });
3202
- spinner17.stop("Pushed to GitHub");
3276
+ spinner16.stop("Pushed to GitHub");
3203
3277
  }
3204
3278
  }
3205
3279
  } else {
3206
- spinner17.stop("No uncommitted changes");
3280
+ spinner16.stop("No uncommitted changes");
3207
3281
  }
3208
3282
  const deployType = options.preview ? "preview" : "production";
3209
- spinner17.start(`Deploying to ${deployType}...`);
3283
+ spinner16.start(`Deploying to ${deployType}...`);
3210
3284
  try {
3211
3285
  const vercel = new VercelService(config);
3212
3286
  const deployment = await vercel.deploy(process.cwd(), !options.preview);
3213
- spinner17.stop("Deployment complete!");
3287
+ spinner16.stop("Deployment complete!");
3214
3288
  console.log(boxedOutput(`
3215
3289
  ${chalk8.green("\u2713")} Deployed successfully!
3216
3290
 
@@ -3222,7 +3296,7 @@ ${chalk8.dim("View in Vercel Dashboard:")}
3222
3296
  ${chalk8.dim(deployment.dashboardUrl || "https://vercel.com/dashboard")}
3223
3297
  `));
3224
3298
  } catch (error) {
3225
- spinner17.stop("");
3299
+ spinner16.stop("");
3226
3300
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
3227
3301
  p7.log.error(`Deployment failed: ${errorMsg}`);
3228
3302
  if (errorMsg.includes("Build failed")) {
@@ -3231,8 +3305,8 @@ ${chalk8.dim(deployment.dashboardUrl || "https://vercel.com/dashboard")}
3231
3305
  initialValue: true
3232
3306
  });
3233
3307
  if (retry && !p7.isCancel(retry)) {
3234
- spinner17.start("Analyzing Vercel build error...");
3235
- spinner17.stop("Fix attempted");
3308
+ spinner16.start("Analyzing Vercel build error...");
3309
+ spinner16.stop("Fix attempted");
3236
3310
  }
3237
3311
  }
3238
3312
  p7.outro(chalk8.red("Deploy failed."));
@@ -3598,10 +3672,10 @@ you'll need a Meta Business account.
3598
3672
  initialValue: true
3599
3673
  });
3600
3674
  if (!proceed || p9.isCancel(proceed)) return;
3601
- const spinner17 = p9.spinner();
3602
- spinner17.start("Generating QR code...");
3675
+ const spinner16 = p9.spinner();
3676
+ spinner16.start("Generating QR code...");
3603
3677
  try {
3604
- spinner17.stop("");
3678
+ spinner16.stop("");
3605
3679
  console.log(chalk10.cyan(`
3606
3680
  \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\u2557
3607
3681
  \u2551 \u2551
@@ -3633,7 +3707,7 @@ Scan this QR code with WhatsApp:
3633
3707
  p9.log.success("WhatsApp connected!");
3634
3708
  }
3635
3709
  } catch (error) {
3636
- spinner17.stop("Error connecting WhatsApp");
3710
+ spinner16.stop("Error connecting WhatsApp");
3637
3711
  p9.log.error(error instanceof Error ? error.message : "Unknown error");
3638
3712
  }
3639
3713
  }
@@ -3664,15 +3738,15 @@ To create a Telegram bot:
3664
3738
  }
3665
3739
  });
3666
3740
  if (p9.isCancel(token)) return;
3667
- const spinner17 = p9.spinner();
3668
- spinner17.start("Verifying bot token...");
3741
+ const spinner16 = p9.spinner();
3742
+ spinner16.start("Verifying bot token...");
3669
3743
  try {
3670
3744
  const response = await fetch(`https://api.telegram.org/bot${token}/getMe`);
3671
3745
  const data = await response.json();
3672
3746
  if (!data.ok) {
3673
3747
  throw new Error(data.description || "Invalid token");
3674
3748
  }
3675
- spinner17.stop("Bot verified!");
3749
+ spinner16.stop("Bot verified!");
3676
3750
  config.setChannelConfig("telegram", {
3677
3751
  enabled: true,
3678
3752
  botToken: token,
@@ -3680,7 +3754,7 @@ To create a Telegram bot:
3680
3754
  });
3681
3755
  p9.log.success(`Connected to @${data.result.username}`);
3682
3756
  } catch (error) {
3683
- spinner17.stop("Verification failed");
3757
+ spinner16.stop("Verification failed");
3684
3758
  p9.log.error(error instanceof Error ? error.message : "Invalid token");
3685
3759
  }
3686
3760
  }
@@ -3818,10 +3892,10 @@ This requires:
3818
3892
  p9.log.info("iMessage support coming soon.");
3819
3893
  }
3820
3894
  async function startAllChannels(config) {
3821
- const spinner17 = p9.spinner();
3822
- spinner17.start("Starting channel gateway...");
3895
+ const spinner16 = p9.spinner();
3896
+ spinner16.start("Starting channel gateway...");
3823
3897
  await new Promise((resolve) => setTimeout(resolve, 1e3));
3824
- spinner17.stop("Gateway started");
3898
+ spinner16.stop("Gateway started");
3825
3899
  console.log(chalk10.green(`
3826
3900
  \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\u2557
3827
3901
  \u2551 Gateway is running! \u2551
@@ -3835,10 +3909,10 @@ async function startAllChannels(config) {
3835
3909
  `));
3836
3910
  }
3837
3911
  async function stopAllChannels(config) {
3838
- const spinner17 = p9.spinner();
3839
- spinner17.start("Stopping gateway...");
3912
+ const spinner16 = p9.spinner();
3913
+ spinner16.start("Stopping gateway...");
3840
3914
  await new Promise((resolve) => setTimeout(resolve, 500));
3841
- spinner17.stop("Gateway stopped");
3915
+ spinner16.stop("Gateway stopped");
3842
3916
  }
3843
3917
  async function deployGatewayWizard(config) {
3844
3918
  p9.log.info(chalk10.bold("Deploy Gateway to Cloud"));
@@ -3953,10 +4027,10 @@ import glob2 from "fast-glob";
3953
4027
  import * as path8 from "path";
3954
4028
  async function securityCommand() {
3955
4029
  p11.intro(chalk12.bgCyan.black(" Security Audit "));
3956
- const spinner17 = p11.spinner();
3957
- spinner17.start("Scanning for security issues...");
4030
+ const spinner16 = p11.spinner();
4031
+ spinner16.start("Scanning for security issues...");
3958
4032
  const issues = await runSecurityScan();
3959
- spinner17.stop("Scan complete");
4033
+ spinner16.stop("Scan complete");
3960
4034
  if (issues.length === 0) {
3961
4035
  console.log(chalk12.green("\n\u2713 No security issues found!\n"));
3962
4036
  displaySecurityScore(100);
@@ -4050,10 +4124,10 @@ async function generateCommand(type) {
4050
4124
  validate: (v) => !v ? "Name is required" : void 0
4051
4125
  });
4052
4126
  if (p12.isCancel(name)) return;
4053
- const spinner17 = p12.spinner();
4054
- spinner17.start("Generating...");
4127
+ const spinner16 = p12.spinner();
4128
+ spinner16.start("Generating...");
4055
4129
  await generateFile(generateType, name);
4056
- spinner17.stop(`Generated ${name}`);
4130
+ spinner16.stop(`Generated ${name}`);
4057
4131
  p12.outro("");
4058
4132
  }
4059
4133
  async function generateFile(type, name) {
@@ -4198,13 +4272,13 @@ import * as p13 from "@clack/prompts";
4198
4272
  import chalk14 from "chalk";
4199
4273
  async function fixCommand() {
4200
4274
  p13.intro(chalk14.bgCyan.black(" Auto-Fix "));
4201
- const spinner17 = p13.spinner();
4202
- spinner17.start("Analyzing code...");
4275
+ const spinner16 = p13.spinner();
4276
+ spinner16.start("Analyzing code...");
4203
4277
  const result = await runPatternCheck(true);
4204
4278
  if (result.passed) {
4205
- spinner17.stop("No issues found!");
4279
+ spinner16.stop("No issues found!");
4206
4280
  } else {
4207
- spinner17.stop(`Fixed ${result.violations.length} issues`);
4281
+ spinner16.stop(`Fixed ${result.violations.length} issues`);
4208
4282
  }
4209
4283
  p13.outro(chalk14.green("Done!"));
4210
4284
  }
@@ -4405,8 +4479,8 @@ function generateColorPalette(hex) {
4405
4479
  };
4406
4480
  }
4407
4481
  async function checkDesign() {
4408
- const spinner17 = p14.spinner();
4409
- spinner17.start("Checking design quality...");
4482
+ const spinner16 = p14.spinner();
4483
+ spinner16.start("Checking design quality...");
4410
4484
  const cwd = process.cwd();
4411
4485
  const issues = [];
4412
4486
  const glob3 = (await import("fast-glob")).default;
@@ -4432,7 +4506,7 @@ async function checkDesign() {
4432
4506
  }
4433
4507
  }
4434
4508
  }
4435
- spinner17.stop("Check complete");
4509
+ spinner16.stop("Check complete");
4436
4510
  if (issues.length === 0) {
4437
4511
  console.log(chalk15.green("\n\u2713 No design issues found!\n"));
4438
4512
  } else {
@@ -4530,8 +4604,8 @@ async function detectMigrationTool() {
4530
4604
  return null;
4531
4605
  }
4532
4606
  async function checkMigrationStatus(tool) {
4533
- const spinner17 = p15.spinner();
4534
- spinner17.start("Checking migration status...");
4607
+ const spinner16 = p15.spinner();
4608
+ spinner16.start("Checking migration status...");
4535
4609
  try {
4536
4610
  let result;
4537
4611
  switch (tool) {
@@ -4545,7 +4619,7 @@ async function checkMigrationStatus(tool) {
4545
4619
  result = await execa8("npx", ["supabase", "migration", "list"], { cwd: process.cwd(), reject: false });
4546
4620
  break;
4547
4621
  }
4548
- spinner17.stop("Status check complete");
4622
+ spinner16.stop("Status check complete");
4549
4623
  if (result?.stdout) {
4550
4624
  console.log(chalk16.dim(result.stdout));
4551
4625
  }
@@ -4553,7 +4627,7 @@ async function checkMigrationStatus(tool) {
4553
4627
  console.log(chalk16.yellow(result.stderr));
4554
4628
  }
4555
4629
  } catch (error) {
4556
- spinner17.stop("Error checking status");
4630
+ spinner16.stop("Error checking status");
4557
4631
  p15.log.error(error instanceof Error ? error.message : "Unknown error");
4558
4632
  }
4559
4633
  }
@@ -4564,8 +4638,8 @@ async function generateMigration(tool) {
4564
4638
  validate: (v) => !v ? "Name required" : void 0
4565
4639
  });
4566
4640
  if (p15.isCancel(name)) return;
4567
- const spinner17 = p15.spinner();
4568
- spinner17.start("Generating migration...");
4641
+ const spinner16 = p15.spinner();
4642
+ spinner16.start("Generating migration...");
4569
4643
  try {
4570
4644
  let result;
4571
4645
  switch (tool) {
@@ -4579,18 +4653,18 @@ async function generateMigration(tool) {
4579
4653
  result = await execa8("npx", ["supabase", "migration", "new", name], { cwd: process.cwd(), reject: false });
4580
4654
  break;
4581
4655
  }
4582
- spinner17.stop("Migration generated");
4656
+ spinner16.stop("Migration generated");
4583
4657
  if (result?.stdout) {
4584
4658
  console.log(chalk16.dim(result.stdout));
4585
4659
  }
4586
4660
  } catch (error) {
4587
- spinner17.stop("Error generating migration");
4661
+ spinner16.stop("Error generating migration");
4588
4662
  p15.log.error(error instanceof Error ? error.message : "Unknown error");
4589
4663
  }
4590
4664
  }
4591
4665
  async function pushMigration(tool) {
4592
- const spinner17 = p15.spinner();
4593
- spinner17.start("Pushing migration to database...");
4666
+ const spinner16 = p15.spinner();
4667
+ spinner16.start("Pushing migration to database...");
4594
4668
  try {
4595
4669
  let result;
4596
4670
  let migrationSql = "";
@@ -4616,7 +4690,7 @@ async function pushMigration(tool) {
4616
4690
  break;
4617
4691
  }
4618
4692
  if (result?.exitCode !== 0) {
4619
- spinner17.stop("Migration push failed");
4693
+ spinner16.stop("Migration push failed");
4620
4694
  const errorOutput = result?.stderr || result?.stdout || "";
4621
4695
  console.log(chalk16.red("\nError:\n"));
4622
4696
  console.log(chalk16.dim(errorOutput));
@@ -4669,13 +4743,13 @@ Also saved to: ${sqlPath}
4669
4743
  }
4670
4744
  return;
4671
4745
  }
4672
- spinner17.stop("Migration pushed successfully!");
4746
+ spinner16.stop("Migration pushed successfully!");
4673
4747
  if (result?.stdout) {
4674
4748
  console.log(chalk16.dim(result.stdout));
4675
4749
  }
4676
4750
  p15.log.success("Database updated!");
4677
4751
  } catch (error) {
4678
- spinner17.stop("Error");
4752
+ spinner16.stop("Error");
4679
4753
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
4680
4754
  p15.log.error(errorMsg);
4681
4755
  const migrationSql = await extractMigrationSQL(tool, errorMsg);
@@ -4687,8 +4761,8 @@ Also saved to: ${sqlPath}
4687
4761
  }
4688
4762
  }
4689
4763
  async function pullSchema(tool) {
4690
- const spinner17 = p15.spinner();
4691
- spinner17.start("Pulling schema from database...");
4764
+ const spinner16 = p15.spinner();
4765
+ spinner16.start("Pulling schema from database...");
4692
4766
  try {
4693
4767
  let result;
4694
4768
  switch (tool) {
@@ -4702,12 +4776,12 @@ async function pullSchema(tool) {
4702
4776
  result = await execa8("npx", ["supabase", "db", "pull"], { cwd: process.cwd(), reject: false });
4703
4777
  break;
4704
4778
  }
4705
- spinner17.stop("Schema pulled");
4779
+ spinner16.stop("Schema pulled");
4706
4780
  if (result?.stdout) {
4707
4781
  console.log(chalk16.dim(result.stdout));
4708
4782
  }
4709
4783
  } catch (error) {
4710
- spinner17.stop("Error pulling schema");
4784
+ spinner16.stop("Error pulling schema");
4711
4785
  p15.log.error(error instanceof Error ? error.message : "Unknown error");
4712
4786
  }
4713
4787
  }
@@ -4845,10 +4919,10 @@ async function prdMakerCommand() {
4845
4919
  const input = await conductPRDInterview(inputMethod);
4846
4920
  if (!input) return;
4847
4921
  const anthropic = new Anthropic3({ apiKey: anthropicCreds.apiKey });
4848
- const spinner17 = p16.spinner();
4849
- spinner17.start("Generating professional PRD...");
4922
+ const spinner16 = p16.spinner();
4923
+ spinner16.start("Generating professional PRD...");
4850
4924
  const prd = await generatePRD(anthropic, input);
4851
- spinner17.stop("PRD generated!");
4925
+ spinner16.stop("PRD generated!");
4852
4926
  const filename = `${input.projectName.toLowerCase().replace(/[^a-z0-9]/g, "-")}-prd.md`;
4853
4927
  const filepath = path12.join(process.cwd(), filename);
4854
4928
  await fs13.writeFile(filepath, prd);
@@ -4893,10 +4967,10 @@ async function prdMakerCommand() {
4893
4967
  return;
4894
4968
  }
4895
4969
  if (nextStep === "build") {
4896
- const { prdCommand: prdCommand2 } = await import("./prd-HBUCYLVG.js");
4970
+ const { prdCommand: prdCommand2 } = await import("./prd-AIEY63YY.js");
4897
4971
  await prdCommand2(filepath);
4898
4972
  } else if (nextStep === "advisors") {
4899
- const { advisorsCommand: advisorsCommand2 } = await import("./advisors-J3S64IZK.js");
4973
+ const { advisorsCommand: advisorsCommand2 } = await import("./advisors-GGUCFS4E.js");
4900
4974
  await advisorsCommand2();
4901
4975
  }
4902
4976
  }
@@ -5056,8 +5130,8 @@ async function getVoiceInput2(prompt) {
5056
5130
  const text17 = await p16.text({ message: "Type instead:" });
5057
5131
  return p16.isCancel(text17) ? null : text17;
5058
5132
  }
5059
- const spinner17 = p16.spinner();
5060
- spinner17.start("\u{1F534} Recording... (press Ctrl+C to stop)");
5133
+ const spinner16 = p16.spinner();
5134
+ spinner16.start("\u{1F534} Recording... (press Ctrl+C to stop)");
5061
5135
  try {
5062
5136
  let transcription = "";
5063
5137
  if (process.platform === "win32") {
@@ -5067,16 +5141,16 @@ async function getVoiceInput2(prompt) {
5067
5141
  } else {
5068
5142
  transcription = await recordWithLinux2();
5069
5143
  }
5070
- spinner17.stop("Recording complete");
5144
+ spinner16.stop("Recording complete");
5071
5145
  if (transcription) {
5072
5146
  console.log(chalk17.green(`
5073
5147
  Heard: "${transcription}"
5074
5148
  `));
5075
- const confirm12 = await p16.confirm({
5149
+ const confirm13 = await p16.confirm({
5076
5150
  message: "Is this correct?",
5077
5151
  initialValue: true
5078
5152
  });
5079
- if (confirm12 && !p16.isCancel(confirm12)) {
5153
+ if (confirm13 && !p16.isCancel(confirm13)) {
5080
5154
  return transcription;
5081
5155
  } else {
5082
5156
  const retry = await p16.select({
@@ -5096,7 +5170,7 @@ async function getVoiceInput2(prompt) {
5096
5170
  }
5097
5171
  }
5098
5172
  } catch (error) {
5099
- spinner17.stop("Recording failed");
5173
+ spinner16.stop("Recording failed");
5100
5174
  console.log(chalk17.yellow("Voice input failed. Falling back to text."));
5101
5175
  }
5102
5176
  const text16 = await p16.text({ message: prompt });
@@ -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,20 +5419,32 @@ 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 });
5340
- const spinner17 = p17.spinner();
5341
- spinner17.start("Analyzing PRD...");
5444
+ const spinner16 = p17.spinner();
5445
+ spinner16.start("Analyzing PRD...");
5342
5446
  const buildPlan = await analyzePRD(anthropic, prdContent);
5343
- spinner17.stop("PRD analyzed");
5447
+ spinner16.stop("PRD analyzed");
5344
5448
  displayBuildPlan(buildPlan);
5345
5449
  const totalAgents = buildPlan.waves.reduce((sum, w) => sum + w.agents.length, 0);
5346
5450
  const useParallel = !options.sequential && totalAgents > 2;
@@ -5366,9 +5470,9 @@ async function buildCommand(prdPath, options = {}) {
5366
5470
  }
5367
5471
  await fs14.ensureDir(projectPath);
5368
5472
  process.chdir(projectPath);
5369
- spinner17.start("Initializing project...");
5473
+ spinner16.start("Initializing project...");
5370
5474
  await execa9("git", ["init"], { cwd: projectPath });
5371
- spinner17.stop("Project initialized");
5475
+ spinner16.stop("Project initialized");
5372
5476
  await runSetupPhase(anthropic, buildPlan, projectPath);
5373
5477
  const startTime = Date.now();
5374
5478
  if (useParallel) {
@@ -5377,9 +5481,9 @@ async function buildCommand(prdPath, options = {}) {
5377
5481
  await executeSequentialBuild(anthropic, buildPlan, projectPath, config);
5378
5482
  }
5379
5483
  await runIntegrationPhase(anthropic, buildPlan, projectPath);
5380
- spinner17.start("Installing dependencies...");
5484
+ spinner16.start("Installing dependencies...");
5381
5485
  await execa9("npm", ["install"], { cwd: projectPath, reject: false });
5382
- spinner17.stop("Dependencies installed");
5486
+ spinner16.stop("Dependencies installed");
5383
5487
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
5384
5488
  const minutes = Math.floor(elapsed / 60);
5385
5489
  const seconds = elapsed % 60;
@@ -5490,8 +5594,8 @@ function displayBuildPlan(plan) {
5490
5594
  `));
5491
5595
  }
5492
5596
  async function runSetupPhase(anthropic, plan, projectPath) {
5493
- const spinner17 = p17.spinner();
5494
- spinner17.start("Setting up project structure...");
5597
+ const spinner16 = p17.spinner();
5598
+ spinner16.start("Setting up project structure...");
5495
5599
  await fs14.ensureDir(path13.join(projectPath, "src/app"));
5496
5600
  await fs14.ensureDir(path13.join(projectPath, "src/components/ui"));
5497
5601
  await fs14.ensureDir(path13.join(projectPath, "src/lib"));
@@ -5535,7 +5639,7 @@ Use TypeScript. Include all necessary imports.`
5535
5639
  await writeFilesFromResponse(setupText, projectPath);
5536
5640
  await execa9("git", ["add", "."], { cwd: projectPath });
5537
5641
  await execa9("git", ["commit", "-m", "Initial setup"], { cwd: projectPath });
5538
- spinner17.stop("Project structure ready");
5642
+ spinner16.stop("Project structure ready");
5539
5643
  }
5540
5644
  async function executeParallelBuild(anthropic, plan, projectPath, config) {
5541
5645
  for (const wave of plan.waves) {
@@ -5585,15 +5689,15 @@ async function executeAgentWithProgress(anthropic, agent, projectPath, plan, dis
5585
5689
  async function executeSequentialBuild(anthropic, plan, projectPath, config) {
5586
5690
  for (const wave of plan.waves) {
5587
5691
  for (const agent of wave.agents) {
5588
- const spinner17 = p17.spinner();
5589
- spinner17.start(`Building ${agent.name}...`);
5692
+ const spinner16 = p17.spinner();
5693
+ spinner16.start(`Building ${agent.name}...`);
5590
5694
  try {
5591
5695
  await executeAgent(anthropic, agent, projectPath, plan, (progress, action) => {
5592
- spinner17.message = `${agent.name}: ${action} (${progress}%)`;
5696
+ spinner16.message = `${agent.name}: ${action} (${progress}%)`;
5593
5697
  });
5594
- spinner17.stop(`\u2713 ${agent.name} complete`);
5698
+ spinner16.stop(`\u2713 ${agent.name} complete`);
5595
5699
  } catch (error) {
5596
- spinner17.stop(`\u2717 ${agent.name} failed`);
5700
+ spinner16.stop(`\u2717 ${agent.name} failed`);
5597
5701
  throw error;
5598
5702
  }
5599
5703
  await mergeWaveBranches([agent], projectPath);
@@ -5822,8 +5926,8 @@ Common fixes:
5822
5926
  return JSON.parse(jsonMatch[0]);
5823
5927
  }
5824
5928
  async function runIntegrationPhase(anthropic, plan, projectPath) {
5825
- const spinner17 = p17.spinner();
5826
- spinner17.start("Running integration...");
5929
+ const spinner16 = p17.spinner();
5930
+ spinner16.start("Running integration...");
5827
5931
  const features = plan.waves.flatMap((w) => w.agents);
5828
5932
  const response = await anthropic.messages.create({
5829
5933
  model: "claude-sonnet-4-20250514",
@@ -5854,7 +5958,7 @@ Make sure all features are accessible and properly connected.`
5854
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
- spinner17.stop("Integration complete");
5961
+ spinner16.stop("Integration complete");
5858
5962
  }
5859
5963
  async function mergeWaveBranches(agents, projectPath) {
5860
5964
  for (const agent of agents) {
@@ -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"
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
+ ]
6071
6184
  },
6072
6185
  {
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"
6083
- },
6084
- {
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
- 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 spinner17 = p18.spinner();
6642
6687
  if (integration.packages && integration.packages.length > 0) {
6688
+ const spinner17 = p18.spinner();
6643
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
- spinner17.stop(`\u2713 Packages installed`);
6695
+ spinner17.stop(chalk19.green("\u2713 Packages installed"));
6650
6696
  } catch {
6651
- spinner17.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
6705
+ `));
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}
6659
6712
  `));
6660
- await open2(integration.oauthUrl);
6661
- await sleep2(1500);
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
- spinner17.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
- spinner17.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,90 +6777,76 @@ 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));
@@ -6945,12 +6968,27 @@ var TEMPLATES = [
6945
6968
  async function websiteCommand() {
6946
6969
  const config = new Config();
6947
6970
  if (!config.isConfigured()) {
6948
- p19.log.error("Please run `codebakers setup` first.");
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
+ `));
6949
6977
  return;
6950
6978
  }
6951
6979
  const anthropicCreds = config.getCredentials("anthropic");
6952
6980
  if (!anthropicCreds?.apiKey) {
6953
- p19.log.error("Anthropic API key not configured.");
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
+ `));
6954
6992
  return;
6955
6993
  }
6956
6994
  console.log(chalk20.cyan(`
@@ -6989,11 +7027,11 @@ async function websiteCommand() {
6989
7027
  console.log(chalk20.dim(` Style: ${websiteSpec.style}`));
6990
7028
  console.log(chalk20.dim(` Sections: ${websiteSpec.sections.join(", ")}
6991
7029
  `));
6992
- const confirm12 = await p19.confirm({
7030
+ const confirm13 = await p19.confirm({
6993
7031
  message: "Build this website?",
6994
7032
  initialValue: true
6995
7033
  });
6996
- if (!confirm12 || p19.isCancel(confirm12)) return;
7034
+ if (!confirm13 || p19.isCancel(confirm13)) return;
6997
7035
  await buildWebsite(anthropic, websiteSpec, config);
6998
7036
  }
6999
7037
  async function describeWebsite(anthropic) {
@@ -7007,8 +7045,8 @@ async function describeWebsite(anthropic) {
7007
7045
  placeholder: "A landing page for..."
7008
7046
  });
7009
7047
  if (p19.isCancel(description)) return null;
7010
- const spinner17 = p19.spinner();
7011
- spinner17.start("Understanding your vision...");
7048
+ const spinner16 = p19.spinner();
7049
+ spinner16.start("Understanding your vision...");
7012
7050
  const response = await anthropic.messages.create({
7013
7051
  model: "claude-sonnet-4-20250514",
7014
7052
  max_tokens: 2048,
@@ -7048,7 +7086,7 @@ Return JSON only:
7048
7086
  Make the content compelling and professional. Use appropriate sections for the type of website.`
7049
7087
  }]
7050
7088
  });
7051
- spinner17.stop("Got it!");
7089
+ spinner16.stop("Got it!");
7052
7090
  const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7053
7091
  const jsonMatch = text16.match(/\{[\s\S]*\}/);
7054
7092
  if (!jsonMatch) {
@@ -7083,8 +7121,8 @@ async function templateWebsite(anthropic) {
7083
7121
  placeholder: "We help startups with..., Our colors are blue and white..."
7084
7122
  });
7085
7123
  if (p19.isCancel(details)) return null;
7086
- const spinner17 = p19.spinner();
7087
- spinner17.start("Customizing template...");
7124
+ const spinner16 = p19.spinner();
7125
+ spinner16.start("Customizing template...");
7088
7126
  const response = await anthropic.messages.create({
7089
7127
  model: "claude-sonnet-4-20250514",
7090
7128
  max_tokens: 2048,
@@ -7122,7 +7160,7 @@ Return JSON only:
7122
7160
  Make the content specific and compelling for this business.`
7123
7161
  }]
7124
7162
  });
7125
- spinner17.stop("Template customized!");
7163
+ spinner16.stop("Template customized!");
7126
7164
  const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7127
7165
  const jsonMatch = text16.match(/\{[\s\S]*\}/);
7128
7166
  if (!jsonMatch) {
@@ -7147,8 +7185,8 @@ async function cloneDesign(anthropic) {
7147
7185
  placeholder: "A project management tool for designers"
7148
7186
  });
7149
7187
  if (p19.isCancel(purpose)) return null;
7150
- const spinner17 = p19.spinner();
7151
- spinner17.start("Analyzing design inspiration...");
7188
+ const spinner16 = p19.spinner();
7189
+ spinner16.start("Analyzing design inspiration...");
7152
7190
  const response = await anthropic.messages.create({
7153
7191
  model: "claude-sonnet-4-20250514",
7154
7192
  max_tokens: 2048,
@@ -7183,7 +7221,7 @@ Return JSON only:
7183
7221
  Capture the FEEL of the inspiration but make it original.`
7184
7222
  }]
7185
7223
  });
7186
- spinner17.stop("Design analyzed!");
7224
+ spinner16.stop("Design analyzed!");
7187
7225
  const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7188
7226
  const jsonMatch = text16.match(/\{[\s\S]*\}/);
7189
7227
  if (!jsonMatch) {
@@ -7205,8 +7243,8 @@ async function buildWebsite(anthropic, spec, config) {
7205
7243
  console.log(chalk20.cyan(`
7206
7244
  \u{1F3D7}\uFE0F Building ${spec.name}...
7207
7245
  `));
7208
- const spinner17 = p19.spinner();
7209
- spinner17.start("Creating Next.js project...");
7246
+ const spinner16 = p19.spinner();
7247
+ spinner16.start("Creating Next.js project...");
7210
7248
  await execa11("npx", [
7211
7249
  "create-next-app@latest",
7212
7250
  spec.name,
@@ -7219,8 +7257,8 @@ async function buildWebsite(anthropic, spec, config) {
7219
7257
  "@/*",
7220
7258
  "--no-git"
7221
7259
  ], { cwd: process.cwd(), reject: false });
7222
- spinner17.stop("Project created");
7223
- spinner17.start("Setting up shadcn/ui...");
7260
+ spinner16.stop("Project created");
7261
+ spinner16.start("Setting up shadcn/ui...");
7224
7262
  await execa11("npx", ["shadcn@latest", "init", "-y", "-d"], {
7225
7263
  cwd: projectPath,
7226
7264
  reject: false
@@ -7229,8 +7267,8 @@ async function buildWebsite(anthropic, spec, config) {
7229
7267
  cwd: projectPath,
7230
7268
  reject: false
7231
7269
  });
7232
- spinner17.stop("UI components ready");
7233
- spinner17.start("Generating website code...");
7270
+ spinner16.stop("UI components ready");
7271
+ spinner16.start("Generating website code...");
7234
7272
  const response = await anthropic.messages.create({
7235
7273
  model: "claude-sonnet-4-20250514",
7236
7274
  max_tokens: 16e3,
@@ -7295,12 +7333,12 @@ Make it production-quality and visually impressive.`
7295
7333
  await fs16.writeFile(filePath, content);
7296
7334
  fileCount++;
7297
7335
  }
7298
- spinner17.stop(`Generated ${fileCount} files`);
7299
- spinner17.start("Initializing git...");
7336
+ spinner16.stop(`Generated ${fileCount} files`);
7337
+ spinner16.start("Initializing git...");
7300
7338
  await execa11("git", ["init"], { cwd: projectPath, reject: false });
7301
7339
  await execa11("git", ["add", "."], { cwd: projectPath, reject: false });
7302
7340
  await execa11("git", ["commit", "-m", "Initial website build by CodeBakers"], { cwd: projectPath, reject: false });
7303
- spinner17.stop("Git initialized");
7341
+ spinner16.stop("Git initialized");
7304
7342
  console.log(chalk20.green(`
7305
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
7306
7344
  \u2551 \u2705 Website built successfully! \u2551
@@ -7505,11 +7543,11 @@ async function clarifyCommand(parsed) {
7505
7543
  return parsed;
7506
7544
  }
7507
7545
  if (parsed.confidence >= 0.5) {
7508
- const confirm12 = await p20.confirm({
7546
+ const confirm13 = await p20.confirm({
7509
7547
  message: `Did you mean: ${parsed.interpretation}?`,
7510
7548
  initialValue: true
7511
7549
  });
7512
- if (confirm12 && !p20.isCancel(confirm12)) {
7550
+ if (confirm13 && !p20.isCancel(confirm13)) {
7513
7551
  return { ...parsed, confidence: 1 };
7514
7552
  }
7515
7553
  }
@@ -7566,7 +7604,7 @@ async function clarifyDeployTarget() {
7566
7604
  }
7567
7605
 
7568
7606
  // src/index.ts
7569
- var VERSION2 = "2.1.1";
7607
+ var VERSION2 = "2.2.1";
7570
7608
  var logo = `
7571
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
7572
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
@@ -7707,27 +7745,34 @@ async function showMainMenu() {
7707
7745
  }
7708
7746
  function showHelp2() {
7709
7747
  console.log(boxen(`
7710
- ${chalk21.bold("CodeBakers CLI")} \u2014 AI dev team that follows the rules
7711
-
7712
- ${chalk21.bold("Commands:")}
7713
- ${chalk21.cyan("codebakers")} Interactive menu (or just run with no args)
7714
- ${chalk21.cyan("codebakers init")} Create a new project
7715
- ${chalk21.cyan("codebakers code")} Start AI coding session
7716
- ${chalk21.cyan("codebakers check")} Run pattern enforcement
7717
- ${chalk21.cyan("codebakers deploy")} Deploy to production
7718
- ${chalk21.cyan("codebakers fix")} Auto-fix errors
7719
- ${chalk21.cyan("codebakers generate")} Generate components/pages
7720
- ${chalk21.cyan("codebakers connect")} Connect external services
7721
- ${chalk21.cyan("codebakers gateway")} Manage messaging channels
7722
- ${chalk21.cyan("codebakers status")} View project status
7723
- ${chalk21.cyan("codebakers security")} Run security audit
7724
- ${chalk21.cyan("codebakers learn")} View/manage learning
7725
-
7726
- ${chalk21.bold("Help at any time:")}
7727
- Press ${chalk21.yellow("?")} during any command to get contextual help
7728
-
7729
- ${chalk21.bold("Documentation:")}
7730
- ${chalk21.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")}
7731
7776
  `, { padding: 1, borderColor: "cyan", borderStyle: "round" }));
7732
7777
  }
7733
7778
  var program = new Command();