codebakers 1.0.45 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +275 -60
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +4260 -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/design.ts +298 -0
  16. package/src/commands/fix.ts +20 -0
  17. package/src/commands/gateway.ts +604 -0
  18. package/src/commands/generate.ts +178 -0
  19. package/src/commands/init.ts +574 -0
  20. package/src/commands/learn.ts +36 -0
  21. package/src/commands/security.ts +102 -0
  22. package/src/commands/setup.ts +448 -0
  23. package/src/commands/status.ts +56 -0
  24. package/src/index.ts +278 -0
  25. package/src/patterns/loader.ts +337 -0
  26. package/src/services/github.ts +61 -0
  27. package/src/services/supabase.ts +147 -0
  28. package/src/services/vercel.ts +61 -0
  29. package/src/utils/claude-md.ts +287 -0
  30. package/src/utils/config.ts +282 -0
  31. package/src/utils/updates.ts +27 -0
  32. package/tsconfig.json +17 -10
  33. package/.vscodeignore +0 -18
  34. package/LICENSE +0 -21
  35. package/codebakers-1.0.0.vsix +0 -0
  36. package/codebakers-1.0.10.vsix +0 -0
  37. package/codebakers-1.0.11.vsix +0 -0
  38. package/codebakers-1.0.12.vsix +0 -0
  39. package/codebakers-1.0.13.vsix +0 -0
  40. package/codebakers-1.0.14.vsix +0 -0
  41. package/codebakers-1.0.15.vsix +0 -0
  42. package/codebakers-1.0.16.vsix +0 -0
  43. package/codebakers-1.0.17.vsix +0 -0
  44. package/codebakers-1.0.18.vsix +0 -0
  45. package/codebakers-1.0.19.vsix +0 -0
  46. package/codebakers-1.0.20.vsix +0 -0
  47. package/codebakers-1.0.21.vsix +0 -0
  48. package/codebakers-1.0.22.vsix +0 -0
  49. package/codebakers-1.0.23.vsix +0 -0
  50. package/codebakers-1.0.24.vsix +0 -0
  51. package/codebakers-1.0.25.vsix +0 -0
  52. package/codebakers-1.0.26.vsix +0 -0
  53. package/codebakers-1.0.27.vsix +0 -0
  54. package/codebakers-1.0.28.vsix +0 -0
  55. package/codebakers-1.0.29.vsix +0 -0
  56. package/codebakers-1.0.30.vsix +0 -0
  57. package/codebakers-1.0.31.vsix +0 -0
  58. package/codebakers-1.0.32.vsix +0 -0
  59. package/codebakers-1.0.35.vsix +0 -0
  60. package/codebakers-1.0.36.vsix +0 -0
  61. package/codebakers-1.0.37.vsix +0 -0
  62. package/codebakers-1.0.38.vsix +0 -0
  63. package/codebakers-1.0.39.vsix +0 -0
  64. package/codebakers-1.0.40.vsix +0 -0
  65. package/codebakers-1.0.41.vsix +0 -0
  66. package/codebakers-1.0.42.vsix +0 -0
  67. package/codebakers-1.0.43.vsix +0 -0
  68. package/codebakers-1.0.44.vsix +0 -0
  69. package/codebakers-1.0.45.vsix +0 -0
  70. package/dist/extension.js +0 -1394
  71. package/esbuild.js +0 -63
  72. package/media/icon.png +0 -0
  73. package/media/icon.svg +0 -7
  74. package/nul +0 -1
  75. package/preview.html +0 -547
  76. package/src/ChatPanelProvider.ts +0 -1815
  77. package/src/ChatViewProvider.ts +0 -749
  78. package/src/CodeBakersClient.ts +0 -1146
  79. package/src/CodeValidator.ts +0 -645
  80. package/src/FileOperations.ts +0 -410
  81. package/src/ProjectContext.ts +0 -526
  82. package/src/extension.ts +0 -332
