codebakers 2.0.10 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,8 +11,8 @@ import {
11
11
 
12
12
  // src/index.ts
13
13
  import { Command } from "commander";
14
- import * as p19 from "@clack/prompts";
15
- import chalk19 from "chalk";
14
+ import * as p21 from "@clack/prompts";
15
+ import chalk21 from "chalk";
16
16
  import boxen from "boxen";
17
17
  import gradient from "gradient-string";
18
18
 
@@ -65,10 +65,10 @@ async function setupCommand() {
65
65
  return;
66
66
  }
67
67
  if (action === "reset") {
68
- const confirm11 = await p.confirm({
68
+ const confirm12 = await p.confirm({
69
69
  message: "Are you sure? This will remove all credentials and settings."
70
70
  });
71
- if (confirm11) {
71
+ if (confirm12) {
72
72
  p.outro(chalk2.yellow("Configuration reset. Run `codebakers setup` again."));
73
73
  }
74
74
  return;
@@ -132,7 +132,7 @@ ${chalk2.dim("and are never sent to our servers.")}`,
132
132
  p.outro(chalk2.green("\u2713 Setup complete! Run `codebakers init` to create your first project."));
133
133
  }
134
134
  async function connectService(config, serviceKey, serviceName, required) {
135
- const spinner15 = p.spinner();
135
+ const spinner17 = p.spinner();
136
136
  switch (serviceKey) {
137
137
  case "github": {
138
138
  p.log.info(`${chalk2.bold("GitHub")} - Opens browser for OAuth authorization`);
@@ -606,10 +606,10 @@ var SupabaseService = class {
606
606
  throw new Error("Failed to list projects");
607
607
  }
608
608
  const projects = await response.json();
609
- return projects.map((p20) => ({
610
- id: p20.id,
611
- name: p20.name,
612
- region: p20.region
609
+ return projects.map((p22) => ({
610
+ id: p22.id,
611
+ name: p22.name,
612
+ region: p22.region
613
613
  }));
614
614
  }
615
615
  };
@@ -1029,20 +1029,20 @@ Domain: ${domain || "Vercel default"}`,
1029
1029
  p2.cancel("Project creation cancelled.");
1030
1030
  return;
1031
1031
  }
1032
- const spinner15 = p2.spinner();
1032
+ const spinner17 = p2.spinner();
1033
1033
  const projectPath = path.join(process.cwd(), projectName);
1034
1034
  try {
1035
- spinner15.start("Creating local project...");
1035
+ spinner17.start("Creating local project...");
1036
1036
  await createLocalProject(projectPath, projectConfig);
1037
- spinner15.stop("Local project created");
1038
- spinner15.start("Installing dependencies...");
1037
+ spinner17.stop("Local project created");
1038
+ spinner17.start("Installing dependencies...");
1039
1039
  await execa3("pnpm", ["install"], { cwd: projectPath });
1040
- spinner15.stop("Dependencies installed");
1040
+ spinner17.stop("Dependencies installed");
1041
1041
  if (services.includes("github")) {
1042
- spinner15.start("Creating GitHub repository...");
1042
+ spinner17.start("Creating GitHub repository...");
1043
1043
  const github = new GitHubService(config);
1044
1044
  const repo = await github.createRepo(projectName, { private: true });
1045
- spinner15.stop(`GitHub repo created: ${repo.html_url}`);
1045
+ spinner17.stop(`GitHub repo created: ${repo.html_url}`);
1046
1046
  await execa3("git", ["init"], { cwd: projectPath });
1047
1047
  await execa3("git", ["add", "."], { cwd: projectPath });
1048
1048
  await execa3("git", ["commit", "-m", "Initial commit by CodeBakers"], { cwd: projectPath });
@@ -1050,10 +1050,10 @@ Domain: ${domain || "Vercel default"}`,
1050
1050
  await execa3("git", ["push", "-u", "origin", "main"], { cwd: projectPath });
1051
1051
  }
1052
1052
  if (services.includes("supabase")) {
1053
- spinner15.start("Creating Supabase project...");
1053
+ spinner17.start("Creating Supabase project...");
1054
1054
  const supabase = new SupabaseService(config);
1055
1055
  const project = await supabase.createProject(projectName);
1056
- spinner15.stop(`Supabase project created: ${project.name}`);
1056
+ spinner17.stop(`Supabase project created: ${project.name}`);
1057
1057
  await fs.writeJson(
1058
1058
  path.join(projectPath, ".codebakers", "supabase.json"),
1059
1059
  { projectId: project.id, projectUrl: project.api_url },
@@ -1061,26 +1061,26 @@ Domain: ${domain || "Vercel default"}`,
1061
1061
  );
1062
1062
  }
1063
1063
  if (services.includes("vercel")) {
1064
- spinner15.start("Creating Vercel project...");
1064
+ spinner17.start("Creating Vercel project...");
1065
1065
  const vercel = new VercelService(config);
1066
1066
  const project = await vercel.createProject(projectName);
1067
- spinner15.stop(`Vercel project created`);
1067
+ spinner17.stop(`Vercel project created`);
1068
1068
  if (domain) {
1069
- spinner15.start(`Configuring domain: ${domain}...`);
1069
+ spinner17.start(`Configuring domain: ${domain}...`);
1070
1070
  await vercel.addDomain(projectName, domain);
1071
- spinner15.stop("Domain configured");
1071
+ spinner17.stop("Domain configured");
1072
1072
  }
1073
- spinner15.start("Deploying to Vercel...");
1073
+ spinner17.start("Deploying to Vercel...");
1074
1074
  const deployment = await vercel.deploy(projectPath);
1075
- spinner15.stop(`Deployed: ${deployment.url}`);
1075
+ spinner17.stop(`Deployed: ${deployment.url}`);
1076
1076
  }
1077
- spinner15.start("Generating CLAUDE.md...");
1077
+ spinner17.start("Generating CLAUDE.md...");
1078
1078
  const claudeMd = generateClaudeMd(projectConfig);
1079
1079
  await fs.writeFile(path.join(projectPath, "CLAUDE.md"), claudeMd);
1080
- spinner15.stop("CLAUDE.md generated");
1081
- spinner15.start("Setting up CodeBakers enforcement...");
1080
+ spinner17.stop("CLAUDE.md generated");
1081
+ spinner17.start("Setting up CodeBakers enforcement...");
1082
1082
  await setupGitHooks(projectPath);
1083
- spinner15.stop("CodeBakers enforcement configured");
1083
+ spinner17.stop("CodeBakers enforcement configured");
1084
1084
  config.addProject({
1085
1085
  name: projectName,
1086
1086
  path: projectPath,
@@ -1100,7 +1100,7 @@ ${chalk3.dim("Shortcuts:")}
1100
1100
  ${chalk3.cyan("codebakers deploy")} \u2014 Deploy to production
1101
1101
  `));
1102
1102
  } catch (error) {
1103
- spinner15.stop("Error occurred");
1103
+ spinner17.stop("Error occurred");
1104
1104
  p2.log.error(`Failed to create project: ${error instanceof Error ? error.message : "Unknown error"}`);
1105
1105
  const cleanup = await p2.confirm({
1106
1106
  message: "Clean up partially created project?"
@@ -1826,17 +1826,17 @@ async function checkCommand(options = {}) {
1826
1826
  return;
1827
1827
  }
1828
1828
  p3.intro(chalk4.bgCyan.black(" CodeBakers Pattern Check "));
1829
- const spinner15 = p3.spinner();
1830
- spinner15.start("Analyzing code...");
1829
+ const spinner17 = p3.spinner();
1830
+ spinner17.start("Analyzing code...");
1831
1831
  const result = await runPatternCheck(options.fix || false);
1832
- spinner15.stop("Analysis complete");
1832
+ spinner17.stop("Analysis complete");
1833
1833
  displayResults(result);
1834
1834
  if (result.violations.length > 0 && options.fix) {
1835
1835
  const fixable = result.violations.filter((v) => v.autoFixable);
1836
1836
  if (fixable.length > 0) {
1837
- spinner15.start(`Auto-fixing ${fixable.length} violations...`);
1837
+ spinner17.start(`Auto-fixing ${fixable.length} violations...`);
1838
1838
  await autoFix(fixable);
1839
- spinner15.stop("Auto-fix complete");
1839
+ spinner17.stop("Auto-fix complete");
1840
1840
  }
1841
1841
  }
1842
1842
  const errors = result.violations.filter((v) => v.severity === "error");
@@ -2040,8 +2040,8 @@ async function getVoiceInput(prompt) {
2040
2040
  return null;
2041
2041
  }
2042
2042
  await playBeep();
2043
- const spinner15 = p4.spinner();
2044
- spinner15.start("\u{1F534} Recording...");
2043
+ const spinner17 = p4.spinner();
2044
+ spinner17.start("\u{1F534} Recording...");
2045
2045
  try {
2046
2046
  let transcription = "";
2047
2047
  if (process.platform === "win32") {
@@ -2051,16 +2051,16 @@ async function getVoiceInput(prompt) {
2051
2051
  } else {
2052
2052
  transcription = await recordWithLinux();
2053
2053
  }
2054
- spinner15.stop("Recording complete");
2054
+ spinner17.stop("Recording complete");
2055
2055
  if (transcription) {
2056
2056
  console.log(chalk5.green(`
2057
2057
  \u2713 Heard: "${transcription}"
2058
2058
  `));
2059
- const confirm11 = await p4.confirm({
2059
+ const confirm12 = await p4.confirm({
2060
2060
  message: "Is this correct?",
2061
2061
  initialValue: true
2062
2062
  });
2063
- if (confirm11 && !p4.isCancel(confirm11)) {
2063
+ if (confirm12 && !p4.isCancel(confirm12)) {
2064
2064
  return transcription;
2065
2065
  } else {
2066
2066
  const action = await p4.select({
@@ -2075,20 +2075,20 @@ async function getVoiceInput(prompt) {
2075
2075
  if (action === "retry") {
2076
2076
  return await getVoiceInput(prompt);
2077
2077
  } else {
2078
- const text14 = await p4.text({ message: "Type your response:" });
2079
- return p4.isCancel(text14) ? null : text14;
2078
+ const text16 = await p4.text({ message: "Type your response:" });
2079
+ return p4.isCancel(text16) ? null : text16;
2080
2080
  }
2081
2081
  }
2082
2082
  } else {
2083
2083
  console.log(chalk5.yellow("\n No speech detected. Try again or type your response.\n"));
2084
- const text14 = await p4.text({ message: "Type instead:" });
2085
- return p4.isCancel(text14) ? null : text14;
2084
+ const text16 = await p4.text({ message: "Type instead:" });
2085
+ return p4.isCancel(text16) ? null : text16;
2086
2086
  }
2087
2087
  } catch (error) {
2088
- spinner15.stop("Recording failed");
2088
+ spinner17.stop("Recording failed");
2089
2089
  console.log(chalk5.yellow("Voice input failed. Please type instead."));
2090
- const text14 = await p4.text({ message: prompt });
2091
- return p4.isCancel(text14) ? null : text14;
2090
+ const text16 = await p4.text({ message: prompt });
2091
+ return p4.isCancel(text16) ? null : text16;
2092
2092
  }
2093
2093
  }
2094
2094
  async function playBeep() {
@@ -2223,10 +2223,10 @@ async function transcribeWithWhisper(audioFile) {
2223
2223
  ], { timeout: 6e4 });
2224
2224
  const txtFile = outputBase + ".txt";
2225
2225
  if (await fs4.pathExists(txtFile)) {
2226
- const text14 = await fs4.readFile(txtFile, "utf-8");
2226
+ const text16 = await fs4.readFile(txtFile, "utf-8");
2227
2227
  await fs4.remove(txtFile).catch(() => {
2228
2228
  });
2229
- return text14.trim();
2229
+ return text16.trim();
2230
2230
  }
2231
2231
  } catch {
2232
2232
  }
@@ -2333,9 +2333,9 @@ async function readFile5(filePath) {
2333
2333
  name
2334
2334
  };
2335
2335
  } else if (pdfExtensions.includes(ext)) {
2336
- const text14 = await extractPdfText(cleanPath);
2337
- if (text14) {
2338
- return { content: text14, type: "pdf", name };
2336
+ const text16 = await extractPdfText(cleanPath);
2337
+ if (text16) {
2338
+ return { content: text16, type: "pdf", name };
2339
2339
  }
2340
2340
  return {
2341
2341
  content: `[PDF file: ${name} - text extraction not available]`,
@@ -2407,19 +2407,19 @@ function formatFilesForContext(files) {
2407
2407
  context += "--- END FILES ---\n\n";
2408
2408
  return context;
2409
2409
  }
2410
- function looksLikePaste(text14) {
2411
- if (text14.includes("\n") && text14.split("\n").length > 3) return true;
2412
- if (text14.includes("function ") || text14.includes("const ") || text14.includes("import ") || text14.includes("export ") || text14.includes("class ") || text14.includes("def ") || text14.includes("public ") || text14.includes("private ")) return true;
2413
- if (text14.startsWith("{") && text14.endsWith("}") || text14.startsWith("[") && text14.endsWith("]")) return true;
2414
- if (text14.length > 200 && !text14.includes("\n")) return true;
2410
+ function looksLikePaste(text16) {
2411
+ if (text16.includes("\n") && text16.split("\n").length > 3) return true;
2412
+ if (text16.includes("function ") || text16.includes("const ") || text16.includes("import ") || text16.includes("export ") || text16.includes("class ") || text16.includes("def ") || text16.includes("public ") || text16.includes("private ")) return true;
2413
+ if (text16.startsWith("{") && text16.endsWith("}") || text16.startsWith("[") && text16.endsWith("]")) return true;
2414
+ if (text16.length > 200 && !text16.includes("\n")) return true;
2415
2415
  return false;
2416
2416
  }
2417
- async function handlePastedContent(text14) {
2418
- if (!looksLikePaste(text14)) {
2417
+ async function handlePastedContent(text16) {
2418
+ if (!looksLikePaste(text16)) {
2419
2419
  return null;
2420
2420
  }
2421
2421
  console.log(chalk6.cyan("\n\u{1F4CB} Detected pasted content!\n"));
2422
- const preview = text14.length > 200 ? text14.slice(0, 200) + "..." : text14;
2422
+ const preview = text16.length > 200 ? text16.slice(0, 200) + "..." : text16;
2423
2423
  console.log(chalk6.dim(preview));
2424
2424
  console.log("");
2425
2425
  const action = await p5.select({
@@ -2435,7 +2435,7 @@ async function handlePastedContent(text14) {
2435
2435
  });
2436
2436
  if (p5.isCancel(action)) return null;
2437
2437
  if (action === "literal") {
2438
- return { prompt: text14, context: "" };
2438
+ return { prompt: text16, context: "" };
2439
2439
  }
2440
2440
  if (action === "custom") {
2441
2441
  const instruction = await p5.text({
@@ -2449,7 +2449,7 @@ async function handlePastedContent(text14) {
2449
2449
 
2450
2450
  --- PASTED CODE ---
2451
2451
  \`\`\`
2452
- ${text14}
2452
+ ${text16}
2453
2453
  \`\`\`
2454
2454
  --- END ---
2455
2455
 
@@ -2468,7 +2468,7 @@ ${text14}
2468
2468
 
2469
2469
  --- PASTED CODE ---
2470
2470
  \`\`\`
2471
- ${text14}
2471
+ ${text16}
2472
2472
  \`\`\`
2473
2473
  --- END ---
2474
2474
 
@@ -2629,14 +2629,14 @@ ${clipContent}
2629
2629
  }
2630
2630
  }
2631
2631
  async function processUserInput(userInput, messages, anthropic, systemPrompt, projectContext, config) {
2632
- const spinner15 = p6.spinner();
2632
+ const spinner17 = p6.spinner();
2633
2633
  messages.push({ role: "user", content: userInput });
2634
2634
  const wizardResult = await checkForWizard(userInput);
2635
2635
  if (wizardResult) {
2636
2636
  messages[messages.length - 1].content = wizardResult;
2637
2637
  }
2638
2638
  try {
2639
- spinner15.start("Thinking...");
2639
+ spinner17.start("Thinking...");
2640
2640
  const response = await anthropic.messages.create({
2641
2641
  model: "claude-sonnet-4-20250514",
2642
2642
  max_tokens: 8192,
@@ -2646,7 +2646,7 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2646
2646
  content: m.content
2647
2647
  }))
2648
2648
  });
2649
- spinner15.stop("");
2649
+ spinner17.stop("");
2650
2650
  const assistantMessage = response.content[0].type === "text" ? response.content[0].text : "";
2651
2651
  messages.push({ role: "assistant", content: assistantMessage });
2652
2652
  const actions = parseActions(assistantMessage);
@@ -2661,11 +2661,11 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2661
2661
  initialValue: true
2662
2662
  });
2663
2663
  if (proceed && !p6.isCancel(proceed)) {
2664
- spinner15.start("Building...");
2664
+ spinner17.start("Building...");
2665
2665
  for (const action of actions) {
2666
- await executeAction(action, spinner15);
2666
+ await executeAction(action, spinner17);
2667
2667
  }
2668
- spinner15.stop("Build complete");
2668
+ spinner17.stop("Build complete");
2669
2669
  console.log(chalk7.dim("\n\u{1F50D} Running CodeBakers check..."));
2670
2670
  const checkResult = await runPatternCheck(false);
2671
2671
  if (checkResult.violations.length > 0) {
@@ -2676,9 +2676,9 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2676
2676
  initialValue: true
2677
2677
  });
2678
2678
  if (autoFix2 && !p6.isCancel(autoFix2)) {
2679
- spinner15.start("Auto-fixing...");
2679
+ spinner17.start("Auto-fixing...");
2680
2680
  await autoFixViolations(checkResult.violations, anthropic, systemPrompt);
2681
- spinner15.stop("Violations fixed");
2681
+ spinner17.stop("Violations fixed");
2682
2682
  }
2683
2683
  } else {
2684
2684
  console.log(chalk7.green("\u2713 All patterns satisfied"));
@@ -2689,7 +2689,7 @@ async function processUserInput(userInput, messages, anthropic, systemPrompt, pr
2689
2689
  console.log("\n" + assistantMessage + "\n");
2690
2690
  }
2691
2691
  } catch (error) {
2692
- spinner15.stop("Error");
2692
+ spinner17.stop("Error");
2693
2693
  console.log(chalk7.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2694
2694
  }
2695
2695
  }
@@ -2801,14 +2801,14 @@ function parseActions(response) {
2801
2801
  }
2802
2802
  return actions;
2803
2803
  }
2804
- async function executeAction(action, spinner15) {
2804
+ async function executeAction(action, spinner17) {
2805
2805
  const cwd = process.cwd();
2806
2806
  switch (action.type) {
2807
2807
  case "CREATE_FILE": {
2808
2808
  const filePath = path5.join(cwd, action.path);
2809
2809
  await fs6.ensureDir(path5.dirname(filePath));
2810
2810
  await fs6.writeFile(filePath, action.content);
2811
- spinner15.message(`Created ${action.path}`);
2811
+ spinner17.message(`Created ${action.path}`);
2812
2812
  break;
2813
2813
  }
2814
2814
  case "EDIT_FILE": {
@@ -2818,13 +2818,13 @@ async function executeAction(action, spinner15) {
2818
2818
  if (action.find && content.includes(action.find)) {
2819
2819
  content = content.replace(action.find, action.replace || "");
2820
2820
  await fs6.writeFile(filePath, content);
2821
- spinner15.message(`Edited ${action.path}`);
2821
+ spinner17.message(`Edited ${action.path}`);
2822
2822
  }
2823
2823
  }
2824
2824
  break;
2825
2825
  }
2826
2826
  case "RUN_COMMAND": {
2827
- spinner15.message(`Running: ${action.command}`);
2827
+ spinner17.message(`Running: ${action.command}`);
2828
2828
  const [cmd, ...args2] = action.command.split(" ");
2829
2829
  await execa6(cmd, args2, { cwd, stdio: "pipe" });
2830
2830
  break;
@@ -2833,7 +2833,7 @@ async function executeAction(action, spinner15) {
2833
2833
  const filePath = path5.join(cwd, action.path);
2834
2834
  if (await fs6.pathExists(filePath)) {
2835
2835
  await fs6.remove(filePath);
2836
- spinner15.message(`Deleted ${action.path}`);
2836
+ spinner17.message(`Deleted ${action.path}`);
2837
2837
  }
2838
2838
  break;
2839
2839
  }
@@ -3081,12 +3081,12 @@ async function deployCommand(options = {}) {
3081
3081
  return;
3082
3082
  }
3083
3083
  p7.intro(chalk8.bgCyan.black(" Deploy to Production "));
3084
- const spinner15 = p7.spinner();
3084
+ const spinner17 = p7.spinner();
3085
3085
  if (options.check !== false) {
3086
- spinner15.start("Running CodeBakers check...");
3086
+ spinner17.start("Running CodeBakers check...");
3087
3087
  const checkResult = await runPatternCheck(false);
3088
3088
  if (!checkResult.passed) {
3089
- spinner15.stop("");
3089
+ spinner17.stop("");
3090
3090
  const errors = checkResult.violations.filter((v) => v.severity === "error");
3091
3091
  if (errors.length > 0) {
3092
3092
  p7.log.error(`${errors.length} pattern violations found. Fix before deploying.`);
@@ -3107,9 +3107,9 @@ async function deployCommand(options = {}) {
3107
3107
  p7.outro(chalk8.red("Deploy cancelled."));
3108
3108
  return;
3109
3109
  }
3110
- spinner15.start("Auto-fixing with AI...");
3110
+ spinner17.start("Auto-fixing with AI...");
3111
3111
  await autoFixWithAI(config, errors);
3112
- spinner15.stop("Auto-fix complete");
3112
+ spinner17.stop("Auto-fix complete");
3113
3113
  const recheck = await runPatternCheck(false);
3114
3114
  if (!recheck.passed) {
3115
3115
  p7.log.error("Some violations remain. Manual fix required.");
@@ -3118,23 +3118,23 @@ async function deployCommand(options = {}) {
3118
3118
  }
3119
3119
  }
3120
3120
  }
3121
- spinner15.stop("Pattern check passed");
3121
+ spinner17.stop("Pattern check passed");
3122
3122
  }
3123
- spinner15.start("Running TypeScript check...");
3123
+ spinner17.start("Running TypeScript check...");
3124
3124
  try {
3125
3125
  await execa7("npx", ["tsc", "--noEmit"], { cwd: process.cwd() });
3126
- spinner15.stop("TypeScript check passed");
3126
+ spinner17.stop("TypeScript check passed");
3127
3127
  } catch (error) {
3128
- spinner15.stop("");
3128
+ spinner17.stop("");
3129
3129
  p7.log.error("TypeScript errors found.");
3130
3130
  const fix = await p7.confirm({
3131
3131
  message: "Attempt to fix TypeScript errors?",
3132
3132
  initialValue: true
3133
3133
  });
3134
3134
  if (fix && !p7.isCancel(fix)) {
3135
- spinner15.start("Fixing TypeScript errors...");
3135
+ spinner17.start("Fixing TypeScript errors...");
3136
3136
  const fixed = await fixTypeScriptErrors(config);
3137
- spinner15.stop(fixed ? "TypeScript errors fixed" : "Could not auto-fix all errors");
3137
+ spinner17.stop(fixed ? "TypeScript errors fixed" : "Could not auto-fix all errors");
3138
3138
  if (!fixed) {
3139
3139
  p7.outro(chalk8.red("Deploy cancelled."));
3140
3140
  return;
@@ -3144,12 +3144,12 @@ async function deployCommand(options = {}) {
3144
3144
  return;
3145
3145
  }
3146
3146
  }
3147
- spinner15.start("Building project...");
3147
+ spinner17.start("Building project...");
3148
3148
  try {
3149
3149
  await execa7("pnpm", ["build"], { cwd: process.cwd() });
3150
- spinner15.stop("Build successful");
3150
+ spinner17.stop("Build successful");
3151
3151
  } catch (error) {
3152
- spinner15.stop("");
3152
+ spinner17.stop("");
3153
3153
  p7.log.error("Build failed.");
3154
3154
  const errorOutput = error instanceof Error ? error.message : "Unknown error";
3155
3155
  console.log(chalk8.dim(errorOutput));
@@ -3158,16 +3158,16 @@ async function deployCommand(options = {}) {
3158
3158
  initialValue: true
3159
3159
  });
3160
3160
  if (fix && !p7.isCancel(fix)) {
3161
- spinner15.start("Fixing build errors...");
3161
+ spinner17.start("Fixing build errors...");
3162
3162
  const fixed = await fixBuildErrors(config, errorOutput);
3163
- spinner15.stop(fixed ? "Build errors fixed" : "Could not auto-fix");
3163
+ spinner17.stop(fixed ? "Build errors fixed" : "Could not auto-fix");
3164
3164
  if (fixed) {
3165
- spinner15.start("Retrying build...");
3165
+ spinner17.start("Retrying build...");
3166
3166
  try {
3167
3167
  await execa7("pnpm", ["build"], { cwd: process.cwd() });
3168
- spinner15.stop("Build successful");
3168
+ spinner17.stop("Build successful");
3169
3169
  } catch {
3170
- spinner15.stop("Build still failing");
3170
+ spinner17.stop("Build still failing");
3171
3171
  p7.outro(chalk8.red("Deploy cancelled."));
3172
3172
  return;
3173
3173
  }
@@ -3180,10 +3180,10 @@ async function deployCommand(options = {}) {
3180
3180
  return;
3181
3181
  }
3182
3182
  }
3183
- spinner15.start("Checking for uncommitted changes...");
3183
+ spinner17.start("Checking for uncommitted changes...");
3184
3184
  const { stdout: gitStatus } = await execa7("git", ["status", "--porcelain"], { cwd: process.cwd() });
3185
3185
  if (gitStatus.trim()) {
3186
- spinner15.stop("");
3186
+ spinner17.stop("");
3187
3187
  const commit = await p7.confirm({
3188
3188
  message: "You have uncommitted changes. Commit before deploying?",
3189
3189
  initialValue: true
@@ -3197,20 +3197,20 @@ async function deployCommand(options = {}) {
3197
3197
  if (!p7.isCancel(message)) {
3198
3198
  await execa7("git", ["add", "."], { cwd: process.cwd() });
3199
3199
  await execa7("git", ["commit", "-m", message], { cwd: process.cwd() });
3200
- spinner15.start("Pushing to GitHub...");
3200
+ spinner17.start("Pushing to GitHub...");
3201
3201
  await execa7("git", ["push"], { cwd: process.cwd() });
3202
- spinner15.stop("Pushed to GitHub");
3202
+ spinner17.stop("Pushed to GitHub");
3203
3203
  }
3204
3204
  }
3205
3205
  } else {
3206
- spinner15.stop("No uncommitted changes");
3206
+ spinner17.stop("No uncommitted changes");
3207
3207
  }
3208
3208
  const deployType = options.preview ? "preview" : "production";
3209
- spinner15.start(`Deploying to ${deployType}...`);
3209
+ spinner17.start(`Deploying to ${deployType}...`);
3210
3210
  try {
3211
3211
  const vercel = new VercelService(config);
3212
3212
  const deployment = await vercel.deploy(process.cwd(), !options.preview);
3213
- spinner15.stop("Deployment complete!");
3213
+ spinner17.stop("Deployment complete!");
3214
3214
  console.log(boxedOutput(`
3215
3215
  ${chalk8.green("\u2713")} Deployed successfully!
3216
3216
 
@@ -3222,7 +3222,7 @@ ${chalk8.dim("View in Vercel Dashboard:")}
3222
3222
  ${chalk8.dim(deployment.dashboardUrl || "https://vercel.com/dashboard")}
3223
3223
  `));
3224
3224
  } catch (error) {
3225
- spinner15.stop("");
3225
+ spinner17.stop("");
3226
3226
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
3227
3227
  p7.log.error(`Deployment failed: ${errorMsg}`);
3228
3228
  if (errorMsg.includes("Build failed")) {
@@ -3231,8 +3231,8 @@ ${chalk8.dim(deployment.dashboardUrl || "https://vercel.com/dashboard")}
3231
3231
  initialValue: true
3232
3232
  });
3233
3233
  if (retry && !p7.isCancel(retry)) {
3234
- spinner15.start("Analyzing Vercel build error...");
3235
- spinner15.stop("Fix attempted");
3234
+ spinner17.start("Analyzing Vercel build error...");
3235
+ spinner17.stop("Fix attempted");
3236
3236
  }
3237
3237
  }
3238
3238
  p7.outro(chalk8.red("Deploy failed."));
@@ -3598,10 +3598,10 @@ you'll need a Meta Business account.
3598
3598
  initialValue: true
3599
3599
  });
3600
3600
  if (!proceed || p9.isCancel(proceed)) return;
3601
- const spinner15 = p9.spinner();
3602
- spinner15.start("Generating QR code...");
3601
+ const spinner17 = p9.spinner();
3602
+ spinner17.start("Generating QR code...");
3603
3603
  try {
3604
- spinner15.stop("");
3604
+ spinner17.stop("");
3605
3605
  console.log(chalk10.cyan(`
3606
3606
  \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
3607
  \u2551 \u2551
@@ -3633,7 +3633,7 @@ Scan this QR code with WhatsApp:
3633
3633
  p9.log.success("WhatsApp connected!");
3634
3634
  }
3635
3635
  } catch (error) {
3636
- spinner15.stop("Error connecting WhatsApp");
3636
+ spinner17.stop("Error connecting WhatsApp");
3637
3637
  p9.log.error(error instanceof Error ? error.message : "Unknown error");
3638
3638
  }
3639
3639
  }
@@ -3651,8 +3651,8 @@ To create a Telegram bot:
3651
3651
  initialValue: true
3652
3652
  });
3653
3653
  if (openBotFather && !p9.isCancel(openBotFather)) {
3654
- const open2 = (await import("open")).default;
3655
- await open2("https://t.me/botfather");
3654
+ const open3 = (await import("open")).default;
3655
+ await open3("https://t.me/botfather");
3656
3656
  }
3657
3657
  const token = await p9.text({
3658
3658
  message: "Paste your bot token:",
@@ -3664,15 +3664,15 @@ To create a Telegram bot:
3664
3664
  }
3665
3665
  });
