@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 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 <description>')
138
+ .command('go [description...]')
137
139
  .description('Magic mode: one command, full automated build')
138
- .option('-t, --template <name>', 'Use template (landing, saas, cli, api)')
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
- .action(goCommand);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nclamvn/vibecode-cli",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Build software with discipline - AI coding with guardrails",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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 (!description || description.trim().length < 5) {
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(description);
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(description, projectName);
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
+ }