@nclamvn/vibecode-cli 2.1.0 → 2.2.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.
- package/bin/vibecode.js +26 -3
- package/package.json +1 -1
- package/src/commands/go.js +251 -3
- package/src/commands/templates.js +397 -0
- package/src/index.js +13 -0
- package/src/templates/index.js +724 -0
package/bin/vibecode.js
CHANGED
|
@@ -33,6 +33,8 @@ import {
|
|
|
33
33
|
securityCommand,
|
|
34
34
|
askCommand,
|
|
35
35
|
migrateCommand,
|
|
36
|
+
// Phase M Commands
|
|
37
|
+
templatesCommand,
|
|
36
38
|
VERSION
|
|
37
39
|
} from '../src/index.js';
|
|
38
40
|
|
|
@@ -133,13 +135,34 @@ program
|
|
|
133
135
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
136
|
|
|
135
137
|
program
|
|
136
|
-
.command('go
|
|
138
|
+
.command('go [description...]')
|
|
137
139
|
.description('Magic mode: one command, full automated build')
|
|
138
|
-
.option('-t, --template <
|
|
140
|
+
.option('-t, --template <id>', 'Use a template (run "vibecode templates" to see list)')
|
|
141
|
+
.option('--name <name>', 'Project/Company name')
|
|
142
|
+
.option('--color <color>', 'Primary brand color (hex)')
|
|
139
143
|
.option('-i, --iterate', 'Enable iterative build mode')
|
|
140
144
|
.option('-m, --max <n>', 'Max iterations for iterative mode', parseInt)
|
|
141
145
|
.option('-o, --open', 'Auto-open result when done')
|
|
142
|
-
.
|
|
146
|
+
.option('-p, --preview', 'Auto-open in browser after build')
|
|
147
|
+
.action((description, options) => {
|
|
148
|
+
const desc = Array.isArray(description) ? description.join(' ') : description;
|
|
149
|
+
goCommand(desc, options);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
153
|
+
// Phase M Commands - Templates
|
|
154
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
program
|
|
157
|
+
.command('templates')
|
|
158
|
+
.alias('tpl')
|
|
159
|
+
.description('Browse and use project templates')
|
|
160
|
+
.option('-l, --list', 'List all templates')
|
|
161
|
+
.option('-i, --info <id>', 'Show template details')
|
|
162
|
+
.option('-s, --search <query>', 'Search templates')
|
|
163
|
+
.option('-p, --preview <id>', 'Preview template in browser')
|
|
164
|
+
.option('-q, --quiet', 'Non-interactive mode')
|
|
165
|
+
.action(templatesCommand);
|
|
143
166
|
|
|
144
167
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
168
|
// Phase F Commands - Agent Mode
|
package/package.json
CHANGED
package/src/commands/go.js
CHANGED
|
@@ -41,20 +41,31 @@ import {
|
|
|
41
41
|
import { runTests } from '../core/test-runner.js';
|
|
42
42
|
import { analyzeErrors } from '../core/error-analyzer.js';
|
|
43
43
|
import { ensureDir, appendToFile } from '../utils/files.js';
|
|
44
|
+
import { getTemplate, getCategoryIcon } from '../templates/index.js';
|
|
44
45
|
|
|
45
46
|
const execAsync = promisify(exec);
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* Magic Mode: One command, full build
|
|
49
50
|
* vibecode go "description" → Everything automated
|
|
51
|
+
* vibecode go --template <id> → Use template
|
|
50
52
|
*/
|
|
51
53
|
export async function goCommand(description, options = {}) {
|
|
54
|
+
// Handle template mode
|
|
55
|
+
if (options.template) {
|
|
56
|
+
return goWithTemplate(options.template, options);
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
const startTime = Date.now();
|
|
53
60
|
|
|
61
|
+
// Handle description as array (from commander variadic)
|
|
62
|
+
const desc = Array.isArray(description) ? description.join(' ') : description;
|
|
63
|
+
|
|
54
64
|
// Validate description
|
|
55
|
-
if (!
|
|
65
|
+
if (!desc || desc.trim().length < 5) {
|
|
56
66
|
printError('Description too short. Please provide more details.');
|
|
57
67
|
console.log(chalk.gray('Example: vibecode go "Landing page for my startup"'));
|
|
68
|
+
console.log(chalk.gray('Or use: vibecode go --template landing-saas'));
|
|
58
69
|
process.exit(1);
|
|
59
70
|
}
|
|
60
71
|
|
|
@@ -67,7 +78,7 @@ export async function goCommand(description, options = {}) {
|
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
// Generate project name
|
|
70
|
-
const projectName = generateProjectName(
|
|
81
|
+
const projectName = options.name ? sanitizeProjectName(options.name) : generateProjectName(desc);
|
|
71
82
|
const projectPath = path.join(process.cwd(), projectName);
|
|
72
83
|
|
|
73
84
|
// Check if directory exists
|
|
@@ -78,7 +89,7 @@ export async function goCommand(description, options = {}) {
|
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
// Show magic header
|
|
81
|
-
showMagicHeader(
|
|
92
|
+
showMagicHeader(desc, projectName);
|
|
82
93
|
|
|
83
94
|
// Create backup of current directory before go command
|
|
84
95
|
// (backup in parent directory since we're creating a new project)
|
|
@@ -385,3 +396,240 @@ async function openProject(projectPath) {
|
|
|
385
396
|
console.log(chalk.gray(`Could not auto-open: ${error.message}`));
|
|
386
397
|
}
|
|
387
398
|
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Sanitize project name
|
|
402
|
+
*/
|
|
403
|
+
function sanitizeProjectName(name) {
|
|
404
|
+
return name
|
|
405
|
+
.toLowerCase()
|
|
406
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
407
|
+
.replace(/\s+/g, '-')
|
|
408
|
+
.replace(/-+/g, '-')
|
|
409
|
+
.replace(/^-|-$/g, '')
|
|
410
|
+
.substring(0, 50);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Template Mode: Use pre-defined template
|
|
415
|
+
* vibecode go --template <id>
|
|
416
|
+
*/
|
|
417
|
+
async function goWithTemplate(templateId, options = {}) {
|
|
418
|
+
const template = getTemplate(templateId);
|
|
419
|
+
|
|
420
|
+
if (!template) {
|
|
421
|
+
printError(`Template "${templateId}" not found.`);
|
|
422
|
+
console.log(chalk.gray('Run "vibecode templates" to see available templates.'));
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Check Claude Code availability
|
|
427
|
+
const claudeAvailable = await isClaudeCodeAvailable();
|
|
428
|
+
if (!claudeAvailable) {
|
|
429
|
+
printError('Claude Code CLI not found.');
|
|
430
|
+
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const startTime = Date.now();
|
|
435
|
+
const icon = getCategoryIcon(template.category);
|
|
436
|
+
|
|
437
|
+
// Generate project name
|
|
438
|
+
const projectName = options.name
|
|
439
|
+
? sanitizeProjectName(options.name)
|
|
440
|
+
: `${templateId}-${Date.now().toString(36)}`;
|
|
441
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
442
|
+
|
|
443
|
+
// Check if directory exists
|
|
444
|
+
if (await fs.pathExists(projectPath)) {
|
|
445
|
+
printError(`Directory already exists: ${projectName}`);
|
|
446
|
+
console.log(chalk.gray('Use --name to specify a different name.'));
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Show template header
|
|
451
|
+
showTemplateHeader(template, projectName, icon);
|
|
452
|
+
|
|
453
|
+
// Build customized prompt from template
|
|
454
|
+
let prompt = template.prompt;
|
|
455
|
+
|
|
456
|
+
// Apply template variables from options
|
|
457
|
+
for (const [key, config] of Object.entries(template.variables)) {
|
|
458
|
+
const value = options[key] !== undefined ? options[key] : config.default;
|
|
459
|
+
// Replace placeholders like {name} in prompt
|
|
460
|
+
prompt = prompt.replace(new RegExp(`\\{${key}\\}`, 'gi'), value);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Add customizations from common options
|
|
464
|
+
if (options.name) {
|
|
465
|
+
prompt += `\n\nProject/Company name: "${options.name}"`;
|
|
466
|
+
}
|
|
467
|
+
if (options.color) {
|
|
468
|
+
prompt += `\n\nPrimary brand color: ${options.color}`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Create backup
|
|
472
|
+
const backup = new BackupManager(process.cwd());
|
|
473
|
+
await backup.createBackup('go-template');
|
|
474
|
+
|
|
475
|
+
// Define steps
|
|
476
|
+
const steps = [
|
|
477
|
+
{ name: 'INIT', label: 'Creating project', weight: 5 },
|
|
478
|
+
{ name: 'SETUP', label: 'Setting up template', weight: 5 },
|
|
479
|
+
{ name: 'BUILD', label: `Building ${template.name}`, weight: 80 },
|
|
480
|
+
{ name: 'REVIEW', label: 'Running tests', weight: 10 }
|
|
481
|
+
];
|
|
482
|
+
|
|
483
|
+
const results = {
|
|
484
|
+
templateId,
|
|
485
|
+
templateName: template.name
|
|
486
|
+
};
|
|
487
|
+
let currentProgress = 0;
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
// Step 1: INIT
|
|
491
|
+
await executeStep(steps[0], currentProgress, async () => {
|
|
492
|
+
await fs.ensureDir(projectPath);
|
|
493
|
+
process.chdir(projectPath);
|
|
494
|
+
await createWorkspace();
|
|
495
|
+
results.projectPath = projectPath;
|
|
496
|
+
});
|
|
497
|
+
currentProgress += steps[0].weight;
|
|
498
|
+
|
|
499
|
+
// Step 2: SETUP
|
|
500
|
+
await executeStep(steps[1], currentProgress, async () => {
|
|
501
|
+
const sessionId = await createSession(projectName);
|
|
502
|
+
results.sessionId = sessionId;
|
|
503
|
+
|
|
504
|
+
// Write template info to session
|
|
505
|
+
const templateInfo = `# Template: ${template.name}
|
|
506
|
+
|
|
507
|
+
## ID: ${templateId}
|
|
508
|
+
## Category: ${template.category}
|
|
509
|
+
## Stack: ${template.stack.join(', ')}
|
|
510
|
+
|
|
511
|
+
## Features
|
|
512
|
+
${template.features.map(f => `- ${f}`).join('\n')}
|
|
513
|
+
|
|
514
|
+
## Prompt
|
|
515
|
+
${prompt}
|
|
516
|
+
`;
|
|
517
|
+
await writeSessionFile('template.md', templateInfo);
|
|
518
|
+
await transitionTo(STATES.INTAKE_CAPTURED, 'template_setup');
|
|
519
|
+
});
|
|
520
|
+
currentProgress += steps[1].weight;
|
|
521
|
+
|
|
522
|
+
// Step 3: BUILD
|
|
523
|
+
await executeStep(steps[2], currentProgress, async () => {
|
|
524
|
+
const sessionPath = await getCurrentSessionPath();
|
|
525
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
526
|
+
await ensureDir(evidencePath);
|
|
527
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
528
|
+
|
|
529
|
+
await transitionTo(STATES.BUILD_IN_PROGRESS, 'template_build_start');
|
|
530
|
+
|
|
531
|
+
const fullPrompt = await buildPromptWithContext(prompt, process.cwd());
|
|
532
|
+
|
|
533
|
+
await appendToFile(logPath, `[${new Date().toISOString()}] Template Build: ${templateId}\n`);
|
|
534
|
+
await appendToFile(logPath, `Prompt:\n${prompt}\n\n`);
|
|
535
|
+
|
|
536
|
+
const buildResult = await spawnClaudeCode(fullPrompt, {
|
|
537
|
+
cwd: process.cwd(),
|
|
538
|
+
logPath: logPath
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
await appendToFile(logPath, `[${new Date().toISOString()}] Build completed with code: ${buildResult.code}\n`);
|
|
542
|
+
|
|
543
|
+
// Count files created
|
|
544
|
+
const files = await fs.readdir(process.cwd());
|
|
545
|
+
results.filesCreated = files.filter(f => !f.startsWith('.')).length;
|
|
546
|
+
|
|
547
|
+
const freshState = await loadState();
|
|
548
|
+
freshState.build_completed = new Date().toISOString();
|
|
549
|
+
freshState.template_used = templateId;
|
|
550
|
+
await saveState(freshState);
|
|
551
|
+
|
|
552
|
+
await transitionTo(STATES.BUILD_DONE, 'template_build_done');
|
|
553
|
+
});
|
|
554
|
+
currentProgress += steps[2].weight;
|
|
555
|
+
|
|
556
|
+
// Step 4: REVIEW
|
|
557
|
+
await executeStep(steps[3], currentProgress, async () => {
|
|
558
|
+
const testResult = await runTests(process.cwd());
|
|
559
|
+
results.testsPassed = testResult.summary.passed;
|
|
560
|
+
results.testsTotal = testResult.summary.total;
|
|
561
|
+
results.allPassed = testResult.passed;
|
|
562
|
+
|
|
563
|
+
await transitionTo(STATES.REVIEW_PASSED, 'template_review');
|
|
564
|
+
});
|
|
565
|
+
currentProgress = 100;
|
|
566
|
+
|
|
567
|
+
// Show final progress
|
|
568
|
+
console.log(renderProgressBar(100));
|
|
569
|
+
console.log();
|
|
570
|
+
|
|
571
|
+
// Show summary
|
|
572
|
+
const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
|
|
573
|
+
showTemplateSummary(template, projectName, projectPath, duration, results, options);
|
|
574
|
+
|
|
575
|
+
// Auto-open if requested
|
|
576
|
+
if (options.preview || options.open) {
|
|
577
|
+
await openProject(projectPath);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.log();
|
|
582
|
+
printError(`Template build failed: ${error.message}`);
|
|
583
|
+
console.log(chalk.gray(`Project location: ${projectPath}`));
|
|
584
|
+
console.log(chalk.gray('Check .vibecode/sessions/*/evidence/build.log for details.'));
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Show template mode header
|
|
591
|
+
*/
|
|
592
|
+
function showTemplateHeader(template, projectName, icon) {
|
|
593
|
+
console.log();
|
|
594
|
+
console.log(chalk.magenta('╭' + '─'.repeat(68) + '╮'));
|
|
595
|
+
console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
|
|
596
|
+
console.log(chalk.magenta('│') + chalk.bold.white(` ${icon} TEMPLATE: ${template.name.toUpperCase()}`) + ' '.repeat(Math.max(0, 53 - template.name.length)) + chalk.magenta('│'));
|
|
597
|
+
console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
|
|
598
|
+
|
|
599
|
+
const desc = template.description.substring(0, 55);
|
|
600
|
+
console.log(chalk.magenta('│') + chalk.gray(` ${desc}`) + ' '.repeat(Math.max(0, 65 - desc.length)) + chalk.magenta('│'));
|
|
601
|
+
|
|
602
|
+
console.log(chalk.magenta('│') + chalk.gray(` Stack: ${template.stack.join(', ').substring(0, 52)}`) + ' '.repeat(Math.max(0, 55 - template.stack.join(', ').length)) + chalk.magenta('│'));
|
|
603
|
+
console.log(chalk.magenta('│') + chalk.gray(` → ${projectName}`) + ' '.repeat(Math.max(0, 63 - projectName.length)) + chalk.magenta('│'));
|
|
604
|
+
|
|
605
|
+
console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
|
|
606
|
+
console.log(chalk.magenta('╰' + '─'.repeat(68) + '╯'));
|
|
607
|
+
console.log();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Show template mode summary
|
|
612
|
+
*/
|
|
613
|
+
function showTemplateSummary(template, projectName, projectPath, duration, results, options) {
|
|
614
|
+
const icon = getCategoryIcon(template.category);
|
|
615
|
+
const testsStatus = results.allPassed
|
|
616
|
+
? chalk.green(`${results.testsPassed}/${results.testsTotal} passed`)
|
|
617
|
+
: chalk.yellow(`${results.testsPassed}/${results.testsTotal} passed`);
|
|
618
|
+
|
|
619
|
+
console.log(chalk.green('╭' + '─'.repeat(68) + '╮'));
|
|
620
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
621
|
+
console.log(chalk.green('│') + chalk.bold.white(` ${icon} ${template.name.toUpperCase()} - COMPLETE!`) + ' '.repeat(Math.max(0, 48 - template.name.length)) + chalk.green('│'));
|
|
622
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
623
|
+
console.log(chalk.green('│') + chalk.white(` 📁 Project: ${projectName}`) + ' '.repeat(Math.max(0, 51 - projectName.length)) + chalk.green('│'));
|
|
624
|
+
console.log(chalk.green('│') + chalk.white(` 📦 Template: ${template.id}`) + ' '.repeat(Math.max(0, 51 - template.id.length)) + chalk.green('│'));
|
|
625
|
+
console.log(chalk.green('│') + chalk.white(` 📄 Files: ${results.filesCreated} created`) + ' '.repeat(42) + chalk.green('│'));
|
|
626
|
+
console.log(chalk.green('│') + chalk.white(` 🧪 Tests: `) + testsStatus + ' '.repeat(Math.max(0, 40)) + chalk.green('│'));
|
|
627
|
+
console.log(chalk.green('│') + chalk.white(` ⏱️ Duration: ${duration} minutes`) + ' '.repeat(Math.max(0, 44 - duration.length)) + chalk.green('│'));
|
|
628
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
629
|
+
console.log(chalk.green('│') + chalk.gray(` 💡 Next steps:`) + ' '.repeat(50) + chalk.green('│'));
|
|
630
|
+
console.log(chalk.green('│') + chalk.gray(` cd ${projectName}`) + ' '.repeat(Math.max(0, 57 - projectName.length)) + chalk.green('│'));
|
|
631
|
+
console.log(chalk.green('│') + chalk.gray(` npm install && npm run dev`) + ' '.repeat(33) + chalk.green('│'));
|
|
632
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
633
|
+
console.log(chalk.green('╰' + '─'.repeat(68) + '╯'));
|
|
634
|
+
console.log();
|
|
635
|
+
}
|