3666
3666
  if (p9.isCancel(token)) return;
3667
- const spinner15 = p9.spinner();
3668
- spinner15.start("Verifying bot token...");
3667
+ const spinner17 = p9.spinner();
3668
+ spinner17.start("Verifying bot token...");
3669
3669
  try {
3670
3670
  const response = await fetch(`https://api.telegram.org/bot${token}/getMe`);
3671
3671
  const data = await response.json();
3672
3672
  if (!data.ok) {
3673
3673
  throw new Error(data.description || "Invalid token");
3674
3674
  }
3675
- spinner15.stop("Bot verified!");
3675
+ spinner17.stop("Bot verified!");
3676
3676
  config.setChannelConfig("telegram", {
3677
3677
  enabled: true,
3678
3678
  botToken: token,
@@ -3680,7 +3680,7 @@ To create a Telegram bot:
3680
3680
  });
3681
3681
  p9.log.success(`Connected to @${data.result.username}`);
3682
3682
  } catch (error) {
3683
- spinner15.stop("Verification failed");
3683
+ spinner17.stop("Verification failed");
3684
3684
  p9.log.error(error instanceof Error ? error.message : "Invalid token");
3685
3685
  }
3686
3686
  }
@@ -3699,8 +3699,8 @@ To create a Discord bot:
3699
3699
  initialValue: true
