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
@@ -0,0 +1,574 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import * as fs from 'fs-extra';
4
+ import * as path from 'path';
5
+ import { execa } from 'execa';
6
+ import { Config } from '../utils/config.js';
7
+ import { GitHubService } from '../services/github.js';
8
+ import { VercelService } from '../services/vercel.js';
9
+ import { SupabaseService } from '../services/supabase.js';
10
+ import { generateClaudeMd } from '../utils/claude-md.js';
11
+
12
+ interface InitOptions {
13
+ name?: string;
14
+ template?: string;
15
+ git?: boolean;
16
+ vercel?: boolean;
17
+ supabase?: boolean;
18
+ }
19
+
20
+ export async function initCommand(options: InitOptions = {}): Promise<void> {
21
+ const config = new Config();
22
+
23
+ // Check if configured
24
+ if (!config.isConfigured()) {
25
+ p.log.error('Please run `codebakers setup` first.');
26
+ return;
27
+ }
28
+
29
+ p.intro(chalk.bgCyan.black(' Create New Project '));
30
+
31
+ // Project name
32
+ let projectName = options.name;
33
+ if (!projectName) {
34
+ const name = await p.text({
35
+ message: 'Project name:',
36
+ placeholder: 'my-app',
37
+ validate: (value) => {
38
+ if (!value) return 'Project name is required';
39
+ if (!/^[a-z0-9-]+$/.test(value)) return 'Use lowercase letters, numbers, and hyphens only';
40
+ return undefined;
41
+ },
42
+ });
43
+ if (p.isCancel(name)) return;
44
+ projectName = name as string;
45
+ }
46
+
47
+ // Project type
48
+ const projectType = await p.select({
49
+ message: 'What are you building?',
50
+ options: [
51
+ { value: 'web', label: '🌐 Web app', hint: 'Next.js → Vercel' },
52
+ { value: 'mobile', label: 'šŸ“± Mobile app', hint: 'Expo → App stores' },
53
+ { value: 'desktop', label: 'šŸ–„ļø Desktop app', hint: 'Tauri → Windows/Mac/Linux' },
54
+ { value: 'fullstack', label: 'šŸ“¦ Full stack', hint: 'Web + Mobile + Desktop' },
55
+ ],
56
+ });
57
+ if (p.isCancel(projectType)) return;
58
+
59
+ // Framework selection (for web)
60
+ let framework = 'nextjs';
61
+ if (projectType === 'web' || projectType === 'fullstack') {
62
+ const selected = await p.select({
63
+ message: 'Framework:',
64
+ options: [
65
+ { value: 'nextjs', label: 'Next.js 14+', hint: config.getPreference('defaultFramework') === 'nextjs' ? '(your usual choice)' : '' },
66
+ { value: 'remix', label: 'Remix' },
67
+ { value: 'vite', label: 'Vite + React' },
68
+ { value: 'astro', label: 'Astro' },
69
+ ],
70
+ });
71
+ if (p.isCancel(selected)) return;
72
+ framework = selected as string;
73
+ config.learnPreference('defaultFramework', framework);
74
+ }
75
+
76
+ // UI Library
77
+ const uiLibrary = await p.select({
78
+ message: 'UI Library:',
79
+ options: [
80
+ { value: 'shadcn', label: 'shadcn/ui', hint: config.getPreference('defaultUI') === 'shadcn' ? '(your usual choice)' : 'Recommended' },
81
+ { value: 'chakra', label: 'Chakra UI' },
82
+ { value: 'mantine', label: 'Mantine' },
83
+ { value: 'mui', label: 'Material UI' },
84
+ { value: 'headless', label: 'Headless UI' },
85
+ { value: 'tailwind', label: 'Tailwind only' },
86
+ ],
87
+ });
88
+ if (p.isCancel(uiLibrary)) return;
89
+ config.learnPreference('defaultUI', uiLibrary);
90
+
91
+ // Packages
92
+ const packages = await p.multiselect({
93
+ message: 'Additional packages:',
94
+ options: [
95
+ { value: 'typescript', label: 'TypeScript', hint: 'Always recommended' },
96
+ { value: 'tailwind', label: 'Tailwind CSS' },
97
+ { value: 'zod', label: 'Zod', hint: 'Validation' },
98
+ { value: 'tanstack-query', label: 'TanStack Query', hint: 'Data fetching' },
99
+ { value: 'zustand', label: 'Zustand', hint: 'State management' },
100
+ { value: 'react-hook-form', label: 'React Hook Form' },
101
+ { value: 'lucide', label: 'Lucide Icons' },
102
+ { value: 'trpc', label: 'tRPC' },
103
+ ],
104
+ initialValues: ['typescript', 'tailwind', 'zod', 'zustand', 'lucide'],
105
+ });
106
+ if (p.isCancel(packages)) return;
107
+
108
+ // Services to provision
109
+ const services = await p.multiselect({
110
+ message: 'Services to provision:',
111
+ options: [
112
+ { value: 'github', label: 'GitHub', hint: 'Repository' },
113
+ { value: 'vercel', label: 'Vercel', hint: 'Hosting' },
114
+ { value: 'supabase', label: 'Supabase', hint: 'Database + Auth' },
115
+ { value: 'stripe', label: 'Stripe', hint: 'Payments' },
116
+ { value: 'resend', label: 'Resend', hint: 'Email' },
117
+ { value: 'vapi', label: 'VAPI', hint: 'Voice AI' },
118
+ ],
119
+ initialValues: ['github', 'vercel', 'supabase'],
120
+ });
121
+ if (p.isCancel(services)) return;
122
+
123
+ // Domain
124
+ let domain: string | undefined;
125
+ const domainChoice = await p.select({
126
+ message: 'Domain:',
127
+ options: [
128
+ { value: 'subdomain', label: 'Subdomain', hint: `${projectName}.yourdomain.com` },
129
+ { value: 'vercel', label: 'Vercel subdomain', hint: `${projectName}.vercel.app` },
130
+ { value: 'purchase', label: 'Purchase new domain' },
131
+ { value: 'skip', label: 'Skip for now' },
132
+ ],
133
+ });
134
+ if (p.isCancel(domainChoice)) return;
135
+
136
+ if (domainChoice === 'subdomain') {
137
+ const sub = await p.text({
138
+ message: 'Subdomain:',
139
+ placeholder: projectName,
140
+ initialValue: projectName,
141
+ });
142
+ if (!p.isCancel(sub)) {
143
+ domain = `${sub}.yourdomain.com`;
144
+ }
145
+ }
146
+
147
+ // Confirm
148
+ const projectConfig = {
149
+ name: projectName,
150
+ type: projectType,
151
+ framework,
152
+ ui: uiLibrary,
153
+ packages,
154
+ services,
155
+ domain,
156
+ };
157
+
158
+ p.note(
159
+ `Name: ${projectName}
160
+ Type: ${projectType}
161
+ Framework: ${framework}
162
+ UI: ${uiLibrary}
163
+ Packages: ${(packages as string[]).join(', ')}
164
+ Services: ${(services as string[]).join(', ')}
165
+ Domain: ${domain || 'Vercel default'}`,
166
+ 'Configuration'
167
+ );
168
+
169
+ const confirmed = await p.confirm({
170
+ message: 'Create project?',
171
+ initialValue: true,
172
+ });
173
+
174
+ if (!confirmed || p.isCancel(confirmed)) {
175
+ p.cancel('Project creation cancelled.');
176
+ return;
177
+ }
178
+
179
+ // Create project
180
+ const spinner = p.spinner();
181
+ const projectPath = path.join(process.cwd(), projectName);
182
+
183
+ try {
184
+ // Step 1: Create local project
185
+ spinner.start('Creating local project...');
186
+ await createLocalProject(projectPath, projectConfig);
187
+ spinner.stop('Local project created');
188
+
189
+ // Step 2: Install dependencies
190
+ spinner.start('Installing dependencies...');
191
+ await execa('pnpm', ['install'], { cwd: projectPath });
192
+ spinner.stop('Dependencies installed');
193
+
194
+ // Step 3: GitHub
195
+ if ((services as string[]).includes('github')) {
196
+ spinner.start('Creating GitHub repository...');
197
+ const github = new GitHubService(config);
198
+ const repo = await github.createRepo(projectName, { private: true });
199
+ spinner.stop(`GitHub repo created: ${repo.html_url}`);
200
+
201
+ // Init git and push
202
+ await execa('git', ['init'], { cwd: projectPath });
203
+ await execa('git', ['add', '.'], { cwd: projectPath });
204
+ await execa('git', ['commit', '-m', 'Initial commit by CodeBakers'], { cwd: projectPath });
205
+ await execa('git', ['remote', 'add', 'origin', repo.clone_url], { cwd: projectPath });
206
+ await execa('git', ['push', '-u', 'origin', 'main'], { cwd: projectPath });
207
+ }
208
+
209
+ // Step 4: Supabase
210
+ if ((services as string[]).includes('supabase')) {
211
+ spinner.start('Creating Supabase project...');
212
+ const supabase = new SupabaseService(config);
213
+ const project = await supabase.createProject(projectName);
214
+ spinner.stop(`Supabase project created: ${project.name}`);
215
+
216
+ // Save Supabase config
217
+ await fs.writeJson(
218
+ path.join(projectPath, '.codebakers', 'supabase.json'),
219
+ { projectId: project.id, projectUrl: project.api_url },
220
+ { spaces: 2 }
221
+ );
222
+ }
223
+
224
+ // Step 5: Vercel
225
+ if ((services as string[]).includes('vercel')) {
226
+ spinner.start('Creating Vercel project...');
227
+ const vercel = new VercelService(config);
228
+ const project = await vercel.createProject(projectName);
229
+ spinner.stop(`Vercel project created`);
230
+
231
+ // Configure domain if specified
232
+ if (domain) {
233
+ spinner.start(`Configuring domain: ${domain}...`);
234
+ await vercel.addDomain(projectName, domain);
235
+ spinner.stop('Domain configured');
236
+ }
237
+
238
+ // Deploy
239
+ spinner.start('Deploying to Vercel...');
240
+ const deployment = await vercel.deploy(projectPath);
241
+ spinner.stop(`Deployed: ${deployment.url}`);
242
+ }
243
+
244
+ // Step 6: Generate CLAUDE.md
245
+ spinner.start('Generating CLAUDE.md...');
246
+ const claudeMd = generateClaudeMd(projectConfig);
247
+ await fs.writeFile(path.join(projectPath, 'CLAUDE.md'), claudeMd);
248
+ spinner.stop('CLAUDE.md generated');
249
+
250
+ // Step 7: Setup git hooks
251
+ spinner.start('Setting up CodeBakers enforcement...');
252
+ await setupGitHooks(projectPath);
253
+ spinner.stop('CodeBakers enforcement configured');
254
+
255
+ // Save project to config
256
+ config.addProject({
257
+ name: projectName,
258
+ path: projectPath,
259
+ createdAt: new Date().toISOString(),
260
+ });
261
+
262
+ // Success!
263
+ p.outro(chalk.green(`
264
+ āœ“ Project created!
265
+
266
+ ${chalk.bold('Your project is ready:')}
267
+ ${chalk.cyan(`cd ${projectName}`)}
268
+ ${chalk.cyan('codebakers code')}
269
+
270
+ ${chalk.dim('Shortcuts:')}
271
+ ${chalk.cyan('codebakers')} — Interactive menu
272
+ ${chalk.cyan('codebakers code')} — AI coding agent
273
+ ${chalk.cyan('codebakers check')} — Pattern enforcement
274
+ ${chalk.cyan('codebakers deploy')} — Deploy to production
275
+ `));
276
+
277
+ } catch (error) {
278
+ spinner.stop('Error occurred');
279
+ p.log.error(`Failed to create project: ${error instanceof Error ? error.message : 'Unknown error'}`);
280
+
281
+ // Offer to clean up
282
+ const cleanup = await p.confirm({
283
+ message: 'Clean up partially created project?',
284
+ });
285
+
286
+ if (cleanup && !p.isCancel(cleanup)) {
287
+ await fs.remove(projectPath);
288
+ p.log.info('Cleaned up.');
289
+ }
290
+ }
291
+ }
292
+
293
+ async function createLocalProject(
294
+ projectPath: string,
295
+ config: Record<string, unknown>
296
+ ): Promise<void> {
297
+ const framework = config.framework as string;
298
+ const ui = config.ui as string;
299
+ const packages = config.packages as string[];
300
+
301
+ // Create directory structure
302
+ await fs.ensureDir(projectPath);
303
+ await fs.ensureDir(path.join(projectPath, '.codebakers'));
304
+ await fs.ensureDir(path.join(projectPath, 'src', 'app'));
305
+ await fs.ensureDir(path.join(projectPath, 'src', 'components', 'ui'));
306
+ await fs.ensureDir(path.join(projectPath, 'src', 'components', 'features'));
307
+ await fs.ensureDir(path.join(projectPath, 'src', 'lib'));
308
+ await fs.ensureDir(path.join(projectPath, 'src', 'hooks'));
309
+ await fs.ensureDir(path.join(projectPath, 'src', 'types'));
310
+ await fs.ensureDir(path.join(projectPath, 'src', 'stores'));
311
+ await fs.ensureDir(path.join(projectPath, 'src', 'services'));
312
+
313
+ // Create package.json
314
+ const packageJson = {
315
+ name: path.basename(projectPath),
316
+ version: '0.1.0',
317
+ private: true,
318
+ scripts: {
319
+ dev: 'next dev',
320
+ build: 'next build',
321
+ start: 'next start',
322
+ lint: 'next lint',
323
+ typecheck: 'tsc --noEmit',
324
+ 'codebakers:check': 'codebakers check',
325
+ },
326
+ dependencies: {
327
+ next: '^14.2.0',
328
+ react: '^18.3.0',
329
+ 'react-dom': '^18.3.0',
330
+ },
331
+ devDependencies: {
332
+ typescript: '^5.5.0',
333
+ '@types/node': '^22.0.0',
334
+ '@types/react': '^18.3.0',
335
+ '@types/react-dom': '^18.3.0',
336
+ },
337
+ };
338
+
339
+ // Add packages based on selection
340
+ if (packages.includes('tailwind')) {
341
+ packageJson.devDependencies['tailwindcss'] = '^3.4.0';
342
+ packageJson.devDependencies['postcss'] = '^8.4.0';
343
+ packageJson.devDependencies['autoprefixer'] = '^10.4.0';
344
+ }
345
+ if (packages.includes('zod')) {
346
+ packageJson.dependencies['zod'] = '^3.23.0';
347
+ }
348
+ if (packages.includes('zustand')) {
349
+ packageJson.dependencies['zustand'] = '^4.5.0';
350
+ }
351
+ if (packages.includes('tanstack-query')) {
352
+ packageJson.dependencies['@tanstack/react-query'] = '^5.50.0';
353
+ }
354
+ if (packages.includes('react-hook-form')) {
355
+ packageJson.dependencies['react-hook-form'] = '^7.52.0';
356
+ packageJson.dependencies['@hookform/resolvers'] = '^3.9.0';
357
+ }
358
+ if (packages.includes('lucide')) {
359
+ packageJson.dependencies['lucide-react'] = '^0.400.0';
360
+ }
361
+ if (ui === 'shadcn') {
362
+ packageJson.dependencies['class-variance-authority'] = '^0.7.0';
363
+ packageJson.dependencies['clsx'] = '^2.1.0';
364
+ packageJson.dependencies['tailwind-merge'] = '^2.4.0';
365
+ packageJson.dependencies['sonner'] = '^1.5.0';
366
+ }
367
+
368
+ // Add Supabase
369
+ packageJson.dependencies['@supabase/supabase-js'] = '^2.45.0';
370
+ packageJson.dependencies['@supabase/ssr'] = '^0.4.0';
371
+
372
+ await fs.writeJson(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
373
+
374
+ // Create other config files
375
+ await createConfigFiles(projectPath, config);
376
+
377
+ // Create CodeBakers project config
378
+ await fs.writeJson(
379
+ path.join(projectPath, '.codebakers', 'config.json'),
380
+ {
381
+ version: '1.0.0',
382
+ framework,
383
+ ui,
384
+ packages,
385
+ patterns: ['00-core', '01-database', '02-auth', '03-api', '04-frontend'],
386
+ createdAt: new Date().toISOString(),
387
+ },
388
+ { spaces: 2 }
389
+ );
390
+ }
391
+
392
+ async function createConfigFiles(
393
+ projectPath: string,
394
+ config: Record<string, unknown>
395
+ ): Promise<void> {
396
+ // TypeScript config
397
+ const tsConfig = {
398
+ compilerOptions: {
399
+ target: 'ES2022',
400
+ lib: ['dom', 'dom.iterable', 'ES2022'],
401
+ allowJs: true,
402
+ skipLibCheck: true,
403
+ strict: true,
404
+ noEmit: true,
405
+ esModuleInterop: true,
406
+ module: 'esnext',
407
+ moduleResolution: 'bundler',
408
+ resolveJsonModule: true,
409
+ isolatedModules: true,
410
+ jsx: 'preserve',
411
+ incremental: true,
412
+ plugins: [{ name: 'next' }],
413
+ paths: { '@/*': ['./src/*'] },
414
+ },
415
+ include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
416
+ exclude: ['node_modules'],
417
+ };
418
+ await fs.writeJson(path.join(projectPath, 'tsconfig.json'), tsConfig, { spaces: 2 });
419
+
420
+ // Next.js config
421
+ const nextConfig = `/** @type {import('next').NextConfig} */
422
+ const nextConfig = {
423
+ experimental: {
424
+ typedRoutes: true,
425
+ },
426
+ };
427
+
428
+ export default nextConfig;
429
+ `;
430
+ await fs.writeFile(path.join(projectPath, 'next.config.mjs'), nextConfig);
431
+
432
+ // Tailwind config
433
+ const tailwindConfig = `import type { Config } from 'tailwindcss';
434
+
435
+ const config: Config = {
436
+ darkMode: ['class'],
437
+ content: [
438
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
439
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
440
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
441
+ ],
442
+ theme: {
443
+ extend: {
444
+ colors: {
445
+ border: 'hsl(var(--border))',
446
+ input: 'hsl(var(--input))',
447
+ ring: 'hsl(var(--ring))',
448
+ background: 'hsl(var(--background))',
449
+ foreground: 'hsl(var(--foreground))',
450
+ primary: {
451
+ DEFAULT: 'hsl(var(--primary))',
452
+ foreground: 'hsl(var(--primary-foreground))',
453
+ },
454
+ secondary: {
455
+ DEFAULT: 'hsl(var(--secondary))',
456
+ foreground: 'hsl(var(--secondary-foreground))',
457
+ },
458
+ destructive: {
459
+ DEFAULT: 'hsl(var(--destructive))',
460
+ foreground: 'hsl(var(--destructive-foreground))',
461
+ },
462
+ muted: {
463
+ DEFAULT: 'hsl(var(--muted))',
464
+ foreground: 'hsl(var(--muted-foreground))',
465
+ },
466
+ accent: {
467
+ DEFAULT: 'hsl(var(--accent))',
468
+ foreground: 'hsl(var(--accent-foreground))',
469
+ },
470
+ },
471
+ borderRadius: {
472
+ lg: 'var(--radius)',
473
+ md: 'calc(var(--radius) - 2px)',
474
+ sm: 'calc(var(--radius) - 4px)',
475
+ },
476
+ },
477
+ },
478
+ plugins: [require('tailwindcss-animate')],
479
+ };
480
+
481
+ export default config;
482
+ `;
483
+ await fs.writeFile(path.join(projectPath, 'tailwind.config.ts'), tailwindConfig);
484
+
485
+ // .env.example
486
+ const envExample = `# Supabase
487
+ NEXT_PUBLIC_SUPABASE_URL=
488
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=
489
+ SUPABASE_SERVICE_ROLE_KEY=
490
+
491
+ # Stripe (optional)
492
+ STRIPE_SECRET_KEY=
493
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
494
+ STRIPE_WEBHOOK_SECRET=
495
+
496
+ # App
497
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
498
+ `;
499
+ await fs.writeFile(path.join(projectPath, '.env.example'), envExample);
500
+ await fs.writeFile(path.join(projectPath, '.env.local'), envExample);
501
+
502
+ // .gitignore
503
+ const gitignore = `# Dependencies
504
+ node_modules/
505
+ .pnp
506
+ .pnp.js
507
+
508
+ # Build
509
+ .next/
510
+ out/
511
+ build/
512
+ dist/
513
+
514
+ # Environment
515
+ .env
516
+ .env.local
517
+ .env.*.local
518
+
519
+ # IDE
520
+ .vscode/
521
+ .idea/
522
+ *.swp
523
+ *.swo
524
+
525
+ # OS
526
+ .DS_Store
527
+ Thumbs.db
528
+
529
+ # Logs
530
+ npm-debug.log*
531
+ yarn-debug.log*
532
+ yarn-error.log*
533
+
534
+ # Testing
535
+ coverage/
536
+ .nyc_output/
537
+
538
+ # Misc
539
+ *.pem
540
+ `;
541
+ await fs.writeFile(path.join(projectPath, '.gitignore'), gitignore);
542
+ }
543
+
544
+ async function setupGitHooks(projectPath: string): Promise<void> {
545
+ // Create pre-commit hook
546
+ const hooksDir = path.join(projectPath, '.git', 'hooks');
547
+
548
+ // Check if git is initialized
549
+ if (!await fs.pathExists(path.join(projectPath, '.git'))) {
550
+ await execa('git', ['init'], { cwd: projectPath });
551
+ }
552
+
553
+ await fs.ensureDir(hooksDir);
554
+
555
+ const preCommitHook = `#!/bin/sh
556
+ # CodeBakers pre-commit hook
557
+
558
+ echo "šŸ” Running CodeBakers check..."
559
+
560
+ # Run pattern check
561
+ codebakers check
562
+
563
+ # If check fails, abort commit
564
+ if [ $? -ne 0 ]; then
565
+ echo "āŒ CodeBakers check failed. Fix issues or use --no-verify to bypass."
566
+ exit 1
567
+ fi
568
+
569
+ echo "āœ“ CodeBakers check passed"
570
+ `;
571
+
572
+ await fs.writeFile(path.join(hooksDir, 'pre-commit'), preCommitHook);
573
+ await fs.chmod(path.join(hooksDir, 'pre-commit'), '755');
574
+ }
@@ -0,0 +1,36 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import { Config } from '../utils/config.js';
4
+
5
+ export async function learnCommand(): Promise<void> {
6
+ const config = new Config();
7
+ const learning = config.getLearning();
8
+
9
+ p.intro(chalk.bgCyan.black(' Learning Settings '));
10
+
11
+ console.log(chalk.bold('\n🧠 What CodeBakers has learned about you:\n'));
12
+
13
+ const shortcuts = Object.entries(learning.shortcuts);
14
+ if (shortcuts.length > 0) {
15
+ console.log(chalk.bold('Shortcuts:'));
16
+ for (const [short, full] of shortcuts) {
17
+ console.log(` ${chalk.cyan(short)} → ${full}`);
18
+ }
19
+ console.log('');
20
+ }
21
+
22
+ const prefs = Object.entries(learning.preferences);
23
+ if (prefs.length > 0) {
24
+ console.log(chalk.bold('Preferences:'));
25
+ for (const [key, value] of prefs) {
26
+ console.log(` ${key}: ${chalk.cyan(String(value))}`);
27
+ }
28
+ console.log('');
29
+ }
30
+
31
+ if (shortcuts.length === 0 && prefs.length === 0) {
32
+ console.log(chalk.dim(' Nothing learned yet. Use CodeBakers more!\n'));
33
+ }
34
+
35
+ p.outro('');
36
+ }
@@ -0,0 +1,102 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import * as fs from 'fs-extra';
4
+ import glob from 'fast-glob';
5
+ import * as path from 'path';
6
+
7
+ export async function securityCommand(): Promise<void> {
8
+ p.intro(chalk.bgCyan.black(' Security Audit '));
9
+
10
+ const spinner = p.spinner();
11
+ spinner.start('Scanning for security issues...');
12
+
13
+ const issues = await runSecurityScan();
14
+
15
+ spinner.stop('Scan complete');
16
+
17
+ if (issues.length === 0) {
18
+ console.log(chalk.green('\nāœ“ No security issues found!\n'));
19
+ displaySecurityScore(100);
20
+ } else {
21
+ console.log(chalk.yellow(`\nāš ļø ${issues.length} security issues found:\n`));
22
+
23
+ for (const issue of issues) {
24
+ const icon = issue.severity === 'critical' ? chalk.red('šŸ”“') :
25
+ issue.severity === 'high' ? chalk.red('🟠') :
26
+ chalk.yellow('🟔');
27
+
28
+ console.log(`${icon} ${chalk.bold(issue.title)}`);
29
+ console.log(chalk.dim(` ${issue.file}:${issue.line}`));
30
+ console.log(` ${issue.description}\n`);
31
+ }
32
+
33
+ const score = Math.max(0, 100 -
34
+ (issues.filter(i => i.severity === 'critical').length * 30) -
35
+ (issues.filter(i => i.severity === 'high').length * 15));
36
+ displaySecurityScore(score);
37
+ }
38
+
39
+ p.outro('');
40
+ }
41
+
42
+ interface SecurityIssue {
43
+ title: string;
44
+ description: string;
45
+ file: string;
46
+ line: number;
47
+ severity: 'critical' | 'high' | 'medium';
48
+ }
49
+
50
+ async function runSecurityScan(): Promise<SecurityIssue[]> {
51
+ const cwd = process.cwd();
52
+ const issues: SecurityIssue[] = [];
53
+
54
+ const files = await glob(['src/**/*.{ts,tsx,js,jsx}'], {
55
+ cwd,
56
+ ignore: ['**/node_modules/**'],
57
+ });
58
+
59
+ const patterns = [
60
+ {
61
+ pattern: /(sk_live_|sk_test_)[a-zA-Z0-9]+/g,
62
+ title: 'Hardcoded Stripe key',
63
+ severity: 'critical' as const,
64
+ description: 'Stripe API key should be in environment variables',
65
+ },
66
+ {
67
+ pattern: /\beval\s*\(/g,
68
+ title: 'Use of eval()',
69
+ severity: 'critical' as const,
70
+ description: 'eval() is a security vulnerability',
71
+ },
72
+ {
73
+ pattern: /dangerouslySetInnerHTML/g,
74
+ title: 'dangerouslySetInnerHTML',
75
+ severity: 'high' as const,
76
+ description: 'Ensure content is sanitized with DOMPurify',
77
+ },
78
+ ];
79
+
80
+ for (const file of files) {
81
+ const content = await fs.readFile(path.join(cwd, file), 'utf-8');
82
+
83
+ for (const { pattern, title, severity, description } of patterns) {
84
+ let match;
85
+ const regex = new RegExp(pattern.source, pattern.flags);
86
+ while ((match = regex.exec(content)) !== null) {
87
+ const line = content.substring(0, match.index).split('\n').length;
88
+ issues.push({ title, description, file, line, severity });
89
+ }
90
+ }
91
+ }
92
+
93
+ return issues;
94
+ }
95
+
96
+ function displaySecurityScore(score: number): void {
97
+ const color = score >= 80 ? chalk.green : score >= 50 ? chalk.yellow : chalk.red;
98
+ const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'F';
99
+
100
+ console.log(chalk.bold('Security Score:'));
101
+ console.log(color(`\n ${score}/100 Grade: ${grade}\n`));
102
+ }