codebakers 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +763 -250
- package/package.json +1 -1
- package/src/commands/website.ts +643 -0
- package/src/index.ts +14 -2
package/package.json
CHANGED
|
@@ -0,0 +1,643 @@
|
|
|
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 Anthropic from '@anthropic-ai/sdk';
|
|
6
|
+
import { execa } from 'execa';
|
|
7
|
+
import { Config } from '../utils/config.js';
|
|
8
|
+
import { textWithVoice } from '../utils/voice.js';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// WEBSITE TEMPLATES
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
interface WebsiteTemplate {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
icon: string;
|
|
19
|
+
sections: string[];
|
|
20
|
+
style: 'minimal' | 'bold' | 'elegant' | 'playful' | 'corporate';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const TEMPLATES: WebsiteTemplate[] = [
|
|
24
|
+
{
|
|
25
|
+
id: 'landing',
|
|
26
|
+
name: 'Landing Page',
|
|
27
|
+
description: 'Convert visitors into customers',
|
|
28
|
+
icon: '🚀',
|
|
29
|
+
sections: ['hero', 'features', 'testimonials', 'pricing', 'cta', 'footer'],
|
|
30
|
+
style: 'bold',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'saas',
|
|
34
|
+
name: 'SaaS Website',
|
|
35
|
+
description: 'Software product marketing site',
|
|
36
|
+
icon: '💻',
|
|
37
|
+
sections: ['hero', 'features', 'how-it-works', 'pricing', 'faq', 'testimonials', 'cta', 'footer'],
|
|
38
|
+
style: 'minimal',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'portfolio',
|
|
42
|
+
name: 'Portfolio',
|
|
43
|
+
description: 'Showcase your work',
|
|
44
|
+
icon: '🎨',
|
|
45
|
+
sections: ['hero', 'about', 'projects', 'skills', 'contact', 'footer'],
|
|
46
|
+
style: 'elegant',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'agency',
|
|
50
|
+
name: 'Agency Website',
|
|
51
|
+
description: 'Professional services company',
|
|
52
|
+
icon: '🏢',
|
|
53
|
+
sections: ['hero', 'services', 'portfolio', 'team', 'testimonials', 'contact', 'footer'],
|
|
54
|
+
style: 'corporate',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'startup',
|
|
58
|
+
name: 'Startup Page',
|
|
59
|
+
description: 'Early stage company',
|
|
60
|
+
icon: '⚡',
|
|
61
|
+
sections: ['hero', 'problem', 'solution', 'features', 'team', 'waitlist', 'footer'],
|
|
62
|
+
style: 'bold',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'restaurant',
|
|
66
|
+
name: 'Restaurant',
|
|
67
|
+
description: 'Food & dining establishment',
|
|
68
|
+
icon: '🍽️',
|
|
69
|
+
sections: ['hero', 'menu', 'about', 'gallery', 'reservations', 'location', 'footer'],
|
|
70
|
+
style: 'elegant',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'ecommerce',
|
|
74
|
+
name: 'E-commerce',
|
|
75
|
+
description: 'Online store',
|
|
76
|
+
icon: '🛒',
|
|
77
|
+
sections: ['hero', 'featured-products', 'categories', 'bestsellers', 'newsletter', 'footer'],
|
|
78
|
+
style: 'minimal',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'blog',
|
|
82
|
+
name: 'Blog',
|
|
83
|
+
description: 'Content & articles',
|
|
84
|
+
icon: '📝',
|
|
85
|
+
sections: ['hero', 'featured-posts', 'categories', 'newsletter', 'about', 'footer'],
|
|
86
|
+
style: 'minimal',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'event',
|
|
90
|
+
name: 'Event Page',
|
|
91
|
+
description: 'Conference or meetup',
|
|
92
|
+
icon: '🎉',
|
|
93
|
+
sections: ['hero', 'speakers', 'schedule', 'venue', 'sponsors', 'tickets', 'footer'],
|
|
94
|
+
style: 'bold',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'nonprofit',
|
|
98
|
+
name: 'Nonprofit',
|
|
99
|
+
description: 'Charity or cause',
|
|
100
|
+
icon: '💚',
|
|
101
|
+
sections: ['hero', 'mission', 'impact', 'programs', 'donate', 'volunteer', 'footer'],
|
|
102
|
+
style: 'elegant',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'personal',
|
|
106
|
+
name: 'Personal Website',
|
|
107
|
+
description: 'Your online presence',
|
|
108
|
+
icon: '👤',
|
|
109
|
+
sections: ['hero', 'about', 'experience', 'projects', 'blog', 'contact', 'footer'],
|
|
110
|
+
style: 'minimal',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'coming-soon',
|
|
114
|
+
name: 'Coming Soon',
|
|
115
|
+
description: 'Pre-launch teaser',
|
|
116
|
+
icon: '⏳',
|
|
117
|
+
sections: ['hero', 'countdown', 'features-preview', 'waitlist', 'footer'],
|
|
118
|
+
style: 'bold',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'custom',
|
|
122
|
+
name: 'Custom',
|
|
123
|
+
description: 'Build from scratch with AI',
|
|
124
|
+
icon: '✨',
|
|
125
|
+
sections: [],
|
|
126
|
+
style: 'minimal',
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// MAIN COMMAND
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
export async function websiteCommand(): Promise<void> {
|
|
135
|
+
const config = new Config();
|
|
136
|
+
|
|
137
|
+
if (!config.isConfigured()) {
|
|
138
|
+
p.log.error('Please run `codebakers setup` first.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const anthropicCreds = config.getCredentials('anthropic');
|
|
143
|
+
if (!anthropicCreds?.apiKey) {
|
|
144
|
+
p.log.error('Anthropic API key not configured.');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(chalk.cyan(`
|
|
149
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
150
|
+
║ 🌐 WEBSITE BUILDER ║
|
|
151
|
+
║ ║
|
|
152
|
+
║ Describe your website in plain English. ║
|
|
153
|
+
║ AI builds it in minutes. ║
|
|
154
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
155
|
+
`));
|
|
156
|
+
|
|
157
|
+
// Step 1: Choose approach
|
|
158
|
+
const approach = await p.select({
|
|
159
|
+
message: 'How would you like to build your website?',
|
|
160
|
+
options: [
|
|
161
|
+
{ value: 'describe', label: '💬 Describe it', hint: 'Tell me what you want in plain English' },
|
|
162
|
+
{ value: 'template', label: '📋 Start from template', hint: 'Choose a pre-made structure' },
|
|
163
|
+
{ value: 'url', label: '🔗 Clone a design', hint: 'Describe a site you like' },
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (p.isCancel(approach)) return;
|
|
168
|
+
|
|
169
|
+
const anthropic = new Anthropic({ apiKey: anthropicCreds.apiKey });
|
|
170
|
+
let websiteSpec: WebsiteSpec;
|
|
171
|
+
|
|
172
|
+
if (approach === 'describe') {
|
|
173
|
+
websiteSpec = await describeWebsite(anthropic);
|
|
174
|
+
} else if (approach === 'template') {
|
|
175
|
+
websiteSpec = await templateWebsite(anthropic);
|
|
176
|
+
} else {
|
|
177
|
+
websiteSpec = await cloneDesign(anthropic);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!websiteSpec) return;
|
|
181
|
+
|
|
182
|
+
// Step 2: Confirm spec
|
|
183
|
+
console.log(chalk.cyan(`\n 📋 Website Plan:\n`));
|
|
184
|
+
console.log(chalk.bold(` ${websiteSpec.name}`));
|
|
185
|
+
console.log(chalk.dim(` ${websiteSpec.description}\n`));
|
|
186
|
+
console.log(chalk.dim(` Style: ${websiteSpec.style}`));
|
|
187
|
+
console.log(chalk.dim(` Sections: ${websiteSpec.sections.join(', ')}\n`));
|
|
188
|
+
|
|
189
|
+
const confirm = await p.confirm({
|
|
190
|
+
message: 'Build this website?',
|
|
191
|
+
initialValue: true,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (!confirm || p.isCancel(confirm)) return;
|
|
195
|
+
|
|
196
|
+
// Step 3: Build it
|
|
197
|
+
await buildWebsite(anthropic, websiteSpec, config);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// TYPES
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
interface WebsiteSpec {
|
|
205
|
+
name: string;
|
|
206
|
+
description: string;
|
|
207
|
+
style: string;
|
|
208
|
+
colorScheme: {
|
|
209
|
+
primary: string;
|
|
210
|
+
secondary: string;
|
|
211
|
+
accent: string;
|
|
212
|
+
background: string;
|
|
213
|
+
text: string;
|
|
214
|
+
};
|
|
215
|
+
sections: string[];
|
|
216
|
+
content: Record<string, any>;
|
|
217
|
+
features: string[];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// DESCRIBE WEBSITE
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
async function describeWebsite(anthropic: Anthropic): Promise<WebsiteSpec | null> {
|
|
225
|
+
console.log(chalk.dim('\n Describe your website. Be as detailed as you want.\n'));
|
|
226
|
+
console.log(chalk.dim(' Examples:'));
|
|
227
|
+
console.log(chalk.dim(' • "A landing page for my AI writing tool called WriteBot"'));
|
|
228
|
+
console.log(chalk.dim(' • "Portfolio site for a photographer, dark theme, minimal"'));
|
|
229
|
+
console.log(chalk.dim(' • "Coffee shop website with menu, location, and online ordering"\n'));
|
|
230
|
+
|
|
231
|
+
const description = await textWithVoice({
|
|
232
|
+
message: 'Describe your website:',
|
|
233
|
+
placeholder: 'A landing page for...',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (p.isCancel(description)) return null;
|
|
237
|
+
|
|
238
|
+
const spinner = p.spinner();
|
|
239
|
+
spinner.start('Understanding your vision...');
|
|
240
|
+
|
|
241
|
+
const response = await anthropic.messages.create({
|
|
242
|
+
model: 'claude-sonnet-4-20250514',
|
|
243
|
+
max_tokens: 2048,
|
|
244
|
+
messages: [{
|
|
245
|
+
role: 'user',
|
|
246
|
+
content: `Based on this website description, create a detailed specification.
|
|
247
|
+
|
|
248
|
+
Description: "${description}"
|
|
249
|
+
|
|
250
|
+
Return JSON only:
|
|
251
|
+
{
|
|
252
|
+
"name": "Project name (kebab-case)",
|
|
253
|
+
"description": "One line description",
|
|
254
|
+
"style": "minimal | bold | elegant | playful | corporate",
|
|
255
|
+
"colorScheme": {
|
|
256
|
+
"primary": "#hex",
|
|
257
|
+
"secondary": "#hex",
|
|
258
|
+
"accent": "#hex",
|
|
259
|
+
"background": "#hex",
|
|
260
|
+
"text": "#hex"
|
|
261
|
+
},
|
|
262
|
+
"sections": ["hero", "features", ...],
|
|
263
|
+
"content": {
|
|
264
|
+
"hero": {
|
|
265
|
+
"headline": "...",
|
|
266
|
+
"subheadline": "...",
|
|
267
|
+
"cta": "..."
|
|
268
|
+
},
|
|
269
|
+
"features": [
|
|
270
|
+
{ "title": "...", "description": "...", "icon": "..." }
|
|
271
|
+
]
|
|
272
|
+
// etc for each section
|
|
273
|
+
},
|
|
274
|
+
"features": ["responsive", "dark-mode", "animations", etc]
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
Make the content compelling and professional. Use appropriate sections for the type of website.`
|
|
278
|
+
}],
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
spinner.stop('Got it!');
|
|
282
|
+
|
|
283
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
284
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
285
|
+
|
|
286
|
+
if (!jsonMatch) {
|
|
287
|
+
p.log.error('Failed to understand website description');
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return JSON.parse(jsonMatch[0]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ============================================================================
|
|
295
|
+
// TEMPLATE WEBSITE
|
|
296
|
+
// ============================================================================
|
|
297
|
+
|
|
298
|
+
async function templateWebsite(anthropic: Anthropic): Promise<WebsiteSpec | null> {
|
|
299
|
+
const template = await p.select({
|
|
300
|
+
message: 'Choose a template:',
|
|
301
|
+
options: TEMPLATES.map(t => ({
|
|
302
|
+
value: t.id,
|
|
303
|
+
label: `${t.icon} ${t.name}`,
|
|
304
|
+
hint: t.description,
|
|
305
|
+
})),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (p.isCancel(template)) return null;
|
|
309
|
+
|
|
310
|
+
const selectedTemplate = TEMPLATES.find(t => t.id === template)!;
|
|
311
|
+
|
|
312
|
+
// Get customization details
|
|
313
|
+
const name = await p.text({
|
|
314
|
+
message: 'What is your business/project name?',
|
|
315
|
+
placeholder: 'My Awesome Project',
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
if (p.isCancel(name)) return null;
|
|
319
|
+
|
|
320
|
+
const tagline = await p.text({
|
|
321
|
+
message: 'Tagline or one-line description:',
|
|
322
|
+
placeholder: 'The best solution for...',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (p.isCancel(tagline)) return null;
|
|
326
|
+
|
|
327
|
+
const details = await textWithVoice({
|
|
328
|
+
message: 'Any other details? (or press enter to skip)',
|
|
329
|
+
placeholder: 'We help startups with..., Our colors are blue and white...',
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (p.isCancel(details)) return null;
|
|
333
|
+
|
|
334
|
+
const spinner = p.spinner();
|
|
335
|
+
spinner.start('Customizing template...');
|
|
336
|
+
|
|
337
|
+
const response = await anthropic.messages.create({
|
|
338
|
+
model: 'claude-sonnet-4-20250514',
|
|
339
|
+
max_tokens: 2048,
|
|
340
|
+
messages: [{
|
|
341
|
+
role: 'user',
|
|
342
|
+
content: `Create a website spec based on this template and customization.
|
|
343
|
+
|
|
344
|
+
Template: ${selectedTemplate.name}
|
|
345
|
+
Sections: ${selectedTemplate.sections.join(', ')}
|
|
346
|
+
Default Style: ${selectedTemplate.style}
|
|
347
|
+
|
|
348
|
+
Business Name: ${name}
|
|
349
|
+
Tagline: ${tagline}
|
|
350
|
+
Additional Details: ${details || 'None provided'}
|
|
351
|
+
|
|
352
|
+
Return JSON only:
|
|
353
|
+
{
|
|
354
|
+
"name": "project-name-kebab",
|
|
355
|
+
"description": "${tagline}",
|
|
356
|
+
"style": "${selectedTemplate.style}",
|
|
357
|
+
"colorScheme": {
|
|
358
|
+
"primary": "#hex",
|
|
359
|
+
"secondary": "#hex",
|
|
360
|
+
"accent": "#hex",
|
|
361
|
+
"background": "#hex",
|
|
362
|
+
"text": "#hex"
|
|
363
|
+
},
|
|
364
|
+
"sections": ${JSON.stringify(selectedTemplate.sections)},
|
|
365
|
+
"content": {
|
|
366
|
+
// Content for each section, tailored to the business
|
|
367
|
+
},
|
|
368
|
+
"features": ["responsive", "dark-mode", etc]
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
Make the content specific and compelling for this business.`
|
|
372
|
+
}],
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
spinner.stop('Template customized!');
|
|
376
|
+
|
|
377
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
378
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
379
|
+
|
|
380
|
+
if (!jsonMatch) {
|
|
381
|
+
p.log.error('Failed to customize template');
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return JSON.parse(jsonMatch[0]);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// CLONE DESIGN
|
|
390
|
+
// ============================================================================
|
|
391
|
+
|
|
392
|
+
async function cloneDesign(anthropic: Anthropic): Promise<WebsiteSpec | null> {
|
|
393
|
+
console.log(chalk.dim('\n Describe a website design you like.\n'));
|
|
394
|
+
console.log(chalk.dim(' Examples:'));
|
|
395
|
+
console.log(chalk.dim(' • "Like Linear.app - minimal, clean, dark mode"'));
|
|
396
|
+
console.log(chalk.dim(' • "Like Stripe - professional, lots of gradients"'));
|
|
397
|
+
console.log(chalk.dim(' • "Like Notion - simple, friendly, illustrated"\n'));
|
|
398
|
+
|
|
399
|
+
const inspiration = await textWithVoice({
|
|
400
|
+
message: 'What site do you want to be inspired by?',
|
|
401
|
+
placeholder: 'Like Linear.app but for...',
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
if (p.isCancel(inspiration)) return null;
|
|
405
|
+
|
|
406
|
+
const purpose = await p.text({
|
|
407
|
+
message: 'What is YOUR website for?',
|
|
408
|
+
placeholder: 'A project management tool for designers',
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
if (p.isCancel(purpose)) return null;
|
|
412
|
+
|
|
413
|
+
const spinner = p.spinner();
|
|
414
|
+
spinner.start('Analyzing design inspiration...');
|
|
415
|
+
|
|
416
|
+
const response = await anthropic.messages.create({
|
|
417
|
+
model: 'claude-sonnet-4-20250514',
|
|
418
|
+
max_tokens: 2048,
|
|
419
|
+
messages: [{
|
|
420
|
+
role: 'user',
|
|
421
|
+
content: `Create a website spec inspired by another site's design.
|
|
422
|
+
|
|
423
|
+
Inspiration: "${inspiration}"
|
|
424
|
+
Purpose: "${purpose}"
|
|
425
|
+
|
|
426
|
+
Analyze the design style of the inspiration (based on your knowledge of popular websites) and create a spec that captures that aesthetic for this new purpose.
|
|
427
|
+
|
|
428
|
+
Return JSON only:
|
|
429
|
+
{
|
|
430
|
+
"name": "project-name-kebab",
|
|
431
|
+
"description": "One line description",
|
|
432
|
+
"style": "minimal | bold | elegant | playful | corporate",
|
|
433
|
+
"colorScheme": {
|
|
434
|
+
"primary": "#hex - inspired by the reference",
|
|
435
|
+
"secondary": "#hex",
|
|
436
|
+
"accent": "#hex",
|
|
437
|
+
"background": "#hex",
|
|
438
|
+
"text": "#hex"
|
|
439
|
+
},
|
|
440
|
+
"sections": ["appropriate sections for the purpose"],
|
|
441
|
+
"content": {
|
|
442
|
+
// Compelling content for each section
|
|
443
|
+
},
|
|
444
|
+
"features": ["responsive", "dark-mode", "animations", "gradients", etc - based on inspiration]
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
Capture the FEEL of the inspiration but make it original.`
|
|
448
|
+
}],
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
spinner.stop('Design analyzed!');
|
|
452
|
+
|
|
453
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
454
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
455
|
+
|
|
456
|
+
if (!jsonMatch) {
|
|
457
|
+
p.log.error('Failed to analyze design');
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return JSON.parse(jsonMatch[0]);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ============================================================================
|
|
465
|
+
// BUILD WEBSITE
|
|
466
|
+
// ============================================================================
|
|
467
|
+
|
|
468
|
+
async function buildWebsite(
|
|
469
|
+
anthropic: Anthropic,
|
|
470
|
+
spec: WebsiteSpec,
|
|
471
|
+
config: Config
|
|
472
|
+
): Promise<void> {
|
|
473
|
+
const projectPath = path.join(process.cwd(), spec.name);
|
|
474
|
+
|
|
475
|
+
// Check if exists
|
|
476
|
+
if (await fs.pathExists(projectPath)) {
|
|
477
|
+
const overwrite = await p.confirm({
|
|
478
|
+
message: `${spec.name} already exists. Overwrite?`,
|
|
479
|
+
initialValue: false,
|
|
480
|
+
});
|
|
481
|
+
if (!overwrite || p.isCancel(overwrite)) return;
|
|
482
|
+
await fs.remove(projectPath);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
console.log(chalk.cyan(`\n 🏗️ Building ${spec.name}...\n`));
|
|
486
|
+
|
|
487
|
+
const spinner = p.spinner();
|
|
488
|
+
|
|
489
|
+
// Step 1: Create project
|
|
490
|
+
spinner.start('Creating Next.js project...');
|
|
491
|
+
|
|
492
|
+
await execa('npx', [
|
|
493
|
+
'create-next-app@latest',
|
|
494
|
+
spec.name,
|
|
495
|
+
'--typescript',
|
|
496
|
+
'--tailwind',
|
|
497
|
+
'--eslint',
|
|
498
|
+
'--app',
|
|
499
|
+
'--src-dir',
|
|
500
|
+
'--import-alias', '@/*',
|
|
501
|
+
'--no-git',
|
|
502
|
+
], { cwd: process.cwd(), reject: false });
|
|
503
|
+
|
|
504
|
+
spinner.stop('Project created');
|
|
505
|
+
|
|
506
|
+
// Step 2: Install shadcn
|
|
507
|
+
spinner.start('Setting up shadcn/ui...');
|
|
508
|
+
|
|
509
|
+
await execa('npx', ['shadcn@latest', 'init', '-y', '-d'], {
|
|
510
|
+
cwd: projectPath,
|
|
511
|
+
reject: false,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Install common components
|
|
515
|
+
await execa('npx', ['shadcn@latest', 'add', 'button', 'card', 'input', 'badge', '-y'], {
|
|
516
|
+
cwd: projectPath,
|
|
517
|
+
reject: false,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
spinner.stop('UI components ready');
|
|
521
|
+
|
|
522
|
+
// Step 3: Generate pages
|
|
523
|
+
spinner.start('Generating website code...');
|
|
524
|
+
|
|
525
|
+
const response = await anthropic.messages.create({
|
|
526
|
+
model: 'claude-sonnet-4-20250514',
|
|
527
|
+
max_tokens: 16000,
|
|
528
|
+
messages: [{
|
|
529
|
+
role: 'user',
|
|
530
|
+
content: `Generate a complete Next.js website based on this specification.
|
|
531
|
+
|
|
532
|
+
Website Spec:
|
|
533
|
+
${JSON.stringify(spec, null, 2)}
|
|
534
|
+
|
|
535
|
+
Generate these files:
|
|
536
|
+
|
|
537
|
+
1. src/app/page.tsx - Main landing page with ALL sections
|
|
538
|
+
2. src/app/layout.tsx - Root layout with fonts, metadata
|
|
539
|
+
3. src/app/globals.css - Tailwind config with custom colors
|
|
540
|
+
4. src/components/sections/Hero.tsx
|
|
541
|
+
5. src/components/sections/[OtherSections].tsx - One for each section
|
|
542
|
+
6. src/components/ui/Navbar.tsx
|
|
543
|
+
7. src/components/ui/Footer.tsx
|
|
544
|
+
8. tailwind.config.ts - With custom colors from colorScheme
|
|
545
|
+
|
|
546
|
+
Requirements:
|
|
547
|
+
- Use TypeScript
|
|
548
|
+
- Use Tailwind CSS for styling
|
|
549
|
+
- Use shadcn/ui components (Button, Card, Input, Badge)
|
|
550
|
+
- Make it responsive (mobile-first)
|
|
551
|
+
- Add smooth scroll behavior
|
|
552
|
+
- Use modern design patterns
|
|
553
|
+
- Include hover effects and transitions
|
|
554
|
+
- Use Lucide icons where appropriate
|
|
555
|
+
|
|
556
|
+
Color scheme to use:
|
|
557
|
+
- Primary: ${spec.colorScheme.primary}
|
|
558
|
+
- Secondary: ${spec.colorScheme.secondary}
|
|
559
|
+
- Accent: ${spec.colorScheme.accent}
|
|
560
|
+
- Background: ${spec.colorScheme.background}
|
|
561
|
+
- Text: ${spec.colorScheme.text}
|
|
562
|
+
|
|
563
|
+
Style: ${spec.style}
|
|
564
|
+
${spec.style === 'minimal' ? 'Clean, lots of whitespace, simple' : ''}
|
|
565
|
+
${spec.style === 'bold' ? 'Strong colors, big typography, impactful' : ''}
|
|
566
|
+
${spec.style === 'elegant' ? 'Refined, sophisticated, subtle animations' : ''}
|
|
567
|
+
${spec.style === 'playful' ? 'Fun, colorful, friendly illustrations' : ''}
|
|
568
|
+
${spec.style === 'corporate' ? 'Professional, trustworthy, structured' : ''}
|
|
569
|
+
|
|
570
|
+
Output format:
|
|
571
|
+
<<<FILE: path/to/file.tsx>>>
|
|
572
|
+
content
|
|
573
|
+
<<<END_FILE>>>
|
|
574
|
+
|
|
575
|
+
Make it production-quality and visually impressive.`
|
|
576
|
+
}],
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
580
|
+
|
|
581
|
+
// Write files
|
|
582
|
+
const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
|
|
583
|
+
let match;
|
|
584
|
+
let fileCount = 0;
|
|
585
|
+
|
|
586
|
+
while ((match = fileRegex.exec(text)) !== null) {
|
|
587
|
+
const filePath = path.join(projectPath, match[1].trim());
|
|
588
|
+
const content = match[2].trim();
|
|
589
|
+
|
|
590
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
591
|
+
await fs.writeFile(filePath, content);
|
|
592
|
+
fileCount++;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
spinner.stop(`Generated ${fileCount} files`);
|
|
596
|
+
|
|
597
|
+
// Step 4: Git init
|
|
598
|
+
spinner.start('Initializing git...');
|
|
599
|
+
await execa('git', ['init'], { cwd: projectPath, reject: false });
|
|
600
|
+
await execa('git', ['add', '.'], { cwd: projectPath, reject: false });
|
|
601
|
+
await execa('git', ['commit', '-m', 'Initial website build by CodeBakers'], { cwd: projectPath, reject: false });
|
|
602
|
+
spinner.stop('Git initialized');
|
|
603
|
+
|
|
604
|
+
// Done!
|
|
605
|
+
console.log(chalk.green(`
|
|
606
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
607
|
+
║ ✅ Website built successfully! ║
|
|
608
|
+
╠═══════════════════════════════════════════════════════════════╣
|
|
609
|
+
║ ║
|
|
610
|
+
║ ${spec.name.padEnd(55)}║
|
|
611
|
+
║ ${spec.description.substring(0, 55).padEnd(55)}║
|
|
612
|
+
║ ║
|
|
613
|
+
║ Next steps: ║
|
|
614
|
+
║ cd ${spec.name.padEnd(52)}║
|
|
615
|
+
║ npm run dev ║
|
|
616
|
+
║ ║
|
|
617
|
+
║ Then open http://localhost:3000 ║
|
|
618
|
+
║ ║
|
|
619
|
+
║ Ready to deploy? ║
|
|
620
|
+
║ codebakers deploy ║
|
|
621
|
+
║ ║
|
|
622
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
623
|
+
`));
|
|
624
|
+
|
|
625
|
+
// Offer to open in browser
|
|
626
|
+
const openDev = await p.confirm({
|
|
627
|
+
message: 'Start development server now?',
|
|
628
|
+
initialValue: true,
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
if (openDev && !p.isCancel(openDev)) {
|
|
632
|
+
console.log(chalk.dim('\n Starting dev server...\n'));
|
|
633
|
+
|
|
634
|
+
// Change to project directory and run dev
|
|
635
|
+
process.chdir(projectPath);
|
|
636
|
+
await execa('npm', ['run', 'dev'], {
|
|
637
|
+
stdio: 'inherit',
|
|
638
|
+
reject: false,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
export { TEMPLATES };
|