codebakers 1.0.45 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +275 -60
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +3999 -0
  4. package/install.bat +9 -0
  5. package/package.json +71 -115
  6. package/src/channels/discord.ts +5 -0
  7. package/src/channels/slack.ts +5 -0
  8. package/src/channels/sms.ts +4 -0
  9. package/src/channels/telegram.ts +5 -0
  10. package/src/channels/whatsapp.ts +7 -0
  11. package/src/commands/check.ts +365 -0
  12. package/src/commands/code.ts +684 -0
  13. package/src/commands/connect.ts +12 -0
  14. package/src/commands/deploy.ts +414 -0
  15. package/src/commands/fix.ts +20 -0
  16. package/src/commands/gateway.ts +604 -0
  17. package/src/commands/generate.ts +178 -0
  18. package/src/commands/init.ts +574 -0
  19. package/src/commands/learn.ts +36 -0
  20. package/src/commands/security.ts +102 -0
  21. package/src/commands/setup.ts +448 -0
  22. package/src/commands/status.ts +56 -0
  23. package/src/index.ts +268 -0
  24. package/src/patterns/loader.ts +337 -0
  25. package/src/services/github.ts +61 -0
  26. package/src/services/supabase.ts +147 -0
  27. package/src/services/vercel.ts +61 -0
  28. package/src/utils/claude-md.ts +287 -0
  29. package/src/utils/config.ts +282 -0
  30. package/src/utils/updates.ts +27 -0
  31. package/tsconfig.json +17 -10
  32. package/.vscodeignore +0 -18
  33. package/LICENSE +0 -21
  34. package/codebakers-1.0.0.vsix +0 -0
  35. package/codebakers-1.0.10.vsix +0 -0
  36. package/codebakers-1.0.11.vsix +0 -0
  37. package/codebakers-1.0.12.vsix +0 -0
  38. package/codebakers-1.0.13.vsix +0 -0
  39. package/codebakers-1.0.14.vsix +0 -0
  40. package/codebakers-1.0.15.vsix +0 -0
  41. package/codebakers-1.0.16.vsix +0 -0
  42. package/codebakers-1.0.17.vsix +0 -0
  43. package/codebakers-1.0.18.vsix +0 -0
  44. package/codebakers-1.0.19.vsix +0 -0
  45. package/codebakers-1.0.20.vsix +0 -0
  46. package/codebakers-1.0.21.vsix +0 -0
  47. package/codebakers-1.0.22.vsix +0 -0
  48. package/codebakers-1.0.23.vsix +0 -0
  49. package/codebakers-1.0.24.vsix +0 -0
  50. package/codebakers-1.0.25.vsix +0 -0
  51. package/codebakers-1.0.26.vsix +0 -0
  52. package/codebakers-1.0.27.vsix +0 -0
  53. package/codebakers-1.0.28.vsix +0 -0
  54. package/codebakers-1.0.29.vsix +0 -0
  55. package/codebakers-1.0.30.vsix +0 -0
  56. package/codebakers-1.0.31.vsix +0 -0
  57. package/codebakers-1.0.32.vsix +0 -0
  58. package/codebakers-1.0.35.vsix +0 -0
  59. package/codebakers-1.0.36.vsix +0 -0
  60. package/codebakers-1.0.37.vsix +0 -0
  61. package/codebakers-1.0.38.vsix +0 -0
  62. package/codebakers-1.0.39.vsix +0 -0
  63. package/codebakers-1.0.40.vsix +0 -0
  64. package/codebakers-1.0.41.vsix +0 -0
  65. package/codebakers-1.0.42.vsix +0 -0
  66. package/codebakers-1.0.43.vsix +0 -0
  67. package/codebakers-1.0.44.vsix +0 -0
  68. package/codebakers-1.0.45.vsix +0 -0
  69. package/dist/extension.js +0 -1394
  70. package/esbuild.js +0 -63
  71. package/media/icon.png +0 -0
  72. package/media/icon.svg +0 -7
  73. package/nul +0 -1
  74. package/preview.html +0 -547
  75. package/src/ChatPanelProvider.ts +0 -1815
  76. package/src/ChatViewProvider.ts +0 -749
  77. package/src/CodeBakersClient.ts +0 -1146
  78. package/src/CodeValidator.ts +0 -645
  79. package/src/FileOperations.ts +0 -410
  80. package/src/ProjectContext.ts +0 -526
  81. package/src/extension.ts +0 -332