3700
3700
  });
3701
3701
  if (openPortal && !p9.isCancel(openPortal)) {
3702
- const open2 = (await import("open")).default;
3703
- await open2("https://discord.com/developers/applications");
3702
+ const open3 = (await import("open")).default;
3703
+ await open3("https://discord.com/developers/applications");
3704
3704
  }
3705
3705
  const token = await p9.text({
3706
3706
  message: "Paste your bot token:",
@@ -3736,8 +3736,8 @@ To create a Slack app:
3736
3736
  initialValue: true
3737
3737
  });
3738
3738
  if (openSlack && !p9.isCancel(openSlack)) {
3739
- const open2 = (await import("open")).default;
3740
- await open2("https://api.slack.com/apps");
3739
+ const open3 = (await import("open")).default;
3740
+ await open3("https://api.slack.com/apps");
3741
3741
  }
3742
3742
  const token = await p9.text({
3743
3743
  message: "Paste your Bot User OAuth Token:",
@@ -3775,8 +3775,8 @@ async function connectSMS(config) {
3775
3775
  initialValue: true
3776
3776
  });
3777
3777
  if (openTwilio && !p9.isCancel(openTwilio)) {
3778
- const open2 = (await import("open")).default;
3779
- await open2("https://console.twilio.com/");
3778
+ const open3 = (await import("open")).default;
3779
+ await open3("https://console.twilio.com/");
3780
3780
  }
3781
3781
  const accountSid = await p9.text({
3782
3782
  message: "Account SID:",
@@ -3818,10 +3818,10 @@ This requires:
3818
3818
  p9.log.info("iMessage support coming soon.");
3819
3819
  }
3820
3820
  async function startAllChannels(config) {
3821
- const spinner15 = p9.spinner();
3822
- spinner15.start("Starting channel gateway...");
3821
+ const spinner17 = p9.spinner();
3822
+ spinner17.start("Starting channel gateway...");
3823
3823
  await new Promise((resolve) => setTimeout(resolve, 1e3));
3824
- spinner15.stop("Gateway started");
3824
+ spinner17.stop("Gateway started");
3825
3825
  console.log(chalk10.green(`
3826
3826
  \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
3827
  \u2551 Gateway is running! \u2551
@@ -3835,10 +3835,10 @@ async function startAllChannels(config) {
3835
3835
  `));
3836
3836
  }
3837
3837
  async function stopAllChannels(config) {
3838
- const spinner15 = p9.spinner();
3839
- spinner15.start("Stopping gateway...");
3838
+ const spinner17 = p9.spinner();
3839
+ spinner17.start("Stopping gateway...");
3840
3840
  await new Promise((resolve) => setTimeout(resolve, 500));
3841
- spinner15.stop("Gateway stopped");
3841
+ spinner17.stop("Gateway stopped");
3842
3842
  }
3843
3843
  async function deployGatewayWizard(config) {
3844
3844
  p9.log.info(chalk10.bold("Deploy Gateway to Cloud"));
@@ -3896,8 +3896,8 @@ async function deployToVPS(config) {
3896
3896
  initialValue: true
3897
3897
  });
3898
3898
  if (proceed && !p9.isCancel(proceed)) {
3899
- const open2 = (await import("open")).default;
3900
- await open2("https://cloud.digitalocean.com/");
3899
+ const open3 = (await import("open")).default;
3900
+ await open3("https://cloud.digitalocean.com/");
3901
3901
  }
3902
3902
  } else {
3903
3903
  p9.log.info(chalk10.dim(`
@@ -3953,10 +3953,10 @@ import glob2 from "fast-glob";
3953
3953
  import * as path8 from "path";
3954
3954
  async function securityCommand() {
3955
3955
  p11.intro(chalk12.bgCyan.black(" Security Audit "));
3956
- const spinner15 = p11.spinner();
3957
- spinner15.start("Scanning for security issues...");
3956
+ const spinner17 = p11.spinner();
3957
+ spinner17.start("Scanning for security issues...");
3958
3958
  const issues = await runSecurityScan();
3959
- spinner15.stop("Scan complete");
3959
+ spinner17.stop("Scan complete");
3960
3960
  if (issues.length === 0) {
3961
3961
  console.log(chalk12.green("\n\u2713 No security issues found!\n"));
3962
3962
  displaySecurityScore(100);
@@ -4050,10 +4050,10 @@ async function generateCommand(type) {
4050
4050
  validate: (v) => !v ? "Name is required" : void 0
4051
4051
  });
4052
4052
  if (p12.isCancel(name)) return;
4053
- const spinner15 = p12.spinner();
4054
- spinner15.start("Generating...");
4053
+ const spinner17 = p12.spinner();
4054
+ spinner17.start("Generating...");
4055
4055
  await generateFile(generateType, name);
4056
- spinner15.stop(`Generated ${name}`);
4056
+ spinner17.stop(`Generated ${name}`);
4057
4057
  p12.outro("");
4058
4058
  }
4059
4059
  async function generateFile(type, name) {
@@ -4198,13 +4198,13 @@ import * as p13 from "@clack/prompts";
4198
4198
  import chalk14 from "chalk";
4199
4199
  async function fixCommand() {
4200
4200
  p13.intro(chalk14.bgCyan.black(" Auto-Fix "));
4201
- const spinner15 = p13.spinner();
4202
- spinner15.start("Analyzing code...");
4201
+ const spinner17 = p13.spinner();
4202
+ spinner17.start("Analyzing code...");
4203
4203
  const result = await runPatternCheck(true);
4204
4204
  if (result.passed) {
4205
- spinner15.stop("No issues found!");
4205
+ spinner17.stop("No issues found!");
4206
4206
  } else {
4207
- spinner15.stop(`Fixed ${result.violations.length} issues`);
4207
+ spinner17.stop(`Fixed ${result.violations.length} issues`);
4208
4208
  }
4209
4209
  p13.outro(chalk14.green("Done!"));
4210
4210
  }
@@ -4405,8 +4405,8 @@ function generateColorPalette(hex) {
4405
4405
  };
4406
4406
  }
4407
4407
  async function checkDesign() {
4408
- const spinner15 = p14.spinner();
4409
- spinner15.start("Checking design quality...");
4408
+ const spinner17 = p14.spinner();
4409
+ spinner17.start("Checking design quality...");
4410
4410
  const cwd = process.cwd();
4411
4411
  const issues = [];
4412
4412
  const glob3 = (await import("fast-glob")).default;
@@ -4432,7 +4432,7 @@ async function checkDesign() {
4432
4432
  }
4433
4433
  }
4434
4434
  }
4435
- spinner15.stop("Check complete");
4435
+ spinner17.stop("Check complete");
4436
4436
  if (issues.length === 0) {
4437
4437
  console.log(chalk15.green("\n\u2713 No design issues found!\n"));
4438
4438
  } else {
@@ -4530,8 +4530,8 @@ async function detectMigrationTool() {
4530
4530
  return null;
4531
4531
  }
4532
4532
  async function checkMigrationStatus(tool) {
4533
- const spinner15 = p15.spinner();
4534
- spinner15.start("Checking migration status...");
4533
+ const spinner17 = p15.spinner();
4534
+ spinner17.start("Checking migration status...");
4535
4535
  try {
4536
4536
  let result;
4537
4537
  switch (tool) {
@@ -4545,7 +4545,7 @@ async function checkMigrationStatus(tool) {
4545
4545
  result = await execa8("npx", ["supabase", "migration", "list"], { cwd: process.cwd(), reject: false });
4546
4546
  break;
4547
4547
  }
4548
- spinner15.stop("Status check complete");
4548
+ spinner17.stop("Status check complete");
4549
4549
  if (result?.stdout) {
4550
4550
  console.log(chalk16.dim(result.stdout));
4551
4551
  }
@@ -4553,7 +4553,7 @@ async function checkMigrationStatus(tool) {
4553
4553
  console.log(chalk16.yellow(result.stderr));
4554
4554
  }
4555
4555
  } catch (error) {
4556
- spinner15.stop("Error checking status");
4556
+ spinner17.stop("Error checking status");
4557
4557
  p15.log.error(error instanceof Error ? error.message : "Unknown error");
4558
4558
  }
4559
4559
  }
@@ -4564,8 +4564,8 @@ async function generateMigration(tool) {
4564
4564
  validate: (v) => !v ? "Name required" : void 0
4565
4565
  });
4566
4566
  if (p15.isCancel(name)) return;
4567
- const spinner15 = p15.spinner();
4568
- spinner15.start("Generating migration...");
4567
+ const spinner17 = p15.spinner();
4568
+ spinner17.start("Generating migration...");
4569
4569
  try {
4570
4570
  let result;
4571
4571
  switch (tool) {
@@ -4579,18 +4579,18 @@ async function generateMigration(tool) {
4579
4579
  result = await execa8("npx", ["supabase", "migration", "new", name], { cwd: process.cwd(), reject: false });
4580
4580
  break;
4581
4581
  }
4582
- spinner15.stop("Migration generated");
4582
+ spinner17.stop("Migration generated");
4583
4583
  if (result?.stdout) {
4584
4584
  console.log(chalk16.dim(result.stdout));
4585
4585
  }
4586
4586
  } catch (error) {
4587
- spinner15.stop("Error generating migration");
4587
+ spinner17.stop("Error generating migration");
4588
4588
  p15.log.error(error instanceof Error ? error.message : "Unknown error");
4589
4589
  }
4590
4590
  }
4591
4591
  async function pushMigration(tool) {
4592
- const spinner15 = p15.spinner();
4593
- spinner15.start("Pushing migration to database...");
4592
+ const spinner17 = p15.spinner();
4593
+ spinner17.start("Pushing migration to database...");
4594
4594
  try {
4595
4595
  let result;
4596
4596
  let migrationSql = "";
@@ -4616,7 +4616,7 @@ async function pushMigration(tool) {
4616
4616
  break;
4617
4617
  }
4618
4618
  if (result?.exitCode !== 0) {
4619
- spinner15.stop("Migration push failed");
4619
+ spinner17.stop("Migration push failed");
4620
4620
  const errorOutput = result?.stderr || result?.stdout || "";
4621
4621
  console.log(chalk16.red("\nError:\n"));
4622
4622
  console.log(chalk16.dim(errorOutput));
@@ -4669,13 +4669,13 @@ Also saved to: ${sqlPath}
4669
4669
  }
4670
4670
  return;
4671
4671
  }
4672
- spinner15.stop("Migration pushed successfully!");
4672
+ spinner17.stop("Migration pushed successfully!");
4673
4673
  if (result?.stdout) {
4674
4674
  console.log(chalk16.dim(result.stdout));
4675
4675
  }
4676
4676
  p15.log.success("Database updated!");
4677
4677
  } catch (error) {
4678
- spinner15.stop("Error");
4678
+ spinner17.stop("Error");
4679
4679
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
4680
4680
  p15.log.error(errorMsg);
4681
4681
  const migrationSql = await extractMigrationSQL(tool, errorMsg);
@@ -4687,8 +4687,8 @@ Also saved to: ${sqlPath}
4687
4687
  }
4688
4688
  }
4689
4689
  async function pullSchema(tool) {
4690
- const spinner15 = p15.spinner();
4691
- spinner15.start("Pulling schema from database...");
4690
+ const spinner17 = p15.spinner();
4691
+ spinner17.start("Pulling schema from database...");
4692
4692
  try {
4693
4693
  let result;
4694
4694
  switch (tool) {
@@ -4702,12 +4702,12 @@ async function pullSchema(tool) {
4702
4702
  result = await execa8("npx", ["supabase", "db", "pull"], { cwd: process.cwd(), reject: false });
4703
4703
  break;
4704
4704
  }
4705
- spinner15.stop("Schema pulled");
4705
+ spinner17.stop("Schema pulled");
4706
4706
  if (result?.stdout) {
4707
4707
  console.log(chalk16.dim(result.stdout));
4708
4708
  }
4709
4709
  } catch (error) {
4710
- spinner15.stop("Error pulling schema");
4710
+ spinner17.stop("Error pulling schema");
4711
4711
  p15.log.error(error instanceof Error ? error.message : "Unknown error");
4712
4712
  }
4713
4713
  }
@@ -4770,21 +4770,21 @@ async function extractMigrationSQL(tool, errorOutput) {
4770
4770
  }
4771
4771
  return null;
4772
4772
  }
4773
- async function copyToClipboard(text14) {
4773
+ async function copyToClipboard(text16) {
4774
4774
  try {
4775
4775
  const platform = process.platform;
4776
4776
  if (platform === "win32") {
4777
- const proc = await execa8("clip", { input: text14, reject: false });
4777
+ const proc = await execa8("clip", { input: text16, reject: false });
4778
4778
  return proc.exitCode === 0;
4779
4779
  } else if (platform === "darwin") {
4780
- const proc = await execa8("pbcopy", { input: text14, reject: false });
4780
+ const proc = await execa8("pbcopy", { input: text16, reject: false });
4781
4781
  return proc.exitCode === 0;
4782
4782
  } else {
4783
4783
  try {
4784
- const proc = await execa8("xclip", ["-selection", "clipboard"], { input: text14, reject: false });
4784
+ const proc = await execa8("xclip", ["-selection", "clipboard"], { input: text16, reject: false });
4785
4785
  return proc.exitCode === 0;
4786
4786
  } catch {
4787
- const proc = await execa8("xsel", ["--clipboard", "--input"], { input: text14, reject: false });
4787
+ const proc = await execa8("xsel", ["--clipboard", "--input"], { input: text16, reject: false });
4788
4788
  return proc.exitCode === 0;
4789
4789
  }
4790
4790
  }
@@ -4845,10 +4845,10 @@ async function prdMakerCommand() {
4845
4845
  const input = await conductPRDInterview(inputMethod);
4846
4846
  if (!input) return;
4847
4847
  const anthropic = new Anthropic3({ apiKey: anthropicCreds.apiKey });
4848
- const spinner15 = p16.spinner();
4849
- spinner15.start("Generating professional PRD...");
4848
+ const spinner17 = p16.spinner();
4849
+ spinner17.start("Generating professional PRD...");
4850
4850
  const prd = await generatePRD(anthropic, input);
4851
- spinner15.stop("PRD generated!");
4851
+ spinner17.stop("PRD generated!");
4852
4852
  const filename = `${input.projectName.toLowerCase().replace(/[^a-z0-9]/g, "-")}-prd.md`;
4853
4853
  const filepath = path12.join(process.cwd(), filename);
4854
4854
  await fs13.writeFile(filepath, prd);
@@ -5053,11 +5053,11 @@ async function getVoiceInput2(prompt) {
5053
5053
  initialValue: true
5054
5054
  });
5055
5055
  if (!ready || p16.isCancel(ready)) {
5056
- const text15 = await p16.text({ message: "Type instead:" });
5057
- return p16.isCancel(text15) ? null : text15;
5056
+ const text17 = await p16.text({ message: "Type instead:" });
5057
+ return p16.isCancel(text17) ? null : text17;
5058
5058
  }
5059
- const spinner15 = p16.spinner();
5060
- spinner15.start("\u{1F534} Recording... (press Ctrl+C to stop)");
5059
+ const spinner17 = p16.spinner();
5060
+ spinner17.start("\u{1F534} Recording... (press Ctrl+C to stop)");
5061
5061
  try {
5062
5062
  let transcription = "";
5063
5063
  if (process.platform === "win32") {
@@ -5067,16 +5067,16 @@ async function getVoiceInput2(prompt) {
5067
5067
  } else {
5068
5068
  transcription = await recordWithLinux2();
5069
5069
  }
5070
- spinner15.stop("Recording complete");
5070
+ spinner17.stop("Recording complete");
5071
5071
  if (transcription) {
5072
5072
  console.log(chalk17.green(`
5073
5073
  Heard: "${transcription}"
5074
5074
  `));
5075
- const confirm11 = await p16.confirm({
5075
+ const confirm12 = await p16.confirm({
5076
5076
  message: "Is this correct?",
5077
5077
  initialValue: true
5078
5078
  });
5079
- if (confirm11 && !p16.isCancel(confirm11)) {
5079
+ if (confirm12 && !p16.isCancel(confirm12)) {
5080
5080
  return transcription;
5081
5081
  } else {
5082
5082
  const retry = await p16.select({
@@ -5090,17 +5090,17 @@ async function getVoiceInput2(prompt) {
5090
5090
  if (retry === "retry") {
5091
5091
  return await getVoiceInput2(prompt);
5092
5092
  } else {
5093
- const text15 = await p16.text({ message: "Type your response:" });
5094
- return p16.isCancel(text15) ? null : text15;
5093
+ const text17 = await p16.text({ message: "Type your response:" });
5094
+ return p16.isCancel(text17) ? null : text17;
5095
5095
  }
5096
5096
  }
5097
5097
  }
5098
5098
  } catch (error) {
5099
- spinner15.stop("Recording failed");
5099
+ spinner17.stop("Recording failed");
5100
5100
  console.log(chalk17.yellow("Voice input failed. Falling back to text."));
5101
5101
  }
5102
- const text14 = await p16.text({ message: prompt });
5103
- return p16.isCancel(text14) ? null : text14;
5102
+ const text16 = await p16.text({ message: prompt });
5103
+ return p16.isCancel(text16) ? null : text16;
5104
5104
  }
5105
5105
  async function recordWithWindowsSpeech2() {
5106
5106
  const psScript = `
@@ -5155,9 +5155,9 @@ async function recordWithMacOS2() {
5155
5155
  });
5156
5156
  const txtFile = tempFile.replace(".wav", ".txt");
5157
5157
  if (await fs13.pathExists(txtFile)) {
5158
- const text14 = await fs13.readFile(txtFile, "utf-8");
5158
+ const text16 = await fs13.readFile(txtFile, "utf-8");
5159
5159
  await fs13.remove(txtFile);
5160
- return text14.trim();
5160
+ return text16.trim();
5161
5161
  }
5162
5162
  } catch {
5163
5163
  }
@@ -5205,9 +5205,9 @@ async function recordWithLinux2() {
5205
5205
  });
5206
5206
  const txtFile = tempFile.replace(".wav", ".txt");
5207
5207
  if (await fs13.pathExists(txtFile)) {
5208
- const text14 = await fs13.readFile(txtFile, "utf-8");
5208
+ const text16 = await fs13.readFile(txtFile, "utf-8");
5209
5209
  await fs13.remove(txtFile);
5210
- return text14.trim();
5210
+ return text16.trim();
5211
5211
  }
5212
5212
  } catch {
5213
5213
  }