@@ -0,0 +1,414 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import { execa } from 'execa';
4
+ import * as fs from 'fs-extra';
5
+ import * as path from 'path';
6
+ import Anthropic from '@anthropic-ai/sdk';
7
+ import { Config } from '../utils/config.js';
8
+ import { VercelService } from '../services/vercel.js';
9
+ import { runPatternCheck } from './check.js';
10
+
11
+ interface DeployOptions {
12
+ preview?: boolean;
13
+ check?: boolean;
14
+ }
15
+
16
+ export async function deployCommand(options: DeployOptions = {}): Promise<void> {
17
+ const config = new Config();
18
+
19
+ if (!config.isInProject()) {
20
+ p.log.error('Not in a CodeBakers project.');
21
+ return;
22
+ }
23
+
24
+ p.intro(chalk.bgCyan.black(' Deploy to Production '));
25
+
26
+ const spinner = p.spinner();
27
+
28
+ // Step 1: Run pattern check (unless skipped)
29
+ if (options.check !== false) {
30
+ spinner.start('Running CodeBakers check...');
31
+ const checkResult = await runPatternCheck(false);
32
+
33
+ if (!checkResult.passed) {
34
+ spinner.stop('');
35
+ const errors = checkResult.violations.filter(v => v.severity === 'error');
36
+
37
+ if (errors.length > 0) {
38
+ p.log.error(`${errors.length} pattern violations found. Fix before deploying.`);
39
+
40
+ const showViolations = await p.confirm({
41
+ message: 'Show violations?',
42
+ initialValue: true,
43
+ });
44
+
45
+ if (showViolations && !p.isCancel(showViolations)) {
46
+ for (const v of errors) {
47
+ console.log(chalk.red(` ✗ ${v.file}:${v.line} - ${v.message}`));
48
+ }
49
+ }
50
+
51
+ const autoFix = await p.confirm({
52
+ message: 'Attempt auto-fix with AI?',
53
+ initialValue: true,
54
+ });
55
+
56
+ if (!autoFix || p.isCancel(autoFix)) {
57
+ p.outro(chalk.red('Deploy cancelled.'));
58
+ return;
59
+ }
60
+
61
+ // Auto-fix with AI
62
+ spinner.start('Auto-fixing with AI...');
63
+ await autoFixWithAI(config, errors);
64
+ spinner.stop('Auto-fix complete');
65
+
66
+ // Re-run check
67
+ const recheck = await runPatternCheck(false);
68
+ if (!recheck.passed) {
69
+ p.log.error('Some violations remain. Manual fix required.');
70
+ p.outro(chalk.red('Deploy cancelled.'));
71
+ return;
72
+ }
73
+ }
74
+ }
75
+ spinner.stop('Pattern check passed');
76
+ }
77
+
78
+ // Step 2: Run TypeScript check
79
+ spinner.start('Running TypeScript check...');
80
+ try {
81
+ await execa('npx', ['tsc', '--noEmit'], { cwd: process.cwd() });
82
+ spinner.stop('TypeScript check passed');
83
+ } catch (error) {
84
+ spinner.stop('');
85
+ p.log.error('TypeScript errors found.');
86
+
87
+ const fix = await p.confirm({
88
+ message: 'Attempt to fix TypeScript errors?',
89
+ initialValue: true,
90
+ });
91
+
92
+ if (fix && !p.isCancel(fix)) {
93
+ spinner.start('Fixing TypeScript errors...');
94
+ const fixed = await fixTypeScriptErrors(config);
95
+ spinner.stop(fixed ? 'TypeScript errors fixed' : 'Could not auto-fix all errors');
96
+
97
+ if (!fixed) {
98
+ p.outro(chalk.red('Deploy cancelled.'));
99
+ return;
100
+ }
101
+ } else {
102
+ p.outro(chalk.red('Deploy cancelled.'));
103
+ return;
104
+ }
105
+ }
106
+
107
+ // Step 3: Run build
108
+ spinner.start('Building project...');
109
+ try {
110
+ await execa('pnpm', ['build'], { cwd: process.cwd() });
111
+ spinner.stop('Build successful');
112
+ } catch (error) {
113
+ spinner.stop('');
114
+ p.log.error('Build failed.');
115
+
116
+ const errorOutput = error instanceof Error ? error.message : 'Unknown error';
117
+ console.log(chalk.dim(errorOutput));
118
+
119
+ const fix = await p.confirm({
120
+ message: 'Attempt to fix build errors with AI?',
121
+ initialValue: true,
122
+ });
123
+
124
+ if (fix && !p.isCancel(fix)) {
125
+ spinner.start('Fixing build errors...');
126
+ const fixed = await fixBuildErrors(config, errorOutput);
127
+ spinner.stop(fixed ? 'Build errors fixed' : 'Could not auto-fix');
128
+
129
+ if (fixed) {
130
+ // Retry build
131
+ spinner.start('Retrying build...');
132
+ try {
133
+ await execa('pnpm', ['build'], { cwd: process.cwd() });
134
+ spinner.stop('Build successful');
135
+ } catch {
136
+ spinner.stop('Build still failing');
137
+ p.outro(chalk.red('Deploy cancelled.'));
138
+ return;
139
+ }
140
+ } else {
141
+ p.outro(chalk.red('Deploy cancelled.'));
142
+ return;
143
+ }
144
+ } else {
145
+ p.outro(chalk.red('Deploy cancelled.'));
146
+ return;
147
+ }
148
+ }
149
+
150
+ // Step 4: Git commit if there are changes
151
+ spinner.start('Checking for uncommitted changes...');
152
+ const { stdout: gitStatus } = await execa('git', ['status', '--porcelain'], { cwd: process.cwd() });
153
+
154
+ if (gitStatus.trim()) {
155
+ spinner.stop('');
156
+ const commit = await p.confirm({
157
+ message: 'You have uncommitted changes. Commit before deploying?',
158
+ initialValue: true,
159
+ });
160
+
161
+ if (commit && !p.isCancel(commit)) {
162
+ const message = await p.text({
163
+ message: 'Commit message:',
164
+ placeholder: 'Pre-deploy changes',
165
+ initialValue: 'Pre-deploy changes',
166
+ });
167
+
168
+ if (!p.isCancel(message)) {
169
+ await execa('git', ['add', '.'], { cwd: process.cwd() });
170
+ await execa('git', ['commit', '-m', message as string], { cwd: process.cwd() });
171
+
172
+ spinner.start('Pushing to GitHub...');
173
+ await execa('git', ['push'], { cwd: process.cwd() });
174
+ spinner.stop('Pushed to GitHub');
175
+ }
176
+ }
177
+ } else {
178
+ spinner.stop('No uncommitted changes');
179
+ }
180
+
181
+ // Step 5: Deploy to Vercel
182
+ const deployType = options.preview ? 'preview' : 'production';
183
+ spinner.start(`Deploying to ${deployType}...`);
184
+
185
+ try {
186
+ const vercel = new VercelService(config);
187
+ const deployment = await vercel.deploy(process.cwd(), !options.preview);
188
+
189
+ spinner.stop('Deployment complete!');
190
+
191
+ console.log(boxedOutput(`
192
+ ${chalk.green('✓')} Deployed successfully!
193
+
194
+ ${chalk.bold('URL:')} ${deployment.url}
195
+ ${chalk.bold('Type:')} ${deployType}
196
+ ${chalk.bold('Time:')} ${new Date().toLocaleTimeString()}
197
+
198
+ ${chalk.dim('View in Vercel Dashboard:')}
199
+ ${chalk.dim(deployment.dashboardUrl || 'https://vercel.com/dashboard')}
200
+ `));
201
+
202
+ } catch (error) {
203
+ spinner.stop('');
204
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
205
+ p.log.error(`Deployment failed: ${errorMsg}`);
206
+
207
+ // Check if it's a build error from Vercel
208
+ if (errorMsg.includes('Build failed')) {
209
+ const retry = await p.confirm({
210
+ message: 'Attempt to fix and retry?',
211
+ initialValue: true,
212
+ });
213
+
214
+ if (retry && !p.isCancel(retry)) {
215
+ spinner.start('Analyzing Vercel build error...');
216
+ // Would fetch Vercel logs and auto-fix
217
+ spinner.stop('Fix attempted');
218
+ }
219
+ }
220
+
221
+ p.outro(chalk.red('Deploy failed.'));
222
+ }
223
+ }
224
+
225
+ async function autoFixWithAI(
226
+ config: Config,
227
+ violations: Array<{ file: string; line: number; rule: string; message: string }>
228
+ ): Promise<void> {
229
+ const anthropicCreds = config.getCredentials('anthropic');
230
+ if (!anthropicCreds?.apiKey) {
231
+ throw new Error('Anthropic API key not configured');
232
+ }
233
+
234
+ const anthropic = new Anthropic({
235
+ apiKey: anthropicCreds.apiKey,
236
+ });
237
+
238
+ // Group by file
239
+ const byFile = new Map<string, typeof violations>();
240
+ for (const v of violations) {
241
+ if (!byFile.has(v.file)) {
242
+ byFile.set(v.file, []);
243
+ }
244
+ byFile.get(v.file)!.push(v);
245
+ }
246
+
247
+ for (const [file, fileViolations] of byFile) {
248
+ const filePath = path.join(process.cwd(), file);
249
+ const content = await fs.readFile(filePath, 'utf-8');
250
+
251
+ const prompt = `Fix these CodeBakers pattern violations in this file:
252
+
253
+ Violations:
254
+ ${fileViolations.map(v => `- Line ${v.line}: ${v.rule} - ${v.message}`).join('\n')}
255
+
256
+ Current file content:
257
+ \`\`\`typescript
258
+ ${content}
259
+ \`\`\`
260
+
261
+ Output ONLY the corrected file content, no explanations. Keep all existing functionality.`;
262
+
263
+ const response = await anthropic.messages.create({
264
+ model: 'claude-sonnet-4-20250514',
265
+ max_tokens: 8192,
266
+ messages: [{ role: 'user', content: prompt }],
267
+ });
268
+
269
+ let fixed = response.content[0].type === 'text' ? response.content[0].text : '';
270
+
271
+ // Extract code from markdown if present
272
+ const codeMatch = fixed.match(/```(?:typescript|tsx?)?\n([\s\S]+?)\n```/);
273
+ if (codeMatch) {
274
+ fixed = codeMatch[1];
275
+ }
276
+
277
+ await fs.writeFile(filePath, fixed);
278
+ }
279
+ }
280
+
281
+ async function fixTypeScriptErrors(config: Config): Promise<boolean> {
282
+ try {
283
+ // Get TypeScript errors
284
+ const { stderr } = await execa('npx', ['tsc', '--noEmit'], {
285
+ cwd: process.cwd(),
286
+ reject: false,
287
+ });
288
+
289
+ if (!stderr) return true;
290
+
291
+ const anthropicCreds = config.getCredentials('anthropic');
292
+ if (!anthropicCreds?.apiKey) {
293
+ return false;
294
+ }
295
+
296
+ const anthropic = new Anthropic({
297
+ apiKey: anthropicCreds.apiKey,
298
+ });
299
+
300
+ // Parse error locations
301
+ const errorLines = stderr.split('\n').filter(line => line.includes('error TS'));
302
+
303
+ for (const errorLine of errorLines.slice(0, 10)) { // Limit to 10 errors
304
+ const match = errorLine.match(/^(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)$/);
305
+ if (!match) continue;
306
+
307
+ const [, file, line, , , message] = match;
308
+ const filePath = path.join(process.cwd(), file);
309
+
310
+ if (!await fs.pathExists(filePath)) continue;
311
+
312
+ const content = await fs.readFile(filePath, 'utf-8');
313
+
314
+ const prompt = `Fix this TypeScript error:
315
+
316
+ File: ${file}
317
+ Line: ${line}
318
+ Error: ${message}
319
+
320
+ Current file content:
321
+ \`\`\`typescript
322
+ ${content}
323
+ \`\`\`
324
+
325
+ Output ONLY the corrected file content, no explanations.`;
326
+
327
+ const response = await anthropic.messages.create({
328
+ model: 'claude-sonnet-4-20250514',
329
+ max_tokens: 8192,
330
+ messages: [{ role: 'user', content: prompt }],
331
+ });
332
+
333
+ let fixed = response.content[0].type === 'text' ? response.content[0].text : '';
334
+
335
+ const codeMatch = fixed.match(/```(?:typescript|tsx?)?\n([\s\S]+?)\n```/);
336
+ if (codeMatch) {
337
+ fixed = codeMatch[1];
338
+ }
339
+
340
+ await fs.writeFile(filePath, fixed);
341
+ }
342
+
343
+ return true;
344
+ } catch {
345
+ return false;
346
+ }
347
+ }
348
+
349
+ async function fixBuildErrors(config: Config, errorOutput: string): Promise<boolean> {
350
+ const anthropicCreds = config.getCredentials('anthropic');
351
+ if (!anthropicCreds?.apiKey) {
352
+ return false;
353
+ }
354
+
355
+ const anthropic = new Anthropic({
356
+ apiKey: anthropicCreds.apiKey,
357
+ });
358
+
359
+ // Parse error to find file
360
+ const fileMatch = errorOutput.match(/(?:Error|error).*?(?:in|at)\s+(.+?\.tsx?)(?::|$)/);
361
+ if (!fileMatch) return false;
362
+
363
+ const file = fileMatch[1];
364
+ const filePath = path.join(process.cwd(), file);
365
+
366
+ if (!await fs.pathExists(filePath)) return false;
367
+
368
+ const content = await fs.readFile(filePath, 'utf-8');
369
+
370
+ const prompt = `Fix this build error:
371
+
372
+ Error output:
373
+ ${errorOutput}
374
+
375
+ File: ${file}
376
+
377
+ Current file content:
378
+ \`\`\`typescript
379
+ ${content}
380
+ \`\`\`
381
+
382
+ Output ONLY the corrected file content, no explanations.`;
383
+
384
+ try {
385
+ const response = await anthropic.messages.create({
386
+ model: 'claude-sonnet-4-20250514',
387
+ max_tokens: 8192,
388
+ messages: [{ role: 'user', content: prompt }],
389
+ });
390
+
391
+ let fixed = response.content[0].type === 'text' ? response.content[0].text : '';
392
+
393
+ const codeMatch = fixed.match(/```(?:typescript|tsx?)?\n([\s\S]+?)\n```/);
394
+ if (codeMatch) {
395
+ fixed = codeMatch[1];
396
+ }
397
+
398
+ await fs.writeFile(filePath, fixed);
399
+ return true;
400
+ } catch {
401
+ return false;
402
+ }
403
+ }
404
+
405
+ function boxedOutput(content: string): string {
406
+ const lines = content.split('\n');
407
+ const maxLength = Math.max(...lines.map(l => l.replace(/\x1b\[[0-9;]*m/g, '').length));
408
+ const border = '─'.repeat(maxLength + 2);
409
+
410
+ return `
411
+ ╭${border}╮
412
+ ${lines.map(l => `│ ${l.padEnd(maxLength)} │`).join('\n')}
413
+ ╰${border}╯`;
414
+ }
@@ -0,0 +1,298 @@
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 { Config } from '../utils/config.js';
6
+
7
+ const DESIGN_PROFILES = {
8
+ minimal: {
9
+ name: 'Minimal',
10
+ inspiration: 'Linear, Notion, Vercel',
11
+ fonts: { heading: 'Inter', body: 'Inter' },
12
+ corners: 'rounded-md',
13
+ shadows: 'none',
14
+ spacing: 'generous',
15
+ },
16
+ bold: {
17
+ name: 'Bold',
18
+ inspiration: 'Stripe, Ramp, Mercury',
19
+ fonts: { heading: 'Plus Jakarta Sans', body: 'Inter' },
20
+ corners: 'rounded-2xl',
21
+ shadows: 'elevated',
22
+ spacing: 'generous',
23
+ },
24
+ editorial: {
25
+ name: 'Editorial',
26
+ inspiration: 'Medium, Substack, NY Times',
27
+ fonts: { heading: 'Playfair Display', body: 'Source Serif Pro' },
28
+ corners: 'rounded-none',
29
+ shadows: 'none',
30
+ spacing: 'reading',
31
+ },
32
+ playful: {
33
+ name: 'Playful',
34
+ inspiration: 'Figma, Slack, Notion',
35
+ fonts: { heading: 'Nunito', body: 'Nunito' },
36
+ corners: 'rounded-full',
37
+ shadows: 'soft',
38
+ spacing: 'comfortable',
39
+ },
40
+ premium: {
41
+ name: 'Premium',
42
+ inspiration: 'Apple, Porsche, Amex',
43
+ fonts: { heading: 'Cormorant Garamond', body: 'Lato' },
44
+ corners: 'rounded-lg',
45
+ shadows: 'subtle',
46
+ spacing: 'luxurious',
47
+ },
48
+ dashboard: {
49
+ name: 'Dashboard',
50
+ inspiration: 'Datadog, Grafana, Linear',
51
+ fonts: { heading: 'Inter', body: 'Inter' },
52
+ corners: 'rounded-md',
53
+ shadows: 'card',
54
+ spacing: 'compact',
55
+ },
56
+ };
57
+
58
+ export async function designCommand(subcommand?: string): Promise<void> {
59
+ const config = new Config();
60
+
61
+ if (!config.isInProject()) {
62
+ p.log.error('Not in a CodeBakers project. Run `codebakers init` first.');
63
+ return;
64
+ }
65
+
66
+ p.intro(chalk.bgCyan.black(' Design System '));
67
+
68
+ const action = subcommand || await p.select({
69
+ message: 'What do you want to do?',
70
+ options: [
71
+ { value: 'profile', label: '🎨 Set design profile' },
72
+ { value: 'palette', label: '🌈 Generate color palette' },
73
+ { value: 'check', label: '✅ Check design quality' },
74
+ { value: 'view', label: '👀 View current settings' },
75
+ ],
76
+ });
77
+
78
+ if (p.isCancel(action)) return;
79
+
80
+ switch (action) {
81
+ case 'profile':
82
+ await setProfile();
83
+ break;
84
+ case 'palette':
85
+ await generatePalette();
86
+ break;
87
+ case 'check':
88
+ await checkDesign();
89
+ break;
90
+ case 'view':
91
+ await viewSettings();
92
+ break;
93
+ }
94
+ }
95
+
96
+ async function setProfile(): Promise<void> {
97
+ const profile = await p.select({
98
+ message: 'Choose your design profile:',
99
+ options: Object.entries(DESIGN_PROFILES).map(([key, value]) => ({
100
+ value: key,
101
+ label: `${value.name}`,
102
+ hint: value.inspiration,
103
+ })),
104
+ });
105
+
106
+ if (p.isCancel(profile)) return;
107
+
108
+ const selected = DESIGN_PROFILES[profile as keyof typeof DESIGN_PROFILES];
109
+
110
+ // Save to project config
111
+ const configPath = path.join(process.cwd(), '.codebakers', 'design.json');
112
+ await fs.ensureDir(path.dirname(configPath));
113
+ await fs.writeJson(configPath, {
114
+ profile,
115
+ ...selected,
116
+ updatedAt: new Date().toISOString(),
117
+ }, { spaces: 2 });
118
+
119
+ p.log.success(`Design profile set to ${selected.name}`);
120
+
121
+ console.log(chalk.dim(`
122
+ Fonts: ${selected.fonts.heading} / ${selected.fonts.body}
123
+ Corners: ${selected.corners}
124
+ Shadows: ${selected.shadows}
125
+ Spacing: ${selected.spacing}
126
+ `));
127
+ }
128
+
129
+ async function generatePalette(): Promise<void> {
130
+ const brandColor = await p.text({
131
+ message: 'Enter your brand color (hex):',
132
+ placeholder: '#6366F1',
133
+ validate: (v) => {
134
+ if (!v.match(/^#[0-9A-Fa-f]{6}$/)) return 'Enter a valid hex color (#RRGGBB)';
135
+ return undefined;
136
+ },
137
+ });
138
+
139
+ if (p.isCancel(brandColor)) return;
140
+
141
+ const palette = generateColorPalette(brandColor as string);
142
+
143
+ // Save to project
144
+ const configPath = path.join(process.cwd(), '.codebakers', 'design.json');
145
+ let config = {};
146
+ if (await fs.pathExists(configPath)) {
147
+ config = await fs.readJson(configPath);
148
+ }
149
+
150
+ await fs.writeJson(configPath, {
151
+ ...config,
152
+ colors: palette,
153
+ updatedAt: new Date().toISOString(),
154
+ }, { spaces: 2 });
155
+
156
+ p.log.success('Color palette generated!');
157
+
158
+ console.log(`
159
+ ${chalk.bgHex(palette.brand[500]).black(' Brand ')} ${palette.brand[500]}
160
+ ${chalk.bgHex(palette.brand[100]).black(' Light ')} ${palette.brand[100]}
161
+ ${chalk.bgHex(palette.brand[900]).white(' Dark ')} ${palette.brand[900]}
162
+ ${chalk.bgHex(palette.accent).black(' Accent ')} ${palette.accent}
163
+ `);
164
+ }
165
+
166
+ function generateColorPalette(hex: string): Record<string, any> {
167
+ // Convert hex to HSL
168
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
169
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
170
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
171
+
172
+ const max = Math.max(r, g, b);
173
+ const min = Math.min(r, g, b);
174
+ let h = 0, s = 0, l = (max + min) / 2;
175
+
176
+ if (max !== min) {
177
+ const d = max - min;
178
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
179
+ switch (max) {
180
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
181
+ case g: h = ((b - r) / d + 2) / 6; break;
182
+ case b: h = ((r - g) / d + 4) / 6; break;
183
+ }
184
+ }
185
+
186
+ h = Math.round(h * 360);
187
+ s = Math.round(s * 100);
188
+ l = Math.round(l * 100);
189
+
190
+ // Generate scale
191
+ const hslToHex = (h: number, s: number, l: number): string => {
192
+ l /= 100;
193
+ const a = s * Math.min(l, 1 - l) / 100;
194
+ const f = (n: number) => {
195
+ const k = (n + h / 30) % 12;
196
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
197
+ return Math.round(255 * color).toString(16).padStart(2, '0');
198
+ };
199
+ return `#${f(0)}${f(8)}${f(4)}`;
200
+ };
201
+
202
+ return {
203
+ brand: {
204
+ 50: hslToHex(h, s * 0.3, 97),
205
+ 100: hslToHex(h, s * 0.4, 94),
206
+ 200: hslToHex(h, s * 0.5, 86),
207
+ 300: hslToHex(h, s * 0.6, 74),
208
+ 400: hslToHex(h, s * 0.8, 62),
209
+ 500: hex, // Original
210
+ 600: hslToHex(h, s, l * 0.85),
211
+ 700: hslToHex(h, s, l * 0.7),
212
+ 800: hslToHex(h, s, l * 0.55),
213
+ 900: hslToHex(h, s, l * 0.4),
214
+ },
215
+ accent: hslToHex((h + 180) % 360, s, l), // Complementary
216
+ };
217
+ }
218
+
219
+ async function checkDesign(): Promise<void> {
220
+ const spinner = p.spinner();
221
+ spinner.start('Checking design quality...');
222
+
223
+ const cwd = process.cwd();
224
+ const issues: string[] = [];
225
+
226
+ // Check for anti-patterns in code
227
+ const glob = (await import('fast-glob')).default;
228
+ const files = await glob(['src/**/*.{tsx,jsx}'], { cwd });
229
+
230
+ for (const file of files) {
231
+ const content = await fs.readFile(path.join(cwd, file), 'utf-8');
232
+
233
+ // Check for generic gradient hero
234
+ if (content.includes('bg-gradient-to-r from-blue-500') ||
235
+ content.includes('bg-gradient-to-r from-purple-500')) {
236
+ issues.push(`${file}: Generic gradient hero detected`);
237
+ }
238
+
239
+ // Check for icon spam
240
+ const iconMatches = content.match(/<(Rocket|Shield|Zap|Star|Check|Lightning)/g);
241
+ if (iconMatches && iconMatches.length > 3) {
242
+ issues.push(`${file}: Too many generic icons (${iconMatches.length})`);
243
+ }
244
+
245
+ // Check for generic CTAs
246
+ if (content.includes('>Get Started<') || content.includes('>Learn More<')) {
247
+ issues.push(`${file}: Generic CTA text - be more specific`);
248
+ }
249
+
250
+ // Check for lazy empty states
251
+ if (content.includes('No data') || content.includes('Nothing here')) {
252
+ issues.push(`${file}: Lazy empty state - add helpful message`);
253
+ }
254
+
255
+ // Check for small section padding
256
+ if (content.includes('py-4') || content.includes('py-6') || content.includes('py-8')) {
257
+ if (content.includes('<section')) {
258
+ issues.push(`${file}: Section padding too small - use py-16 or larger`);
259
+ }
260
+ }
261
+ }
262
+
263
+ spinner.stop('Check complete');
264
+
265
+ if (issues.length === 0) {
266
+ console.log(chalk.green('\n✓ No design issues found!\n'));
267
+ } else {
268
+ console.log(chalk.yellow(`\n⚠️ ${issues.length} design issues found:\n`));
269
+ for (const issue of issues) {
270
+ console.log(chalk.dim(` • ${issue}`));
271
+ }
272
+ console.log('');
273
+ }
274
+ }
275
+
276
+ async function viewSettings(): Promise<void> {
277
+ const configPath = path.join(process.cwd(), '.codebakers', 'design.json');
278
+
279
+ if (!await fs.pathExists(configPath)) {
280
+ p.log.info('No design settings configured. Run `codebakers design profile` to set up.');
281
+ return;
282
+ }
283
+
284
+ const config = await fs.readJson(configPath);
285
+
286
+ console.log(chalk.bold('\nCurrent Design Settings:\n'));
287
+ console.log(` Profile: ${config.name || config.profile}`);
288
+ console.log(` Fonts: ${config.fonts?.heading} / ${config.fonts?.body}`);
289
+ console.log(` Corners: ${config.corners}`);
290
+ console.log(` Shadows: ${config.shadows}`);
291
+ console.log(` Spacing: ${config.spacing}`);
292
+
293
+ if (config.colors) {
294
+ console.log(` Brand: ${config.colors.brand?.[500]}`);
295
+ console.log(` Accent: ${config.colors.accent}`);
296
+ }
297
+ console.log('');
298
+ }