package/dist/index.js ADDED
@@ -0,0 +1,3999 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import * as p12 from "@clack/prompts";
6
+ import chalk13 from "chalk";
7
+ import boxen from "boxen";
8
+ import gradient from "gradient-string";
9
+
10
+ // src/utils/config.ts
11
+ import Conf from "conf";
12
+ import * as fs from "fs-extra";
13
+ import * as path from "path";
14
+ import os from "os";
15
+ import { z } from "zod";
16
+ var ConfigSchema = z.object({
17
+ version: z.string().default("1.0.0"),
18
+ credentials: z.object({
19
+ github: z.object({
20
+ token: z.string().optional(),
21
+ username: z.string().optional()
22
+ }).optional(),
23
+ vercel: z.object({
24
+ token: z.string().optional(),
25
+ teamId: z.string().optional()
26
+ }).optional(),
27
+ supabase: z.object({
28
+ accessToken: z.string().optional(),
29
+ orgId: z.string().optional()
30
+ }).optional(),
31
+ anthropic: z.object({
32
+ apiKey: z.string().optional()
33
+ }).optional(),
34
+ openai: z.object({
35
+ apiKey: z.string().optional()
36
+ }).optional(),
37
+ stripe: z.object({
38
+ secretKey: z.string().optional(),
39
+ publishableKey: z.string().optional(),
40
+ webhookSecret: z.string().optional()
41
+ }).optional(),
42
+ twilio: z.object({
43
+ accountSid: z.string().optional(),
44
+ authToken: z.string().optional(),
45
+ phoneNumber: z.string().optional()
46
+ }).optional(),
47
+ vapi: z.object({
48
+ apiKey: z.string().optional()
49
+ }).optional(),
50
+ resend: z.object({
51
+ apiKey: z.string().optional()
52
+ }).optional(),
53
+ elevenLabs: z.object({
54
+ apiKey: z.string().optional()
55
+ }).optional(),
56
+ microsoft: z.object({
57
+ clientId: z.string().optional(),
58
+ clientSecret: z.string().optional(),
59
+ tenantId: z.string().optional()
60
+ }).optional(),
61
+ google: z.object({
62
+ clientId: z.string().optional(),
63
+ clientSecret: z.string().optional()
64
+ }).optional()
65
+ }).default({}),
66
+ preferences: z.object({
67
+ defaultFramework: z.string().optional(),
68
+ defaultUI: z.string().optional(),
69
+ defaultPackages: z.array(z.string()).optional(),
70
+ deployToPreviewFirst: z.boolean().default(true)
71
+ }).default({}),
72
+ learning: z.object({
73
+ enabled: z.boolean().default(true),
74
+ shortcuts: z.record(z.string()).default({}),
75
+ preferences: z.record(z.any()).default({}),
76
+ rejections: z.array(z.string()).default([]),
77
+ workflows: z.record(z.array(z.string())).default({})
78
+ }).default({}),
79
+ projects: z.array(z.object({
80
+ name: z.string(),
81
+ path: z.string(),
82
+ github: z.string().optional(),
83
+ vercel: z.string().optional(),
84
+ supabase: z.string().optional(),
85
+ createdAt: z.string()
86
+ })).default([]),
87
+ channels: z.object({
88
+ whatsapp: z.object({
89
+ enabled: z.boolean().default(false),
90
+ phoneNumber: z.string().optional()
91
+ }).optional(),
92
+ telegram: z.object({
93
+ enabled: z.boolean().default(false),
94
+ botToken: z.string().optional()
95
+ }).optional(),
96
+ discord: z.object({
97
+ enabled: z.boolean().default(false),
98
+ botToken: z.string().optional()
99
+ }).optional(),
100
+ slack: z.object({
101
+ enabled: z.boolean().default(false),
102
+ botToken: z.string().optional()
103
+ }).optional()
104
+ }).default({})
105
+ });
106
+ var Config = class {
107
+ conf;
108
+ configDir;
109
+ constructor() {
110
+ this.configDir = path.join(os.homedir(), ".codebakers");
111
+ fs.ensureDirSync(this.configDir);
112
+ fs.ensureDirSync(path.join(this.configDir, "patterns"));
113
+ fs.ensureDirSync(path.join(this.configDir, "templates"));
114
+ fs.ensureDirSync(path.join(this.configDir, "learning"));
115
+ this.conf = new Conf({
116
+ projectName: "codebakers",
117
+ cwd: this.configDir,
118
+ schema: {
119
+ version: { type: "string", default: "1.0.0" },
120
+ credentials: { type: "object", default: {} },
121
+ preferences: { type: "object", default: {} },
122
+ learning: { type: "object", default: { enabled: true, shortcuts: {}, preferences: {}, rejections: [], workflows: {} } },
123
+ projects: { type: "array", default: [] },
124
+ channels: { type: "object", default: {} }
125
+ }
126
+ });
127
+ }
128
+ // Check if initial setup is complete
129
+ isConfigured() {
130
+ const creds = this.conf.get("credentials") || {};
131
+ return !!(creds.github?.token && creds.vercel?.token && creds.supabase?.accessToken && creds.anthropic?.apiKey);
132
+ }
133
+ // Check if current directory is a CodeBakers project
134
+ isInProject() {
135
+ const cwd = process.cwd();
136
+ const codebakersDir = path.join(cwd, ".codebakers");
137
+ const claudeFile = path.join(cwd, "CLAUDE.md");
138
+ return fs.existsSync(codebakersDir) || fs.existsSync(claudeFile);
139
+ }
140
+ // Get current project config
141
+ getProjectConfig() {
142
+ const cwd = process.cwd();
143
+ const configPath = path.join(cwd, ".codebakers", "config.json");
144
+ if (fs.existsSync(configPath)) {
145
+ return fs.readJsonSync(configPath);
146
+ }
147
+ return null;
148
+ }
149
+ // Credentials
150
+ getCredentials(service) {
151
+ const creds = this.conf.get("credentials") || {};
152
+ return creds[service];
153
+ }
154
+ setCredentials(service, credentials) {
155
+ const current = this.conf.get("credentials") || {};
156
+ this.conf.set("credentials", {
157
+ ...current,
158
+ [service]: { ...current[service], ...credentials }
159
+ });
160
+ }
161
+ // Preferences
162
+ getPreference(key) {
163
+ const prefs = this.conf.get("preferences") || {};
164
+ return prefs[key];
165
+ }
166
+ setPreference(key, value) {
167
+ const current = this.conf.get("preferences") || {};
168
+ this.conf.set("preferences", { ...current, [key]: value });
169
+ }
170
+ // Learning
171
+ getLearning() {
172
+ return this.conf.get("learning") || { enabled: true, shortcuts: {}, preferences: {}, rejections: [], workflows: {} };
173
+ }
174
+ addShortcut(shortcut, expansion) {
175
+ const learning = this.getLearning();
176
+ learning.shortcuts[shortcut] = expansion;
177
+ this.conf.set("learning", learning);
178
+ }
179
+ addRejection(item) {
180
+ const learning = this.getLearning();
181
+ if (!learning.rejections.includes(item)) {
182
+ learning.rejections.push(item);
183
+ this.conf.set("learning", learning);
184
+ }
185
+ }
186
+ addWorkflow(name, steps) {
187
+ const learning = this.getLearning();
188
+ learning.workflows[name] = steps;
189
+ this.conf.set("learning", learning);
190
+ }
191
+ learnPreference(key, value) {
192
+ const learning = this.getLearning();
193
+ learning.preferences[key] = value;
194
+ this.conf.set("learning", learning);
195
+ }
196
+ forgetPreference(key) {
197
+ const learning = this.getLearning();
198
+ delete learning.preferences[key];
199
+ this.conf.set("learning", learning);
200
+ }
201
+ resetLearning() {
202
+ this.conf.set("learning", { enabled: true, shortcuts: {}, preferences: {}, rejections: [], workflows: {} });
203
+ }
204
+ // Projects
205
+ getProjects() {
206
+ return this.conf.get("projects") || [];
207
+ }
208
+ addProject(project) {
209
+ const projects = this.getProjects();
210
+ projects.push(project);
211
+ this.conf.set("projects", projects);
212
+ }
213
+ // Channels
214
+ getChannelConfig(channel) {
215
+ const channels = this.conf.get("channels") || {};
216
+ return channels[channel];
217
+ }
218
+ setChannelConfig(channel, config) {
219
+ const current = this.conf.get("channels") || {};
220
+ this.conf.set("channels", { ...current, [channel]: config });
221
+ }
222
+ // Config directory access
223
+ getConfigDir() {
224
+ return this.configDir;
225
+ }
226
+ getPatternsDir() {
227
+ return path.join(this.configDir, "patterns");
228
+ }
229
+ getTemplatesDir() {
230
+ return path.join(this.configDir, "templates");
231
+ }
232
+ getLearningDir() {
233
+ return path.join(this.configDir, "learning");
234
+ }
235
+ // Export/Import
236
+ exportConfig() {
237
+ return {
238
+ version: this.conf.get("version") || "1.0.0",
239
+ credentials: {},
240
+ // Don't export credentials
241
+ preferences: this.conf.get("preferences") || {},
242
+ learning: this.getLearning(),
243
+ projects: this.getProjects(),
244
+ channels: {}
245
+ // Don't export channel tokens
246
+ };
247
+ }
248
+ importConfig(config) {
249
+ if (config.preferences) {
250
+ this.conf.set("preferences", config.preferences);
251
+ }
252
+ if (config.learning) {
253
+ this.conf.set("learning", config.learning);
254
+ }
255
+ }
256
+ };
257
+
258
+ // src/utils/updates.ts
259
+ import chalk from "chalk";
260
+ var VERSION = "1.0.0";
261
+ async function checkForUpdates() {
262
+ try {
263
+ const response = await fetch("https://registry.npmjs.org/codebakers/latest", {
264
+ signal: AbortSignal.timeout(3e3)
265
+ });
266
+ if (!response.ok) return;
267
+ const data = await response.json();
268
+ const latestVersion = data.version;
269
+ if (latestVersion && latestVersion !== VERSION) {
270
+ console.log(chalk.yellow(`
271
+ \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E
272
+ \u2502 Update available: ${VERSION} \u2192 ${latestVersion.padEnd(10)} \u2502
273
+ \u2502 Run: npm install -g codebakers \u2502
274
+ \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F
275
+ `));
276
+ }
277
+ } catch {
278
+ }
279
+ }
280
+
281
+ // src/commands/setup.ts
282
+ import * as p from "@clack/prompts";
283
+ import chalk2 from "chalk";
284
+ import open from "open";
285
+ async function setupCommand() {
286
+ const config = new Config();
287
+ p.intro(chalk2.bgCyan.black(" CodeBakers Setup "));
288
+ if (config.isConfigured()) {
289
+ const action = await p.select({
290
+ message: "CodeBakers is already configured. What do you want to do?",
291
+ options: [
292
+ { value: "view", label: "\u{1F440} View connected services" },
293
+ { value: "add", label: "\u2795 Add another service" },
294
+ { value: "update", label: "\u{1F504} Update credentials" },
295
+ { value: "reset", label: "\u{1F5D1}\uFE0F Reset all configuration" },
296
+ { value: "back", label: "\u2190 Back" }
297
+ ]
298
+ });
299
+ if (p.isCancel(action) || action === "back") {
300
+ return;
301
+ }
302
+ if (action === "view") {
303
+ showConnectedServices(config);
304
+ return;
305
+ }
306
+ if (action === "reset") {
307
+ const confirm6 = await p.confirm({
308
+ message: "Are you sure? This will remove all credentials and settings."
309
+ });
310
+ if (confirm6) {
311
+ p.outro(chalk2.yellow("Configuration reset. Run `codebakers setup` again."));
312
+ }
313
+ return;
314
+ }
315
+ if (action === "add") {
316
+ await addService(config);
317
+ return;
318
+ }
319
+ }
320
+ p.note(
321
+ `Let's connect your accounts. This only takes a few minutes.
322
+
323
+ ${chalk2.dim("Your credentials are stored locally in ~/.codebakers/")}
324
+ ${chalk2.dim("and are never sent to our servers.")}`,
325
+ "Welcome to CodeBakers!"
326
+ );
327
+ const requiredServices = [
328
+ { name: "GitHub", key: "github", required: true },
329
+ { name: "Vercel", key: "vercel", required: true },
330
+ { name: "Supabase", key: "supabase", required: true },
331
+ { name: "Anthropic (Claude)", key: "anthropic", required: true }
332
+ ];
333
+ const optionalServices = [
334
+ { name: "OpenAI", key: "openai" },
335
+ { name: "Stripe", key: "stripe" },
336
+ { name: "Twilio", key: "twilio" },
337
+ { name: "VAPI", key: "vapi" },
338
+ { name: "Resend", key: "resend" },
339
+ { name: "ElevenLabs", key: "elevenLabs" },
340
+ { name: "Microsoft Graph", key: "microsoft" },
341
+ { name: "Google APIs", key: "google" }
342
+ ];
343
+ p.log.step("Connecting required services...");
344
+ for (const service of requiredServices) {
345
+ await connectService(config, service.key, service.name, true);
346
+ }
347
+ const addOptional = await p.confirm({
348
+ message: "Do you want to connect optional services? (Stripe, Twilio, etc.)",
349
+ initialValue: false
350
+ });
351
+ if (addOptional && !p.isCancel(addOptional)) {
352
+ const selected = await p.multiselect({
353
+ message: "Select services to connect:",
354
+ options: optionalServices.map((s) => ({
355
+ value: s.key,
356
+ label: s.name
357
+ })),
358
+ required: false
359
+ });
360
+ if (!p.isCancel(selected)) {
361
+ for (const serviceKey of selected) {
362
+ const service = optionalServices.find((s) => s.key === serviceKey);
363
+ if (service) {
364
+ await connectService(config, service.key, service.name, false);
365
+ }
366
+ }
367
+ }
368
+ }
369
+ p.log.step("Installing CodeBakers patterns...");
370
+ await installPatterns(config);
371
+ p.outro(chalk2.green("\u2713 Setup complete! Run `codebakers init` to create your first project."));
372
+ }
373
+ async function connectService(config, serviceKey, serviceName, required) {
374
+ const spinner10 = p.spinner();
375
+ switch (serviceKey) {
376
+ case "github": {
377
+ p.log.info(`${chalk2.bold("GitHub")} - Opens browser for OAuth authorization`);
378
+ const proceed = await p.confirm({
379
+ message: "Open browser to authorize GitHub?",
380
+ initialValue: true
381
+ });
382
+ if (p.isCancel(proceed) || !proceed) {
383
+ if (required) {
384
+ p.log.warn("GitHub is required. Skipping for now.");
385
+ }
386
+ return false;
387
+ }
388
+ p.log.info(chalk2.dim("Opening browser..."));
389
+ await open("https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&scope=repo,user");
390
+ const token = await p.text({
391
+ message: "Paste your GitHub token (or press Enter if OAuth completed):",
392
+ placeholder: "ghp_..."
393
+ });
394
+ if (!p.isCancel(token) && token) {
395
+ config.setCredentials("github", { token });
396
+ p.log.success("GitHub connected!");
397
+ return true;
398
+ }
399
+ break;
400
+ }
401
+ case "vercel": {
402
+ p.log.info(`${chalk2.bold("Vercel")} - Opens browser for OAuth authorization`);
403
+ const proceed = await p.confirm({
404
+ message: "Open browser to authorize Vercel?",
405
+ initialValue: true
406
+ });
407
+ if (p.isCancel(proceed) || !proceed) {
408
+ if (required) {
409
+ p.log.warn("Vercel is required. Skipping for now.");
410
+ }
411
+ return false;
412
+ }
413
+ p.log.info(chalk2.dim("Opening browser..."));
414
+ await open("https://vercel.com/account/tokens");
415
+ const token = await p.text({
416
+ message: "Paste your Vercel token:",
417
+ placeholder: "vercel_..."
418
+ });
419
+ if (!p.isCancel(token) && token) {
420
+ config.setCredentials("vercel", { token });
421
+ p.log.success("Vercel connected!");
422
+ return true;
423
+ }
424
+ break;
425
+ }
426
+ case "supabase": {
427
+ p.log.info(`${chalk2.bold("Supabase")} - Opens browser for OAuth authorization`);
428
+ const proceed = await p.confirm({
429
+ message: "Open browser to authorize Supabase?",
430
+ initialValue: true
431
+ });
432
+ if (p.isCancel(proceed) || !proceed) {
433
+ if (required) {
434
+ p.log.warn("Supabase is required. Skipping for now.");
435
+ }
436
+ return false;
437
+ }
438
+ p.log.info(chalk2.dim("Opening browser..."));
439
+ await open("https://supabase.com/dashboard/account/tokens");
440
+ const token = await p.text({
441
+ message: "Paste your Supabase access token:",
442
+ placeholder: "sbp_..."
443
+ });
444
+ if (!p.isCancel(token) && token) {
445
+ config.setCredentials("supabase", { accessToken: token });
446
+ p.log.success("Supabase connected!");
447
+ return true;
448
+ }
449
+ break;
450
+ }
451
+ case "anthropic": {
452
+ p.log.info(`${chalk2.bold("Anthropic (Claude)")} - Powers the AI coding agent`);
453
+ const openBrowser = await p.confirm({
454
+ message: "Open browser to get API key?",
455
+ initialValue: true
456
+ });
457
+ if (openBrowser && !p.isCancel(openBrowser)) {
458
+ await open("https://console.anthropic.com/settings/keys");
459
+ }
460
+ const apiKey = await p.text({
461
+ message: "Paste your Anthropic API key:",
462
+ placeholder: "sk-ant-...",
463
+ validate: (value) => {
464
+ if (!value && required) return "API key is required";
465
+ if (value && !value.startsWith("sk-ant-")) return "Invalid API key format";
466
+ return void 0;
467
+ }
468
+ });
469
+ if (!p.isCancel(apiKey) && apiKey) {
470
+ config.setCredentials("anthropic", { apiKey });
471
+ p.log.success("Anthropic connected!");
472
+ return true;
473
+ }
474
+ break;
475
+ }
476
+ case "openai": {
477
+ const openBrowser = await p.confirm({
478
+ message: "Open browser to get OpenAI API key?",
479
+ initialValue: true
480
+ });
481
+ if (openBrowser && !p.isCancel(openBrowser)) {
482
+ await open("https://platform.openai.com/api-keys");
483
+ }
484
+ const apiKey = await p.text({
485
+ message: "Paste your OpenAI API key:",
486
+ placeholder: "sk-..."
487
+ });
488
+ if (!p.isCancel(apiKey) && apiKey) {
489
+ config.setCredentials("openai", { apiKey });
490
+ p.log.success("OpenAI connected!");
491
+ return true;
492
+ }
493
+ break;
494
+ }
495
+ case "stripe": {
496
+ const openBrowser = await p.confirm({
497
+ message: "Open browser to get Stripe API keys?",
498
+ initialValue: true
499
+ });
500
+ if (openBrowser && !p.isCancel(openBrowser)) {
501
+ await open("https://dashboard.stripe.com/apikeys");
502
+ }
503
+ const secretKey = await p.text({
504
+ message: "Paste your Stripe secret key:",
505
+ placeholder: "sk_live_... or sk_test_..."
506
+ });
507
+ if (!p.isCancel(secretKey) && secretKey) {
508
+ config.setCredentials("stripe", { secretKey });
509
+ p.log.success("Stripe connected!");
510
+ return true;
511
+ }
512
+ break;
513
+ }
514
+ case "twilio": {
515
+ const openBrowser = await p.confirm({
516
+ message: "Open browser to get Twilio credentials?",
517
+ initialValue: true
518
+ });
519
+ if (openBrowser && !p.isCancel(openBrowser)) {
520
+ await open("https://console.twilio.com/");
521
+ }
522
+ const accountSid = await p.text({
523
+ message: "Paste your Twilio Account SID:",
524
+ placeholder: "AC..."
525
+ });
526
+ const authToken = await p.text({
527
+ message: "Paste your Twilio Auth Token:",
528
+ placeholder: "..."
529
+ });
530
+ if (!p.isCancel(accountSid) && !p.isCancel(authToken) && accountSid && authToken) {
531
+ config.setCredentials("twilio", {
532
+ accountSid,
533
+ authToken
534
+ });
535
+ p.log.success("Twilio connected!");
536
+ return true;
537
+ }
538
+ break;
539
+ }
540
+ case "vapi": {
541
+ const openBrowser = await p.confirm({
542
+ message: "Open browser to get VAPI API key?",
543
+ initialValue: true
544
+ });
545
+ if (openBrowser && !p.isCancel(openBrowser)) {
546
+ await open("https://dashboard.vapi.ai/");
547
+ }
548
+ const apiKey = await p.text({
549
+ message: "Paste your VAPI API key:",
550
+ placeholder: "..."
551
+ });
552
+ if (!p.isCancel(apiKey) && apiKey) {
553
+ config.setCredentials("vapi", { apiKey });
554
+ p.log.success("VAPI connected!");
555
+ return true;
556
+ }
557
+ break;
558
+ }
559
+ case "resend": {
560
+ const openBrowser = await p.confirm({
561
+ message: "Open browser to get Resend API key?",
562
+ initialValue: true
563
+ });
564
+ if (openBrowser && !p.isCancel(openBrowser)) {
565
+ await open("https://resend.com/api-keys");
566
+ }
567
+ const apiKey = await p.text({
568
+ message: "Paste your Resend API key:",
569
+ placeholder: "re_..."
570
+ });
571
+ if (!p.isCancel(apiKey) && apiKey) {
572
+ config.setCredentials("resend", { apiKey });
573
+ p.log.success("Resend connected!");
574
+ return true;
575
+ }
576
+ break;
577
+ }
578
+ default:
579
+ p.log.warn(`Service ${serviceName} not yet implemented`);
580
+ return false;
581
+ }
582
+ return false;
583
+ }
584
+ async function addService(config) {
585
+ const services = [
586
+ { value: "github", label: "GitHub" },
587
+ { value: "vercel", label: "Vercel" },
588
+ { value: "supabase", label: "Supabase" },
589
+ { value: "anthropic", label: "Anthropic (Claude)" },
590
+ { value: "openai", label: "OpenAI" },
591
+ { value: "stripe", label: "Stripe" },
592
+ { value: "twilio", label: "Twilio" },
593
+ { value: "vapi", label: "VAPI" },
594
+ { value: "resend", label: "Resend" },
595
+ { value: "elevenLabs", label: "ElevenLabs" },
596
+ { value: "microsoft", label: "Microsoft Graph" },
597
+ { value: "google", label: "Google APIs" }
598
+ ];
599
+ const service = await p.select({
600
+ message: "Which service do you want to connect?",
601
+ options: services
602
+ });
603
+ if (!p.isCancel(service)) {
604
+ const serviceInfo = services.find((s) => s.value === service);
605
+ if (serviceInfo) {
606
+ await connectService(config, serviceInfo.value, serviceInfo.label, false);
607
+ }
608
+ }
609
+ }
610
+ function showConnectedServices(config) {
611
+ const services = [
612
+ { key: "github", name: "GitHub" },
613
+ { key: "vercel", name: "Vercel" },
614
+ { key: "supabase", name: "Supabase" },
615
+ { key: "anthropic", name: "Anthropic" },
616
+ { key: "openai", name: "OpenAI" },
617
+ { key: "stripe", name: "Stripe" },
618
+ { key: "twilio", name: "Twilio" },
619
+ { key: "vapi", name: "VAPI" },
620
+ { key: "resend", name: "Resend" },
621
+ { key: "elevenLabs", name: "ElevenLabs" },
622
+ { key: "microsoft", name: "Microsoft" },
623
+ { key: "google", name: "Google" }
624
+ ];
625
+ console.log("\n" + chalk2.bold("Connected Services:") + "\n");
626
+ for (const service of services) {
627
+ const creds = config.getCredentials(service.key);
628
+ const isConnected = creds && Object.values(creds).some((v) => v);
629
+ const status = isConnected ? chalk2.green("\u2713 Connected") : chalk2.dim("\u25CB Not connected");
630
+ console.log(` ${service.name.padEnd(15)} ${status}`);
631
+ }
632
+ console.log("");
633
+ }
634
+ async function installPatterns(config) {
635
+ const patternsDir = config.getPatternsDir();
636
+ p.log.info(chalk2.dim(`Patterns installed to ${patternsDir}`));
637
+ }
638
+
639
+ // src/commands/init.ts
640
+ import * as p2 from "@clack/prompts";
641
+ import chalk3 from "chalk";
642
+ import * as fs2 from "fs-extra";
643
+ import * as path2 from "path";
644
+ import { execa as execa2 } from "execa";
645
+
646
+ // src/services/github.ts
647
+ import { Octokit } from "@octokit/rest";
648
+ var GitHubService = class {
649
+ octokit;
650
+ config;
651
+ constructor(config) {
652
+ this.config = config;
653
+ const creds = config.getCredentials("github");
654
+ if (!creds?.token) {
655
+ throw new Error("GitHub not configured. Run `codebakers setup`.");
656
+ }
657
+ this.octokit = new Octokit({ auth: creds.token });
658
+ }
659
+ async createRepo(name, options = {}) {
660
+ const response = await this.octokit.repos.createForAuthenticatedUser({
661
+ name,
662
+ private: options.private ?? true,
663
+ auto_init: false
664
+ });
665
+ return {
666
+ id: response.data.id,
667
+ name: response.data.name,
668
+ full_name: response.data.full_name,
669
+ html_url: response.data.html_url,
670
+ clone_url: response.data.clone_url
671
+ };
672
+ }
673
+ async getUser() {
674
+ const response = await this.octokit.users.getAuthenticated();
675
+ return {
676
+ login: response.data.login,
677
+ name: response.data.name
678
+ };
679
+ }
680
+ async listRepos() {
681
+ const response = await this.octokit.repos.listForAuthenticatedUser({
682
+ sort: "updated",
683
+ per_page: 100
684
+ });
685
+ return response.data.map((repo) => ({
686
+ name: repo.name,
687
+ full_name: repo.full_name,
688
+ private: repo.private
689
+ }));
690
+ }
691
+ };
692
+
693
+ // src/services/vercel.ts
694
+ import { execa } from "execa";
695
+ var VercelService = class {
696
+ config;
697
+ token;
698
+ constructor(config) {
699
+ this.config = config;
700
+ const creds = config.getCredentials("vercel");
701
+ if (!creds?.token) {
702
+ throw new Error("Vercel not configured. Run `codebakers setup`.");
703
+ }
704
+ this.token = creds.token;
705
+ }
706
+ async createProject(name) {
707
+ const response = await fetch("https://api.vercel.com/v9/projects", {
708
+ method: "POST",
709
+ headers: {
710
+ Authorization: `Bearer ${this.token}`,
711
+ "Content-Type": "application/json"
712
+ },
713
+ body: JSON.stringify({ name, framework: "nextjs" })
714
+ });
715
+ if (!response.ok) {
716
+ const error = await response.json();
717
+ throw new Error(error.error?.message || "Failed to create project");
718
+ }
719
+ const data = await response.json();
720
+ return { id: data.id, name: data.name };
721
+ }
722
+ async deploy(projectPath, production = false) {
723
+ const args2 = ["vercel", "--yes"];
724
+ if (production) args2.push("--prod");
725
+ const result = await execa("npx", args2, {
726
+ cwd: projectPath,
727
+ env: { ...process.env, VERCEL_TOKEN: this.token }
728
+ });
729
+ const urlMatch = result.stdout.match(/https:\/\/[^\s]+\.vercel\.app/);
730
+ return { url: urlMatch ? urlMatch[0] : "https://vercel.app" };
731
+ }
732
+ async addDomain(projectName, domain) {
733
+ await fetch(`https://api.vercel.com/v10/projects/${projectName}/domains`, {
734
+ method: "POST",
735
+ headers: {
736
+ Authorization: `Bearer ${this.token}`,
737
+ "Content-Type": "application/json"
738
+ },
739
+ body: JSON.stringify({ name: domain })
740
+ });
741
+ }
742
+ };
743
+
744
+ // src/services/supabase.ts
745
+ var SupabaseService = class {
746
+ config;
747
+ accessToken;
748
+ constructor(config) {
749
+ this.config = config;
750
+ const creds = config.getCredentials("supabase");
751
+ if (!creds?.accessToken) {
752
+ throw new Error("Supabase not configured. Run `codebakers setup`.");
753
+ }
754
+ this.accessToken = creds.accessToken;
755
+ }
756
+ async createProject(name) {
757
+ const dbPassword = this.generatePassword();
758
+ const response = await fetch("https://api.supabase.com/v1/projects", {
759
+ method: "POST",
760
+ headers: {
761
+ Authorization: `Bearer ${this.accessToken}`,
762
+ "Content-Type": "application/json"
763
+ },
764
+ body: JSON.stringify({
765
+ name,
766
+ organization_id: await this.getDefaultOrgId(),
767
+ region: "us-west-1",
768
+ plan: "free",
769
+ db_pass: dbPassword
770
+ })
771
+ });
772
+ if (!response.ok) {
773
+ const error = await response.json();
774
+ throw new Error(error.message || "Failed to create Supabase project");
775
+ }
776
+ const data = await response.json();
777
+ await this.waitForProject(data.id);
778
+ const keys = await this.getApiKeys(data.id);
779
+ return {
780
+ id: data.id,
781
+ name: data.name,
782
+ api_url: `https://${data.id}.supabase.co`,
783
+ anon_key: keys.anon_key
784
+ };
785
+ }
786
+ async getDefaultOrgId() {
787
+ const response = await fetch("https://api.supabase.com/v1/organizations", {
788
+ headers: { Authorization: `Bearer ${this.accessToken}` }
789
+ });
790
+ if (!response.ok) {
791
+ throw new Error("Failed to get organizations");
792
+ }
793
+ const orgs = await response.json();
794
+ if (orgs.length === 0) {
795
+ throw new Error("No Supabase organization found");
796
+ }
797
+ return orgs[0].id;
798
+ }
799
+ async waitForProject(projectId, maxWait = 12e4) {
800
+ const startTime = Date.now();
801
+ while (Date.now() - startTime < maxWait) {
802
+ const response = await fetch(
803
+ `https://api.supabase.com/v1/projects/${projectId}`,
804
+ { headers: { Authorization: `Bearer ${this.accessToken}` } }
805
+ );
806
+ if (response.ok) {
807
+ const project = await response.json();
808
+ if (project.status === "ACTIVE_HEALTHY") {
809
+ return;
810
+ }
811
+ }
812
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
813
+ }
814
+ throw new Error("Timeout waiting for Supabase project");
815
+ }
816
+ async getApiKeys(projectId) {
817
+ const response = await fetch(
818
+ `https://api.supabase.com/v1/projects/${projectId}/api-keys`,
819
+ { headers: { Authorization: `Bearer ${this.accessToken}` } }
820
+ );
821
+ if (!response.ok) {
822
+ throw new Error("Failed to get API keys");
823
+ }
824
+ const keys = await response.json();
825
+ const anonKey = keys.find((k) => k.name === "anon");
826
+ const serviceKey = keys.find((k) => k.name === "service_role");
827
+ return {
828
+ anon_key: anonKey?.api_key || "",
829
+ service_role_key: serviceKey?.api_key || ""
830
+ };
831
+ }
832
+ generatePassword() {
833
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%";
834
+ let password = "";
835
+ for (let i = 0; i < 24; i++) {
836
+ password += chars.charAt(Math.floor(Math.random() * chars.length));
837
+ }
838
+ return password;
839
+ }
840
+ async listProjects() {
841
+ const response = await fetch("https://api.supabase.com/v1/projects", {
842
+ headers: { Authorization: `Bearer ${this.accessToken}` }
843
+ });
844
+ if (!response.ok) {
845
+ throw new Error("Failed to list projects");
846
+ }
847
+ const projects = await response.json();
848
+ return projects.map((p13) => ({
849
+ id: p13.id,
850
+ name: p13.name,
851
+ region: p13.region
852
+ }));
853
+ }
854
+ };
855
+
856
+ // src/utils/claude-md.ts
857
+ function generateClaudeMd(config) {
858
+ return `# ${config.name.toUpperCase()} - CLAUDE.md
859
+
860
+ Generated by CodeBakers CLI
861
+
862
+ ---
863
+
864
+ # CORE DEVELOPMENT STANDARDS
865
+
866
+ ## \u{1F9E0} MANDATORY THINKING PROTOCOL
867
+
868
+ **BEFORE WRITING ANY CODE**, complete this mental checklist:
869
+
870
+ \`\`\`
871
+ \u25A2 What is the user actually asking for?
872
+ \u25A2 What are ALL the components needed (UI, API, DB, types)?
873
+ \u25A2 What are the edge cases?
874
+ \u25A2 What error handling is required?
875
+ \u25A2 What loading/empty states are needed?
876
+ \u25A2 How does this integrate with existing code?
877
+ \u25A2 What security implications exist?
878
+ \u25A2 What could break in production?
879
+ \`\`\`
880
+
881
+ **NEVER skip this step.**
882
+
883
+ ---
884
+
885
+ ## \u{1F6AB} ABSOLUTE PROHIBITIONS
886
+
887
+ These will NEVER appear in your code under ANY circumstances:
888
+
889
+ \`\`\`typescript
890
+ // \u274C BANNED FOREVER - NON-FUNCTIONAL CODE
891
+ onClick={handleClick} // where handleClick doesn't exist
892
+ onSubmit={handleSubmit} // where handleSubmit doesn't exist
893
+ href="/some-page" // where the page doesn't exist
894
+
895
+ // \u274C BANNED FOREVER - INCOMPLETE CODE
896
+ TODO: // No TODOs ever
897
+ FIXME: // No FIXMEs ever
898
+ // ... // No placeholder comments
899
+ throw new Error('Not implemented')
900
+
901
+ // \u274C BANNED FOREVER - DEBUG CODE
902
+ console.log('test') // No debug logs in final code
903
+ debugger; // No debugger statements
904
+
905
+ // \u274C BANNED FOREVER - TYPE SAFETY VIOLATIONS
906
+ any // No 'any' types
907
+ @ts-ignore // No ignoring TypeScript
908
+ as any // No casting to any
909
+
910
+ // \u274C BANNED FOREVER - SECURITY VIOLATIONS
911
+ eval() // No eval ever
912
+ innerHTML = // No direct innerHTML
913
+ dangerouslySetInnerHTML // Without DOMPurify
914
+ \`\`\`
915
+
916
+ ---
917
+
918
+ ## \u2705 MANDATORY PATTERNS
919
+
920
+ ### Every Button MUST Have:
921
+
922
+ \`\`\`typescript
923
+ // \u2705 CORRECT - Fully functional button
924
+ <Button
925
+ onClick={handleAction}
926
+ disabled={isLoading || isDisabled}
927
+ aria-label="Descriptive action name"
928
+ aria-busy={isLoading}
929
+ >
930
+ {isLoading ? (
931
+ <>
932
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
933
+ Processing...
934
+ </>
935
+ ) : (
936
+ 'Action Name'
937
+ )}
938
+ </Button>
939
+
940
+ // The handler MUST exist and be complete:
941
+ const handleAction = async () => {
942
+ setIsLoading(true);
943
+ try {
944
+ await performAction();
945
+ toast.success('Action completed successfully');
946
+ } catch (error) {
947
+ const message = error instanceof Error ? error.message : 'Action failed';
948
+ toast.error(message);
949
+ } finally {
950
+ setIsLoading(false);
951
+ }
952
+ };
953
+ \`\`\`
954
+
955
+ ### Every Form MUST Have:
956
+
957
+ \`\`\`typescript
958
+ 'use client';
959
+
960
+ import { useState } from 'react';
961
+ import { z } from 'zod';
962
+ import { useForm } from 'react-hook-form';
963
+ import { zodResolver } from '@hookform/resolvers/zod';
964
+ import { toast } from 'sonner';
965
+
966
+ const formSchema = z.object({
967
+ email: z.string().email('Please enter a valid email'),
968
+ name: z.string().min(1, 'Name is required').max(100),
969
+ });
970
+
971
+ type FormData = z.infer<typeof formSchema>;
972
+
973
+ export function ContactForm({ onSuccess }: { onSuccess?: () => void }) {
974
+ const [isSubmitting, setIsSubmitting] = useState(false);
975
+
976
+ const form = useForm<FormData>({
977
+ resolver: zodResolver(formSchema),
978
+ defaultValues: { email: '', name: '' },
979
+ });
980
+
981
+ const onSubmit = async (data: FormData) => {
982
+ setIsSubmitting(true);
983
+ try {
984
+ const response = await fetch('/api/contact', {
985
+ method: 'POST',
986
+ headers: { 'Content-Type': 'application/json' },
987
+ body: JSON.stringify(data),
988
+ });
989
+
990
+ if (!response.ok) {
991
+ const error = await response.json();
992
+ throw new Error(error.message || 'Failed to send');
993
+ }
994
+
995
+ toast.success('Message sent!');
996
+ form.reset();
997
+ onSuccess?.();
998
+ } catch (error) {
999
+ const message = error instanceof Error ? error.message : 'Something went wrong';
1000
+ toast.error(message);
1001
+ } finally {
1002
+ setIsSubmitting(false);
1003
+ }
1004
+ };
1005
+
1006
+ return (
1007
+ <Form {...form}>
1008
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
1009
+ {/* Form fields with FormField, FormLabel, FormControl, FormMessage */}
1010
+ <Button type="submit" disabled={isSubmitting}>
1011
+ {isSubmitting ? 'Sending...' : 'Send Message'}
1012
+ </Button>
1013
+ </form>
1014
+ </Form>
1015
+ );
1016
+ }
1017
+ \`\`\`
1018
+
1019
+ ### Every List MUST Have:
1020
+
1021
+ \`\`\`typescript
1022
+ interface ItemListProps {
1023
+ items: Item[];
1024
+ isLoading: boolean;
1025
+ error: string | null;
1026
+ onRetry?: () => void;
1027
+ }
1028
+
1029
+ export function ItemList({ items, isLoading, error, onRetry }: ItemListProps) {
1030
+ // Loading state
1031
+ if (isLoading) {
1032
+ return (
1033
+ <div className="flex items-center justify-center p-8">
1034
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
1035
+ </div>
1036
+ );
1037
+ }
1038
+
1039
+ // Error state
1040
+ if (error) {
1041
+ return (
1042
+ <div className="flex flex-col items-center gap-4 p-8 text-center">
1043
+ <AlertCircle className="h-12 w-12 text-destructive" />
1044
+ <p className="text-destructive">{error}</p>
1045
+ {onRetry && <Button variant="outline" onClick={onRetry}>Try Again</Button>}
1046
+ </div>
1047
+ );
1048
+ }
1049
+
1050
+ // Empty state
1051
+ if (items.length === 0) {
1052
+ return (
1053
+ <div className="flex flex-col items-center gap-4 p-8 text-center">
1054
+ <Package className="h-12 w-12 text-muted-foreground" />
1055
+ <div>
1056
+ <h3 className="font-medium">No items found</h3>
1057
+ <p className="text-sm text-muted-foreground">Get started by creating your first item.</p>
1058
+ </div>
1059
+ </div>
1060
+ );
1061
+ }
1062
+
1063
+ // Success state with data
1064
+ return (
1065
+ <ul className="divide-y divide-border">
1066
+ {items.map((item) => (
1067
+ <li key={item.id} className="p-4">
1068
+ {item.name}
1069
+ </li>
1070
+ ))}
1071
+ </ul>
1072
+ );
1073
+ }
1074
+ \`\`\`
1075
+
1076
+ ---
1077
+
1078
+ ## \u{1F4C1} PROJECT STRUCTURE
1079
+
1080
+ \`\`\`
1081
+ ${config.name}/
1082
+ \u251C\u2500\u2500 src/
1083
+ \u2502 \u251C\u2500\u2500 app/ # Next.js App Router
1084
+ \u2502 \u2502 \u251C\u2500\u2500 (auth)/ # Routes requiring authentication
1085
+ \u2502 \u2502 \u251C\u2500\u2500 (public)/ # Public routes
1086
+ \u2502 \u2502 \u251C\u2500\u2500 api/ # API routes
1087
+ \u2502 \u2502 \u2514\u2500\u2500 layout.tsx # Root layout
1088
+ \u2502 \u251C\u2500\u2500 components/
1089
+ \u2502 \u2502 \u251C\u2500\u2500 ui/ # shadcn/ui components
1090
+ \u2502 \u2502 \u251C\u2500\u2500 forms/ # Form components
1091
+ \u2502 \u2502 \u2514\u2500\u2500 [feature]/ # Feature-specific
1092
+ \u2502 \u251C\u2500\u2500 lib/
1093
+ \u2502 \u2502 \u251C\u2500\u2500 supabase/ # Supabase clients
1094
+ \u2502 \u2502 \u251C\u2500\u2500 utils.ts # Utilities
1095
+ \u2502 \u2502 \u2514\u2500\u2500 validations.ts # Zod schemas
1096
+ \u2502 \u251C\u2500\u2500 hooks/ # Custom hooks
1097
+ \u2502 \u251C\u2500\u2500 types/ # TypeScript types
1098
+ \u2502 \u251C\u2500\u2500 stores/ # Zustand stores
1099
+ \u2502 \u2514\u2500\u2500 services/ # Business logic
1100
+ \u251C\u2500\u2500 tests/ # Playwright tests
1101
+ \u251C\u2500\u2500 .codebakers/ # CodeBakers config
1102
+ \u2514\u2500\u2500 CLAUDE.md # This file
1103
+ \`\`\`
1104
+
1105
+ ---
1106
+
1107
+ ## \u{1F510} SECURITY REQUIREMENTS
1108
+
1109
+ 1. **Never expose secrets** - Use environment variables
1110
+ 2. **Validate all inputs** - Use Zod on both client and server
1111
+ 3. **Enable RLS** - Every Supabase table must have Row Level Security
1112
+ 4. **Sanitize user content** - Use DOMPurify for any user HTML
1113
+ 5. **Use HTTPS** - Never use HTTP in production
1114
+ 6. **Rate limit APIs** - Protect public endpoints
1115
+
1116
+ ---
1117
+
1118
+ ## \u{1F9EA} TESTING REQUIREMENTS
1119
+
1120
+ After building ANY feature, automatically test it:
1121
+
1122
+ 1. Run \`codebakers check\` to verify patterns
1123
+ 2. Check TypeScript: \`npx tsc --noEmit\`
1124
+ 3. Run tests: \`npm test\`
1125
+ 4. Fix any failures before saying "done"
1126
+
1127
+ ---
1128
+
1129
+ **END OF CLAUDE.md**
1130
+
1131
+ Generated by CodeBakers CLI v1.0.0
1132
+ `;
1133
+ }
1134
+
1135
+ // src/commands/init.ts
1136
+ async function initCommand(options = {}) {
1137
+ const config = new Config();
1138
+ if (!config.isConfigured()) {
1139
+ p2.log.error("Please run `codebakers setup` first.");
1140
+ return;
1141
+ }
1142
+ p2.intro(chalk3.bgCyan.black(" Create New Project "));
1143
+ let projectName = options.name;
1144
+ if (!projectName) {
1145
+ const name = await p2.text({
1146
+ message: "Project name:",
1147
+ placeholder: "my-app",
1148
+ validate: (value) => {
1149
+ if (!value) return "Project name is required";
1150
+ if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only";
1151
+ return void 0;
1152
+ }
1153
+ });
1154
+ if (p2.isCancel(name)) return;
1155
+ projectName = name;
1156
+ }
1157
+ const projectType = await p2.select({
1158
+ message: "What are you building?",
1159
+ options: [
1160
+ { value: "web", label: "\u{1F310} Web app", hint: "Next.js \u2192 Vercel" },
1161
+ { value: "mobile", label: "\u{1F4F1} Mobile app", hint: "Expo \u2192 App stores" },
1162
+ { value: "desktop", label: "\u{1F5A5}\uFE0F Desktop app", hint: "Tauri \u2192 Windows/Mac/Linux" },
1163
+ { value: "fullstack", label: "\u{1F4E6} Full stack", hint: "Web + Mobile + Desktop" }
1164
+ ]
1165
+ });
1166
+ if (p2.isCancel(projectType)) return;
1167
+ let framework = "nextjs";
1168
+ if (projectType === "web" || projectType === "fullstack") {
1169
+ const selected = await p2.select({
1170
+ message: "Framework:",
1171
+ options: [
1172
+ { value: "nextjs", label: "Next.js 14+", hint: config.getPreference("defaultFramework") === "nextjs" ? "(your usual choice)" : "" },
1173
+ { value: "remix", label: "Remix" },
1174
+ { value: "vite", label: "Vite + React" },
1175
+ { value: "astro", label: "Astro" }
1176
+ ]
1177
+ });
1178
+ if (p2.isCancel(selected)) return;
1179
+ framework = selected;
1180
+ config.learnPreference("defaultFramework", framework);
1181
+ }
1182
+ const uiLibrary = await p2.select({
1183
+ message: "UI Library:",
1184
+ options: [
1185
+ { value: "shadcn", label: "shadcn/ui", hint: config.getPreference("defaultUI") === "shadcn" ? "(your usual choice)" : "Recommended" },
1186
+ { value: "chakra", label: "Chakra UI" },
1187
+ { value: "mantine", label: "Mantine" },
1188
+ { value: "mui", label: "Material UI" },
1189
+ { value: "headless", label: "Headless UI" },
1190
+ { value: "tailwind", label: "Tailwind only" }
1191
+ ]
1192
+ });
1193
+ if (p2.isCancel(uiLibrary)) return;
1194
+ config.learnPreference("defaultUI", uiLibrary);
1195
+ const packages = await p2.multiselect({
1196
+ message: "Additional packages:",
1197
+ options: [
1198
+ { value: "typescript", label: "TypeScript", hint: "Always recommended" },
1199
+ { value: "tailwind", label: "Tailwind CSS" },
1200
+ { value: "zod", label: "Zod", hint: "Validation" },
1201
+ { value: "tanstack-query", label: "TanStack Query", hint: "Data fetching" },
1202
+ { value: "zustand", label: "Zustand", hint: "State management" },
1203
+ { value: "react-hook-form", label: "React Hook Form" },
1204
+ { value: "lucide", label: "Lucide Icons" },
1205
+ { value: "trpc", label: "tRPC" }
1206
+ ],
1207
+ initialValues: ["typescript", "tailwind", "zod", "zustand", "lucide"]
1208
+ });
1209
+ if (p2.isCancel(packages)) return;
1210
+ const services = await p2.multiselect({
1211
+ message: "Services to provision:",
1212
+ options: [
1213
+ { value: "github", label: "GitHub", hint: "Repository" },
1214
+ { value: "vercel", label: "Vercel", hint: "Hosting" },
1215
+ { value: "supabase", label: "Supabase", hint: "Database + Auth" },
1216
+ { value: "stripe", label: "Stripe", hint: "Payments" },
1217
+ { value: "resend", label: "Resend", hint: "Email" },
1218
+ { value: "vapi", label: "VAPI", hint: "Voice AI" }
1219
+ ],
1220
+ initialValues: ["github", "vercel", "supabase"]
1221
+ });
1222
+ if (p2.isCancel(services)) return;
1223
+ let domain;
1224
+ const domainChoice = await p2.select({
1225
+ message: "Domain:",
1226
+ options: [
1227
+ { value: "subdomain", label: "Subdomain", hint: `${projectName}.yourdomain.com` },
1228
+ { value: "vercel", label: "Vercel subdomain", hint: `${projectName}.vercel.app` },
1229
+ { value: "purchase", label: "Purchase new domain" },
1230
+ { value: "skip", label: "Skip for now" }
1231
+ ]
1232
+ });
1233
+ if (p2.isCancel(domainChoice)) return;
1234
+ if (domainChoice === "subdomain") {
1235
+ const sub = await p2.text({
1236
+ message: "Subdomain:",
1237
+ placeholder: projectName,
1238
+ initialValue: projectName
1239
+ });
1240
+ if (!p2.isCancel(sub)) {
1241
+ domain = `${sub}.yourdomain.com`;
1242
+ }
1243
+ }
1244
+ const projectConfig = {
1245
+ name: projectName,
1246
+ type: projectType,
1247
+ framework,
1248
+ ui: uiLibrary,
1249
+ packages,
1250
+ services,
1251
+ domain
1252
+ };
1253
+ p2.note(
1254
+ `Name: ${projectName}
1255
+ Type: ${projectType}
1256
+ Framework: ${framework}
1257
+ UI: ${uiLibrary}
1258
+ Packages: ${packages.join(", ")}
1259
+ Services: ${services.join(", ")}
1260
+ Domain: ${domain || "Vercel default"}`,
1261
+ "Configuration"
1262
+ );
1263
+ const confirmed = await p2.confirm({
1264
+ message: "Create project?",
1265
+ initialValue: true
1266
+ });
1267
+ if (!confirmed || p2.isCancel(confirmed)) {
1268
+ p2.cancel("Project creation cancelled.");
1269
+ return;
1270
+ }
1271
+ const spinner10 = p2.spinner();
1272
+ const projectPath = path2.join(process.cwd(), projectName);
1273
+ try {
1274
+ spinner10.start("Creating local project...");
1275
+ await createLocalProject(projectPath, projectConfig);
1276
+ spinner10.stop("Local project created");
1277
+ spinner10.start("Installing dependencies...");
1278
+ await execa2("pnpm", ["install"], { cwd: projectPath });
1279
+ spinner10.stop("Dependencies installed");
1280
+ if (services.includes("github")) {
1281
+ spinner10.start("Creating GitHub repository...");
1282
+ const github = new GitHubService(config);
1283
+ const repo = await github.createRepo(projectName, { private: true });
1284
+ spinner10.stop(`GitHub repo created: ${repo.html_url}`);
1285
+ await execa2("git", ["init"], { cwd: projectPath });
1286
+ await execa2("git", ["add", "."], { cwd: projectPath });
1287
+ await execa2("git", ["commit", "-m", "Initial commit by CodeBakers"], { cwd: projectPath });
1288
+ await execa2("git", ["remote", "add", "origin", repo.clone_url], { cwd: projectPath });
1289
+ await execa2("git", ["push", "-u", "origin", "main"], { cwd: projectPath });
1290
+ }
1291
+ if (services.includes("supabase")) {
1292
+ spinner10.start("Creating Supabase project...");
1293
+ const supabase = new SupabaseService(config);
1294
+ const project = await supabase.createProject(projectName);
1295
+ spinner10.stop(`Supabase project created: ${project.name}`);
1296
+ await fs2.writeJson(
1297
+ path2.join(projectPath, ".codebakers", "supabase.json"),
1298
+ { projectId: project.id, projectUrl: project.api_url },
1299
+ { spaces: 2 }
1300
+ );
1301
+ }
1302
+ if (services.includes("vercel")) {
1303
+ spinner10.start("Creating Vercel project...");
1304
+ const vercel = new VercelService(config);
1305
+ const project = await vercel.createProject(projectName);
1306
+ spinner10.stop(`Vercel project created`);
1307
+ if (domain) {
1308
+ spinner10.start(`Configuring domain: ${domain}...`);
1309
+ await vercel.addDomain(projectName, domain);
1310
+ spinner10.stop("Domain configured");
1311
+ }
1312
+ spinner10.start("Deploying to Vercel...");
1313
+ const deployment = await vercel.deploy(projectPath);
1314
+ spinner10.stop(`Deployed: ${deployment.url}`);
1315
+ }
1316
+ spinner10.start("Generating CLAUDE.md...");
1317
+ const claudeMd = generateClaudeMd(projectConfig);
1318
+ await fs2.writeFile(path2.join(projectPath, "CLAUDE.md"), claudeMd);
1319
+ spinner10.stop("CLAUDE.md generated");
1320
+ spinner10.start("Setting up CodeBakers enforcement...");
1321
+ await setupGitHooks(projectPath);
1322
+ spinner10.stop("CodeBakers enforcement configured");
1323
+ config.addProject({
1324
+ name: projectName,
1325
+ path: projectPath,
1326
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1327
+ });
1328
+ p2.outro(chalk3.green(`
1329
+ \u2713 Project created!
1330
+
1331
+ ${chalk3.bold("Your project is ready:")}
1332
+ ${chalk3.cyan(`cd ${projectName}`)}
1333
+ ${chalk3.cyan("codebakers code")}
1334
+
1335
+ ${chalk3.dim("Shortcuts:")}
1336
+ ${chalk3.cyan("codebakers")} \u2014 Interactive menu
1337
+ ${chalk3.cyan("codebakers code")} \u2014 AI coding agent
1338
+ ${chalk3.cyan("codebakers check")} \u2014 Pattern enforcement
1339
+ ${chalk3.cyan("codebakers deploy")} \u2014 Deploy to production
1340
+ `));
1341
+ } catch (error) {
1342
+ spinner10.stop("Error occurred");
1343
+ p2.log.error(`Failed to create project: ${error instanceof Error ? error.message : "Unknown error"}`);
1344
+ const cleanup = await p2.confirm({
1345
+ message: "Clean up partially created project?"
1346
+ });
1347
+ if (cleanup && !p2.isCancel(cleanup)) {
1348
+ await fs2.remove(projectPath);
1349
+ p2.log.info("Cleaned up.");
1350
+ }
1351
+ }
1352
+ }
1353
+ async function createLocalProject(projectPath, config) {
1354
+ const framework = config.framework;
1355
+ const ui = config.ui;
1356
+ const packages = config.packages;
1357
+ await fs2.ensureDir(projectPath);
1358
+ await fs2.ensureDir(path2.join(projectPath, ".codebakers"));
1359
+ await fs2.ensureDir(path2.join(projectPath, "src", "app"));
1360
+ await fs2.ensureDir(path2.join(projectPath, "src", "components", "ui"));
1361
+ await fs2.ensureDir(path2.join(projectPath, "src", "components", "features"));
1362
+ await fs2.ensureDir(path2.join(projectPath, "src", "lib"));
1363
+ await fs2.ensureDir(path2.join(projectPath, "src", "hooks"));
1364
+ await fs2.ensureDir(path2.join(projectPath, "src", "types"));
1365
+ await fs2.ensureDir(path2.join(projectPath, "src", "stores"));
1366
+ await fs2.ensureDir(path2.join(projectPath, "src", "services"));
1367
+ const packageJson = {
1368
+ name: path2.basename(projectPath),
1369
+ version: "0.1.0",
1370
+ private: true,
1371
+ scripts: {
1372
+ dev: "next dev",
1373
+ build: "next build",
1374
+ start: "next start",
1375
+ lint: "next lint",
1376
+ typecheck: "tsc --noEmit",
1377
+ "codebakers:check": "codebakers check"
1378
+ },
1379
+ dependencies: {
1380
+ next: "^14.2.0",
1381
+ react: "^18.3.0",
1382
+ "react-dom": "^18.3.0"
1383
+ },
1384
+ devDependencies: {
1385
+ typescript: "^5.5.0",
1386
+ "@types/node": "^22.0.0",
1387
+ "@types/react": "^18.3.0",
1388
+ "@types/react-dom": "^18.3.0"
1389
+ }
1390
+ };
1391
+ if (packages.includes("tailwind")) {
1392
+ packageJson.devDependencies["tailwindcss"] = "^3.4.0";
1393
+ packageJson.devDependencies["postcss"] = "^8.4.0";
1394
+ packageJson.devDependencies["autoprefixer"] = "^10.4.0";
1395
+ }
1396
+ if (packages.includes("zod")) {
1397
+ packageJson.dependencies["zod"] = "^3.23.0";
1398
+ }
1399
+ if (packages.includes("zustand")) {
1400
+ packageJson.dependencies["zustand"] = "^4.5.0";
1401
+ }
1402
+ if (packages.includes("tanstack-query")) {
1403
+ packageJson.dependencies["@tanstack/react-query"] = "^5.50.0";
1404
+ }
1405
+ if (packages.includes("react-hook-form")) {
1406
+ packageJson.dependencies["react-hook-form"] = "^7.52.0";
1407
+ packageJson.dependencies["@hookform/resolvers"] = "^3.9.0";
1408
+ }
1409
+ if (packages.includes("lucide")) {
1410
+ packageJson.dependencies["lucide-react"] = "^0.400.0";
1411
+ }
1412
+ if (ui === "shadcn") {
1413
+ packageJson.dependencies["class-variance-authority"] = "^0.7.0";
1414
+ packageJson.dependencies["clsx"] = "^2.1.0";
1415
+ packageJson.dependencies["tailwind-merge"] = "^2.4.0";
1416
+ packageJson.dependencies["sonner"] = "^1.5.0";
1417
+ }
1418
+ packageJson.dependencies["@supabase/supabase-js"] = "^2.45.0";
1419
+ packageJson.dependencies["@supabase/ssr"] = "^0.4.0";
1420
+ await fs2.writeJson(path2.join(projectPath, "package.json"), packageJson, { spaces: 2 });
1421
+ await createConfigFiles(projectPath, config);
1422
+ await fs2.writeJson(
1423
+ path2.join(projectPath, ".codebakers", "config.json"),
1424
+ {
1425
+ version: "1.0.0",
1426
+ framework,
1427
+ ui,
1428
+ packages,
1429
+ patterns: ["00-core", "01-database", "02-auth", "03-api", "04-frontend"],
1430
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1431
+ },
1432
+ { spaces: 2 }
1433
+ );
1434
+ }
1435
+ async function createConfigFiles(projectPath, config) {
1436
+ const tsConfig = {
1437
+ compilerOptions: {
1438
+ target: "ES2022",
1439
+ lib: ["dom", "dom.iterable", "ES2022"],
1440
+ allowJs: true,
1441
+ skipLibCheck: true,
1442
+ strict: true,
1443
+ noEmit: true,
1444
+ esModuleInterop: true,
1445
+ module: "esnext",
1446
+ moduleResolution: "bundler",
1447
+ resolveJsonModule: true,
1448
+ isolatedModules: true,
1449
+ jsx: "preserve",
1450
+ incremental: true,
1451
+ plugins: [{ name: "next" }],
1452
+ paths: { "@/*": ["./src/*"] }
1453
+ },
1454
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1455
+ exclude: ["node_modules"]
1456
+ };
1457
+ await fs2.writeJson(path2.join(projectPath, "tsconfig.json"), tsConfig, { spaces: 2 });
1458
+ const nextConfig = `/** @type {import('next').NextConfig} */
1459
+ const nextConfig = {
1460
+ experimental: {
1461
+ typedRoutes: true,
1462
+ },
1463
+ };
1464
+
1465
+ export default nextConfig;
1466
+ `;
1467
+ await fs2.writeFile(path2.join(projectPath, "next.config.mjs"), nextConfig);
1468
+ const tailwindConfig = `import type { Config } from 'tailwindcss';
1469
+
1470
+ const config: Config = {
1471
+ darkMode: ['class'],
1472
+ content: [
1473
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
1474
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
1475
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
1476
+ ],
1477
+ theme: {
1478
+ extend: {
1479
+ colors: {
1480
+ border: 'hsl(var(--border))',
1481
+ input: 'hsl(var(--input))',
1482
+ ring: 'hsl(var(--ring))',
1483
+ background: 'hsl(var(--background))',
1484
+ foreground: 'hsl(var(--foreground))',
1485
+ primary: {
1486
+ DEFAULT: 'hsl(var(--primary))',
1487
+ foreground: 'hsl(var(--primary-foreground))',
1488
+ },
1489
+ secondary: {
1490
+ DEFAULT: 'hsl(var(--secondary))',
1491
+ foreground: 'hsl(var(--secondary-foreground))',
1492
+ },
1493
+ destructive: {
1494
+ DEFAULT: 'hsl(var(--destructive))',
1495
+ foreground: 'hsl(var(--destructive-foreground))',
1496
+ },
1497
+ muted: {
1498
+ DEFAULT: 'hsl(var(--muted))',
1499
+ foreground: 'hsl(var(--muted-foreground))',
1500
+ },
1501
+ accent: {
1502
+ DEFAULT: 'hsl(var(--accent))',
1503
+ foreground: 'hsl(var(--accent-foreground))',
1504
+ },
1505
+ },
1506
+ borderRadius: {
1507
+ lg: 'var(--radius)',
1508
+ md: 'calc(var(--radius) - 2px)',
1509
+ sm: 'calc(var(--radius) - 4px)',
1510
+ },
1511
+ },
1512
+ },
1513
+ plugins: [require('tailwindcss-animate')],
1514
+ };
1515
+
1516
+ export default config;
1517
+ `;
1518
+ await fs2.writeFile(path2.join(projectPath, "tailwind.config.ts"), tailwindConfig);
1519
+ const envExample = `# Supabase
1520
+ NEXT_PUBLIC_SUPABASE_URL=
1521
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=
1522
+ SUPABASE_SERVICE_ROLE_KEY=
1523
+
1524
+ # Stripe (optional)
1525
+ STRIPE_SECRET_KEY=
1526
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
1527
+ STRIPE_WEBHOOK_SECRET=
1528
+
1529
+ # App
1530
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
1531
+ `;
1532
+ await fs2.writeFile(path2.join(projectPath, ".env.example"), envExample);
1533
+ await fs2.writeFile(path2.join(projectPath, ".env.local"), envExample);
1534
+ const gitignore = `# Dependencies
1535
+ node_modules/
1536
+ .pnp
1537
+ .pnp.js
1538
+
1539
+ # Build
1540
+ .next/
1541
+ out/
1542
+ build/
1543
+ dist/
1544
+
1545
+ # Environment
1546
+ .env
1547
+ .env.local
1548
+ .env.*.local
1549
+
1550
+ # IDE
1551
+ .vscode/
1552
+ .idea/
1553
+ *.swp
1554
+ *.swo
1555
+
1556
+ # OS
1557
+ .DS_Store
1558
+ Thumbs.db
1559
+
1560
+ # Logs
1561
+ npm-debug.log*
1562
+ yarn-debug.log*
1563
+ yarn-error.log*
1564
+
1565
+ # Testing
1566
+ coverage/
1567
+ .nyc_output/
1568
+
1569
+ # Misc
1570
+ *.pem
1571
+ `;
1572
+ await fs2.writeFile(path2.join(projectPath, ".gitignore"), gitignore);
1573
+ }
1574
+ async function setupGitHooks(projectPath) {
1575
+ const hooksDir = path2.join(projectPath, ".git", "hooks");
1576
+ if (!await fs2.pathExists(path2.join(projectPath, ".git"))) {
1577
+ await execa2("git", ["init"], { cwd: projectPath });
1578
+ }
1579
+ await fs2.ensureDir(hooksDir);
1580
+ const preCommitHook = `#!/bin/sh
1581
+ # CodeBakers pre-commit hook
1582
+
1583
+ echo "\u{1F50D} Running CodeBakers check..."
1584
+
1585
+ # Run pattern check
1586
+ codebakers check
1587
+
1588
+ # If check fails, abort commit
1589
+ if [ $? -ne 0 ]; then
1590
+ echo "\u274C CodeBakers check failed. Fix issues or use --no-verify to bypass."
1591
+ exit 1
1592
+ fi
1593
+
1594
+ echo "\u2713 CodeBakers check passed"
1595
+ `;
1596
+ await fs2.writeFile(path2.join(hooksDir, "pre-commit"), preCommitHook);
1597
+ await fs2.chmod(path2.join(hooksDir, "pre-commit"), "755");
1598
+ }
1599
+
1600
+ // src/commands/code.ts
1601
+ import * as p4 from "@clack/prompts";
1602
+ import chalk5 from "chalk";
1603
+ import * as fs5 from "fs-extra";
1604
+ import * as path5 from "path";
1605
+ import Anthropic from "@anthropic-ai/sdk";
1606
+ import { execa as execa3 } from "execa";
1607
+
1608
+ // src/patterns/loader.ts
1609
+ import * as fs3 from "fs-extra";
1610
+ import * as path3 from "path";
1611
+ var CORE_PATTERNS = `
1612
+ # CODEBAKERS CORE PATTERNS
1613
+
1614
+ ## ABSOLUTE PROHIBITIONS
1615
+
1616
+ These will NEVER appear in your code:
1617
+
1618
+ \`\`\`typescript
1619
+ // \u274C BANNED - NON-FUNCTIONAL CODE
1620
+ onClick={handleClick} // where handleClick doesn't exist
1621
+ onSubmit={handleSubmit} // where handleSubmit doesn't exist
1622
+ href="/some-page" // where the page doesn't exist
1623
+
1624
+ // \u274C BANNED - INCOMPLETE CODE
1625
+ TODO: // No TODOs ever
1626
+ FIXME: // No FIXMEs ever
1627
+ // ... // No placeholder comments
1628
+ throw new Error('Not implemented')
1629
+
1630
+ // \u274C BANNED - DEBUG CODE
1631
+ console.log('test') // No debug logs
1632
+ debugger; // No debugger statements
1633
+
1634
+ // \u274C BANNED - TYPE SAFETY VIOLATIONS
1635
+ any // No 'any' types
1636
+ @ts-ignore // No ignoring TypeScript
1637
+ as any // No casting to any
1638
+
1639
+ // \u274C BANNED - SECURITY VIOLATIONS
1640
+ eval() // No eval ever
1641
+ innerHTML = // No direct innerHTML
1642
+ dangerouslySetInnerHTML // Without DOMPurify
1643
+ \`\`\`
1644
+
1645
+ ## MANDATORY PATTERNS
1646
+
1647
+ ### Every Button MUST Have:
1648
+
1649
+ \`\`\`typescript
1650
+ <Button
1651
+ onClick={handleAction}
1652
+ disabled={isLoading || isDisabled}
1653
+ aria-label="Descriptive action name"
1654
+ >
1655
+ {isLoading ? (
1656
+ <>
1657
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
1658
+ Processing...
1659
+ </>
1660
+ ) : (
1661
+ 'Action Name'
1662
+ )}
1663
+ </Button>
1664
+
1665
+ // The handler MUST exist and be complete:
1666
+ const handleAction = async () => {
1667
+ setIsLoading(true);
1668
+ try {
1669
+ await performAction();
1670
+ toast.success('Action completed successfully');
1671
+ } catch (error) {
1672
+ const message = error instanceof Error ? error.message : 'Action failed';
1673
+ toast.error(message);
1674
+ } finally {
1675
+ setIsLoading(false);
1676
+ }
1677
+ };
1678
+ \`\`\`
1679
+
1680
+ ### Every Form MUST Have:
1681
+
1682
+ 1. React Hook Form with Zod validation
1683
+ 2. Loading state during submission
1684
+ 3. Error messages for each field
1685
+ 4. Success/error toast notifications
1686
+ 5. Proper disabled state while submitting
1687
+
1688
+ ### Every Async Operation MUST Have:
1689
+
1690
+ \`\`\`typescript
1691
+ const [data, setData] = useState<DataType | null>(null);
1692
+ const [isLoading, setIsLoading] = useState(false);
1693
+ const [error, setError] = useState<string | null>(null);
1694
+
1695
+ const fetchData = async () => {
1696
+ setIsLoading(true);
1697
+ setError(null);
1698
+
1699
+ try {
1700
+ const response = await fetch('/api/data');
1701
+ if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
1702
+ const result = await response.json();
1703
+ setData(result.data);
1704
+ } catch (err) {
1705
+ const message = err instanceof Error ? err.message : 'Failed to fetch';
1706
+ setError(message);
1707
+ toast.error(message);
1708
+ } finally {
1709
+ setIsLoading(false);
1710
+ }
1711
+ };
1712
+ \`\`\`
1713
+
1714
+ ### Every List MUST Have:
1715
+
1716
+ 1. Loading state with spinner/skeleton
1717
+ 2. Error state with retry button
1718
+ 3. Empty state with helpful message
1719
+ 4. Success state with data
1720
+
1721
+ ## FILE STRUCTURE
1722
+
1723
+ \`\`\`
1724
+ src/
1725
+ \u251C\u2500\u2500 app/ # Next.js App Router
1726
+ \u2502 \u251C\u2500\u2500 (auth)/ # Routes requiring authentication
1727
+ \u2502 \u251C\u2500\u2500 (public)/ # Public routes
1728
+ \u2502 \u251C\u2500\u2500 (marketing)/ # Marketing pages
1729
+ \u2502 \u251C\u2500\u2500 api/ # API routes
1730
+ \u2502 \u2514\u2500\u2500 layout.tsx # Root layout
1731
+ \u251C\u2500\u2500 components/
1732
+ \u2502 \u251C\u2500\u2500 ui/ # shadcn/ui components
1733
+ \u2502 \u251C\u2500\u2500 forms/ # Form components
1734
+ \u2502 \u251C\u2500\u2500 layouts/ # Layout components
1735
+ \u2502 \u2514\u2500\u2500 [feature]/ # Feature-specific components
1736
+ \u251C\u2500\u2500 lib/
1737
+ \u2502 \u251C\u2500\u2500 supabase/ # Supabase clients
1738
+ \u2502 \u251C\u2500\u2500 utils.ts # Utility functions
1739
+ \u2502 \u2514\u2500\u2500 validations.ts # Zod schemas
1740
+ \u251C\u2500\u2500 hooks/ # Custom React hooks
1741
+ \u251C\u2500\u2500 types/ # TypeScript types
1742
+ \u251C\u2500\u2500 stores/ # Zustand stores
1743
+ \u2514\u2500\u2500 services/ # Business logic
1744
+ \`\`\`
1745
+
1746
+ ## API ROUTE PATTERN
1747
+
1748
+ \`\`\`typescript
1749
+ import { NextRequest, NextResponse } from 'next/server';
1750
+ import { z } from 'zod';
1751
+ import { createServerClient } from '@/lib/supabase/server';
1752
+
1753
+ const requestSchema = z.object({
1754
+ // Define request body schema
1755
+ });
1756
+
1757
+ export async function POST(request: NextRequest) {
1758
+ try {
1759
+ // 1. Auth check
1760
+ const supabase = createServerClient();
1761
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
1762
+
1763
+ if (authError || !user) {
1764
+ return NextResponse.json(
1765
+ { error: 'Unauthorized' },
1766
+ { status: 401 }
1767
+ );
1768
+ }
1769
+
1770
+ // 2. Validate input
1771
+ const body = await request.json();
1772
+ const result = requestSchema.safeParse(body);
1773
+
1774
+ if (!result.success) {
1775
+ return NextResponse.json(
1776
+ { error: 'Validation failed', details: result.error.flatten() },
1777
+ { status: 400 }
1778
+ );
1779
+ }
1780
+
1781
+ // 3. Business logic
1782
+ const data = result.data;
1783
+ // ... do something
1784
+
1785
+ // 4. Return success
1786
+ return NextResponse.json({ data }, { status: 201 });
1787
+
1788
+ } catch (error) {
1789
+ console.error('[API] Error:', error);
1790
+ return NextResponse.json(
1791
+ { error: 'Internal server error' },
1792
+ { status: 500 }
1793
+ );
1794
+ }
1795
+ }
1796
+ \`\`\`
1797
+
1798
+ ## SUPABASE RLS PATTERN
1799
+
1800
+ Every table MUST have Row Level Security enabled:
1801
+
1802
+ \`\`\`sql
1803
+ -- Enable RLS
1804
+ ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
1805
+
1806
+ -- User can only see their own data
1807
+ CREATE POLICY "Users can view own data"
1808
+ ON your_table FOR SELECT
1809
+ TO authenticated
1810
+ USING (user_id = auth.uid());
1811
+
1812
+ -- User can only insert their own data
1813
+ CREATE POLICY "Users can insert own data"
1814
+ ON your_table FOR INSERT
1815
+ TO authenticated
1816
+ WITH CHECK (user_id = auth.uid());
1817
+
1818
+ -- User can only update their own data
1819
+ CREATE POLICY "Users can update own data"
1820
+ ON your_table FOR UPDATE
1821
+ TO authenticated
1822
+ USING (user_id = auth.uid())
1823
+ WITH CHECK (user_id = auth.uid());
1824
+
1825
+ -- User can only delete their own data
1826
+ CREATE POLICY "Users can delete own data"
1827
+ ON your_table FOR DELETE
1828
+ TO authenticated
1829
+ USING (user_id = auth.uid());
1830
+ \`\`\`
1831
+
1832
+ ## STATE MANAGEMENT
1833
+
1834
+ Use Zustand for global state:
1835
+
1836
+ \`\`\`typescript
1837
+ import { create } from 'zustand';
1838
+
1839
+ interface AppState {
1840
+ user: User | null;
1841
+ isLoading: boolean;
1842
+ setUser: (user: User | null) => void;
1843
+ reset: () => void;
1844
+ }
1845
+
1846
+ export const useAppStore = create<AppState>((set) => ({
1847
+ user: null,
1848
+ isLoading: false,
1849
+ setUser: (user) => set({ user }),
1850
+ reset: () => set({ user: null, isLoading: false }),
1851
+ }));
1852
+ \`\`\`
1853
+
1854
+ ## ERROR HANDLING
1855
+
1856
+ Use typed error classes:
1857
+
1858
+ \`\`\`typescript
1859
+ export class AppError extends Error {
1860
+ constructor(
1861
+ message: string,
1862
+ public code: string,
1863
+ public statusCode: number = 500
1864
+ ) {
1865
+ super(message);
1866
+ }
1867
+ }
1868
+
1869
+ export class AuthenticationError extends AppError {
1870
+ constructor(message = 'Authentication required') {
1871
+ super(message, 'AUTH_ERROR', 401);
1872
+ }
1873
+ }
1874
+
1875
+ export class ValidationError extends AppError {
1876
+ constructor(message = 'Validation failed', public details?: Record<string, string[]>) {
1877
+ super(message, 'VALIDATION_ERROR', 400);
1878
+ }
1879
+ }
1880
+ \`\`\`
1881
+ `;
1882
+ async function loadPatterns(config) {
1883
+ const projectDir = process.cwd();
1884
+ const patterns = [CORE_PATTERNS];
1885
+ const projectPatternDir = path3.join(projectDir, ".codebakers", "patterns");
1886
+ if (await fs3.pathExists(projectPatternDir)) {
1887
+ const files = await fs3.readdir(projectPatternDir);
1888
+ for (const file of files) {
1889
+ if (file.endsWith(".md")) {
1890
+ const content = await fs3.readFile(path3.join(projectPatternDir, file), "utf-8");
1891
+ patterns.push(content);
1892
+ }
1893
+ }
1894
+ }
1895
+ const globalPatternDir = config.getPatternsDir();
1896
+ if (await fs3.pathExists(globalPatternDir)) {
1897
+ const files = await fs3.readdir(globalPatternDir);
1898
+ for (const file of files.slice(0, 10)) {
1899
+ if (file.endsWith(".md")) {
1900
+ const content = await fs3.readFile(path3.join(globalPatternDir, file), "utf-8");
1901
+ patterns.push(content.slice(0, 1e4));
1902
+ }
1903
+ }
1904
+ }
1905
+ return patterns.join("\n\n---\n\n");
1906
+ }
1907
+
1908
+ // src/commands/check.ts
1909
+ import * as p3 from "@clack/prompts";
1910
+ import chalk4 from "chalk";
1911
+ import * as fs4 from "fs-extra";
1912
+ import * as path4 from "path";
1913
+ import glob from "fast-glob";
1914
+ var RULES = [
1915
+ {
1916
+ id: "no-any",
1917
+ name: "No any type",
1918
+ description: 'Avoid using the "any" type',
1919
+ severity: "error",
1920
+ pattern: /:\s*any\b(?!\s*\=\>)/g,
1921
+ message: 'Avoid using "any" type. Use proper TypeScript types.',
1922
+ autoFixable: false
1923
+ },
1924
+ {
1925
+ id: "no-ts-ignore",
1926
+ name: "No @ts-ignore",
1927
+ description: "Avoid using @ts-ignore",
1928
+ severity: "error",
1929
+ pattern: /@ts-ignore/g,
1930
+ message: "@ts-ignore bypasses type checking. Fix the underlying issue.",
1931
+ autoFixable: false
1932
+ },
1933
+ {
1934
+ id: "no-console-log",
1935
+ name: "No console.log",
1936
+ description: "Remove debug console.log statements",
1937
+ severity: "warning",
1938
+ pattern: /console\.log\(/g,
1939
+ message: "Remove console.log before committing. Use proper logging.",
1940
+ autoFixable: true
1941
+ },
1942
+ {
1943
+ id: "no-todo",
1944
+ name: "No TODO comments",
1945
+ description: "Complete all TODOs before committing",
1946
+ severity: "warning",
1947
+ pattern: /\/\/\s*TODO:/gi,
1948
+ message: "Complete or remove TODO comments before committing.",
1949
+ autoFixable: false
1950
+ },
1951
+ {
1952
+ id: "no-fixme",
1953
+ name: "No FIXME comments",
1954
+ description: "Fix all FIXME issues before committing",
1955
+ severity: "error",
1956
+ pattern: /\/\/\s*FIXME:/gi,
1957
+ message: "Address FIXME issues before committing.",
1958
+ autoFixable: false
1959
+ },
1960
+ {
1961
+ id: "button-has-handler",
1962
+ name: "Button has onClick",
1963
+ description: "Buttons should have onClick handlers",
1964
+ severity: "error",
1965
+ pattern: /<Button[^>]*(?!onClick)[^>]*>/g,
1966
+ message: "Button must have an onClick handler.",
1967
+ autoFixable: false,
1968
+ validator: (content, match) => {
1969
+ const buttonTag = match[0];
1970
+ return !buttonTag.includes("onClick") && !buttonTag.includes('type="submit"');
1971
+ }
1972
+ },
1973
+ {
1974
+ id: "async-has-try-catch",
1975
+ name: "Async has try/catch",
1976
+ description: "Async functions should have error handling",
1977
+ severity: "error",
1978
+ pattern: /async\s+(?:function\s+\w+|\([^)]*\)\s*=>|\w+\s*=\s*async\s*\([^)]*\)\s*=>)\s*\{[^}]*\}/g,
1979
+ message: "Async function should have try/catch error handling.",
1980
+ autoFixable: false,
1981
+ validator: (content, match) => {
1982
+ const funcBody = match[0];
1983
+ return !funcBody.includes("try") && !funcBody.includes("catch");
1984
+ }
1985
+ },
1986
+ {
1987
+ id: "no-hardcoded-secrets",
1988
+ name: "No hardcoded secrets",
1989
+ description: "Secrets should be in environment variables",
1990
+ severity: "error",
1991
+ pattern: /(sk_live_|sk_test_|pk_live_|pk_test_|api_key\s*=\s*['"][^'"]+['"])/gi,
1992
+ message: "Possible hardcoded secret. Use environment variables.",
1993
+ autoFixable: false
1994
+ },
1995
+ {
1996
+ id: "no-eval",
1997
+ name: "No eval()",
1998
+ description: "Never use eval() - security risk",
1999
+ severity: "error",
2000
+ pattern: /\beval\s*\(/g,
2001
+ message: "Never use eval(). It is a security vulnerability.",
2002
+ autoFixable: false
2003
+ },
2004
+ {
2005
+ id: "no-innerhtml",
2006
+ name: "No innerHTML",
2007
+ description: "Avoid direct innerHTML assignment",
2008
+ severity: "error",
2009
+ pattern: /\.innerHTML\s*=/g,
2010
+ message: "Avoid innerHTML. Use proper React rendering or DOMPurify.",
2011
+ autoFixable: false
2012
+ },
2013
+ {
2014
+ id: "loading-state",
2015
+ name: "Loading state in components",
2016
+ description: "Components with fetch should have loading states",
2017
+ severity: "warning",
2018
+ pattern: /useEffect\s*\([^)]*fetch\([^)]*\)/g,
2019
+ message: "Components fetching data should have loading states.",
2020
+ autoFixable: false,
2021
+ validator: (content) => {
2022
+ return content.includes("fetch(") && !content.includes("isLoading") && !content.includes("loading") && !content.includes("isPending");
2023
+ }
2024
+ },
2025
+ {
2026
+ id: "form-validation",
2027
+ name: "Form has validation",
2028
+ description: "Forms should use Zod validation",
2029
+ severity: "warning",
2030
+ pattern: /<form[^>]*onSubmit/gi,
2031
+ message: "Forms should use Zod schema validation.",
2032
+ autoFixable: false,
2033
+ validator: (content) => {
2034
+ return content.includes("<form") && !content.includes("zodResolver") && !content.includes("z.object");
2035
+ }
2036
+ },
2037
+ {
2038
+ id: "use-client-directive",
2039
+ name: "use client directive",
2040
+ description: 'Client components should have "use client" directive',
2041
+ severity: "error",
2042
+ pattern: /(useState|useEffect|useReducer|useContext|useRef|useCallback|useMemo)\s*\(/g,
2043
+ message: 'Components using hooks must have "use client" directive.',
2044
+ autoFixable: true,
2045
+ validator: (content) => {
2046
+ const hasHooks = /(useState|useEffect|useReducer|useContext|useRef|useCallback|useMemo)\s*\(/.test(content);
2047
+ const hasDirective = content.trim().startsWith("'use client'") || content.trim().startsWith('"use client"');
2048
+ return hasHooks && !hasDirective;
2049
+ }
2050
+ },
2051
+ {
2052
+ id: "key-prop-in-map",
2053
+ name: "Key prop in map",
2054
+ description: "Elements in .map() should have key prop",
2055
+ severity: "error",
2056
+ pattern: /\.map\s*\([^)]*\)\s*=>\s*(?:\(?\s*<[A-Z][^>]*(?!key=)[^>]*>)/g,
2057
+ message: "Elements in .map() must have a unique key prop.",
2058
+ autoFixable: false
2059
+ }
2060
+ ];
2061
+ async function checkCommand(options = {}) {
2062
+ const config = new Config();
2063
+ if (!config.isInProject()) {
2064
+ p3.log.error("Not in a CodeBakers project.");
2065
+ return;
2066
+ }
2067
+ p3.intro(chalk4.bgCyan.black(" CodeBakers Pattern Check "));
2068
+ const spinner10 = p3.spinner();
2069
+ spinner10.start("Analyzing code...");
2070
+ const result = await runPatternCheck(options.fix || false);
2071
+ spinner10.stop("Analysis complete");
2072
+ displayResults(result);
2073
+ if (result.violations.length > 0 && options.fix) {
2074
+ const fixable = result.violations.filter((v) => v.autoFixable);
2075
+ if (fixable.length > 0) {
2076
+ spinner10.start(`Auto-fixing ${fixable.length} violations...`);
2077
+ await autoFix(fixable);
2078
+ spinner10.stop("Auto-fix complete");
2079
+ }
2080
+ }
2081
+ const errors = result.violations.filter((v) => v.severity === "error");
2082
+ if (errors.length > 0) {
2083
+ p3.outro(chalk4.red(`\u274C ${errors.length} errors found. Fix before committing.`));
2084
+ process.exit(1);
2085
+ } else if (result.violations.length > 0) {
2086
+ p3.outro(chalk4.yellow(`\u26A0\uFE0F ${result.violations.length} warnings. Consider fixing.`));
2087
+ } else {
2088
+ p3.outro(chalk4.green("\u2713 All patterns satisfied!"));
2089
+ }
2090
+ }
2091
+ async function runPatternCheck(autoFix2) {
2092
+ const cwd = process.cwd();
2093
+ const violations = [];
2094
+ const files = await glob(["src/**/*.{ts,tsx,js,jsx}"], {
2095
+ cwd,
2096
+ ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**"]
2097
+ });
2098
+ for (const file of files) {
2099
+ const filePath = path4.join(cwd, file);
2100
+ const content = await fs4.readFile(filePath, "utf-8");
2101
+ const lines = content.split("\n");
2102
+ for (const rule of RULES) {
2103
+ if (rule.validator) {
2104
+ const regex = new RegExp(rule.pattern.source, rule.pattern.flags);
2105
+ let match;
2106
+ while ((match = regex.exec(content)) !== null) {
2107
+ if (rule.validator(content, match)) {
2108
+ const line = content.substring(0, match.index).split("\n").length;
2109
+ violations.push({
2110
+ file,
2111
+ line,
2112
+ rule: rule.id,
2113
+ message: rule.message,
2114
+ severity: rule.severity,
2115
+ autoFixable: rule.autoFixable
2116
+ });
2117
+ break;
2118
+ }
2119
+ }
2120
+ } else {
2121
+ const regex = new RegExp(rule.pattern.source, rule.pattern.flags);
2122
+ let match;
2123
+ while ((match = regex.exec(content)) !== null) {
2124
+ const line = content.substring(0, match.index).split("\n").length;
2125
+ violations.push({
2126
+ file,
2127
+ line,
2128
+ rule: rule.id,
2129
+ message: rule.message,
2130
+ severity: rule.severity,
2131
+ autoFixable: rule.autoFixable
2132
+ });
2133
+ }
2134
+ }
2135
+ }
2136
+ }
2137
+ return {
2138
+ violations,
2139
+ filesChecked: files.length,
2140
+ passed: violations.length === 0
2141
+ };
2142
+ }
2143
+ function displayResults(result) {
2144
+ console.log(chalk4.dim(`
2145
+ Checked ${result.filesChecked} files
2146
+ `));
2147
+ if (result.violations.length === 0) {
2148
+ console.log(chalk4.green(" \u2713 No violations found\n"));
2149
+ return;
2150
+ }
2151
+ const byFile = /* @__PURE__ */ new Map();
2152
+ for (const v of result.violations) {
2153
+ if (!byFile.has(v.file)) {
2154
+ byFile.set(v.file, []);
2155
+ }
2156
+ byFile.get(v.file).push(v);
2157
+ }
2158
+ for (const [file, fileViolations] of byFile) {
2159
+ console.log(chalk4.bold(file));
2160
+ for (const v of fileViolations) {
2161
+ const icon = v.severity === "error" ? chalk4.red("\u2717") : chalk4.yellow("\u26A0");
2162
+ const fixable = v.autoFixable ? chalk4.dim(" (auto-fixable)") : "";
2163
+ console.log(` ${icon} Line ${v.line}: ${v.message}${fixable}`);
2164
+ }
2165
+ console.log("");
2166
+ }
2167
+ const errors = result.violations.filter((v) => v.severity === "error").length;
2168
+ const warnings = result.violations.filter((v) => v.severity === "warning").length;
2169
+ console.log(chalk4.bold("Summary:"));
2170
+ if (errors > 0) {
2171
+ console.log(chalk4.red(` ${errors} error(s)`));
2172
+ }
2173
+ if (warnings > 0) {
2174
+ console.log(chalk4.yellow(` ${warnings} warning(s)`));
2175
+ }
2176
+ console.log("");
2177
+ }
2178
+ async function autoFix(violations) {
2179
+ const cwd = process.cwd();
2180
+ const byFile = /* @__PURE__ */ new Map();
2181
+ for (const v of violations) {
2182
+ if (!byFile.has(v.file)) {
2183
+ byFile.set(v.file, []);
2184
+ }
2185
+ byFile.get(v.file).push(v);
2186
+ }
2187
+ for (const [file, fileViolations] of byFile) {
2188
+ const filePath = path4.join(cwd, file);
2189
+ let content = await fs4.readFile(filePath, "utf-8");
2190
+ for (const v of fileViolations) {
2191
+ switch (v.rule) {
2192
+ case "no-console-log":
2193
+ content = content.replace(/console\.log\([^)]*\);?\n?/g, "");
2194
+ break;
2195
+ case "use-client-directive":
2196
+ if (!content.startsWith("'use client'") && !content.startsWith('"use client"')) {
2197
+ content = "'use client';\n\n" + content;
2198
+ }
2199
+ break;
2200
+ }
2201
+ }
2202
+ await fs4.writeFile(filePath, content);
2203
+ }
2204
+ }
2205
+
2206
+ // src/commands/code.ts
2207
+ async function codeCommand(prompt, options = {}) {
2208
+ const config = new Config();
2209
+ if (!config.isConfigured()) {
2210
+ p4.log.error("Please run `codebakers setup` first.");
2211
+ return;
2212
+ }
2213
+ if (!config.isInProject()) {
2214
+ p4.log.error("Not in a CodeBakers project. Run `codebakers init` first.");
2215
+ return;
2216
+ }
2217
+ const anthropicCreds = config.getCredentials("anthropic");
2218
+ if (!anthropicCreds?.apiKey) {
2219
+ p4.log.error("Anthropic API key not configured. Run `codebakers setup`.");
2220
+ return;
2221
+ }
2222
+ const anthropic = new Anthropic({
2223
+ apiKey: anthropicCreds.apiKey
2224
+ });
2225
+ const projectContext = await loadProjectContext();
2226
+ const patterns = await loadPatterns(config);
2227
+ const systemPrompt = buildSystemPrompt(projectContext, patterns);
2228
+ console.log(chalk5.cyan(`
2229
+ \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E
2230
+ \u2502 \u{1F916} CodeBakers AI \u2502
2231
+ \u2502 \u2502
2232
+ \u2502 Project: ${projectContext.name.padEnd(38)}\u2502
2233
+ \u2502 Stack: ${`${projectContext.framework} + ${projectContext.ui}`.padEnd(40)}\u2502
2234
+ \u2502 \u2502
2235
+ \u2502 Type your request or "?" for help \u2502
2236
+ \u2502 Type "exit" or Ctrl+C to quit \u2502
2237
+ \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F
2238
+ `));
2239
+ const messages = [];
2240
+ if (prompt) {
2241
+ await processUserInput(prompt, messages, anthropic, systemPrompt, projectContext, config);
2242
+ }
2243
+ while (true) {
2244
+ const input = await p4.text({
2245
+ message: "",
2246
+ placeholder: "What do you want to build?"
2247
+ });
2248
+ if (p4.isCancel(input)) {
2249
+ p4.outro("Goodbye!");
2250
+ break;
2251
+ }
2252
+ const userInput = input.trim();
2253
+ if (!userInput) continue;
2254
+ if (userInput.toLowerCase() === "exit") {
2255
+ p4.outro("Goodbye!");
2256
+ break;
2257
+ }
2258
+ if (userInput === "?") {
2259
+ showHelp();
2260
+ continue;
2261
+ }
2262
+ if (userInput.startsWith("/")) {
2263
+ await handleSlashCommand(userInput, projectContext, config);
2264
+ continue;
2265
+ }
2266
+ await processUserInput(userInput, messages, anthropic, systemPrompt, projectContext, config);
2267
+ }
2268
+ }
2269
+ async function processUserInput(userInput, messages, anthropic, systemPrompt, projectContext, config) {
2270
+ const spinner10 = p4.spinner();
2271
+ messages.push({ role: "user", content: userInput });
2272
+ const wizardResult = await checkForWizard(userInput);
2273
+ if (wizardResult) {
2274
+ messages[messages.length - 1].content = wizardResult;
2275
+ }
2276
+ try {
2277
+ spinner10.start("Thinking...");
2278
+ const response = await anthropic.messages.create({
2279
+ model: "claude-sonnet-4-20250514",
2280
+ max_tokens: 8192,
2281
+ system: systemPrompt,
2282
+ messages: messages.map((m) => ({
2283
+ role: m.role,
2284
+ content: m.content
2285
+ }))
2286
+ });
2287
+ spinner10.stop("");
2288
+ const assistantMessage = response.content[0].type === "text" ? response.content[0].text : "";
2289
+ messages.push({ role: "assistant", content: assistantMessage });
2290
+ const actions = parseActions(assistantMessage);
2291
+ if (actions.length > 0) {
2292
+ console.log(chalk5.cyan("\n\u{1F4CB} Plan:"));
2293
+ actions.forEach((action, i) => {
2294
+ console.log(chalk5.dim(` ${i + 1}. ${action.description}`));
2295
+ });
2296
+ console.log("");
2297
+ const proceed = await p4.confirm({
2298
+ message: "Execute this plan?",
2299
+ initialValue: true
2300
+ });
2301
+ if (proceed && !p4.isCancel(proceed)) {
2302
+ spinner10.start("Building...");
2303
+ for (const action of actions) {
2304
+ await executeAction(action, spinner10);
2305
+ }
2306
+ spinner10.stop("Build complete");
2307
+ console.log(chalk5.dim("\n\u{1F50D} Running CodeBakers check..."));
2308
+ const checkResult = await runPatternCheck(false);
2309
+ if (checkResult.violations.length > 0) {
2310
+ console.log(chalk5.yellow(`
2311
+ \u26A0\uFE0F ${checkResult.violations.length} pattern violations found`));
2312
+ const autoFix2 = await p4.confirm({
2313
+ message: "Auto-fix violations?",
2314
+ initialValue: true
2315
+ });
2316
+ if (autoFix2 && !p4.isCancel(autoFix2)) {
2317
+ spinner10.start("Auto-fixing...");
2318
+ await autoFixViolations(checkResult.violations, anthropic, systemPrompt);
2319
+ spinner10.stop("Violations fixed");
2320
+ }
2321
+ } else {
2322
+ console.log(chalk5.green("\u2713 All patterns satisfied"));
2323
+ }
2324
+ projectContext.recentChanges.push(...actions.map((a) => a.description));
2325
+ }
2326
+ } else {
2327
+ console.log("\n" + assistantMessage + "\n");
2328
+ }
2329
+ } catch (error) {
2330
+ spinner10.stop("Error");
2331
+ console.log(chalk5.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2332
+ }
2333
+ }
2334
+ function buildSystemPrompt(context, patterns) {
2335
+ return `You are CodeBakers, an AI coding assistant that ALWAYS follows the user's patterns and rules.
2336
+
2337
+ PROJECT CONTEXT:
2338
+ - Name: ${context.name}
2339
+ - Framework: ${context.framework}
2340
+ - UI Library: ${context.ui}
2341
+ - Packages: ${context.packages.join(", ")}
2342
+
2343
+ EXISTING FILES:
2344
+ ${context.existingFiles.slice(0, 50).join("\n")}
2345
+
2346
+ RECENT CHANGES:
2347
+ ${context.recentChanges.slice(-10).join("\n")}
2348
+
2349
+ CODEBAKERS PATTERNS (MUST FOLLOW):
2350
+ ${patterns}
2351
+
2352
+ INSTRUCTIONS:
2353
+ 1. ALWAYS follow the patterns above - they are non-negotiable
2354
+ 2. When creating code, output it in this format:
2355
+
2356
+ <<<ACTION:CREATE_FILE>>>
2357
+ PATH: src/path/to/file.tsx
2358
+ DESCRIPTION: Brief description
2359
+ <<<CONTENT>>>
2360
+ // file content here
2361
+ <<<END_CONTENT>>>
2362
+ <<<END_ACTION>>>
2363
+
2364
+ 3. For editing files:
2365
+
2366
+ <<<ACTION:EDIT_FILE>>>
2367
+ PATH: src/path/to/file.tsx
2368
+ DESCRIPTION: What you're changing
2369
+ <<<FIND>>>
2370
+ // code to find
2371
+ <<<REPLACE>>>
2372
+ // replacement code
2373
+ <<<END_ACTION>>>
2374
+
2375
+ 4. For running commands:
2376
+
2377
+ <<<ACTION:RUN_COMMAND>>>
2378
+ COMMAND: pnpm add some-package
2379
+ DESCRIPTION: Why running this
2380
+ <<<END_ACTION>>>
2381
+
2382
+ 5. Every button MUST have a working onClick handler
2383
+ 6. Every form MUST have proper validation with Zod
2384
+ 7. Every async operation MUST have loading, error, and success states
2385
+ 8. Every list MUST have empty state handling
2386
+ 9. Use Zustand for state management, not prop drilling
2387
+ 10. Put files in the correct folders as specified in patterns
2388
+
2389
+ If you're unsure about requirements, ask clarifying questions before coding.
2390
+ `;
2391
+ }
2392
+ async function loadProjectContext() {
2393
+ const cwd = process.cwd();
2394
+ const codebakersConfig = path5.join(cwd, ".codebakers", "config.json");
2395
+ let projectConfig = {};
2396
+ if (await fs5.pathExists(codebakersConfig)) {
2397
+ projectConfig = await fs5.readJson(codebakersConfig);
2398
+ }
2399
+ const glob3 = (await import("fast-glob")).default;
2400
+ const files = await glob3(["src/**/*.{ts,tsx,js,jsx}"], { cwd });
2401
+ return {
2402
+ name: path5.basename(cwd),
2403
+ framework: projectConfig.framework || "nextjs",
2404
+ ui: projectConfig.ui || "shadcn",
2405
+ packages: projectConfig.packages || [],
2406
+ existingFiles: files,
2407
+ recentChanges: []
2408
+ };
2409
+ }
2410
+ function parseActions(response) {
2411
+ const actions = [];
2412
+ const createFileRegex = /<<<ACTION:CREATE_FILE>>>\s*PATH:\s*(.+?)\s*DESCRIPTION:\s*(.+?)\s*<<<CONTENT>>>\s*([\s\S]+?)\s*<<<END_CONTENT>>>\s*<<<END_ACTION>>>/g;
2413
+ let match;
2414
+ while ((match = createFileRegex.exec(response)) !== null) {
2415
+ actions.push({
2416
+ type: "CREATE_FILE",
2417
+ path: match[1].trim(),
2418
+ description: match[2].trim(),
2419
+ content: match[3].trim()
2420
+ });
2421
+ }
2422
+ const editFileRegex = /<<<ACTION:EDIT_FILE>>>\s*PATH:\s*(.+?)\s*DESCRIPTION:\s*(.+?)\s*<<<FIND>>>\s*([\s\S]+?)\s*<<<REPLACE>>>\s*([\s\S]+?)\s*<<<END_ACTION>>>/g;
2423
+ while ((match = editFileRegex.exec(response)) !== null) {
2424
+ actions.push({
2425
+ type: "EDIT_FILE",
2426
+ path: match[1].trim(),
2427
+ description: match[2].trim(),
2428
+ find: match[3].trim(),
2429
+ replace: match[4].trim()
2430
+ });
2431
+ }
2432
+ const runCommandRegex = /<<<ACTION:RUN_COMMAND>>>\s*COMMAND:\s*(.+?)\s*DESCRIPTION:\s*(.+?)\s*<<<END_ACTION>>>/g;
2433
+ while ((match = runCommandRegex.exec(response)) !== null) {
2434
+ actions.push({
2435
+ type: "RUN_COMMAND",
2436
+ command: match[1].trim(),
2437
+ description: match[2].trim()
2438
+ });
2439
+ }
2440
+ return actions;
2441
+ }
2442
+ async function executeAction(action, spinner10) {
2443
+ const cwd = process.cwd();
2444
+ switch (action.type) {
2445
+ case "CREATE_FILE": {
2446
+ const filePath = path5.join(cwd, action.path);
2447
+ await fs5.ensureDir(path5.dirname(filePath));
2448
+ await fs5.writeFile(filePath, action.content);
2449
+ spinner10.message(`Created ${action.path}`);
2450
+ break;
2451
+ }
2452
+ case "EDIT_FILE": {
2453
+ const filePath = path5.join(cwd, action.path);
2454
+ if (await fs5.pathExists(filePath)) {
2455
+ let content = await fs5.readFile(filePath, "utf-8");
2456
+ if (action.find && content.includes(action.find)) {
2457
+ content = content.replace(action.find, action.replace || "");
2458
+ await fs5.writeFile(filePath, content);
2459
+ spinner10.message(`Edited ${action.path}`);
2460
+ }
2461
+ }
2462
+ break;
2463
+ }
2464
+ case "RUN_COMMAND": {
2465
+ spinner10.message(`Running: ${action.command}`);
2466
+ const [cmd, ...args2] = action.command.split(" ");
2467
+ await execa3(cmd, args2, { cwd, stdio: "pipe" });
2468
+ break;
2469
+ }
2470
+ case "DELETE_FILE": {
2471
+ const filePath = path5.join(cwd, action.path);
2472
+ if (await fs5.pathExists(filePath)) {
2473
+ await fs5.remove(filePath);
2474
+ spinner10.message(`Deleted ${action.path}`);
2475
+ }
2476
+ break;
2477
+ }
2478
+ }
2479
+ }
2480
+ async function checkForWizard(input) {
2481
+ const lower = input.toLowerCase();
2482
+ if (lower.includes("add") || lower.includes("create") || lower.includes("build")) {
2483
+ if (lower.includes("auth") || lower.includes("login") || lower.includes("signup")) {
2484
+ return await runAuthWizard(input);
2485
+ }
2486
+ if (lower.includes("payment") || lower.includes("checkout") || lower.includes("stripe")) {
2487
+ return await runPaymentWizard(input);
2488
+ }
2489
+ if (lower.includes("form")) {
2490
+ return await runFormWizard(input);
2491
+ }
2492
+ if (lower.includes("dashboard")) {
2493
+ return await runDashboardWizard(input);
2494
+ }
2495
+ if (lower.includes("chat") || lower.includes("ai")) {
2496
+ return await runAIWizard(input);
2497
+ }
2498
+ }
2499
+ return null;
2500
+ }
2501
+ async function runAuthWizard(originalInput) {
2502
+ console.log(chalk5.cyan("\n\u{1F510} Auth Wizard"));
2503
+ const authType = await p4.select({
2504
+ message: "What type of authentication?",
2505
+ options: [
2506
+ { value: "magic-link", label: "\u{1F517} Magic link (email)" },
2507
+ { value: "password", label: "\u{1F511} Password (email + password)" },
2508
+ { value: "social", label: "\u{1F310} Social login (Google, GitHub)" },
2509
+ { value: "phone", label: "\u{1F4F1} Phone (SMS code)" }
2510
+ ]
2511
+ });
2512
+ if (p4.isCancel(authType)) return originalInput;
2513
+ const pages = await p4.multiselect({
2514
+ message: "What pages do you need?",
2515
+ options: [
2516
+ { value: "login", label: "Login page" },
2517
+ { value: "signup", label: "Signup page" },
2518
+ { value: "forgot", label: "Forgot password" },
2519
+ { value: "reset", label: "Reset password" },
2520
+ { value: "verify", label: "Email verification" }
2521
+ ],
2522
+ initialValues: ["login", "signup"]
2523
+ });
2524
+ if (p4.isCancel(pages)) return originalInput;
2525
+ const redirect = await p4.text({
2526
+ message: "Where should users go after login?",
2527
+ placeholder: "/dashboard",
2528
+ initialValue: "/dashboard"
2529
+ });
2530
+ return `Create a complete authentication system with:
2531
+ - Auth type: ${authType}
2532
+ - Pages: ${pages.join(", ")}
2533
+ - Redirect after login: ${redirect}
2534
+ - Use Supabase Auth
2535
+ - Include loading states, error handling, form validation
2536
+ - Follow all CodeBakers patterns`;
2537
+ }
2538
+ async function runPaymentWizard(originalInput) {
2539
+ console.log(chalk5.cyan("\n\u{1F4B3} Payment Wizard"));
2540
+ const paymentType = await p4.select({
2541
+ message: "What type of payment flow?",
2542
+ options: [
2543
+ { value: "one-time", label: "\u{1F4B5} One-time purchase" },
2544
+ { value: "subscription", label: "\u{1F504} Subscription (recurring)" },
2545
+ { value: "both", label: "\u{1F4E6} Both" }
2546
+ ]
2547
+ });
2548
+ if (p4.isCancel(paymentType)) return originalInput;
2549
+ const features = await p4.multiselect({
2550
+ message: "What features?",
2551
+ options: [
2552
+ { value: "checkout", label: "Checkout page" },
2553
+ { value: "webhook", label: "Webhook handler" },
2554
+ { value: "portal", label: "Customer portal" },
2555
+ { value: "invoices", label: "Invoice history" }
2556
+ ],
2557
+ initialValues: ["checkout", "webhook"]
2558
+ });
2559
+ if (p4.isCancel(features)) return originalInput;
2560
+ return `Create a complete Stripe payment system with:
2561
+ - Payment type: ${paymentType}
2562
+ - Features: ${features.join(", ")}
2563
+ - Use Stripe SDK
2564
+ - Include webhook signature verification
2565
+ - Handle all error cases
2566
+ - Follow all CodeBakers patterns`;
2567
+ }
2568
+ async function runFormWizard(originalInput) {
2569
+ console.log(chalk5.cyan("\n\u{1F4DD} Form Wizard"));
2570
+ const formName = await p4.text({
2571
+ message: "What is this form for?",
2572
+ placeholder: "Contact form"
2573
+ });
2574
+ if (p4.isCancel(formName)) return originalInput;
2575
+ return `Create a ${formName} form with:
2576
+ - React Hook Form for form handling
2577
+ - Zod validation schema
2578
+ - Loading state during submission
2579
+ - Error messages for each field
2580
+ - Success toast on completion
2581
+ - Proper accessibility (aria labels)
2582
+ - Follow all CodeBakers patterns`;
2583
+ }
2584
+ async function runDashboardWizard(originalInput) {
2585
+ console.log(chalk5.cyan("\n\u{1F4CA} Dashboard Wizard"));
2586
+ const components = await p4.multiselect({
2587
+ message: "What components?",
2588
+ options: [
2589
+ { value: "stats", label: "Stats cards" },
2590
+ { value: "chart", label: "Charts/graphs" },
2591
+ { value: "table", label: "Data table" },
2592
+ { value: "activity", label: "Activity feed" },
2593
+ { value: "sidebar", label: "Sidebar navigation" }
2594
+ ],
2595
+ initialValues: ["stats", "sidebar"]
2596
+ });
2597
+ if (p4.isCancel(components)) return originalInput;
2598
+ return `Create a dashboard with:
2599
+ - Components: ${components.join(", ")}
2600
+ - Responsive layout
2601
+ - Loading skeletons
2602
+ - Empty states
2603
+ - Error boundaries
2604
+ - Follow all CodeBakers patterns`;
2605
+ }
2606
+ async function runAIWizard(originalInput) {
2607
+ console.log(chalk5.cyan("\n\u{1F916} AI Feature Wizard"));
2608
+ const aiType = await p4.select({
2609
+ message: "What AI feature?",
2610
+ options: [
2611
+ { value: "chat", label: "\u{1F4AC} Chat interface" },
2612
+ { value: "generate", label: "\u{1F4DD} Content generation" },
2613
+ { value: "analyze", label: "\u{1F50D} Data analysis" }
2614
+ ]
2615
+ });
2616
+ if (p4.isCancel(aiType)) return originalInput;
2617
+ const streaming = await p4.confirm({
2618
+ message: "Use streaming responses?",
2619
+ initialValue: true
2620
+ });
2621
+ return `Create an AI ${aiType} feature with:
2622
+ - Streaming: ${streaming ? "Yes" : "No"}
2623
+ - Use Anthropic Claude API
2624
+ - Include conversation history
2625
+ - Loading indicators
2626
+ - Error handling
2627
+ - Stop generation button
2628
+ - Follow all CodeBakers patterns`;
2629
+ }
2630
+ async function handleSlashCommand(command, context, config) {
2631
+ const [cmd, ...args2] = command.slice(1).split(" ");
2632
+ switch (cmd.toLowerCase()) {
2633
+ case "deploy":
2634
+ console.log(chalk5.dim("Running deploy..."));
2635
+ break;
2636
+ case "check":
2637
+ console.log(chalk5.dim("Running pattern check..."));
2638
+ await runPatternCheck(false);
2639
+ break;
2640
+ case "status":
2641
+ console.log(chalk5.cyan(`
2642
+ Project: ${context.name}
2643
+ Framework: ${context.framework}
2644
+ UI: ${context.ui}
2645
+ Files: ${context.existingFiles.length}
2646
+ `));
2647
+ break;
2648
+ case "undo":
2649
+ console.log(chalk5.yellow("Undo not yet implemented"));
2650
+ break;
2651
+ case "help":
2652
+ showHelp();
2653
+ break;
2654
+ default:
2655
+ console.log(chalk5.dim(`Unknown command: ${cmd}`));
2656
+ }
2657
+ }
2658
+ function showHelp() {
2659
+ console.log(chalk5.cyan(`
2660
+ Available commands:
2661
+
2662
+ /deploy Deploy to production
2663
+ /check Run pattern check
2664
+ /status Show project status
2665
+ /undo Undo last change
2666
+ /help Show this help
2667
+
2668
+ Tips:
2669
+ \u2022 Just describe what you want to build
2670
+ \u2022 I'll ask clarifying questions if needed
2671
+ \u2022 All code follows your CodeBakers patterns
2672
+ \u2022 Patterns are auto-checked after every change
2673
+ `));
2674
+ }
2675
+ async function autoFixViolations(violations, anthropic, systemPrompt) {
2676
+ const byFile = /* @__PURE__ */ new Map();
2677
+ for (const v of violations) {
2678
+ if (!byFile.has(v.file)) {
2679
+ byFile.set(v.file, []);
2680
+ }
2681
+ byFile.get(v.file).push(v);
2682
+ }
2683
+ for (const [file, fileViolations] of byFile) {
2684
+ const content = await fs5.readFile(path5.join(process.cwd(), file), "utf-8");
2685
+ const fixPrompt = `Fix these violations in ${file}:
2686
+
2687
+ ${fileViolations.map((v) => `- ${v.rule}: ${v.message}`).join("\n")}
2688
+
2689
+ Current file content:
2690
+ \`\`\`
2691
+ ${content}
2692
+ \`\`\`
2693
+
2694
+ Output only the fixed file content, no explanation.`;
2695
+ const response = await anthropic.messages.create({
2696
+ model: "claude-sonnet-4-20250514",
2697
+ max_tokens: 8192,
2698
+ system: systemPrompt,
2699
+ messages: [{ role: "user", content: fixPrompt }]
2700
+ });
2701
+ const fixedContent = response.content[0].type === "text" ? response.content[0].text : "";
2702
+ const codeMatch = fixedContent.match(/```(?:tsx?|jsx?)?\n([\s\S]+?)\n```/);
2703
+ const finalContent = codeMatch ? codeMatch[1] : fixedContent;
2704
+ await fs5.writeFile(path5.join(process.cwd(), file), finalContent);
2705
+ }
2706
+ }
2707
+
2708
+ // src/commands/deploy.ts
2709
+ import * as p5 from "@clack/prompts";
2710
+ import chalk6 from "chalk";
2711
+ import { execa as execa4 } from "execa";
2712
+ import * as fs6 from "fs-extra";
2713
+ import * as path6 from "path";
2714
+ import Anthropic2 from "@anthropic-ai/sdk";
2715
+ async function deployCommand(options = {}) {
2716
+ const config = new Config();
2717
+ if (!config.isInProject()) {
2718
+ p5.log.error("Not in a CodeBakers project.");
2719
+ return;
2720
+ }
2721
+ p5.intro(chalk6.bgCyan.black(" Deploy to Production "));
2722
+ const spinner10 = p5.spinner();
2723
+ if (options.check !== false) {
2724
+ spinner10.start("Running CodeBakers check...");
2725
+ const checkResult = await runPatternCheck(false);
2726
+ if (!checkResult.passed) {
2727
+ spinner10.stop("");
2728
+ const errors = checkResult.violations.filter((v) => v.severity === "error");
2729
+ if (errors.length > 0) {
2730
+ p5.log.error(`${errors.length} pattern violations found. Fix before deploying.`);
2731
+ const showViolations = await p5.confirm({
2732
+ message: "Show violations?",
2733
+ initialValue: true
2734
+ });
2735
+ if (showViolations && !p5.isCancel(showViolations)) {
2736
+ for (const v of errors) {
2737
+ console.log(chalk6.red(` \u2717 ${v.file}:${v.line} - ${v.message}`));
2738
+ }
2739
+ }
2740
+ const autoFix2 = await p5.confirm({
2741
+ message: "Attempt auto-fix with AI?",
2742
+ initialValue: true
2743
+ });
2744
+ if (!autoFix2 || p5.isCancel(autoFix2)) {
2745
+ p5.outro(chalk6.red("Deploy cancelled."));
2746
+ return;
2747
+ }
2748
+ spinner10.start("Auto-fixing with AI...");
2749
+ await autoFixWithAI(config, errors);
2750
+ spinner10.stop("Auto-fix complete");
2751
+ const recheck = await runPatternCheck(false);
2752
+ if (!recheck.passed) {
2753
+ p5.log.error("Some violations remain. Manual fix required.");
2754
+ p5.outro(chalk6.red("Deploy cancelled."));
2755
+ return;
2756
+ }
2757
+ }
2758
+ }
2759
+ spinner10.stop("Pattern check passed");
2760
+ }
2761
+ spinner10.start("Running TypeScript check...");
2762
+ try {
2763
+ await execa4("npx", ["tsc", "--noEmit"], { cwd: process.cwd() });
2764
+ spinner10.stop("TypeScript check passed");
2765
+ } catch (error) {
2766
+ spinner10.stop("");
2767
+ p5.log.error("TypeScript errors found.");
2768
+ const fix = await p5.confirm({
2769
+ message: "Attempt to fix TypeScript errors?",
2770
+ initialValue: true
2771
+ });
2772
+ if (fix && !p5.isCancel(fix)) {
2773
+ spinner10.start("Fixing TypeScript errors...");
2774
+ const fixed = await fixTypeScriptErrors(config);
2775
+ spinner10.stop(fixed ? "TypeScript errors fixed" : "Could not auto-fix all errors");
2776
+ if (!fixed) {
2777
+ p5.outro(chalk6.red("Deploy cancelled."));
2778
+ return;
2779
+ }
2780
+ } else {
2781
+ p5.outro(chalk6.red("Deploy cancelled."));
2782
+ return;
2783
+ }
2784
+ }
2785
+ spinner10.start("Building project...");
2786
+ try {
2787
+ await execa4("pnpm", ["build"], { cwd: process.cwd() });
2788
+ spinner10.stop("Build successful");
2789
+ } catch (error) {
2790
+ spinner10.stop("");
2791
+ p5.log.error("Build failed.");
2792
+ const errorOutput = error instanceof Error ? error.message : "Unknown error";
2793
+ console.log(chalk6.dim(errorOutput));
2794
+ const fix = await p5.confirm({
2795
+ message: "Attempt to fix build errors with AI?",
2796
+ initialValue: true
2797
+ });
2798
+ if (fix && !p5.isCancel(fix)) {
2799
+ spinner10.start("Fixing build errors...");
2800
+ const fixed = await fixBuildErrors(config, errorOutput);
2801
+ spinner10.stop(fixed ? "Build errors fixed" : "Could not auto-fix");
2802
+ if (fixed) {
2803
+ spinner10.start("Retrying build...");
2804
+ try {
2805
+ await execa4("pnpm", ["build"], { cwd: process.cwd() });
2806
+ spinner10.stop("Build successful");
2807
+ } catch {
2808
+ spinner10.stop("Build still failing");
2809
+ p5.outro(chalk6.red("Deploy cancelled."));
2810
+ return;
2811
+ }
2812
+ } else {
2813
+ p5.outro(chalk6.red("Deploy cancelled."));
2814
+ return;
2815
+ }
2816
+ } else {
2817
+ p5.outro(chalk6.red("Deploy cancelled."));
2818
+ return;
2819
+ }
2820
+ }
2821
+ spinner10.start("Checking for uncommitted changes...");
2822
+ const { stdout: gitStatus } = await execa4("git", ["status", "--porcelain"], { cwd: process.cwd() });
2823
+ if (gitStatus.trim()) {
2824
+ spinner10.stop("");
2825
+ const commit = await p5.confirm({
2826
+ message: "You have uncommitted changes. Commit before deploying?",
2827
+ initialValue: true
2828
+ });
2829
+ if (commit && !p5.isCancel(commit)) {
2830
+ const message = await p5.text({
2831
+ message: "Commit message:",
2832
+ placeholder: "Pre-deploy changes",
2833
+ initialValue: "Pre-deploy changes"
2834
+ });
2835
+ if (!p5.isCancel(message)) {
2836
+ await execa4("git", ["add", "."], { cwd: process.cwd() });
2837
+ await execa4("git", ["commit", "-m", message], { cwd: process.cwd() });
2838
+ spinner10.start("Pushing to GitHub...");
2839
+ await execa4("git", ["push"], { cwd: process.cwd() });
2840
+ spinner10.stop("Pushed to GitHub");
2841
+ }
2842
+ }
2843
+ } else {
2844
+ spinner10.stop("No uncommitted changes");
2845
+ }
2846
+ const deployType = options.preview ? "preview" : "production";
2847
+ spinner10.start(`Deploying to ${deployType}...`);
2848
+ try {
2849
+ const vercel = new VercelService(config);
2850
+ const deployment = await vercel.deploy(process.cwd(), !options.preview);
2851
+ spinner10.stop("Deployment complete!");
2852
+ console.log(boxedOutput(`
2853
+ ${chalk6.green("\u2713")} Deployed successfully!
2854
+
2855
+ ${chalk6.bold("URL:")} ${deployment.url}
2856
+ ${chalk6.bold("Type:")} ${deployType}
2857
+ ${chalk6.bold("Time:")} ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}
2858
+
2859
+ ${chalk6.dim("View in Vercel Dashboard:")}
2860
+ ${chalk6.dim(deployment.dashboardUrl || "https://vercel.com/dashboard")}
2861
+ `));
2862
+ } catch (error) {
2863
+ spinner10.stop("");
2864
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
2865
+ p5.log.error(`Deployment failed: ${errorMsg}`);
2866
+ if (errorMsg.includes("Build failed")) {
2867
+ const retry = await p5.confirm({
2868
+ message: "Attempt to fix and retry?",
2869
+ initialValue: true
2870
+ });
2871
+ if (retry && !p5.isCancel(retry)) {
2872
+ spinner10.start("Analyzing Vercel build error...");
2873
+ spinner10.stop("Fix attempted");
2874
+ }
2875
+ }
2876
+ p5.outro(chalk6.red("Deploy failed."));
2877
+ }
2878
+ }
2879
+ async function autoFixWithAI(config, violations) {
2880
+ const anthropicCreds = config.getCredentials("anthropic");
2881
+ if (!anthropicCreds?.apiKey) {
2882
+ throw new Error("Anthropic API key not configured");
2883
+ }
2884
+ const anthropic = new Anthropic2({
2885
+ apiKey: anthropicCreds.apiKey
2886
+ });
2887
+ const byFile = /* @__PURE__ */ new Map();
2888
+ for (const v of violations) {
2889
+ if (!byFile.has(v.file)) {
2890
+ byFile.set(v.file, []);
2891
+ }
2892
+ byFile.get(v.file).push(v);
2893
+ }
2894
+ for (const [file, fileViolations] of byFile) {
2895
+ const filePath = path6.join(process.cwd(), file);
2896
+ const content = await fs6.readFile(filePath, "utf-8");
2897
+ const prompt = `Fix these CodeBakers pattern violations in this file:
2898
+
2899
+ Violations:
2900
+ ${fileViolations.map((v) => `- Line ${v.line}: ${v.rule} - ${v.message}`).join("\n")}
2901
+
2902
+ Current file content:
2903
+ \`\`\`typescript
2904
+ ${content}
2905
+ \`\`\`
2906
+
2907
+ Output ONLY the corrected file content, no explanations. Keep all existing functionality.`;
2908
+ const response = await anthropic.messages.create({
2909
+ model: "claude-sonnet-4-20250514",
2910
+ max_tokens: 8192,
2911
+ messages: [{ role: "user", content: prompt }]
2912
+ });
2913
+ let fixed = response.content[0].type === "text" ? response.content[0].text : "";
2914
+ const codeMatch = fixed.match(/```(?:typescript|tsx?)?\n([\s\S]+?)\n```/);
2915
+ if (codeMatch) {
2916
+ fixed = codeMatch[1];
2917
+ }
2918
+ await fs6.writeFile(filePath, fixed);
2919
+ }
2920
+ }
2921
+ async function fixTypeScriptErrors(config) {
2922
+ try {
2923
+ const { stderr } = await execa4("npx", ["tsc", "--noEmit"], {
2924
+ cwd: process.cwd(),
2925
+ reject: false
2926
+ });
2927
+ if (!stderr) return true;
2928
+ const anthropicCreds = config.getCredentials("anthropic");
2929
+ if (!anthropicCreds?.apiKey) {
2930
+ return false;
2931
+ }
2932
+ const anthropic = new Anthropic2({
2933
+ apiKey: anthropicCreds.apiKey
2934
+ });
2935
+ const errorLines = stderr.split("\n").filter((line) => line.includes("error TS"));
2936
+ for (const errorLine of errorLines.slice(0, 10)) {
2937
+ const match = errorLine.match(/^(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)$/);
2938
+ if (!match) continue;
2939
+ const [, file, line, , , message] = match;
2940
+ const filePath = path6.join(process.cwd(), file);
2941
+ if (!await fs6.pathExists(filePath)) continue;
2942
+ const content = await fs6.readFile(filePath, "utf-8");
2943
+ const prompt = `Fix this TypeScript error:
2944
+
2945
+ File: ${file}
2946
+ Line: ${line}
2947
+ Error: ${message}
2948
+
2949
+ Current file content:
2950
+ \`\`\`typescript
2951
+ ${content}
2952
+ \`\`\`
2953
+
2954
+ Output ONLY the corrected file content, no explanations.`;
2955
+ const response = await anthropic.messages.create({
2956
+ model: "claude-sonnet-4-20250514",
2957
+ max_tokens: 8192,
2958
+ messages: [{ role: "user", content: prompt }]
2959
+ });
2960
+ let fixed = response.content[0].type === "text" ? response.content[0].text : "";
2961
+ const codeMatch = fixed.match(/```(?:typescript|tsx?)?\n([\s\S]+?)\n```/);
2962
+ if (codeMatch) {
2963
+ fixed = codeMatch[1];
2964
+ }
2965
+ await fs6.writeFile(filePath, fixed);
2966
+ }
2967
+ return true;
2968
+ } catch {
2969
+ return false;
2970
+ }
2971
+ }
2972
+ async function fixBuildErrors(config, errorOutput) {
2973
+ const anthropicCreds = config.getCredentials("anthropic");
2974
+ if (!anthropicCreds?.apiKey) {
2975
+ return false;
2976
+ }
2977
+ const anthropic = new Anthropic2({
2978
+ apiKey: anthropicCreds.apiKey
2979
+ });
2980
+ const fileMatch = errorOutput.match(/(?:Error|error).*?(?:in|at)\s+(.+?\.tsx?)(?::|$)/);
2981
+ if (!fileMatch) return false;
2982
+ const file = fileMatch[1];
2983
+ const filePath = path6.join(process.cwd(), file);
2984
+ if (!await fs6.pathExists(filePath)) return false;
2985
+ const content = await fs6.readFile(filePath, "utf-8");
2986
+ const prompt = `Fix this build error:
2987
+
2988
+ Error output:
2989
+ ${errorOutput}
2990
+
2991
+ File: ${file}
2992
+
2993
+ Current file content:
2994
+ \`\`\`typescript
2995
+ ${content}
2996
+ \`\`\`
2997
+
2998
+ Output ONLY the corrected file content, no explanations.`;
2999
+ try {
3000
+ const response = await anthropic.messages.create({
3001
+ model: "claude-sonnet-4-20250514",
3002
+ max_tokens: 8192,
3003
+ messages: [{ role: "user", content: prompt }]
3004
+ });
3005
+ let fixed = response.content[0].type === "text" ? response.content[0].text : "";
3006
+ const codeMatch = fixed.match(/```(?:typescript|tsx?)?\n([\s\S]+?)\n```/);
3007
+ if (codeMatch) {
3008
+ fixed = codeMatch[1];
3009
+ }
3010
+ await fs6.writeFile(filePath, fixed);
3011
+ return true;
3012
+ } catch {
3013
+ return false;
3014
+ }
3015
+ }
3016
+ function boxedOutput(content) {
3017
+ const lines = content.split("\n");
3018
+ const maxLength = Math.max(...lines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "").length));
3019
+ const border = "\u2500".repeat(maxLength + 2);
3020
+ return `
3021
+ \u256D${border}\u256E
3022
+ ${lines.map((l) => `\u2502 ${l.padEnd(maxLength)} \u2502`).join("\n")}
3023
+ \u2570${border}\u256F`;
3024
+ }
3025
+
3026
+ // src/commands/connect.ts
3027
+ async function connectCommand(service) {
3028
+ const config = new Config();
3029
+ await setupCommand();
3030
+ }
3031
+
3032
+ // src/commands/status.ts
3033
+ import * as p6 from "@clack/prompts";
3034
+ import chalk7 from "chalk";
3035
+ import * as fs7 from "fs-extra";
3036
+ import * as path7 from "path";
3037
+ async function statusCommand() {
3038
+ const config = new Config();
3039
+ const cwd = process.cwd();
3040
+ p6.intro(chalk7.bgCyan.black(" Project Status "));
3041
+ if (!config.isInProject()) {
3042
+ const projects = config.getProjects();
3043
+ if (projects.length === 0) {
3044
+ p6.log.info("No projects found. Run `codebakers init` to create one.");
3045
+ return;
3046
+ }
3047
+ console.log(chalk7.bold("\nYour Projects:\n"));
3048
+ for (const project of projects) {
3049
+ const exists = await fs7.pathExists(project.path);
3050
+ const status = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
3051
+ console.log(` ${status} ${project.name}`);
3052
+ console.log(chalk7.dim(` ${project.path}`));
3053
+ console.log("");
3054
+ }
3055
+ return;
3056
+ }
3057
+ const projectConfig = await fs7.readJson(
3058
+ path7.join(cwd, ".codebakers", "config.json")
3059
+ ).catch(() => ({}));
3060
+ const packageJson = await fs7.readJson(
3061
+ path7.join(cwd, "package.json")
3062
+ ).catch(() => ({}));
3063
+ console.log(`
3064
+ ${chalk7.bold("Project:")} ${packageJson.name || path7.basename(cwd)}
3065
+ ${chalk7.bold("Framework:")} ${projectConfig.framework || "unknown"}
3066
+ ${chalk7.bold("UI:")} ${projectConfig.ui || "unknown"}
3067
+
3068
+ ${chalk7.bold("Quick Actions:")}
3069
+ ${chalk7.cyan("codebakers code")} \u2014 AI coding agent
3070
+ ${chalk7.cyan("codebakers check")} \u2014 Pattern enforcement
3071
+ ${chalk7.cyan("codebakers deploy")} \u2014 Deploy to production
3072
+ `);
3073
+ p6.outro("");
3074
+ }
3075
+
3076
+ // src/commands/gateway.ts
3077
+ import * as p7 from "@clack/prompts";
3078
+ import chalk8 from "chalk";
3079
+ async function gatewayCommand(options = {}) {
3080
+ const config = new Config();
3081
+ p7.intro(chalk8.bgCyan.black(" Channel Gateway "));
3082
+ if (options.start) {
3083
+ await startAllChannels(config);
3084
+ return;
3085
+ }
3086
+ if (options.stop) {
3087
+ await stopAllChannels(config);
3088
+ return;
3089
+ }
3090
+ if (options.status) {
3091
+ await showGatewayStatus(config);
3092
+ return;
3093
+ }
3094
+ const action = await p7.select({
3095
+ message: "What do you want to do?",
3096
+ options: [
3097
+ { value: "status", label: "\u{1F4CA} View status", hint: "see all channels" },
3098
+ { value: "connect", label: "\u{1F517} Connect channel", hint: "add WhatsApp, Telegram, etc." },
3099
+ { value: "start", label: "\u25B6\uFE0F Start gateway", hint: "start receiving messages" },
3100
+ { value: "stop", label: "\u23F9\uFE0F Stop gateway", hint: "pause all channels" },
3101
+ { value: "deploy", label: "\u{1F680} Deploy to cloud", hint: "always-on hosting" },
3102
+ { value: "back", label: "\u2190 Back" }
3103
+ ]
3104
+ });
3105
+ if (p7.isCancel(action) || action === "back") {
3106
+ return;
3107
+ }
3108
+ switch (action) {
3109
+ case "status":
3110
+ await showGatewayStatus(config);
3111
+ break;
3112
+ case "connect":
3113
+ await connectChannelWizard(config);
3114
+ break;
3115
+ case "start":
3116
+ await startAllChannels(config);
3117
+ break;
3118
+ case "stop":
3119
+ await stopAllChannels(config);
3120
+ break;
3121
+ case "deploy":
3122
+ await deployGatewayWizard(config);
3123
+ break;
3124
+ }
3125
+ }
3126
+ async function showGatewayStatus(config) {
3127
+ const channels = await getChannelStatuses(config);
3128
+ console.log(chalk8.bold("\n\u{1F4F1} Channel Gateway Status\n"));
3129
+ for (const channel of channels) {
3130
+ const statusIcon = {
3131
+ running: chalk8.green("\u25CF"),
3132
+ stopped: chalk8.gray("\u25CB"),
3133
+ error: chalk8.red("\u2717"),
3134
+ connecting: chalk8.yellow("\u25D0")
3135
+ }[channel.status];
3136
+ const statusText = {
3137
+ running: chalk8.green("Running"),
3138
+ stopped: chalk8.gray("Stopped"),
3139
+ error: chalk8.red("Error"),
3140
+ connecting: chalk8.yellow("Connecting...")
3141
+ }[channel.status];
3142
+ console.log(` ${channel.icon} ${channel.name.padEnd(15)} ${statusIcon} ${statusText}`);
3143
+ }
3144
+ console.log("");
3145
+ }
3146
+ async function getChannelStatuses(config) {
3147
+ const channels = [
3148
+ {
3149
+ id: "whatsapp",
3150
+ name: "WhatsApp",
3151
+ description: "WhatsApp via Baileys",
3152
+ icon: "\u{1F4AC}",
3153
+ connected: !!config.getChannelConfig("whatsapp")?.enabled,
3154
+ status: config.getChannelConfig("whatsapp")?.enabled ? "stopped" : "stopped"
3155
+ },
3156
+ {
3157
+ id: "telegram",
3158
+ name: "Telegram",
3159
+ description: "Telegram Bot API",
3160
+ icon: "\u2708\uFE0F",
3161
+ connected: !!config.getChannelConfig("telegram")?.enabled,
3162
+ status: config.getChannelConfig("telegram")?.enabled ? "stopped" : "stopped"
3163
+ },
3164
+ {
3165
+ id: "discord",
3166
+ name: "Discord",
3167
+ description: "Discord.js bot",
3168
+ icon: "\u{1F3AE}",
3169
+ connected: !!config.getChannelConfig("discord")?.enabled,
3170
+ status: config.getChannelConfig("discord")?.enabled ? "stopped" : "stopped"
3171
+ },
3172
+ {
3173
+ id: "slack",
3174
+ name: "Slack",
3175
+ description: "Slack Bot API",
3176
+ icon: "\u{1F4BC}",
3177
+ connected: !!config.getChannelConfig("slack")?.enabled,
3178
+ status: config.getChannelConfig("slack")?.enabled ? "stopped" : "stopped"
3179
+ },
3180
+ {
3181
+ id: "sms",
3182
+ name: "SMS",
3183
+ description: "Twilio SMS",
3184
+ icon: "\u{1F4F1}",
3185
+ connected: !!config.getCredentials("twilio")?.accountSid,
3186
+ status: config.getCredentials("twilio")?.accountSid ? "stopped" : "stopped"
3187
+ }
3188
+ ];
3189
+ return channels;
3190
+ }
3191
+ async function connectChannelWizard(config) {
3192
+ const channel = await p7.select({
3193
+ message: "Which channel do you want to connect?",
3194
+ options: [
3195
+ { value: "whatsapp", label: "\u{1F4AC} WhatsApp", hint: "Personal or Business" },
3196
+ { value: "telegram", label: "\u2708\uFE0F Telegram", hint: "Create a bot" },
3197
+ { value: "discord", label: "\u{1F3AE} Discord", hint: "Server bot" },
3198
+ { value: "slack", label: "\u{1F4BC} Slack", hint: "Workspace app" },
3199
+ { value: "sms", label: "\u{1F4F1} SMS", hint: "via Twilio" },
3200
+ { value: "imessage", label: "\u{1F34E} iMessage", hint: "Mac only" }
3201
+ ]
3202
+ });
3203
+ if (p7.isCancel(channel)) return;
3204
+ switch (channel) {
3205
+ case "whatsapp":
3206
+ await connectWhatsApp(config);
3207
+ break;
3208
+ case "telegram":
3209
+ await connectTelegram(config);
3210
+ break;
3211
+ case "discord":
3212
+ await connectDiscord(config);
3213
+ break;
3214
+ case "slack":
3215
+ await connectSlack(config);
3216
+ break;
3217
+ case "sms":
3218
+ await connectSMS(config);
3219
+ break;
3220
+ case "imessage":
3221
+ await connectiMessage(config);
3222
+ break;
3223
+ }
3224
+ }
3225
+ async function connectWhatsApp(config) {
3226
+ p7.log.info(chalk8.bold("WhatsApp Setup"));
3227
+ p7.log.info(chalk8.dim(`
3228
+ WhatsApp uses QR code authentication via Baileys library.
3229
+ Your phone needs to scan a QR code to connect.
3230
+
3231
+ Note: This is for personal accounts. For WhatsApp Business API,
3232
+ you'll need a Meta Business account.
3233
+ `));
3234
+ const proceed = await p7.confirm({
3235
+ message: "Ready to scan QR code?",
3236
+ initialValue: true
3237
+ });
3238
+ if (!proceed || p7.isCancel(proceed)) return;
3239
+ const spinner10 = p7.spinner();
3240
+ spinner10.start("Generating QR code...");
3241
+ try {
3242
+ spinner10.stop("");
3243
+ console.log(chalk8.cyan(`
3244
+ \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
3245
+ \u2551 \u2551
3246
+ \u2551 \u2584\u2584\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2584\u2584 \u2551
3247
+ \u2551 \u2588 \u2584\u2584\u2584 \u2588 \u2580\u2588\u2580\u2584\u2588 \u2588 \u2584\u2584\u2584 \u2588 \u2551
3248
+ \u2551 \u2588 \u2588\u2588\u2588 \u2588 \u2584\u2580 \u2580\u2584 \u2588 \u2588\u2588\u2588 \u2588 \u2551
3249
+ \u2551 \u2588\u2584\u2584\u2584\u2584\u2584\u2588 \u2588 \u2584\u2580\u2588 \u2588\u2584\u2584\u2584\u2584\u2584\u2588 \u2551
3250
+ \u2551 \u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2588\u2588\u2580\u2580\u2584 \u2584 \u2584 \u2584 \u2551
3251
+ \u2551 \u2588\u2588\u2580\u2580\u2580\u2584\u2584\u2580\u2584\u2580\u2584\u2584\u2588\u2580\u2588\u2588\u2580\u2584\u2588\u2584\u2584 \u2551
3252
+ \u2551 \u2584\u2584\u2584\u2584\u2584\u2584\u2584 \u2588\u2584\u2580\u2584\u2580 \u2584\u2588\u2588\u2584\u2588\u2584 \u2551
3253
+ \u2551 \u2588 \u2584\u2584\u2584 \u2588 \u2588\u2580\u2580\u2584\u2584\u2584\u2584\u2580\u2584 \u2551
3254
+ \u2551 \u2588 \u2588\u2588\u2588 \u2588 \u2584\u2588\u2588\u2588\u2584\u2588\u2580\u2588\u2584\u2584\u2584\u2588 \u2551
3255
+ \u2551 \u2588\u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2580\u2584\u2580\u2580\u2584\u2588\u2584\u2580\u2588 \u2551
3256
+ \u2551 \u2551
3257
+ \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\u255D
3258
+
3259
+ Scan this QR code with WhatsApp:
3260
+ 1. Open WhatsApp on your phone
3261
+ 2. Go to Settings > Linked Devices
3262
+ 3. Tap "Link a Device"
3263
+ 4. Scan the QR code above
3264
+ `));
3265
+ const connected = await p7.confirm({
3266
+ message: "Did you scan the QR code successfully?",
3267
+ initialValue: false
3268
+ });
3269
+ if (connected && !p7.isCancel(connected)) {
3270
+ config.setChannelConfig("whatsapp", { enabled: true });
3271
+ p7.log.success("WhatsApp connected!");
3272
+ }
3273
+ } catch (error) {
3274
+ spinner10.stop("Error connecting WhatsApp");
3275
+ p7.log.error(error instanceof Error ? error.message : "Unknown error");
3276
+ }
3277
+ }
3278
+ async function connectTelegram(config) {
3279
+ p7.log.info(chalk8.bold("Telegram Bot Setup"));
3280
+ p7.log.info(chalk8.dim(`
3281
+ To create a Telegram bot:
3282
+ 1. Open Telegram and search for @BotFather
3283
+ 2. Send /newbot
3284
+ 3. Follow the prompts to name your bot
3285
+ 4. Copy the API token
3286
+ `));
3287
+ const openBotFather = await p7.confirm({
3288
+ message: "Open BotFather in browser?",
3289
+ initialValue: true
3290
+ });
3291
+ if (openBotFather && !p7.isCancel(openBotFather)) {
3292
+ const open2 = (await import("open")).default;
3293
+ await open2("https://t.me/botfather");
3294
+ }
3295
+ const token = await p7.text({
3296
+ message: "Paste your bot token:",
3297
+ placeholder: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
3298
+ validate: (value) => {
3299
+ if (!value) return "Token is required";
3300
+ if (!value.includes(":")) return "Invalid token format";
3301
+ return void 0;
3302
+ }
3303
+ });
3304
+ if (p7.isCancel(token)) return;
3305
+ const spinner10 = p7.spinner();
3306
+ spinner10.start("Verifying bot token...");
3307
+ try {
3308
+ const response = await fetch(`https://api.telegram.org/bot${token}/getMe`);
3309
+ const data = await response.json();
3310
+ if (!data.ok) {
3311
+ throw new Error(data.description || "Invalid token");
3312
+ }
3313
+ spinner10.stop("Bot verified!");
3314
+ config.setChannelConfig("telegram", {
3315
+ enabled: true,
3316
+ botToken: token,
3317
+ botUsername: data.result.username
3318
+ });
3319
+ p7.log.success(`Connected to @${data.result.username}`);
3320
+ } catch (error) {
3321
+ spinner10.stop("Verification failed");
3322
+ p7.log.error(error instanceof Error ? error.message : "Invalid token");
3323
+ }
3324
+ }
3325
+ async function connectDiscord(config) {
3326
+ p7.log.info(chalk8.bold("Discord Bot Setup"));
3327
+ p7.log.info(chalk8.dim(`
3328
+ To create a Discord bot:
3329
+ 1. Go to Discord Developer Portal
3330
+ 2. Create a new application
3331
+ 3. Go to Bot section
3332
+ 4. Create a bot and copy the token
3333
+ 5. Enable necessary intents (Message Content)
3334
+ `));
3335
+ const openPortal = await p7.confirm({
3336
+ message: "Open Discord Developer Portal?",
3337
+ initialValue: true
3338
+ });
3339
+ if (openPortal && !p7.isCancel(openPortal)) {
3340
+ const open2 = (await import("open")).default;
3341
+ await open2("https://discord.com/developers/applications");
3342
+ }
3343
+ const token = await p7.text({
3344
+ message: "Paste your bot token:",
3345
+ placeholder: "MTIzNDU2Nzg5MDEyMzQ1Njc4.Gg1234.abc..."
3346
+ });
3347
+ if (p7.isCancel(token) || !token) return;
3348
+ const clientId = await p7.text({
3349
+ message: "Paste your application (client) ID:",
3350
+ placeholder: "123456789012345678"
3351
+ });
3352
+ if (p7.isCancel(clientId) || !clientId) return;
3353
+ config.setChannelConfig("discord", {
3354
+ enabled: true,
3355
+ botToken: token,
3356
+ clientId
3357
+ });
3358
+ const inviteUrl = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&permissions=2048&scope=bot`;
3359
+ p7.log.success("Discord bot configured!");
3360
+ p7.log.info(`Add bot to server: ${inviteUrl}`);
3361
+ }
3362
+ async function connectSlack(config) {
3363
+ p7.log.info(chalk8.bold("Slack App Setup"));
3364
+ p7.log.info(chalk8.dim(`
3365
+ To create a Slack app:
3366
+ 1. Go to Slack API portal
3367
+ 2. Create a new app
3368
+ 3. Add bot scopes (chat:write, app_mentions:read, etc.)
3369
+ 4. Install to workspace
3370
+ 5. Copy the Bot Token
3371
+ `));
3372
+ const openSlack = await p7.confirm({
3373
+ message: "Open Slack API portal?",
3374
+ initialValue: true
3375
+ });
3376
+ if (openSlack && !p7.isCancel(openSlack)) {
3377
+ const open2 = (await import("open")).default;
3378
+ await open2("https://api.slack.com/apps");
3379
+ }
3380
+ const token = await p7.text({
3381
+ message: "Paste your Bot User OAuth Token:",
3382
+ placeholder: "xoxb-...",
3383
+ validate: (value) => {
3384
+ if (!value) return "Token is required";
3385
+ if (!value.startsWith("xoxb-")) return "Should start with xoxb-";
3386
+ return void 0;
3387
+ }
3388
+ });
3389
+ if (p7.isCancel(token)) return;
3390
+ config.setChannelConfig("slack", {
3391
+ enabled: true,
3392
+ botToken: token
3393
+ });
3394
+ p7.log.success("Slack app configured!");
3395
+ }
3396
+ async function connectSMS(config) {
3397
+ p7.log.info(chalk8.bold("SMS via Twilio"));
3398
+ const twilioConfig = config.getCredentials("twilio");
3399
+ if (twilioConfig?.accountSid) {
3400
+ p7.log.info("Twilio is already configured.");
3401
+ const reconfigure = await p7.confirm({
3402
+ message: "Reconfigure Twilio?",
3403
+ initialValue: false
3404
+ });
3405
+ if (!reconfigure || p7.isCancel(reconfigure)) {
3406
+ config.setChannelConfig("sms", { enabled: true });
3407
+ p7.log.success("SMS channel enabled!");
3408
+ return;
3409
+ }
3410
+ }
3411
+ const openTwilio = await p7.confirm({
3412
+ message: "Open Twilio Console?",
3413
+ initialValue: true
3414
+ });
3415
+ if (openTwilio && !p7.isCancel(openTwilio)) {
3416
+ const open2 = (await import("open")).default;
3417
+ await open2("https://console.twilio.com/");
3418
+ }
3419
+ const accountSid = await p7.text({
3420
+ message: "Account SID:",
3421
+ placeholder: "AC..."
3422
+ });
3423
+ if (p7.isCancel(accountSid)) return;
3424
+ const authToken = await p7.text({
3425
+ message: "Auth Token:",
3426
+ placeholder: "..."
3427
+ });
3428
+ if (p7.isCancel(authToken)) return;
3429
+ const phoneNumber = await p7.text({
3430
+ message: "Twilio phone number:",
3431
+ placeholder: "+1234567890"
3432
+ });
3433
+ if (p7.isCancel(phoneNumber)) return;
3434
+ config.setCredentials("twilio", {
3435
+ accountSid,
3436
+ authToken,
3437
+ phoneNumber
3438
+ });
3439
+ config.setChannelConfig("sms", { enabled: true });
3440
+ p7.log.success("SMS channel configured!");
3441
+ }
3442
+ async function connectiMessage(config) {
3443
+ p7.log.warn("iMessage support requires macOS and additional setup.");
3444
+ p7.log.info(chalk8.dim(`
3445
+ iMessage integration uses AppleScript on macOS.
3446
+ This requires:
3447
+ 1. macOS with Messages app
3448
+ 2. iCloud signed in
3449
+ 3. Terminal with accessibility permissions
3450
+ `));
3451
+ const isMac = process.platform === "darwin";
3452
+ if (!isMac) {
3453
+ p7.log.error("iMessage is only available on macOS.");
3454
+ return;
3455
+ }
3456
+ p7.log.info("iMessage support coming soon.");
3457
+ }
3458
+ async function startAllChannels(config) {
3459
+ const spinner10 = p7.spinner();
3460
+ spinner10.start("Starting channel gateway...");
3461
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3462
+ spinner10.stop("Gateway started");
3463
+ console.log(chalk8.green(`
3464
+ \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
3465
+ \u2551 Gateway is running! \u2551
3466
+ \u2551 \u2551
3467
+ \u2551 You can now receive messages from: \u2551
3468
+ \u2551 \u2022 WhatsApp \u2022 Telegram \u2022 Discord \u2551
3469
+ \u2551 \u2022 Slack \u2022 SMS \u2551
3470
+ \u2551 \u2551
3471
+ \u2551 Press Ctrl+C to stop \u2551
3472
+ \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\u255D
3473
+ `));
3474
+ }
3475
+ async function stopAllChannels(config) {
3476
+ const spinner10 = p7.spinner();
3477
+ spinner10.start("Stopping gateway...");
3478
+ await new Promise((resolve) => setTimeout(resolve, 500));
3479
+ spinner10.stop("Gateway stopped");
3480
+ }
3481
+ async function deployGatewayWizard(config) {
3482
+ p7.log.info(chalk8.bold("Deploy Gateway to Cloud"));
3483
+ const platform = await p7.select({
3484
+ message: "Where do you want to deploy?",
3485
+ options: [
3486
+ { value: "codebakers", label: "\u2601\uFE0F CodeBakers Cloud", hint: "$29/mo - We handle everything" },
3487
+ { value: "vps", label: "\u{1F5A5}\uFE0F VPS", hint: "$5/mo - DigitalOcean, etc." },
3488
+ { value: "local", label: "\u{1F3E0} Home server", hint: "Raspberry Pi, etc." }
3489
+ ]
3490
+ });
3491
+ if (p7.isCancel(platform)) return;
3492
+ switch (platform) {
3493
+ case "codebakers":
3494
+ p7.log.info("CodeBakers Cloud coming soon!");
3495
+ p7.log.info(chalk8.dim("Sign up at https://codebakers.dev/cloud"));
3496
+ break;
3497
+ case "vps":
3498
+ await deployToVPS(config);
3499
+ break;
3500
+ case "local":
3501
+ p7.log.info("Local deployment guide:");
3502
+ p7.log.info(chalk8.dim(`
3503
+ 1. Install Node.js on your server
3504
+ 2. Clone your project
3505
+ 3. Run: npm install
3506
+ 4. Run: pm2 start codebakers -- gateway --start
3507
+ 5. Configure firewall to allow outbound connections
3508
+ `));
3509
+ break;
3510
+ }
3511
+ }
3512
+ async function deployToVPS(config) {
3513
+ const provider = await p7.select({
3514
+ message: "Which VPS provider?",
3515
+ options: [
3516
+ { value: "digitalocean", label: "DigitalOcean" },
3517
+ { value: "vultr", label: "Vultr" },
3518
+ { value: "linode", label: "Linode" },
3519
+ { value: "hetzner", label: "Hetzner" },
3520
+ { value: "other", label: "Other" }
3521
+ ]
3522
+ });
3523
+ if (p7.isCancel(provider)) return;
3524
+ if (provider === "digitalocean") {
3525
+ p7.log.info("DigitalOcean auto-deploy:");
3526
+ p7.log.info(chalk8.dim(`
3527
+ 1. Opening DigitalOcean login...
3528
+ 2. We'll create a $6/mo droplet
3529
+ 3. Install CodeBakers gateway
3530
+ 4. Connect all your channels
3531
+ `));
3532
+ const proceed = await p7.confirm({
3533
+ message: "Open DigitalOcean?",
3534
+ initialValue: true
3535
+ });
3536
+ if (proceed && !p7.isCancel(proceed)) {
3537
+ const open2 = (await import("open")).default;
3538
+ await open2("https://cloud.digitalocean.com/");
3539
+ }
3540
+ } else {
3541
+ p7.log.info(chalk8.dim(`
3542
+ Manual VPS setup:
3543
+
3544
+ 1. Create a VPS (Ubuntu 22.04 recommended)
3545
+ 2. SSH into your server
3546
+ 3. Run these commands:
3547
+
3548
+ curl -fsSL https://codebakers.dev/install.sh | bash
3549
+ codebakers gateway --start
3550
+
3551
+ 4. Your gateway will start automatically on boot
3552
+ `));
3553
+ }
3554
+ }
3555
+
3556
+ // src/commands/learn.ts
3557
+ import * as p8 from "@clack/prompts";
3558
+ import chalk9 from "chalk";
3559
+ async function learnCommand() {
3560
+ const config = new Config();
3561
+ const learning = config.getLearning();
3562
+ p8.intro(chalk9.bgCyan.black(" Learning Settings "));
3563
+ console.log(chalk9.bold("\n\u{1F9E0} What CodeBakers has learned about you:\n"));
3564
+ const shortcuts = Object.entries(learning.shortcuts);
3565
+ if (shortcuts.length > 0) {
3566
+ console.log(chalk9.bold("Shortcuts:"));
3567
+ for (const [short, full] of shortcuts) {
3568
+ console.log(` ${chalk9.cyan(short)} \u2192 ${full}`);
3569
+ }
3570
+ console.log("");
3571
+ }
3572
+ const prefs = Object.entries(learning.preferences);
3573
+ if (prefs.length > 0) {
3574
+ console.log(chalk9.bold("Preferences:"));
3575
+ for (const [key, value] of prefs) {
3576
+ console.log(` ${key}: ${chalk9.cyan(String(value))}`);
3577
+ }
3578
+ console.log("");
3579
+ }
3580
+ if (shortcuts.length === 0 && prefs.length === 0) {
3581
+ console.log(chalk9.dim(" Nothing learned yet. Use CodeBakers more!\n"));
3582
+ }
3583
+ p8.outro("");
3584
+ }
3585
+
3586
+ // src/commands/security.ts
3587
+ import * as p9 from "@clack/prompts";
3588
+ import chalk10 from "chalk";
3589
+ import * as fs8 from "fs-extra";
3590
+ import glob2 from "fast-glob";
3591
+ import * as path8 from "path";
3592
+ async function securityCommand() {
3593
+ p9.intro(chalk10.bgCyan.black(" Security Audit "));
3594
+ const spinner10 = p9.spinner();
3595
+ spinner10.start("Scanning for security issues...");
3596
+ const issues = await runSecurityScan();
3597
+ spinner10.stop("Scan complete");
3598
+ if (issues.length === 0) {
3599
+ console.log(chalk10.green("\n\u2713 No security issues found!\n"));
3600
+ displaySecurityScore(100);
3601
+ } else {
3602
+ console.log(chalk10.yellow(`
3603
+ \u26A0\uFE0F ${issues.length} security issues found:
3604
+ `));
3605
+ for (const issue of issues) {
3606
+ const icon = issue.severity === "critical" ? chalk10.red("\u{1F534}") : issue.severity === "high" ? chalk10.red("\u{1F7E0}") : chalk10.yellow("\u{1F7E1}");
3607
+ console.log(`${icon} ${chalk10.bold(issue.title)}`);
3608
+ console.log(chalk10.dim(` ${issue.file}:${issue.line}`));
3609
+ console.log(` ${issue.description}
3610
+ `);
3611
+ }
3612
+ const score = Math.max(0, 100 - issues.filter((i) => i.severity === "critical").length * 30 - issues.filter((i) => i.severity === "high").length * 15);
3613
+ displaySecurityScore(score);
3614
+ }
3615
+ p9.outro("");
3616
+ }
3617
+ async function runSecurityScan() {
3618
+ const cwd = process.cwd();
3619
+ const issues = [];
3620
+ const files = await glob2(["src/**/*.{ts,tsx,js,jsx}"], {
3621
+ cwd,
3622
+ ignore: ["**/node_modules/**"]
3623
+ });
3624
+ const patterns = [
3625
+ {
3626
+ pattern: /(sk_live_|sk_test_)[a-zA-Z0-9]+/g,
3627
+ title: "Hardcoded Stripe key",
3628
+ severity: "critical",
3629
+ description: "Stripe API key should be in environment variables"
3630
+ },
3631
+ {
3632
+ pattern: /\beval\s*\(/g,
3633
+ title: "Use of eval()",
3634
+ severity: "critical",
3635
+ description: "eval() is a security vulnerability"
3636
+ },
3637
+ {
3638
+ pattern: /dangerouslySetInnerHTML/g,
3639
+ title: "dangerouslySetInnerHTML",
3640
+ severity: "high",
3641
+ description: "Ensure content is sanitized with DOMPurify"
3642
+ }
3643
+ ];
3644
+ for (const file of files) {
3645
+ const content = await fs8.readFile(path8.join(cwd, file), "utf-8");
3646
+ for (const { pattern, title, severity, description } of patterns) {
3647
+ let match;
3648
+ const regex = new RegExp(pattern.source, pattern.flags);
3649
+ while ((match = regex.exec(content)) !== null) {
3650
+ const line = content.substring(0, match.index).split("\n").length;
3651
+ issues.push({ title, description, file, line, severity });
3652
+ }
3653
+ }
3654
+ }
3655
+ return issues;
3656
+ }
3657
+ function displaySecurityScore(score) {
3658
+ const color = score >= 80 ? chalk10.green : score >= 50 ? chalk10.yellow : chalk10.red;
3659
+ const grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";
3660
+ console.log(chalk10.bold("Security Score:"));
3661
+ console.log(color(`
3662
+ ${score}/100 Grade: ${grade}
3663
+ `));
3664
+ }
3665
+
3666
+ // src/commands/generate.ts
3667
+ import * as p10 from "@clack/prompts";
3668
+ import chalk11 from "chalk";
3669
+ import * as fs9 from "fs-extra";
3670
+ import * as path9 from "path";
3671
+ async function generateCommand(type) {
3672
+ p10.intro(chalk11.bgCyan.black(" Generate "));
3673
+ const generateType = type || await p10.select({
3674
+ message: "What do you want to generate?",
3675
+ options: [
3676
+ { value: "component", label: "\u{1F9E9} Component" },
3677
+ { value: "page", label: "\u{1F4C4} Page" },
3678
+ { value: "api", label: "\u{1F50C} API Route" },
3679
+ { value: "hook", label: "\u{1FA9D} Custom Hook" },
3680
+ { value: "store", label: "\u{1F4E6} Zustand Store" },
3681
+ { value: "form", label: "\u{1F4DD} Form Component" }
3682
+ ]
3683
+ });
3684
+ if (p10.isCancel(generateType)) return;
3685
+ const name = await p10.text({
3686
+ message: "Name:",
3687
+ placeholder: generateType === "page" ? "dashboard" : "MyComponent",
3688
+ validate: (v) => !v ? "Name is required" : void 0
3689
+ });
3690
+ if (p10.isCancel(name)) return;
3691
+ const spinner10 = p10.spinner();
3692
+ spinner10.start("Generating...");
3693
+ await generateFile(generateType, name);
3694
+ spinner10.stop(`Generated ${name}`);
3695
+ p10.outro("");
3696
+ }
3697
+ async function generateFile(type, name) {
3698
+ const cwd = process.cwd();
3699
+ const templates = {
3700
+ component: {
3701
+ path: `src/components/${name}.tsx`,
3702
+ content: `'use client';
3703
+
3704
+ interface ${name}Props {
3705
+ // props
3706
+ }
3707
+
3708
+ export function ${name}({}: ${name}Props) {
3709
+ return (
3710
+ <div>
3711
+ <h2>${name}</h2>
3712
+ </div>
3713
+ );
3714
+ }
3715
+ `
3716
+ },
3717
+ page: {
3718
+ path: `src/app/${name}/page.tsx`,
3719
+ content: `export default function ${name.charAt(0).toUpperCase() + name.slice(1)}Page() {
3720
+ return (
3721
+ <div className="container mx-auto py-8">
3722
+ <h1 className="text-2xl font-bold">${name}</h1>
3723
+ </div>
3724
+ );
3725
+ }
3726
+ `
3727
+ },
3728
+ api: {
3729
+ path: `src/app/api/${name}/route.ts`,
3730
+ content: `import { NextRequest, NextResponse } from 'next/server';
3731
+
3732
+ export async function GET(request: NextRequest) {
3733
+ try {
3734
+ return NextResponse.json({ data: [] });
3735
+ } catch (error) {
3736
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 });
3737
+ }
3738
+ }
3739
+
3740
+ export async function POST(request: NextRequest) {
3741
+ try {
3742
+ const body = await request.json();
3743
+ return NextResponse.json({ success: true }, { status: 201 });
3744
+ } catch (error) {
3745
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 });
3746
+ }
3747
+ }
3748
+ `
3749
+ },
3750
+ hook: {
3751
+ path: `src/hooks/use-${name.toLowerCase()}.ts`,
3752
+ content: `import { useState, useEffect } from 'react';
3753
+
3754
+ export function use${name}() {
3755
+ const [data, setData] = useState(null);
3756
+ const [isLoading, setIsLoading] = useState(false);
3757
+ const [error, setError] = useState<Error | null>(null);
3758
+
3759
+ return { data, isLoading, error };
3760
+ }
3761
+ `
3762
+ },
3763
+ store: {
3764
+ path: `src/stores/${name.toLowerCase()}-store.ts`,
3765
+ content: `import { create } from 'zustand';
3766
+
3767
+ interface ${name}State {
3768
+ items: unknown[];
3769
+ isLoading: boolean;
3770
+ setItems: (items: unknown[]) => void;
3771
+ reset: () => void;
3772
+ }
3773
+
3774
+ export const use${name}Store = create<${name}State>((set) => ({
3775
+ items: [],
3776
+ isLoading: false,
3777
+ setItems: (items) => set({ items }),
3778
+ reset: () => set({ items: [], isLoading: false }),
3779
+ }));
3780
+ `
3781
+ },
3782
+ form: {
3783
+ path: `src/components/forms/${name}-form.tsx`,
3784
+ content: `'use client';
3785
+
3786
+ import { useState } from 'react';
3787
+ import { useForm } from 'react-hook-form';
3788
+ import { zodResolver } from '@hookform/resolvers/zod';
3789
+ import { z } from 'zod';
3790
+ import { toast } from 'sonner';
3791
+
3792
+ const schema = z.object({
3793
+ name: z.string().min(1, 'Required'),
3794
+ });
3795
+
3796
+ type FormData = z.infer<typeof schema>;
3797
+
3798
+ export function ${name}Form({ onSuccess }: { onSuccess?: () => void }) {
3799
+ const [isSubmitting, setIsSubmitting] = useState(false);
3800
+ const form = useForm<FormData>({ resolver: zodResolver(schema) });
3801
+
3802
+ const onSubmit = async (data: FormData) => {
3803
+ setIsSubmitting(true);
3804
+ try {
3805
+ // Submit
3806
+ toast.success('Success!');
3807
+ onSuccess?.();
3808
+ } catch (error) {
3809
+ toast.error('Failed');
3810
+ } finally {
3811
+ setIsSubmitting(false);
3812
+ }
3813
+ };
3814
+
3815
+ return (
3816
+ <form onSubmit={form.handleSubmit(onSubmit)}>
3817
+ {/* Form fields */}
3818
+ <button type="submit" disabled={isSubmitting}>
3819
+ {isSubmitting ? 'Submitting...' : 'Submit'}
3820
+ </button>
3821
+ </form>
3822
+ );
3823
+ }
3824
+ `
3825
+ }
3826
+ };
3827
+ const template = templates[type];
3828
+ if (!template) throw new Error(`Unknown type: ${type}`);
3829
+ const filePath = path9.join(cwd, template.path);
3830
+ await fs9.ensureDir(path9.dirname(filePath));
3831
+ await fs9.writeFile(filePath, template.content);
3832
+ }
3833
+
3834
+ // src/commands/fix.ts
3835
+ import * as p11 from "@clack/prompts";
3836
+ import chalk12 from "chalk";
3837
+ async function fixCommand() {
3838
+ p11.intro(chalk12.bgCyan.black(" Auto-Fix "));
3839
+ const spinner10 = p11.spinner();
3840
+ spinner10.start("Analyzing code...");
3841
+ const result = await runPatternCheck(true);
3842
+ if (result.passed) {
3843
+ spinner10.stop("No issues found!");
3844
+ } else {
3845
+ spinner10.stop(`Fixed ${result.violations.length} issues`);
3846
+ }
3847
+ p11.outro(chalk12.green("Done!"));
3848
+ }
3849
+
3850
+ // src/index.ts
3851
+ var VERSION2 = "1.0.0";
3852
+ var logo = `
3853
+ \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
3854
+ \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
3855
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
3856
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551
3857
+ \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
3858
+ \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
3859
+ `;
3860
+ async function showMainMenu() {
3861
+ const config = new Config();
3862
+ const isSetup = config.isConfigured();
3863
+ console.log(gradient.pastel.multiline(logo));
3864
+ console.log(chalk13.dim(` v${VERSION2} \u2014 AI dev team that follows the rules
3865
+ `));
3866
+ if (!isSetup) {
3867
+ console.log(boxen(
3868
+ chalk13.yellow("Welcome to CodeBakers! Let's get you set up."),
3869
+ { padding: 1, borderColor: "yellow", borderStyle: "round" }
3870
+ ));
3871
+ await setupCommand();
3872
+ return;
3873
+ }
3874
+ const inProject = config.isInProject();
3875
+ const action = await p12.select({
3876
+ message: "What do you want to do?",
3877
+ options: inProject ? [
3878
+ { value: "code", label: "\u{1F4AC} Code with AI", hint: "build features, fix bugs" },
3879
+ { value: "check", label: "\u{1F50D} Check code quality", hint: "run pattern enforcement" },
3880
+ { value: "deploy", label: "\u{1F680} Deploy", hint: "deploy to Vercel" },
3881
+ { value: "fix", label: "\u{1F527} Fix errors", hint: "auto-fix build/deploy errors" },
3882
+ { value: "generate", label: "\u26A1 Generate", hint: "scaffold components, pages" },
3883
+ { value: "status", label: "\u{1F4CA} Project status", hint: "view project health" },
3884
+ { value: "separator1", 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" },
3885
+ { value: "connect", label: "\u{1F517} Connect service", hint: "add integrations" },
3886
+ { value: "gateway", label: "\u{1F4F1} Channel gateway", hint: "WhatsApp, Telegram, etc." },
3887
+ { value: "learn", label: "\u{1F9E0} Learning settings", hint: "view what I've learned" },
3888
+ { value: "security", label: "\u{1F512} Security audit", hint: "check for vulnerabilities" },
3889
+ { 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" },
3890
+ { value: "new", label: "\u{1F195} Create new project" },
3891
+ { value: "settings", label: "\u2699\uFE0F Settings" },
3892
+ { value: "help", label: "\u2753 Help" }
3893
+ ] : [
3894
+ { value: "new", label: "\u{1F195} Create new project" },
3895
+ { value: "open", label: "\u{1F4C2} Open existing project" },
3896
+ { value: "status", label: "\u{1F4CA} View all projects" },
3897
+ { value: "separator1", 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" },
3898
+ { value: "connect", label: "\u{1F517} Connect service" },
3899
+ { value: "gateway", label: "\u{1F4F1} Channel gateway", hint: "WhatsApp, Telegram, etc." },
3900
+ { value: "learn", label: "\u{1F9E0} Learning settings" },
3901
+ { value: "settings", label: "\u2699\uFE0F Settings" },
3902
+ { value: "help", label: "\u2753 Help" }
3903
+ ]
3904
+ });
3905
+ if (p12.isCancel(action)) {
3906
+ p12.cancel("Goodbye!");
3907
+ process.exit(0);
3908
+ }
3909
+ switch (action) {
3910
+ case "code":
3911
+ await codeCommand();
3912
+ break;
3913
+ case "check":
3914
+ await checkCommand();
3915
+ break;
3916
+ case "deploy":
3917
+ await deployCommand();
3918
+ break;
3919
+ case "fix":
3920
+ await fixCommand();
3921
+ break;
3922
+ case "generate":
3923
+ await generateCommand();
3924
+ break;
3925
+ case "status":
3926
+ await statusCommand();
3927
+ break;
3928
+ case "connect":
3929
+ await connectCommand();
3930
+ break;
3931
+ case "gateway":
3932
+ await gatewayCommand();
3933
+ break;
3934
+ case "learn":
3935
+ await learnCommand();
3936
+ break;
3937
+ case "security":
3938
+ await securityCommand();
3939
+ break;
3940
+ case "new":
3941
+ await initCommand();
3942
+ break;
3943
+ case "settings":
3944
+ await setupCommand();
3945
+ break;
3946
+ case "help":
3947
+ showHelp2();
3948
+ break;
3949
+ default:
3950
+ await showMainMenu();
3951
+ }
3952
+ }
3953
+ function showHelp2() {
3954
+ console.log(boxen(`
3955
+ ${chalk13.bold("CodeBakers CLI")} \u2014 AI dev team that follows the rules
3956
+
3957
+ ${chalk13.bold("Commands:")}
3958
+ ${chalk13.cyan("codebakers")} Interactive menu (or just run with no args)
3959
+ ${chalk13.cyan("codebakers init")} Create a new project
3960
+ ${chalk13.cyan("codebakers code")} Start AI coding session
3961
+ ${chalk13.cyan("codebakers check")} Run pattern enforcement
3962
+ ${chalk13.cyan("codebakers deploy")} Deploy to production
3963
+ ${chalk13.cyan("codebakers fix")} Auto-fix errors
3964
+ ${chalk13.cyan("codebakers generate")} Generate components/pages
3965
+ ${chalk13.cyan("codebakers connect")} Connect external services
3966
+ ${chalk13.cyan("codebakers gateway")} Manage messaging channels
3967
+ ${chalk13.cyan("codebakers status")} View project status
3968
+ ${chalk13.cyan("codebakers security")} Run security audit
3969
+ ${chalk13.cyan("codebakers learn")} View/manage learning
3970
+
3971
+ ${chalk13.bold("Help at any time:")}
3972
+ Press ${chalk13.yellow("?")} during any command to get contextual help
3973
+
3974
+ ${chalk13.bold("Documentation:")}
3975
+ ${chalk13.dim("https://codebakers.dev/docs")}
3976
+ `, { padding: 1, borderColor: "cyan", borderStyle: "round" }));
3977
+ }
3978
+ var program = new Command();
3979
+ program.name("codebakers").description("AI dev team that follows the rules").version(VERSION2).action(showMainMenu);
3980
+ program.command("setup").description("Configure CodeBakers (first-time setup)").action(setupCommand);
3981
+ program.command("init").description("Create a new project").option("-n, --name <name>", "Project name").option("-t, --template <template>", "Template (nextjs, voice-agent, saas)").option("--no-git", "Skip GitHub repo creation").option("--no-vercel", "Skip Vercel deployment").option("--no-supabase", "Skip Supabase setup").action(initCommand);
3982
+ program.command("code [prompt]").description("Start AI coding session").option("-w, --watch", "Watch mode - continuous assistance").action(codeCommand);
3983
+ program.command("check").description("Run CodeBakers pattern enforcement").option("-f, --fix", "Auto-fix violations").option("--watch", "Watch mode").action(checkCommand);
3984
+ program.command("deploy").description("Deploy to production").option("-p, --preview", "Deploy to preview only").option("--no-check", "Skip pattern check").action(deployCommand);
3985
+ program.command("fix").description("Auto-fix build and deployment errors").option("--auto", "Fix without asking").action(fixCommand);
3986
+ program.command("generate <type>").alias("g").description("Generate components, pages, API routes").option("-n, --name <name>", "Name of the generated item").action(generateCommand);
3987
+ program.command("connect [service]").description("Connect external services (Stripe, VAPI, etc.)").action(connectCommand);
3988
+ program.command("gateway").description("Manage messaging channel gateway").option("--start", "Start the gateway").option("--stop", "Stop the gateway").option("--status", "Show gateway status").action(gatewayCommand);
3989
+ program.command("status").description("View project status and health").option("-a, --all", "Show all projects").action(statusCommand);
3990
+ program.command("security").description("Run security audit").option("--fix", "Auto-fix security issues").action(securityCommand);
3991
+ program.command("learn").description("View and manage learning settings").option("--forget <item>", "Forget a learned preference").option("--reset", "Reset all learning").option("--export", "Export learned patterns").action(learnCommand);
3992
+ var args = process.argv.slice(2);
3993
+ if (args.length === 0) {
3994
+ checkForUpdates().catch(() => {
3995
+ });
3996
+ showMainMenu().catch(console.error);
3997
+ } else {
3998
+ program.parse();
3999
+ }