@@ -5337,10 +5337,10 @@ async function buildCommand(prdPath, options = {}) {
5337
5337
  \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
5338
  `));
5339
5339
  const anthropic = new Anthropic4({ apiKey: anthropicCreds.apiKey });
5340
- const spinner15 = p17.spinner();
5341
- spinner15.start("Analyzing PRD...");
5340
+ const spinner17 = p17.spinner();
5341
+ spinner17.start("Analyzing PRD...");
5342
5342
  const buildPlan = await analyzePRD(anthropic, prdContent);
5343
- spinner15.stop("PRD analyzed");
5343
+ spinner17.stop("PRD analyzed");
5344
5344
  displayBuildPlan(buildPlan);
5345
5345
  const totalAgents = buildPlan.waves.reduce((sum, w) => sum + w.agents.length, 0);
5346
5346
  const useParallel = !options.sequential && totalAgents > 2;
@@ -5366,9 +5366,9 @@ async function buildCommand(prdPath, options = {}) {
5366
5366
  }
5367
5367
  await fs14.ensureDir(projectPath);
5368
5368
  process.chdir(projectPath);
5369
- spinner15.start("Initializing project...");
5369
+ spinner17.start("Initializing project...");
5370
5370
  await execa9("git", ["init"], { cwd: projectPath });
5371
- spinner15.stop("Project initialized");
5371
+ spinner17.stop("Project initialized");
5372
5372
  await runSetupPhase(anthropic, buildPlan, projectPath);
5373
5373
  const startTime = Date.now();
5374
5374
  if (useParallel) {
@@ -5377,9 +5377,9 @@ async function buildCommand(prdPath, options = {}) {
5377
5377
  await executeSequentialBuild(anthropic, buildPlan, projectPath, config);
5378
5378
  }
5379
5379
  await runIntegrationPhase(anthropic, buildPlan, projectPath);
5380
- spinner15.start("Installing dependencies...");
5380
+ spinner17.start("Installing dependencies...");
5381
5381
  await execa9("npm", ["install"], { cwd: projectPath, reject: false });
5382
- spinner15.stop("Dependencies installed");
5382
+ spinner17.stop("Dependencies installed");
5383
5383
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
5384
5384
  const minutes = Math.floor(elapsed / 60);
5385
5385
  const seconds = elapsed % 60;
@@ -5450,8 +5450,8 @@ Think about:
5450
5450
  - What needs data from other features (dashboards, reports)?`
5451
5451
  }]
5452
5452
  });
5453
- const text14 = response.content[0].type === "text" ? response.content[0].text : "";
5454
- const jsonMatch = text14.match(/\{[\s\S]*\}/);
5453
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5454
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
5455
5455
  if (!jsonMatch) {
5456
5456
  throw new Error("Failed to parse PRD analysis");
5457
5457
  }
@@ -5490,8 +5490,8 @@ function displayBuildPlan(plan) {
5490
5490
  `));
5491
5491
  }
5492
5492
  async function runSetupPhase(anthropic, plan, projectPath) {
5493
- const spinner15 = p17.spinner();
5494
- spinner15.start("Setting up project structure...");
5493
+ const spinner17 = p17.spinner();
5494
+ spinner17.start("Setting up project structure...");
5495
5495
  await fs14.ensureDir(path13.join(projectPath, "src/app"));
5496
5496
  await fs14.ensureDir(path13.join(projectPath, "src/components/ui"));
5497
5497
  await fs14.ensureDir(path13.join(projectPath, "src/lib"));
@@ -5535,7 +5535,7 @@ Use TypeScript. Include all necessary imports.`
5535
5535
  await writeFilesFromResponse(setupText, projectPath);
5536
5536
  await execa9("git", ["add", "."], { cwd: projectPath });
5537
5537
  await execa9("git", ["commit", "-m", "Initial setup"], { cwd: projectPath });
5538
- spinner15.stop("Project structure ready");
5538
+ spinner17.stop("Project structure ready");
5539
5539
  }
5540
5540
  async function executeParallelBuild(anthropic, plan, projectPath, config) {
5541
5541
  for (const wave of plan.waves) {
@@ -5585,15 +5585,15 @@ async function executeAgentWithProgress(anthropic, agent, projectPath, plan, dis
5585
5585
  async function executeSequentialBuild(anthropic, plan, projectPath, config) {
5586
5586
  for (const wave of plan.waves) {
5587
5587
  for (const agent of wave.agents) {
5588
- const spinner15 = p17.spinner();
5589
- spinner15.start(`Building ${agent.name}...`);
5588
+ const spinner17 = p17.spinner();
5589
+ spinner17.start(`Building ${agent.name}...`);
5590
5590
  try {
5591
5591
  await executeAgent(anthropic, agent, projectPath, plan, (progress, action) => {
5592
- spinner15.message = `${agent.name}: ${action} (${progress}%)`;
5592
+ spinner17.message = `${agent.name}: ${action} (${progress}%)`;
5593
5593
  });
5594
- spinner15.stop(`\u2713 ${agent.name} complete`);
5594
+ spinner17.stop(`\u2713 ${agent.name} complete`);
5595
5595
  } catch (error) {
5596
- spinner15.stop(`\u2717 ${agent.name} failed`);
5596
+ spinner17.stop(`\u2717 ${agent.name} failed`);
5597
5597
  throw error;
5598
5598
  }
5599
5599
  await mergeWaveBranches([agent], projectPath);
@@ -5695,9 +5695,9 @@ Start with the index.ts that exports everything.`
5695
5695
  max_tokens: 8192,
5696
5696
  messages
5697
5697
  });
5698
- const text14 = response.content[0].type === "text" ? response.content[0].text : "";
5699
- messages.push({ role: "assistant", content: text14 });
5700
- const questionMatch = text14.match(/<<<ASK_USER>>>([\s\S]*?)<<<END_ASK>>>/);
5698
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5699
+ messages.push({ role: "assistant", content: text16 });
5700
+ const questionMatch = text16.match(/<<<ASK_USER>>>([\s\S]*?)<<<END_ASK>>>/);
5701
5701
  if (questionMatch) {
5702
5702
  const questionBlock = questionMatch[1];
5703
5703
  const questionLine = questionBlock.match(/question:\s*(.+)/);
@@ -5726,7 +5726,7 @@ Now continue building the feature with this choice. Generate the code files.`
5726
5726
  }
5727
5727
  }
5728
5728
  if (onProgress) onProgress(60, "Writing files...");
5729
- const files = await writeFilesFromResponse(text14, projectPath);
5729
+ const files = await writeFilesFromResponse(text16, projectPath);
5730
5730
  agent.files = files;
5731
5731
  if (onProgress) onProgress(80, "Validating...");
5732
5732
  if (files.length === 0) {
@@ -5809,8 +5809,8 @@ Common fixes:
5809
5809
  - Timeout \u2192 retry with simpler approach`
5810
5810
  }]
5811
5811
  });
5812
- const text14 = response.content[0].type === "text" ? response.content[0].text : "";
5813
- const jsonMatch = text14.match(/\{[\s\S]*\}/);
5812
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5813
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
5814
5814
  if (!jsonMatch) {
5815
5815
  return {
5816
5816
  canFix: false,
@@ -5822,8 +5822,8 @@ Common fixes:
5822
5822
  return JSON.parse(jsonMatch[0]);
5823
5823
  }
5824
5824
  async function runIntegrationPhase(anthropic, plan, projectPath) {
5825
- const spinner15 = p17.spinner();
5826
- spinner15.start("Running integration...");
5825
+ const spinner17 = p17.spinner();
5826
+ spinner17.start("Running integration...");
5827
5827
  const features = plan.waves.flatMap((w) => w.agents);
5828
5828
  const response = await anthropic.messages.create({
5829
5829
  model: "claude-sonnet-4-20250514",
@@ -5850,11 +5850,11 @@ content
5850
5850
  Make sure all features are accessible and properly connected.`
5851
5851
  }]
5852
5852
  });
5853
- const text14 = response.content[0].type === "text" ? response.content[0].text : "";
5854
- await writeFilesFromResponse(text14, projectPath);
5853
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
5854
+ await writeFilesFromResponse(text16, projectPath);
5855
5855
  await execa9("git", ["add", "."], { cwd: projectPath });
5856
5856
  await execa9("git", ["commit", "-m", "Integration: wire up all features"], { cwd: projectPath, reject: false });
5857
- spinner15.stop("Integration complete");
5857
+ spinner17.stop("Integration complete");
5858
5858
  }
5859
5859
  async function mergeWaveBranches(agents, projectPath) {
5860
5860
  for (const agent of agents) {
@@ -5943,11 +5943,11 @@ var ProgressDisplay = class {
5943
5943
  return chalk18.green("\u2588".repeat(filled)) + chalk18.gray("\u2591".repeat(empty));
5944
5944
  }
5945
5945
  };
5946
- async function writeFilesFromResponse(text14, projectPath) {
5946
+ async function writeFilesFromResponse(text16, projectPath) {
5947
5947
  const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
5948
5948
  const files = [];
5949
5949
  let match;
5950
- while ((match = fileRegex.exec(text14)) !== null) {
5950
+ while ((match = fileRegex.exec(text16)) !== null) {
5951
5951
  const filePath = path13.join(projectPath, match[1].trim());
5952
5952
  const content = match[2].trim();
5953
5953
  await fs14.ensureDir(path13.dirname(filePath));
@@ -5960,9 +5960,1383 @@ function sleep(ms) {
5960
5960
  return new Promise((resolve) => setTimeout(resolve, ms));
5961
5961
  }
5962
5962
 
5963
- // src/utils/nlp.ts
5963
+ // src/commands/integrate.ts
5964
5964
  import * as p18 from "@clack/prompts";
5965
+ import chalk19 from "chalk";
5966
+ import * as fs15 from "fs-extra";
5967
+ import * as path14 from "path";
5968
+ import open2 from "open";
5969
+ import { execa as execa10 } from "execa";
5970
+ var INTEGRATIONS = [
5971
+ // ═══════════════════════════════════════════════════════════════════════════
5972
+ // AUTH PROVIDERS
5973
+ // ═══════════════════════════════════════════════════════════════════════════
5974
+ {
5975
+ id: "clerk",
5976
+ name: "Clerk",
5977
+ description: "Complete user management & authentication",
5978
+ category: "auth",
5979
+ icon: "\u{1F510}",
5980
+ authType: "oauth",
5981
+ oauthUrl: "https://dashboard.clerk.com/apps/new",
5982
+ envVars: ["NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", "CLERK_SECRET_KEY"],
5983
+ 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"
6008
+ },
6009
+ {
6010
+ id: "supabase-auth",
6011
+ name: "Supabase Auth",
6012
+ description: "Authentication with Supabase",
6013
+ category: "auth",
6014
+ icon: "\u26A1",
6015
+ authType: "oauth",
6016
+ oauthUrl: "https://supabase.com/dashboard/projects",
6017
+ envVars: ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY"],
6018
+ packages: ["@supabase/supabase-js", "@supabase/auth-helpers-nextjs"],
6019
+ docs: "https://supabase.com/docs/guides/auth"
6020
+ },
6021
+ {
6022
+ id: "firebase-auth",
6023
+ name: "Firebase Auth",
6024
+ description: "Google Firebase authentication",
6025
+ 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"
6032
+ },
6033
+ // ═══════════════════════════════════════════════════════════════════════════
6034
+ // DATABASES
6035
+ // ═══════════════════════════════════════════════════════════════════════════
6036
+ {
6037
+ id: "supabase",
6038
+ name: "Supabase",
6039
+ description: "Postgres database with realtime & auth",
6040
+ category: "database",
6041
+ icon: "\u26A1",
6042
+ authType: "oauth",
6043
+ oauthUrl: "https://supabase.com/dashboard/projects",
6044
+ envVars: ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY"],
6045
+ 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"
6059
+ },
6060
+ {
6061
+ id: "neon",
6062
+ name: "Neon",
6063
+ description: "Serverless Postgres",
6064
+ category: "database",
6065
+ icon: "\u{1F418}",
6066
+ authType: "oauth",
6067
+ oauthUrl: "https://console.neon.tech",
6068
+ envVars: ["DATABASE_URL"],
6069
+ packages: ["@neondatabase/serverless"],
6070
+ docs: "https://neon.tech/docs"
6071
+ },
6072
+ {
6073
+ id: "turso",
6074
+ name: "Turso",
6075
+ description: "Edge SQLite database",
6076
+ category: "database",
6077
+ icon: "\u{1F422}",
6078
+ authType: "oauth",
6079
+ oauthUrl: "https://turso.tech/app",
6080
+ envVars: ["TURSO_DATABASE_URL", "TURSO_AUTH_TOKEN"],
6081
+ packages: ["@libsql/client"],
6082
+ docs: "https://docs.turso.tech"
6083
+ },
6084
+ {
6085
+ id: "mongodb",
6086
+ name: "MongoDB Atlas",
6087
+ description: "NoSQL document database",
6088
+ 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"
6095
+ },
6096
+ {
6097
+ id: "prisma",
6098
+ name: "Prisma",
6099
+ description: "Type-safe ORM for any database",
6100
+ category: "database",
6101
+ icon: "\u{1F537}",
6102
+ authType: "npm",
6103
+ envVars: ["DATABASE_URL"],
6104
+ 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"
6117
+ },
6118
+ // ═══════════════════════════════════════════════════════════════════════════
6119
+ // PAYMENTS
6120
+ // ═══════════════════════════════════════════════════════════════════════════
6121
+ {
6122
+ id: "stripe",
6123
+ name: "Stripe",
6124
+ description: "Payment processing & subscriptions",
6125
+ category: "payments",
6126
+ icon: "\u{1F4B3}",
6127
+ authType: "oauth",
6128
+ oauthUrl: "https://dashboard.stripe.com/apikeys",
6129
+ envVars: ["STRIPE_SECRET_KEY", "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY", "STRIPE_WEBHOOK_SECRET"],
6130
+ packages: ["stripe", "@stripe/stripe-js"],
6131
+ docs: "https://stripe.com/docs"
6132
+ },
6133
+ {
6134
+ id: "lemonsqueezy",
6135
+ name: "Lemon Squeezy",
6136
+ description: "Merchant of record for SaaS",
6137
+ category: "payments",
6138
+ icon: "\u{1F34B}",
6139
+ authType: "oauth",
6140
+ oauthUrl: "https://app.lemonsqueezy.com/settings/api",
6141
+ envVars: ["LEMONSQUEEZY_API_KEY", "LEMONSQUEEZY_STORE_ID", "LEMONSQUEEZY_WEBHOOK_SECRET"],
6142
+ 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"
6156
+ },
6157
+ // ═══════════════════════════════════════════════════════════════════════════
6158
+ // EMAIL
6159
+ // ═══════════════════════════════════════════════════════════════════════════
6160
+ {
6161
+ id: "resend",
6162
+ name: "Resend",
6163
+ description: "Modern email API for developers",
6164
+ category: "email",
6165
+ icon: "\u{1F4E7}",
6166
+ authType: "oauth",
6167
+ oauthUrl: "https://resend.com/api-keys",
6168
+ envVars: ["RESEND_API_KEY"],
6169
+ packages: ["resend"],
6170
+ docs: "https://resend.com/docs"
6171
+ },
6172
+ {
6173
+ id: "sendgrid",
6174
+ name: "SendGrid",
6175
+ description: "Email delivery service",
6176
+ category: "email",
6177
+ icon: "\u{1F4E8}",
6178
+ authType: "oauth",
6179
+ oauthUrl: "https://app.sendgrid.com/settings/api_keys",
6180
+ envVars: ["SENDGRID_API_KEY"],
6181
+ 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"
6218
+ },
6219
+ // ═══════════════════════════════════════════════════════════════════════════
6220
+ // STORAGE
6221
+ // ═══════════════════════════════════════════════════════════════════════════
6222
+ {
6223
+ id: "uploadthing",
6224
+ name: "UploadThing",
6225
+ description: "File uploads for Next.js",
6226
+ category: "storage",
6227
+ icon: "\u{1F4E4}",
6228
+ authType: "oauth",
6229
+ oauthUrl: "https://uploadthing.com/dashboard",
6230
+ envVars: ["UPLOADTHING_SECRET", "UPLOADTHING_APP_ID"],
6231
+ packages: ["uploadthing", "@uploadthing/react"],
6232
+ docs: "https://docs.uploadthing.com"
6233
+ },
6234
+ {
6235
+ id: "cloudinary",
6236
+ name: "Cloudinary",
6237
+ description: "Image & video management",
6238
+ category: "storage",
6239
+ icon: "\u2601\uFE0F",
6240
+ authType: "oauth",
6241
+ oauthUrl: "https://console.cloudinary.com/settings/api-keys",
6242
+ envVars: ["CLOUDINARY_CLOUD_NAME", "CLOUDINARY_API_KEY", "CLOUDINARY_API_SECRET"],
6243
+ 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"
6319
+ },
6320
+ // ═══════════════════════════════════════════════════════════════════════════
6321
+ // AI
6322
+ // ═══════════════════════════════════════════════════════════════════════════
6323
+ {
6324
+ id: "openai",
6325
+ name: "OpenAI",
6326
+ description: "GPT models & DALL-E",
6327
+ category: "ai",
6328
+ icon: "\u{1F916}",
6329
+ authType: "oauth",
6330
+ oauthUrl: "https://platform.openai.com/api-keys",
6331
+ envVars: ["OPENAI_API_KEY"],
6332
+ packages: ["openai"],
6333
+ docs: "https://platform.openai.com/docs"
6334
+ },
6335
+ {
6336
+ id: "anthropic",
6337
+ name: "Anthropic",
6338
+ description: "Claude AI models",
6339
+ category: "ai",
6340
+ icon: "\u{1F9E0}",
6341
+ authType: "oauth",
6342
+ oauthUrl: "https://console.anthropic.com/settings/keys",
6343
+ envVars: ["ANTHROPIC_API_KEY"],
6344
+ packages: ["@anthropic-ai/sdk"],
6345
+ docs: "https://docs.anthropic.com"
6346
+ },
6347
+ {
6348
+ id: "replicate",
6349
+ name: "Replicate",
6350
+ description: "Run ML models in the cloud",
6351
+ category: "ai",
6352
+ icon: "\u{1F504}",
6353
+ authType: "oauth",
6354
+ oauthUrl: "https://replicate.com/account/api-tokens",
6355
+ envVars: ["REPLICATE_API_TOKEN"],
6356
+ 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"
6381
+ },
6382
+ // ═══════════════════════════════════════════════════════════════════════════
6383
+ // CMS
6384
+ // ═══════════════════════════════════════════════════════════════════════════
6385
+ {
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"
6396
+ },
6397
+ {
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"
6408
+ },
6409
+ {
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"
6419
+ },
6420
+ // ═══════════════════════════════════════════════════════════════════════════
6421
+ // MESSAGING
6422
+ // ═══════════════════════════════════════════════════════════════════════════
6423
+ {
6424
+ id: "twilio",
6425
+ name: "Twilio",
6426
+ description: "SMS, voice & WhatsApp",
6427
+ category: "messaging",
6428
+ icon: "\u{1F4F1}",
6429
+ authType: "oauth",
6430
+ oauthUrl: "https://console.twilio.com/us1/account/keys-credentials/api-keys",
6431
+ envVars: ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN", "TWILIO_PHONE_NUMBER"],
6432
+ packages: ["twilio"],
6433
+ docs: "https://www.twilio.com/docs"
6434
+ },
6435
+ {
6436
+ id: "pusher",
6437
+ name: "Pusher",
6438
+ description: "Realtime websockets",
6439
+ category: "messaging",
6440
+ 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"],
6444
+ 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"
6497
+ },
6498
+ // ═══════════════════════════════════════════════════════════════════════════
6499
+ // DEPLOYMENT
6500
+ // ═══════════════════════════════════════════════════════════════════════════
6501
+ {
6502
+ id: "vercel",
6503
+ name: "Vercel",
6504
+ description: "Deploy Next.js apps",
6505
+ category: "deployment",
6506
+ icon: "\u25B2",
6507
+ authType: "oauth",
6508
+ oauthUrl: "https://vercel.com/account/tokens",
6509
+ envVars: ["VERCEL_TOKEN"],
6510
+ packages: ["vercel"],
6511
+ docs: "https://vercel.com/docs"
6512
+ },
6513
+ {
6514
+ id: "github",
6515
+ name: "GitHub",
6516
+ description: "Code hosting & CI/CD",
6517
+ category: "deployment",
6518
+ icon: "\u{1F419}",
6519
+ authType: "oauth",
6520
+ oauthUrl: "https://github.com/settings/tokens",
6521
+ envVars: ["GITHUB_TOKEN"],
6522
+ packages: ["octokit"],
6523
+ docs: "https://docs.github.com"
6524
+ },
6525
+ // ═══════════════════════════════════════════════════════════════════════════
6526
+ // OTHER
6527
+ // ═══════════════════════════════════════════════════════════════════════════
6528
+ {
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"
6563
+ }
6564
+ ];
6565
+ async function integrateCommand(integrationId) {
6566
+ const config = new Config();
6567
+ console.log(chalk19.cyan(`
6568
+ \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
+ \u2551 \u{1F50C} ONE-CLICK INTEGRATIONS \u2551
6570
+ \u2551 \u2551
6571
+ \u2551 ${INTEGRATIONS.length} integrations available \u2551
6572
+ \u2551 Browser-based auth \u2022 Auto-install \u2022 Ready in seconds \u2551
6573
+ \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
+ `));
6575
+ if (integrationId) {
6576
+ const integration2 = INTEGRATIONS.find((i) => i.id === integrationId);
6577
+ if (!integration2) {
6578
+ p18.log.error(`Unknown integration: ${integrationId}`);
6579
+ console.log(chalk19.dim(`Run 'codebakers integrate' to see all available integrations`));
6580
+ return;
6581
+ }
6582
+ await installIntegration(integration2, config);
6583
+ return;
6584
+ }
6585
+ const categories = [...new Set(INTEGRATIONS.map((i) => i.category))];
6586
+ const category = await p18.select({
6587
+ message: "Choose a category:",
6588
+ options: [
6589
+ { value: "all", label: "\u{1F4CB} Show all integrations" },
6590
+ { value: "search", label: "\u{1F50D} Search by name" },
6591
+ ...categories.map((c) => ({
6592
+ value: c,
6593
+ label: getCategoryLabel(c),
6594
+ hint: `${INTEGRATIONS.filter((i) => i.category === c).length} integrations`
6595
+ }))
6596
+ ]
6597
+ });
6598
+ if (p18.isCancel(category)) return;
6599
+ let filteredIntegrations = INTEGRATIONS;
6600
+ if (category === "search") {
6601
+ const query = await p18.text({
6602
+ message: "Search integrations:",
6603
+ placeholder: "stripe, auth, email..."
6604
+ });
6605
+ if (p18.isCancel(query)) return;
6606
+ 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)
6609
+ );
6610
+ } 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;
6616
+ }
6617
+ const selected = await p18.select({
6618
+ message: "Select an integration to install:",
6619
+ options: filteredIntegrations.map((i) => ({
6620
+ value: i.id,
6621
+ label: `${i.icon} ${i.name}`,
6622
+ hint: i.description
6623
+ }))
6624
+ });
6625
+ if (p18.isCancel(selected)) return;
6626
+ const integration = INTEGRATIONS.find((i) => i.id === selected);
6627
+ await installIntegration(integration, config);
6628
+ }
6629
+ async function installIntegration(integration, config) {
6630
+ console.log(chalk19.cyan(`
6631
+ Installing ${integration.icon} ${integration.name}...
6632
+ `));
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
+ if (integration.packages && integration.packages.length > 0) {
6643
+ spinner17.start(`Installing ${integration.packages.join(", ")}...`);
6644
+ try {
6645
+ await execa10("npm", ["install", ...integration.packages], {
6646
+ cwd: process.cwd(),
6647
+ reject: false
6648
+ });
6649
+ spinner17.stop(`\u2713 Packages installed`);
6650
+ } catch {
6651
+ spinner17.stop(`\u2713 Packages installed (or already present)`);
6652
+ }
6653
+ }
6654
+ 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.
6659
+ `));
6660
+ await open2(integration.oauthUrl);
6661
+ await sleep2(1500);
6662
+ }
6663
+ const credentials = {};
6664
+ for (const envVar of integration.envVars) {
6665
+ const isPublic = envVar.startsWith("NEXT_PUBLIC_");
6666
+ const hint = isPublic ? "(public, will be in client bundle)" : "(secret, server-only)";
6667
+ const value = await p18.text({
6668
+ message: `${envVar} ${chalk19.dim(hint)}:`,
6669
+ placeholder: "Paste your key here...",
6670
+ validate: (v) => !v ? "Required" : void 0
6671
+ });
6672
+ if (p18.isCancel(value)) return;
6673
+ credentials[envVar] = value;
6674
+ }
6675
+ await saveEnvVars(credentials);
6676
+ console.log(chalk19.green(` \u2713 Credentials saved to .env.local
6677
+ `));
6678
+ }
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");
6688
+ console.log(chalk19.green(`
6689
+ \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!
6691
+ \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}
6701
+ \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;
6720
+ }
6721
+ async function saveEnvVars(vars) {
6722
+ const envPath = path14.join(process.cwd(), ".env.local");
6723
+ let content = "";
6724
+ if (await fs15.pathExists(envPath)) {
6725
+ content = await fs15.readFile(envPath, "utf-8");
6726
+ if (!content.endsWith("\n")) {
6727
+ content += "\n";
6728
+ }
6729
+ content += "\n# Added by CodeBakers\n";
6730
+ }
6731
+ for (const [key, value] of Object.entries(vars)) {
6732
+ const regex = new RegExp(`^${key}=`, "m");
6733
+ if (regex.test(content)) {
6734
+ content = content.replace(regex, `${key}=${value}`);
6735
+ } else {
6736
+ content += `${key}=${value}
6737
+ `;
6738
+ }
6739
+ }
6740
+ await fs15.writeFile(envPath, content);
6741
+ }
6742
+ async function generateSetupCode(integration) {
6743
+ const files = [];
6744
+ switch (integration.id) {
6745
+ case "clerk":
6746
+ files.push({
6747
+ path: "src/middleware.ts",
6748
+ content: `import { clerkMiddleware } from '@clerk/nextjs/server';
6749
+
6750
+ export default clerkMiddleware();
6751
+
6752
+ export const config = {
6753
+ matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
6754
+ };
6755
+ `
6756
+ });
6757
+ break;
6758
+ case "stripe":
6759
+ files.push({
6760
+ path: "src/lib/stripe.ts",
6761
+ content: `import Stripe from 'stripe';
6762
+
6763
+ export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
6764
+ apiVersion: '2023-10-16',
6765
+ typescript: true,
6766
+ });
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
+ });
6781
+ break;
6782
+ case "resend":
6783
+ files.push({
6784
+ path: "src/lib/resend.ts",
6785
+ content: `import { Resend } from 'resend';
6786
+
6787
+ export const resend = new Resend(process.env.RESEND_API_KEY);
6788
+ `
6789
+ });
6790
+ break;
6791
+ case "openai":
6792
+ files.push({
6793
+ path: "src/lib/openai.ts",
6794
+ content: `import OpenAI from 'openai';
6795
+
6796
+ export const openai = new OpenAI({
6797
+ apiKey: process.env.OPENAI_API_KEY,
6798
+ });
6799
+ `
6800
+ });
6801
+ break;
6802
+ case "anthropic":
6803
+ files.push({
6804
+ path: "src/lib/anthropic.ts",
6805
+ content: `import Anthropic from '@anthropic-ai/sdk';
6806
+
6807
+ export const anthropic = new Anthropic({
6808
+ apiKey: process.env.ANTHROPIC_API_KEY,
6809
+ });
6810
+ `
6811
+ });
6812
+ 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
+ break;
6825
+ }
6826
+ return files.length > 0 ? files : null;
6827
+ }
6828
+ function sleep2(ms) {
6829
+ return new Promise((resolve) => setTimeout(resolve, ms));
6830
+ }
6831
+
6832
+ // src/commands/website.ts
6833
+ import * as p19 from "@clack/prompts";
6834
+ import chalk20 from "chalk";
6835
+ import * as fs16 from "fs-extra";
6836
+ import * as path15 from "path";
5965
6837
  import Anthropic5 from "@anthropic-ai/sdk";
6838
+ import { execa as execa11 } from "execa";
6839
+ var TEMPLATES = [
6840
+ {
6841
+ id: "landing",
6842
+ name: "Landing Page",
6843
+ description: "Convert visitors into customers",
6844
+ icon: "\u{1F680}",
6845
+ sections: ["hero", "features", "testimonials", "pricing", "cta", "footer"],
6846
+ style: "bold"
6847
+ },
6848
+ {
6849
+ id: "saas",
6850
+ name: "SaaS Website",
6851
+ description: "Software product marketing site",
6852
+ icon: "\u{1F4BB}",
6853
+ sections: ["hero", "features", "how-it-works", "pricing", "faq", "testimonials", "cta", "footer"],
6854
+ style: "minimal"
6855
+ },
6856
+ {
6857
+ id: "portfolio",
6858
+ name: "Portfolio",
6859
+ description: "Showcase your work",
6860
+ icon: "\u{1F3A8}",
6861
+ sections: ["hero", "about", "projects", "skills", "contact", "footer"],
6862
+ style: "elegant"
6863
+ },
6864
+ {
6865
+ id: "agency",
6866
+ name: "Agency Website",
6867
+ description: "Professional services company",
6868
+ icon: "\u{1F3E2}",
6869
+ sections: ["hero", "services", "portfolio", "team", "testimonials", "contact", "footer"],
6870
+ style: "corporate"
6871
+ },
6872
+ {
6873
+ id: "startup",
6874
+ name: "Startup Page",
6875
+ description: "Early stage company",
6876
+ icon: "\u26A1",
6877
+ sections: ["hero", "problem", "solution", "features", "team", "waitlist", "footer"],
6878
+ style: "bold"
6879
+ },
6880
+ {
6881
+ id: "restaurant",
6882
+ name: "Restaurant",
6883
+ description: "Food & dining establishment",
6884
+ icon: "\u{1F37D}\uFE0F",
6885
+ sections: ["hero", "menu", "about", "gallery", "reservations", "location", "footer"],
6886
+ style: "elegant"
6887
+ },
6888
+ {
6889
+ id: "ecommerce",
6890
+ name: "E-commerce",
6891
+ description: "Online store",
6892
+ icon: "\u{1F6D2}",
6893
+ sections: ["hero", "featured-products", "categories", "bestsellers", "newsletter", "footer"],
6894
+ style: "minimal"
6895
+ },
6896
+ {
6897
+ id: "blog",
6898
+ name: "Blog",
6899
+ description: "Content & articles",
6900
+ icon: "\u{1F4DD}",
6901
+ sections: ["hero", "featured-posts", "categories", "newsletter", "about", "footer"],
6902
+ style: "minimal"
6903
+ },
6904
+ {
6905
+ id: "event",
6906
+ name: "Event Page",
6907
+ description: "Conference or meetup",
6908
+ icon: "\u{1F389}",
6909
+ sections: ["hero", "speakers", "schedule", "venue", "sponsors", "tickets", "footer"],
6910
+ style: "bold"
6911
+ },
6912
+ {
6913
+ id: "nonprofit",
6914
+ name: "Nonprofit",
6915
+ description: "Charity or cause",
6916
+ icon: "\u{1F49A}",
6917
+ sections: ["hero", "mission", "impact", "programs", "donate", "volunteer", "footer"],
6918
+ style: "elegant"
6919
+ },
6920
+ {
6921
+ id: "personal",
6922
+ name: "Personal Website",
6923
+ description: "Your online presence",
6924
+ icon: "\u{1F464}",
6925
+ sections: ["hero", "about", "experience", "projects", "blog", "contact", "footer"],
6926
+ style: "minimal"
6927
+ },
6928
+ {
6929
+ id: "coming-soon",
6930
+ name: "Coming Soon",
6931
+ description: "Pre-launch teaser",
6932
+ icon: "\u23F3",
6933
+ sections: ["hero", "countdown", "features-preview", "waitlist", "footer"],
6934
+ style: "bold"
6935
+ },
6936
+ {
6937
+ id: "custom",
6938
+ name: "Custom",
6939
+ description: "Build from scratch with AI",
6940
+ icon: "\u2728",
6941
+ sections: [],
6942
+ style: "minimal"
6943
+ }
6944
+ ];
6945
+ async function websiteCommand() {
6946
+ const config = new Config();
6947
+ if (!config.isConfigured()) {
6948
+ p19.log.error("Please run `codebakers setup` first.");
6949
+ return;
6950
+ }
6951
+ const anthropicCreds = config.getCredentials("anthropic");
6952
+ if (!anthropicCreds?.apiKey) {
6953
+ p19.log.error("Anthropic API key not configured.");
6954
+ return;
6955
+ }
6956
+ console.log(chalk20.cyan(`
6957
+ \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
6958
+ \u2551 \u{1F310} WEBSITE BUILDER \u2551
6959
+ \u2551 \u2551
6960
+ \u2551 Describe your website in plain English. \u2551
6961
+ \u2551 AI builds it in minutes. \u2551
6962
+ \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
6963
+ `));
6964
+ const approach = await p19.select({
6965
+ message: "How would you like to build your website?",
6966
+ options: [
6967
+ { value: "describe", label: "\u{1F4AC} Describe it", hint: "Tell me what you want in plain English" },
6968
+ { value: "template", label: "\u{1F4CB} Start from template", hint: "Choose a pre-made structure" },
6969
+ { value: "url", label: "\u{1F517} Clone a design", hint: "Describe a site you like" }
6970
+ ]
6971
+ });
6972
+ if (p19.isCancel(approach)) return;
6973
+ const anthropic = new Anthropic5({ apiKey: anthropicCreds.apiKey });
6974
+ let websiteSpec;
6975
+ if (approach === "describe") {
6976
+ websiteSpec = await describeWebsite(anthropic);
6977
+ } else if (approach === "template") {
6978
+ websiteSpec = await templateWebsite(anthropic);
6979
+ } else {
6980
+ websiteSpec = await cloneDesign(anthropic);
6981
+ }
6982
+ if (!websiteSpec) return;
6983
+ console.log(chalk20.cyan(`
6984
+ \u{1F4CB} Website Plan:
6985
+ `));
6986
+ console.log(chalk20.bold(` ${websiteSpec.name}`));
6987
+ console.log(chalk20.dim(` ${websiteSpec.description}
6988
+ `));
6989
+ console.log(chalk20.dim(` Style: ${websiteSpec.style}`));
6990
+ console.log(chalk20.dim(` Sections: ${websiteSpec.sections.join(", ")}
6991
+ `));
6992
+ const confirm12 = await p19.confirm({
6993
+ message: "Build this website?",
6994
+ initialValue: true
6995
+ });
6996
+ if (!confirm12 || p19.isCancel(confirm12)) return;
6997
+ await buildWebsite(anthropic, websiteSpec, config);
6998
+ }
6999
+ async function describeWebsite(anthropic) {
7000
+ console.log(chalk20.dim("\n Describe your website. Be as detailed as you want.\n"));
7001
+ console.log(chalk20.dim(" Examples:"));
7002
+ console.log(chalk20.dim(' \u2022 "A landing page for my AI writing tool called WriteBot"'));
7003
+ console.log(chalk20.dim(' \u2022 "Portfolio site for a photographer, dark theme, minimal"'));
7004
+ console.log(chalk20.dim(' \u2022 "Coffee shop website with menu, location, and online ordering"\n'));
7005
+ const description = await textWithVoice({
7006
+ message: "Describe your website:",
7007
+ placeholder: "A landing page for..."
7008
+ });
7009
+ if (p19.isCancel(description)) return null;
7010
+ const spinner17 = p19.spinner();
7011
+ spinner17.start("Understanding your vision...");
7012
+ const response = await anthropic.messages.create({
7013
+ model: "claude-sonnet-4-20250514",
7014
+ max_tokens: 2048,
7015
+ messages: [{
7016
+ role: "user",
7017
+ content: `Based on this website description, create a detailed specification.
7018
+
7019
+ Description: "${description}"
7020
+
7021
+ Return JSON only:
7022
+ {
7023
+ "name": "Project name (kebab-case)",
7024
+ "description": "One line description",
7025
+ "style": "minimal | bold | elegant | playful | corporate",
7026
+ "colorScheme": {
7027
+ "primary": "#hex",
7028
+ "secondary": "#hex",
7029
+ "accent": "#hex",
7030
+ "background": "#hex",
7031
+ "text": "#hex"
7032
+ },
7033
+ "sections": ["hero", "features", ...],
7034
+ "content": {
7035
+ "hero": {
7036
+ "headline": "...",
7037
+ "subheadline": "...",
7038
+ "cta": "..."
7039
+ },
7040
+ "features": [
7041
+ { "title": "...", "description": "...", "icon": "..." }
7042
+ ]
7043
+ // etc for each section
7044
+ },
7045
+ "features": ["responsive", "dark-mode", "animations", etc]
7046
+ }
7047
+
7048
+ Make the content compelling and professional. Use appropriate sections for the type of website.`
7049
+ }]
7050
+ });
7051
+ spinner17.stop("Got it!");
7052
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7053
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
7054
+ if (!jsonMatch) {
7055
+ p19.log.error("Failed to understand website description");
7056
+ return null;
7057
+ }
7058
+ return JSON.parse(jsonMatch[0]);
7059
+ }
7060
+ async function templateWebsite(anthropic) {
7061
+ const template = await p19.select({
7062
+ message: "Choose a template:",
7063
+ options: TEMPLATES.map((t) => ({
7064
+ value: t.id,
7065
+ label: `${t.icon} ${t.name}`,
7066
+ hint: t.description
7067
+ }))
7068
+ });
7069
+ if (p19.isCancel(template)) return null;
7070
+ const selectedTemplate = TEMPLATES.find((t) => t.id === template);
7071
+ const name = await p19.text({
7072
+ message: "What is your business/project name?",
7073
+ placeholder: "My Awesome Project"
7074
+ });
7075
+ if (p19.isCancel(name)) return null;
7076
+ const tagline = await p19.text({
7077
+ message: "Tagline or one-line description:",
7078
+ placeholder: "The best solution for..."
7079
+ });
7080
+ if (p19.isCancel(tagline)) return null;
7081
+ const details = await textWithVoice({
7082
+ message: "Any other details? (or press enter to skip)",
7083
+ placeholder: "We help startups with..., Our colors are blue and white..."
7084
+ });
7085
+ if (p19.isCancel(details)) return null;
7086
+ const spinner17 = p19.spinner();
7087
+ spinner17.start("Customizing template...");
7088
+ const response = await anthropic.messages.create({
7089
+ model: "claude-sonnet-4-20250514",
7090
+ max_tokens: 2048,
7091
+ messages: [{
7092
+ role: "user",
7093
+ content: `Create a website spec based on this template and customization.
7094
+
7095
+ Template: ${selectedTemplate.name}
7096
+ Sections: ${selectedTemplate.sections.join(", ")}
7097
+ Default Style: ${selectedTemplate.style}
7098
+
7099
+ Business Name: ${name}
7100
+ Tagline: ${tagline}
7101
+ Additional Details: ${details || "None provided"}
7102
+
7103
+ Return JSON only:
7104
+ {
7105
+ "name": "project-name-kebab",
7106
+ "description": "${tagline}",
7107
+ "style": "${selectedTemplate.style}",
7108
+ "colorScheme": {
7109
+ "primary": "#hex",
7110
+ "secondary": "#hex",
7111
+ "accent": "#hex",
7112
+ "background": "#hex",
7113
+ "text": "#hex"
7114
+ },
7115
+ "sections": ${JSON.stringify(selectedTemplate.sections)},
7116
+ "content": {
7117
+ // Content for each section, tailored to the business
7118
+ },
7119
+ "features": ["responsive", "dark-mode", etc]
7120
+ }
7121
+
7122
+ Make the content specific and compelling for this business.`
7123
+ }]
7124
+ });
7125
+ spinner17.stop("Template customized!");
7126
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7127
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
7128
+ if (!jsonMatch) {
7129
+ p19.log.error("Failed to customize template");
7130
+ return null;
7131
+ }
7132
+ return JSON.parse(jsonMatch[0]);
7133
+ }
7134
+ async function cloneDesign(anthropic) {
7135
+ console.log(chalk20.dim("\n Describe a website design you like.\n"));
7136
+ console.log(chalk20.dim(" Examples:"));
7137
+ console.log(chalk20.dim(' \u2022 "Like Linear.app - minimal, clean, dark mode"'));
7138
+ console.log(chalk20.dim(' \u2022 "Like Stripe - professional, lots of gradients"'));
7139
+ console.log(chalk20.dim(' \u2022 "Like Notion - simple, friendly, illustrated"\n'));
7140
+ const inspiration = await textWithVoice({
7141
+ message: "What site do you want to be inspired by?",
7142
+ placeholder: "Like Linear.app but for..."
7143
+ });
7144
+ if (p19.isCancel(inspiration)) return null;
7145
+ const purpose = await p19.text({
7146
+ message: "What is YOUR website for?",
7147
+ placeholder: "A project management tool for designers"
7148
+ });
7149
+ if (p19.isCancel(purpose)) return null;
7150
+ const spinner17 = p19.spinner();
7151
+ spinner17.start("Analyzing design inspiration...");
7152
+ const response = await anthropic.messages.create({
7153
+ model: "claude-sonnet-4-20250514",
7154
+ max_tokens: 2048,
7155
+ messages: [{
7156
+ role: "user",
7157
+ content: `Create a website spec inspired by another site's design.
7158
+
7159
+ Inspiration: "${inspiration}"
7160
+ Purpose: "${purpose}"
7161
+
7162
+ Analyze the design style of the inspiration (based on your knowledge of popular websites) and create a spec that captures that aesthetic for this new purpose.
7163
+
7164
+ Return JSON only:
7165
+ {
7166
+ "name": "project-name-kebab",
7167
+ "description": "One line description",
7168
+ "style": "minimal | bold | elegant | playful | corporate",
7169
+ "colorScheme": {
7170
+ "primary": "#hex - inspired by the reference",
7171
+ "secondary": "#hex",
7172
+ "accent": "#hex",
7173
+ "background": "#hex",
7174
+ "text": "#hex"
7175
+ },
7176
+ "sections": ["appropriate sections for the purpose"],
7177
+ "content": {
7178
+ // Compelling content for each section
7179
+ },
7180
+ "features": ["responsive", "dark-mode", "animations", "gradients", etc - based on inspiration]
7181
+ }
7182
+
7183
+ Capture the FEEL of the inspiration but make it original.`
7184
+ }]
7185
+ });
7186
+ spinner17.stop("Design analyzed!");
7187
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7188
+ const jsonMatch = text16.match(/\{[\s\S]*\}/);
7189
+ if (!jsonMatch) {
7190
+ p19.log.error("Failed to analyze design");
7191
+ return null;
7192
+ }
7193
+ return JSON.parse(jsonMatch[0]);
7194
+ }
7195
+ async function buildWebsite(anthropic, spec, config) {
7196
+ const projectPath = path15.join(process.cwd(), spec.name);
7197
+ if (await fs16.pathExists(projectPath)) {
7198
+ const overwrite = await p19.confirm({
7199
+ message: `${spec.name} already exists. Overwrite?`,
7200
+ initialValue: false
7201
+ });
7202
+ if (!overwrite || p19.isCancel(overwrite)) return;
7203
+ await fs16.remove(projectPath);
7204
+ }
7205
+ console.log(chalk20.cyan(`
7206
+ \u{1F3D7}\uFE0F Building ${spec.name}...
7207
+ `));
7208
+ const spinner17 = p19.spinner();
7209
+ spinner17.start("Creating Next.js project...");
7210
+ await execa11("npx", [
7211
+ "create-next-app@latest",
7212
+ spec.name,
7213
+ "--typescript",
7214
+ "--tailwind",
7215
+ "--eslint",
7216
+ "--app",
7217
+ "--src-dir",
7218
+ "--import-alias",
7219
+ "@/*",
7220
+ "--no-git"
7221
+ ], { cwd: process.cwd(), reject: false });
7222
+ spinner17.stop("Project created");
7223
+ spinner17.start("Setting up shadcn/ui...");
7224
+ await execa11("npx", ["shadcn@latest", "init", "-y", "-d"], {
7225
+ cwd: projectPath,
7226
+ reject: false
7227
+ });
7228
+ await execa11("npx", ["shadcn@latest", "add", "button", "card", "input", "badge", "-y"], {
7229
+ cwd: projectPath,
7230
+ reject: false
7231
+ });
7232
+ spinner17.stop("UI components ready");
7233
+ spinner17.start("Generating website code...");
7234
+ const response = await anthropic.messages.create({
7235
+ model: "claude-sonnet-4-20250514",
7236
+ max_tokens: 16e3,
7237
+ messages: [{
7238
+ role: "user",
7239
+ content: `Generate a complete Next.js website based on this specification.
7240
+
7241
+ Website Spec:
7242
+ ${JSON.stringify(spec, null, 2)}
7243
+
7244
+ Generate these files:
7245
+
7246
+ 1. src/app/page.tsx - Main landing page with ALL sections
7247
+ 2. src/app/layout.tsx - Root layout with fonts, metadata
7248
+ 3. src/app/globals.css - Tailwind config with custom colors
7249
+ 4. src/components/sections/Hero.tsx
7250
+ 5. src/components/sections/[OtherSections].tsx - One for each section
7251
+ 6. src/components/ui/Navbar.tsx
7252
+ 7. src/components/ui/Footer.tsx
7253
+ 8. tailwind.config.ts - With custom colors from colorScheme
7254
+
7255
+ Requirements:
7256
+ - Use TypeScript
7257
+ - Use Tailwind CSS for styling
7258
+ - Use shadcn/ui components (Button, Card, Input, Badge)
7259
+ - Make it responsive (mobile-first)
7260
+ - Add smooth scroll behavior
7261
+ - Use modern design patterns
7262
+ - Include hover effects and transitions
7263
+ - Use Lucide icons where appropriate
7264
+
7265
+ Color scheme to use:
7266
+ - Primary: ${spec.colorScheme.primary}
7267
+ - Secondary: ${spec.colorScheme.secondary}
7268
+ - Accent: ${spec.colorScheme.accent}
7269
+ - Background: ${spec.colorScheme.background}
7270
+ - Text: ${spec.colorScheme.text}
7271
+
7272
+ Style: ${spec.style}
7273
+ ${spec.style === "minimal" ? "Clean, lots of whitespace, simple" : ""}
7274
+ ${spec.style === "bold" ? "Strong colors, big typography, impactful" : ""}
7275
+ ${spec.style === "elegant" ? "Refined, sophisticated, subtle animations" : ""}
7276
+ ${spec.style === "playful" ? "Fun, colorful, friendly illustrations" : ""}
7277
+ ${spec.style === "corporate" ? "Professional, trustworthy, structured" : ""}
7278
+
7279
+ Output format:
7280
+ <<<FILE: path/to/file.tsx>>>
7281
+ content
7282
+ <<<END_FILE>>>
7283
+
7284
+ Make it production-quality and visually impressive.`
7285
+ }]
7286
+ });
7287
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7288
+ const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
7289
+ let match;
7290
+ let fileCount = 0;
7291
+ while ((match = fileRegex.exec(text16)) !== null) {
7292
+ const filePath = path15.join(projectPath, match[1].trim());
7293
+ const content = match[2].trim();
7294
+ await fs16.ensureDir(path15.dirname(filePath));
7295
+ await fs16.writeFile(filePath, content);
7296
+ fileCount++;
7297
+ }
7298
+ spinner17.stop(`Generated ${fileCount} files`);
7299
+ spinner17.start("Initializing git...");
7300
+ await execa11("git", ["init"], { cwd: projectPath, reject: false });
7301
+ await execa11("git", ["add", "."], { cwd: projectPath, reject: false });
7302
+ await execa11("git", ["commit", "-m", "Initial website build by CodeBakers"], { cwd: projectPath, reject: false });
7303
+ spinner17.stop("Git initialized");
7304
+ console.log(chalk20.green(`
7305
+ \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
+ \u2551 \u2705 Website built successfully! \u2551
7307
+ \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
7308
+ \u2551 \u2551
7309
+ \u2551 ${spec.name.padEnd(55)}\u2551
7310
+ \u2551 ${spec.description.substring(0, 55).padEnd(55)}\u2551
7311
+ \u2551 \u2551
7312
+ \u2551 Next steps: \u2551
7313
+ \u2551 cd ${spec.name.padEnd(52)}\u2551
7314
+ \u2551 npm run dev \u2551
7315
+ \u2551 \u2551
7316
+ \u2551 Then open http://localhost:3000 \u2551
7317
+ \u2551 \u2551
7318
+ \u2551 Ready to deploy? \u2551
7319
+ \u2551 codebakers deploy \u2551
7320
+ \u2551 \u2551
7321
+ \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
7322
+ `));
7323
+ const openDev = await p19.confirm({
7324
+ message: "Start development server now?",
7325
+ initialValue: true
7326
+ });
7327
+ if (openDev && !p19.isCancel(openDev)) {
7328
+ console.log(chalk20.dim("\n Starting dev server...\n"));
7329
+ process.chdir(projectPath);
7330
+ await execa11("npm", ["run", "dev"], {
7331
+ stdio: "inherit",
7332
+ reject: false
7333
+ });
7334
+ }
7335
+ }
7336
+
7337
+ // src/utils/nlp.ts
7338
+ import * as p20 from "@clack/prompts";
7339
+ import Anthropic6 from "@anthropic-ai/sdk";
5966
7340
  var COMMAND_PATTERNS = {
5967
7341
  init: {
5968
7342
  patterns: ["new project", "create app", "start project", "initialize", "new app", "start building", "create new"],
@@ -6031,7 +7405,7 @@ async function parseNaturalLanguage(input, config) {
6031
7405
  if (!anthropicCreds?.apiKey) {
6032
7406
  return quickMatch;
6033
7407
  }
6034
- const anthropic = new Anthropic5({ apiKey: anthropicCreds.apiKey });
7408
+ const anthropic = new Anthropic6({ apiKey: anthropicCreds.apiKey });
6035
7409
  try {
6036
7410
  const result = await parseWithAI(anthropic, input);
6037
7411
  return result;
@@ -6083,7 +7457,7 @@ function extractArgs(input, pattern) {
6083
7457
  const patternWords = pattern.toLowerCase().split(/\s+/);
6084
7458
  const inputWords = input.split(/\s+/);
6085
7459
  const args2 = inputWords.filter(
6086
- (word) => !patternWords.some((p20) => word.toLowerCase().includes(p20) || p20.includes(word.toLowerCase()))
7460
+ (word) => !patternWords.some((p22) => word.toLowerCase().includes(p22) || p22.includes(word.toLowerCase()))
6087
7461
  );
6088
7462
  return args2.filter((a) => a.length > 2);
6089
7463
  }
@@ -6114,8 +7488,8 @@ If the input is asking to build/create/add a specific feature, use "code" and pu
6114
7488
  If unclear between multiple commands, use the most likely one with lower confidence.`
6115
7489
  }]
6116
7490
  });
6117
- const text14 = response.content[0].type === "text" ? response.content[0].text : "";
6118
- const jsonMatch = text14.match(/\{[\s\S]*?\}/);
7491
+ const text16 = response.content[0].type === "text" ? response.content[0].text : "";
7492
+ const jsonMatch = text16.match(/\{[\s\S]*?\}/);
6119
7493
  if (!jsonMatch) {
6120
7494
  return {
6121
7495
  command: "code",
@@ -6131,11 +7505,11 @@ async function clarifyCommand(parsed) {
6131
7505
  return parsed;
6132
7506
  }
6133
7507
  if (parsed.confidence >= 0.5) {
6134
- const confirm11 = await p18.confirm({
7508
+ const confirm12 = await p20.confirm({
6135
7509
  message: `Did you mean: ${parsed.interpretation}?`,
6136
7510
  initialValue: true
6137
7511
  });
6138
- if (confirm11 && !p18.isCancel(confirm11)) {
7512
+ if (confirm12 && !p20.isCancel(confirm12)) {
6139
7513
  return { ...parsed, confidence: 1 };
6140
7514
  }
6141
7515
  }
@@ -6150,19 +7524,19 @@ async function clarifyCommand(parsed) {
6150
7524
  { value: "prd-maker", label: "\u{1F4DD} Create PRD", description: "Document your idea" },
6151
7525
  { value: "other", label: "\u2753 Something else", description: "Describe what you need" }
6152
7526
  ];
6153
- const selection = await p18.select({
7527
+ const selection = await p20.select({
6154
7528
  message: "What would you like to do?",
6155
7529
  options: options.map((o) => ({ value: o.value, label: o.label, hint: o.description }))
6156
7530
  });
6157
- if (p18.isCancel(selection)) {
7531
+ if (p20.isCancel(selection)) {
6158
7532
  return { ...parsed, command: "cancel", confidence: 1 };
6159
7533
  }
6160
7534
  if (selection === "other") {
6161
- const description = await p18.text({
7535
+ const description = await p20.text({
6162
7536
  message: "Describe what you want to do:",
6163
7537
  placeholder: "I want to..."
6164
7538
  });
6165
- if (p18.isCancel(description)) {
7539
+ if (p20.isCancel(description)) {
6166
7540
  return { ...parsed, command: "cancel", confidence: 1 };
6167
7541
  }
6168
7542
  return {
@@ -6180,19 +7554,19 @@ async function clarifyCommand(parsed) {
6180
7554
  };
6181
7555
  }
6182
7556
  async function clarifyDeployTarget() {
6183
- const target = await p18.select({
7557
+ const target = await p20.select({
6184
7558
  message: "Where do you want to deploy?",
6185
7559
  options: [
6186
7560
  { value: "preview", label: "\u{1F50D} Preview", hint: "Test URL to review changes" },
6187
7561
  { value: "production", label: "\u{1F680} Production", hint: "Live site for users" }
6188
7562
  ]
6189
7563
  });
6190
- if (p18.isCancel(target)) return null;
7564
+ if (p20.isCancel(target)) return null;
6191
7565
  return target;
6192
7566
  }
6193
7567
 
6194
7568
  // src/index.ts
6195
- var VERSION2 = "2.0.9";
7569
+ var VERSION2 = "2.1.1";
6196
7570
  var logo = `
6197
7571
  \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
6198
7572
  \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
@@ -6205,21 +7579,22 @@ async function showMainMenu() {
6205
7579
  const config = new Config();
6206
7580
  const isSetup = config.isConfigured();
6207
7581
  console.log(gradient.pastel.multiline(logo));
6208
- console.log(chalk19.dim(` v${VERSION2} \u2014 AI dev team that follows the rules
7582
+ console.log(chalk21.dim(` v${VERSION2} \u2014 AI dev team that follows the rules
6209
7583
  `));
6210
7584
  if (!isSetup) {
6211
7585
  console.log(boxen(
6212
- chalk19.yellow("Welcome to CodeBakers! Let's get you set up."),
7586
+ chalk21.yellow("Welcome to CodeBakers! Let's get you set up."),
6213
7587
  { padding: 1, borderColor: "yellow", borderStyle: "round" }
6214
7588
  ));
6215
7589
  await setupCommand();
6216
7590
  return;
6217
7591
  }
6218
7592
  const inProject = config.isInProject();
6219
- const action = await p19.select({
7593
+ const action = await p21.select({
6220
7594
  message: "What do you want to do? (or type naturally)",
6221
7595
  options: inProject ? [
6222
7596
  { value: "code", label: "\u{1F4AC} Code with AI", hint: "build features, fix bugs" },
7597
+ { value: "integrate", label: "\u{1F50C} One-Click Integrations", hint: "50+ services, browser auth" },
6223
7598
  { value: "check", label: "\u{1F50D} Check code quality", hint: "run pattern enforcement" },
6224
7599
  { value: "deploy", label: "\u{1F680} Deploy", hint: "deploy to Vercel" },
6225
7600
  { value: "migrate", label: "\u{1F5C4}\uFE0F Database migrations", hint: "push, generate, status" },
@@ -6234,6 +7609,7 @@ async function showMainMenu() {
6234
7609
  { value: "design", label: "\u{1F3A8} Design system", hint: "set profile, colors" },
6235
7610
  { value: "separator2", label: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" },
6236
7611
  { value: "new", label: "\u{1F195} Create new project" },
7612
+ { value: "website", label: "\u{1F310} Website Builder", hint: "Describe \u2192 AI builds it" },
6237
7613
  { value: "build", label: "\u{1F3D7}\uFE0F Parallel Build", hint: "3 agents from PRD (swarm)" },
6238
7614
  { value: "prd", label: "\u{1F4C4} Build from PRD", hint: "sequential build" },
6239
7615
  { value: "prd-maker", label: "\u270F\uFE0F Create PRD", hint: "interview \u2192 generate PRD" },
@@ -6242,6 +7618,7 @@ async function showMainMenu() {
6242
7618
  { value: "help", label: "\u2753 Help" }
6243
7619
  ] : [
6244
7620
  { value: "new", label: "\u{1F195} Create new project" },
7621
+ { value: "website", label: "\u{1F310} Website Builder", hint: "Describe \u2192 AI builds it" },
6245
7622
  { value: "build", label: "\u{1F3D7}\uFE0F Parallel Build", hint: "3 agents from PRD (swarm)" },
6246
7623
  { value: "prd", label: "\u{1F4C4} Build from PRD", hint: "sequential build" },
6247
7624
  { value: "prd-maker", label: "\u270F\uFE0F Create PRD", hint: "interview \u2192 generate PRD" },
@@ -6256,14 +7633,17 @@ async function showMainMenu() {
6256
7633
  { value: "help", label: "\u2753 Help" }
6257
7634
  ]
6258
7635
  });
6259
- if (p19.isCancel(action)) {
6260
- p19.cancel("Goodbye!");
7636
+ if (p21.isCancel(action)) {
7637
+ p21.cancel("Goodbye!");
6261
7638
  process.exit(0);
6262
7639
  }
6263
7640
  switch (action) {
6264
7641
  case "code":
6265
7642
  await codeCommand();
6266
7643
  break;
7644
+ case "integrate":
7645
+ await integrateCommand();
7646
+ break;
6267
7647
  case "check":
6268
7648
  await checkCommand();
6269
7649
  break;
@@ -6300,6 +7680,9 @@ async function showMainMenu() {
6300
7680
  case "new":
6301
7681
  await initCommand();
6302
7682
  break;
7683
+ case "website":
7684
+ await websiteCommand();
7685
+ break;
6303
7686
  case "build":
6304
7687
  await buildCommand();
6305
7688
  break;
@@ -6324,27 +7707,27 @@ async function showMainMenu() {
6324
7707
  }
6325
7708
  function showHelp2() {
6326
7709
  console.log(boxen(`
6327
- ${chalk19.bold("CodeBakers CLI")} \u2014 AI dev team that follows the rules
6328
-
6329
- ${chalk19.bold("Commands:")}
6330
- ${chalk19.cyan("codebakers")} Interactive menu (or just run with no args)
6331
- ${chalk19.cyan("codebakers init")} Create a new project
6332
- ${chalk19.cyan("codebakers code")} Start AI coding session
6333
- ${chalk19.cyan("codebakers check")} Run pattern enforcement
6334
- ${chalk19.cyan("codebakers deploy")} Deploy to production
6335
- ${chalk19.cyan("codebakers fix")} Auto-fix errors
6336
- ${chalk19.cyan("codebakers generate")} Generate components/pages
6337
- ${chalk19.cyan("codebakers connect")} Connect external services
6338
- ${chalk19.cyan("codebakers gateway")} Manage messaging channels
6339
- ${chalk19.cyan("codebakers status")} View project status
6340
- ${chalk19.cyan("codebakers security")} Run security audit
6341
- ${chalk19.cyan("codebakers learn")} View/manage learning
6342
-
6343
- ${chalk19.bold("Help at any time:")}
6344
- Press ${chalk19.yellow("?")} during any command to get contextual help
6345
-
6346
- ${chalk19.bold("Documentation:")}
6347
- ${chalk19.dim("https://codebakers.dev/docs")}
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")}
6348
7731
  `, { padding: 1, borderColor: "cyan", borderStyle: "round" }));
6349
7732
  }
6350
7733
  var program = new Command();
@@ -6367,9 +7750,11 @@ program.command("advisors").alias("dream-team").description("Consult with the Co
6367
7750
  program.command("migrate").alias("db").description("Database migrations (push, generate, status)").option("--push", "Push migrations to database").option("--generate", "Generate new migration").option("--status", "Check migration status").action(migrateCommand);
6368
7751
  program.command("prd-maker").alias("create-prd").description("Create a PRD through guided interview (supports voice input)").action(prdMakerCommand);
6369
7752
  program.command("build [prd-file]").alias("swarm").description("Parallel build with 3 AI agents (self-healing)").option("--sequential", "Disable parallel execution").action(buildCommand);
7753
+ program.command("integrate [integration]").alias("add").description("One-click integrations (50+ services with browser auth)").action(integrateCommand);
7754
+ program.command("website").alias("site").description("Build a website by describing it in plain English").action(websiteCommand);
6370
7755
  async function handleNaturalLanguage(input) {
6371
7756
  const config = new Config();
6372
- console.log(chalk19.dim("\n Parsing your request...\n"));
7757
+ console.log(chalk21.dim("\n Parsing your request...\n"));
6373
7758
  const parsed = await parseNaturalLanguage(input, config);
6374
7759
  if (!parsed) {
6375
7760
  await codeCommand(input);
@@ -6455,6 +7840,10 @@ if (args.length === 0) {
6455
7840
  "prd-maker",
6456
7841
  "build",
6457
7842
  "swarm",
7843
+ "integrate",
7844
+ "add",
7845
+ "website",
7846
+ "site",
6458
7847
  "help"
6459
7848
  ];
6460
7849
  const firstArg = args[0];