@nclamvn/vibecode-cli 2.0.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.
Files changed (53) hide show
  1. package/.vibecode/learning/fixes.json +1 -0
  2. package/.vibecode/learning/preferences.json +1 -0
  3. package/README.md +310 -49
  4. package/SESSION_NOTES.md +154 -0
  5. package/bin/vibecode.js +235 -2
  6. package/package.json +5 -2
  7. package/src/agent/decomposition.js +476 -0
  8. package/src/agent/index.js +391 -0
  9. package/src/agent/memory.js +542 -0
  10. package/src/agent/orchestrator.js +917 -0
  11. package/src/agent/self-healing.js +516 -0
  12. package/src/commands/agent.js +349 -0
  13. package/src/commands/ask.js +230 -0
  14. package/src/commands/assist.js +413 -0
  15. package/src/commands/build.js +345 -4
  16. package/src/commands/debug.js +565 -0
  17. package/src/commands/docs.js +167 -0
  18. package/src/commands/git.js +1024 -0
  19. package/src/commands/go.js +635 -0
  20. package/src/commands/learn.js +294 -0
  21. package/src/commands/migrate.js +341 -0
  22. package/src/commands/plan.js +8 -2
  23. package/src/commands/refactor.js +205 -0
  24. package/src/commands/review.js +126 -1
  25. package/src/commands/security.js +229 -0
  26. package/src/commands/shell.js +486 -0
  27. package/src/commands/templates.js +397 -0
  28. package/src/commands/test.js +194 -0
  29. package/src/commands/undo.js +281 -0
  30. package/src/commands/watch.js +556 -0
  31. package/src/commands/wizard.js +322 -0
  32. package/src/config/constants.js +5 -1
  33. package/src/config/templates.js +146 -15
  34. package/src/core/backup.js +325 -0
  35. package/src/core/error-analyzer.js +237 -0
  36. package/src/core/fix-generator.js +195 -0
  37. package/src/core/iteration.js +226 -0
  38. package/src/core/learning.js +295 -0
  39. package/src/core/session.js +18 -2
  40. package/src/core/test-runner.js +281 -0
  41. package/src/debug/analyzer.js +329 -0
  42. package/src/debug/evidence.js +228 -0
  43. package/src/debug/fixer.js +348 -0
  44. package/src/debug/image-analyzer.js +304 -0
  45. package/src/debug/index.js +378 -0
  46. package/src/debug/verifier.js +346 -0
  47. package/src/index.js +102 -0
  48. package/src/providers/claude-code.js +12 -7
  49. package/src/templates/index.js +724 -0
  50. package/src/ui/__tests__/error-translator.test.js +390 -0
  51. package/src/ui/dashboard.js +364 -0
  52. package/src/ui/error-translator.js +775 -0
  53. package/src/utils/image.js +222 -0
@@ -0,0 +1,397 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Templates Command
3
+ // Browse and use project templates
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import {
9
+ TEMPLATES,
10
+ getCategories,
11
+ getTemplatesByCategory,
12
+ searchTemplates,
13
+ getTemplate,
14
+ getCategoryIcon
15
+ } from '../templates/index.js';
16
+
17
+ /**
18
+ * Templates command entry point
19
+ */
20
+ export async function templatesCommand(options = {}) {
21
+ // Show template info
22
+ if (options.info) {
23
+ return showTemplateInfo(options.info);
24
+ }
25
+
26
+ // Preview template
27
+ if (options.preview) {
28
+ return previewTemplate(options.preview);
29
+ }
30
+
31
+ // Search templates
32
+ if (options.search) {
33
+ return searchAndShow(options.search);
34
+ }
35
+
36
+ // Default: List all templates
37
+ return listTemplates(options);
38
+ }
39
+
40
+ /**
41
+ * List all templates grouped by category
42
+ */
43
+ async function listTemplates(options) {
44
+ const categories = getCategories();
45
+ const totalTemplates = Object.keys(TEMPLATES).length;
46
+
47
+ console.log(chalk.cyan(`
48
+ ╭────────────────────────────────────────────────────────────────────╮
49
+ │ 📦 VIBECODE TEMPLATE GALLERY │
50
+ │ │
51
+ │ ${String(totalTemplates).padEnd(2)} professional templates ready to use │
52
+ ╰────────────────────────────────────────────────────────────────────╯
53
+ `));
54
+
55
+ // Group by category
56
+ for (const category of categories) {
57
+ const templates = getTemplatesByCategory(category.id);
58
+ const icon = getCategoryIcon(category.id);
59
+
60
+ console.log(chalk.white.bold(`\n ${icon} ${category.name.toUpperCase()} (${category.count})`));
61
+ console.log(chalk.gray(' ' + '─'.repeat(60)));
62
+
63
+ for (const template of templates) {
64
+ const id = chalk.green(template.id.padEnd(22));
65
+ const desc = chalk.gray(truncate(template.description, 40));
66
+ console.log(` ${id} ${desc}`);
67
+ }
68
+ }
69
+
70
+ console.log(chalk.gray(`
71
+ ─────────────────────────────────────────────────────────────────────
72
+
73
+ Usage:
74
+ ${chalk.cyan('vibecode templates --info <id>')} View template details
75
+ ${chalk.cyan('vibecode templates --search <query>')} Search templates
76
+ ${chalk.cyan('vibecode go --template <id>')} Use a template
77
+ ${chalk.cyan('vibecode go -t <id> --name "X"')} With customization
78
+
79
+ Example:
80
+ ${chalk.cyan('vibecode go --template landing-saas')}
81
+ ${chalk.cyan('vibecode go -t dashboard-admin --name "My Dashboard"')}
82
+ `));
83
+
84
+ // Interactive mode unless quiet
85
+ if (!options.quiet) {
86
+ await interactiveMode();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Interactive template browser
92
+ */
93
+ async function interactiveMode() {
94
+ const { action } = await inquirer.prompt([{
95
+ type: 'list',
96
+ name: 'action',
97
+ message: 'What would you like to do?',
98
+ choices: [
99
+ { name: '📋 View template details', value: 'info' },
100
+ { name: '🚀 Use a template', value: 'use' },
101
+ { name: '🔍 Search templates', value: 'search' },
102
+ { name: '👋 Exit', value: 'exit' }
103
+ ]
104
+ }]);
105
+
106
+ if (action === 'exit') {
107
+ return;
108
+ }
109
+
110
+ if (action === 'info') {
111
+ const { templateId } = await inquirer.prompt([{
112
+ type: 'list',
113
+ name: 'templateId',
114
+ message: 'Select template to view:',
115
+ choices: Object.values(TEMPLATES).map(t => ({
116
+ name: `${getCategoryIcon(t.category)} ${t.name} - ${truncate(t.description, 35)}`,
117
+ value: t.id
118
+ })),
119
+ pageSize: 15
120
+ }]);
121
+ return showTemplateInfo(templateId);
122
+ }
123
+
124
+ if (action === 'use') {
125
+ const { templateId } = await inquirer.prompt([{
126
+ type: 'list',
127
+ name: 'templateId',
128
+ message: 'Select template to use:',
129
+ choices: Object.values(TEMPLATES).map(t => ({
130
+ name: `${getCategoryIcon(t.category)} ${t.name}`,
131
+ value: t.id
132
+ })),
133
+ pageSize: 15
134
+ }]);
135
+
136
+ const template = getTemplate(templateId);
137
+
138
+ // Ask for customization if template has variables
139
+ let customOptions = {};
140
+ if (Object.keys(template.variables).length > 0) {
141
+ const { customize } = await inquirer.prompt([{
142
+ type: 'confirm',
143
+ name: 'customize',
144
+ message: 'Customize template options?',
145
+ default: false
146
+ }]);
147
+
148
+ if (customize) {
149
+ customOptions = await promptForVariables(template);
150
+ }
151
+ }
152
+
153
+ console.log(chalk.cyan(`
154
+ ─────────────────────────────────────────────────────────────────────
155
+
156
+ Run this command to create your project:
157
+
158
+ ${chalk.white.bold(`vibecode go --template ${templateId}${formatCustomOptions(customOptions)}`)}
159
+
160
+ `));
161
+ }
162
+
163
+ if (action === 'search') {
164
+ const { query } = await inquirer.prompt([{
165
+ type: 'input',
166
+ name: 'query',
167
+ message: 'Search:'
168
+ }]);
169
+
170
+ const results = searchTemplates(query);
171
+ if (results.length === 0) {
172
+ console.log(chalk.yellow('\n No templates found matching your search.\n'));
173
+ } else {
174
+ console.log(chalk.green(`\n Found ${results.length} template(s):\n`));
175
+ for (const t of results) {
176
+ const icon = getCategoryIcon(t.category);
177
+ console.log(` ${icon} ${chalk.green(t.id.padEnd(22))} ${chalk.gray(truncate(t.description, 40))}`);
178
+ }
179
+ console.log('');
180
+
181
+ // Offer to view details
182
+ const { viewDetails } = await inquirer.prompt([{
183
+ type: 'confirm',
184
+ name: 'viewDetails',
185
+ message: 'View template details?',
186
+ default: true
187
+ }]);
188
+
189
+ if (viewDetails && results.length > 0) {
190
+ const { templateId } = await inquirer.prompt([{
191
+ type: 'list',
192
+ name: 'templateId',
193
+ message: 'Select template:',
194
+ choices: results.map(t => ({ name: t.name, value: t.id }))
195
+ }]);
196
+ return showTemplateInfo(templateId);
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Show detailed template information
204
+ */
205
+ async function showTemplateInfo(templateId) {
206
+ const template = getTemplate(templateId);
207
+
208
+ if (!template) {
209
+ console.log(chalk.red(`\n ❌ Template "${templateId}" not found.\n`));
210
+ console.log(chalk.gray(` Run ${chalk.cyan('vibecode templates')} to see available templates.\n`));
211
+ return;
212
+ }
213
+
214
+ const icon = getCategoryIcon(template.category);
215
+
216
+ console.log(chalk.cyan(`
217
+ ╭────────────────────────────────────────────────────────────────────╮
218
+ │ ${icon} ${template.name.padEnd(56)}│
219
+ ╰────────────────────────────────────────────────────────────────────╯
220
+ `));
221
+
222
+ console.log(chalk.white(` ${template.description}\n`));
223
+
224
+ console.log(chalk.gray(' Category: ') + chalk.white(template.category));
225
+ console.log(chalk.gray(' Tags: ') + chalk.cyan(template.tags.join(', ')));
226
+ console.log(chalk.gray(' Stack: ') + chalk.yellow(template.stack.join(', ')));
227
+
228
+ console.log(chalk.gray('\n Features:'));
229
+ for (const feature of template.features) {
230
+ console.log(chalk.green(` ✓ ${feature}`));
231
+ }
232
+
233
+ // Show customization options
234
+ if (Object.keys(template.variables).length > 0) {
235
+ console.log(chalk.gray('\n Customization Options:'));
236
+ for (const [key, config] of Object.entries(template.variables)) {
237
+ const typeInfo = config.type === 'select'
238
+ ? `[${config.options.join('|')}]`
239
+ : `(${config.type})`;
240
+ console.log(chalk.yellow(` --${key}`) + chalk.gray(` ${typeInfo}`));
241
+ console.log(chalk.gray(` ${config.description}`));
242
+ console.log(chalk.gray(` Default: "${config.default}"`));
243
+ }
244
+ }
245
+
246
+ console.log(chalk.gray(`
247
+ ─────────────────────────────────────────────────────────────────────
248
+
249
+ Use this template:
250
+ ${chalk.cyan(`vibecode go --template ${templateId}`)}
251
+
252
+ With customization:
253
+ ${chalk.cyan(`vibecode go -t ${templateId} --name "MyProject"`)}
254
+ `));
255
+
256
+ // Prompt to use
257
+ const { useNow } = await inquirer.prompt([{
258
+ type: 'confirm',
259
+ name: 'useNow',
260
+ message: 'Use this template now?',
261
+ default: false
262
+ }]);
263
+
264
+ if (useNow) {
265
+ // Ask for customization
266
+ let customOptions = {};
267
+ if (Object.keys(template.variables).length > 0) {
268
+ customOptions = await promptForVariables(template);
269
+ }
270
+
271
+ // Import and call go command
272
+ const { goCommand } = await import('./go.js');
273
+ await goCommand('', { template: templateId, ...customOptions });
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Prompt user for template variables
279
+ */
280
+ async function promptForVariables(template) {
281
+ const questions = [];
282
+
283
+ for (const [key, config] of Object.entries(template.variables)) {
284
+ if (config.type === 'select') {
285
+ questions.push({
286
+ type: 'list',
287
+ name: key,
288
+ message: config.description,
289
+ choices: config.options,
290
+ default: config.default
291
+ });
292
+ } else if (config.type === 'boolean') {
293
+ questions.push({
294
+ type: 'confirm',
295
+ name: key,
296
+ message: config.description,
297
+ default: config.default
298
+ });
299
+ } else if (config.type === 'number') {
300
+ questions.push({
301
+ type: 'number',
302
+ name: key,
303
+ message: config.description,
304
+ default: config.default
305
+ });
306
+ } else {
307
+ questions.push({
308
+ type: 'input',
309
+ name: key,
310
+ message: config.description,
311
+ default: config.default
312
+ });
313
+ }
314
+ }
315
+
316
+ return inquirer.prompt(questions);
317
+ }
318
+
319
+ /**
320
+ * Search and display results
321
+ */
322
+ async function searchAndShow(query) {
323
+ const results = searchTemplates(query);
324
+
325
+ if (results.length === 0) {
326
+ console.log(chalk.yellow(`\n No templates found for "${query}".\n`));
327
+ console.log(chalk.gray(` Try: landing, dashboard, ecommerce, blog, portfolio, app\n`));
328
+ return;
329
+ }
330
+
331
+ console.log(chalk.green(`\n Found ${results.length} template(s) matching "${query}":\n`));
332
+
333
+ for (const t of results) {
334
+ const icon = getCategoryIcon(t.category);
335
+ console.log(` ${icon} ${chalk.green(t.id.padEnd(22))} ${chalk.gray(truncate(t.description, 40))}`);
336
+ }
337
+
338
+ console.log(chalk.gray(`
339
+ ─────────────────────────────────────────────────────────────────────
340
+
341
+ View details:
342
+ ${chalk.cyan(`vibecode templates --info ${results[0].id}`)}
343
+
344
+ Use template:
345
+ ${chalk.cyan(`vibecode go --template ${results[0].id}`)}
346
+ `));
347
+ }
348
+
349
+ /**
350
+ * Preview template (open in browser)
351
+ */
352
+ async function previewTemplate(templateId) {
353
+ const template = getTemplate(templateId);
354
+
355
+ if (!template) {
356
+ console.log(chalk.red(`\n ❌ Template "${templateId}" not found.\n`));
357
+ return;
358
+ }
359
+
360
+ console.log(chalk.cyan(`\n 🖼️ Preview: ${template.name}`));
361
+ console.log(chalk.gray(` URL: ${template.preview}\n`));
362
+
363
+ if (template.preview.startsWith('http')) {
364
+ try {
365
+ const open = (await import('open')).default;
366
+ await open(template.preview);
367
+ console.log(chalk.green(' ✓ Opened preview in browser.\n'));
368
+ } catch (e) {
369
+ console.log(chalk.yellow(' ⚠ Could not open browser. Visit URL manually.\n'));
370
+ }
371
+ } else {
372
+ console.log(chalk.gray(' (Preview not available yet)\n'));
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Truncate string to max length
378
+ */
379
+ function truncate(str, maxLen) {
380
+ if (str.length <= maxLen) return str;
381
+ return str.substring(0, maxLen - 3) + '...';
382
+ }
383
+
384
+ /**
385
+ * Format custom options for display
386
+ */
387
+ function formatCustomOptions(options) {
388
+ const parts = [];
389
+ for (const [key, value] of Object.entries(options)) {
390
+ if (typeof value === 'string') {
391
+ parts.push(`--${key} "${value}"`);
392
+ } else {
393
+ parts.push(`--${key} ${value}`);
394
+ }
395
+ }
396
+ return parts.length > 0 ? ' ' + parts.join(' ') : '';
397
+ }
@@ -0,0 +1,194 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Test Command
3
+ // Phase K2: AI Test Generation
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import { spawn } from 'child_process';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import chalk from 'chalk';
10
+ import inquirer from 'inquirer';
11
+
12
+ export async function testCommand(targetPath, options = {}) {
13
+ const cwd = process.cwd();
14
+
15
+ // Generate tests
16
+ if (options.generate) {
17
+ return generateTests(cwd, targetPath, options);
18
+ }
19
+
20
+ // Run tests (pass through to npm test)
21
+ if (options.run) {
22
+ const { exec } = await import('child_process');
23
+ const child = exec('npm test', { cwd });
24
+ child.stdout?.pipe(process.stdout);
25
+ child.stderr?.pipe(process.stderr);
26
+ return new Promise(resolve => child.on('close', resolve));
27
+ }
28
+
29
+ // Coverage
30
+ if (options.coverage) {
31
+ const { exec } = await import('child_process');
32
+ const child = exec('npm run test:coverage || npm test -- --coverage', { cwd });
33
+ child.stdout?.pipe(process.stdout);
34
+ child.stderr?.pipe(process.stderr);
35
+ return new Promise(resolve => child.on('close', resolve));
36
+ }
37
+
38
+ // Default: show menu
39
+ const { action } = await inquirer.prompt([{
40
+ type: 'list',
41
+ name: 'action',
42
+ message: 'Test options:',
43
+ choices: [
44
+ { name: '🧪 Run tests (npm test)', value: 'run' },
45
+ { name: '✨ Generate tests for file/folder', value: 'generate' },
46
+ { name: '📊 Show coverage', value: 'coverage' },
47
+ { name: '👋 Exit', value: 'exit' }
48
+ ]
49
+ }]);
50
+
51
+ if (action === 'run') {
52
+ return testCommand(null, { run: true });
53
+ }
54
+ if (action === 'generate') {
55
+ const { target } = await inquirer.prompt([{
56
+ type: 'input',
57
+ name: 'target',
58
+ message: 'Path to generate tests for:',
59
+ default: 'src/'
60
+ }]);
61
+ return generateTests(cwd, target, options);
62
+ }
63
+ if (action === 'coverage') {
64
+ return testCommand(null, { coverage: true });
65
+ }
66
+ }
67
+
68
+ async function generateTests(cwd, targetPath, options) {
69
+ console.log(chalk.cyan(`
70
+ ╭────────────────────────────────────────────────────────────────────╮
71
+ │ 🧪 TEST GENERATION │
72
+ │ │
73
+ │ Target: ${(targetPath || 'src/').padEnd(52)}│
74
+ │ │
75
+ ╰────────────────────────────────────────────────────────────────────╯
76
+ `));
77
+
78
+ // Detect test framework
79
+ const framework = await detectTestFramework(cwd);
80
+ console.log(chalk.gray(` Test framework: ${framework}\n`));
81
+
82
+ // Get files to generate tests for
83
+ const files = await getFilesToTest(cwd, targetPath || 'src/');
84
+
85
+ if (files.length === 0) {
86
+ console.log(chalk.yellow(' No files found to generate tests for.\n'));
87
+ return;
88
+ }
89
+
90
+ console.log(chalk.gray(` Found ${files.length} files\n`));
91
+
92
+ const prompt = `
93
+ # Test Generation Request
94
+
95
+ ## Project: ${path.basename(cwd)}
96
+ ## Test Framework: ${framework}
97
+
98
+ ## Files to Generate Tests For:
99
+ ${files.map(f => `- ${f}`).join('\n')}
100
+
101
+ ## Instructions:
102
+ 1. Read each source file
103
+ 2. Generate comprehensive tests including:
104
+ - Unit tests for each function/method
105
+ - Edge cases (null, undefined, empty, boundary values)
106
+ - Error handling tests
107
+ - Mock external dependencies
108
+ - Integration tests where appropriate
109
+
110
+ 3. Use ${framework} syntax and conventions
111
+ 4. Follow AAA pattern (Arrange, Act, Assert)
112
+ 5. Add descriptive test names
113
+ 6. Include setup/teardown if needed
114
+
115
+ ## Output:
116
+ Create test files in __tests__/ or *.test.ts/js format.
117
+ For each source file, create corresponding test file.
118
+
119
+ Generate tests now.
120
+ `;
121
+
122
+ const promptFile = path.join(cwd, '.vibecode', 'test-gen-prompt.md');
123
+ await fs.mkdir(path.dirname(promptFile), { recursive: true });
124
+ await fs.writeFile(promptFile, prompt);
125
+
126
+ console.log(chalk.gray(' Generating tests with Claude Code...\n'));
127
+
128
+ await runClaudeCode(prompt, cwd);
129
+
130
+ console.log(chalk.green('\n✅ Tests generated!'));
131
+ console.log(chalk.gray(' Run: npm test\n'));
132
+ }
133
+
134
+ async function detectTestFramework(cwd) {
135
+ try {
136
+ const pkgPath = path.join(cwd, 'package.json');
137
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
138
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
139
+
140
+ if (deps.vitest) return 'vitest';
141
+ if (deps.jest) return 'jest';
142
+ if (deps.mocha) return 'mocha';
143
+ if (deps['@testing-library/react']) return 'jest + @testing-library/react';
144
+ if (deps.ava) return 'ava';
145
+ if (deps.tap) return 'tap';
146
+ if (deps.uvu) return 'uvu';
147
+
148
+ return 'jest'; // default
149
+ } catch {
150
+ return 'jest';
151
+ }
152
+ }
153
+
154
+ async function getFilesToTest(cwd, targetPath) {
155
+ const files = [];
156
+ const fullPath = path.join(cwd, targetPath);
157
+
158
+ async function scan(dir) {
159
+ try {
160
+ const entries = await fs.readdir(dir, { withFileTypes: true });
161
+
162
+ for (const entry of entries) {
163
+ if (entry.name.startsWith('.')) continue;
164
+ if (entry.name.includes('.test.') || entry.name.includes('.spec.')) continue;
165
+ if (entry.name === '__tests__') continue;
166
+ if (entry.name === 'node_modules') continue;
167
+ if (entry.name === 'dist' || entry.name === 'build') continue;
168
+
169
+ const entryPath = path.join(dir, entry.name);
170
+
171
+ if (entry.isDirectory()) {
172
+ await scan(entryPath);
173
+ } else if (/\.(js|ts|jsx|tsx)$/.test(entry.name)) {
174
+ files.push(path.relative(cwd, entryPath));
175
+ }
176
+ }
177
+ } catch {}
178
+ }
179
+
180
+ await scan(fullPath);
181
+ return files.slice(0, 20); // Limit
182
+ }
183
+
184
+ async function runClaudeCode(prompt, cwd) {
185
+ return new Promise((resolve) => {
186
+ const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
187
+ cwd,
188
+ stdio: 'inherit'
189
+ });
190
+
191
+ child.on('close', resolve);
192
+ child.on('error', () => resolve());
193
+ });
194
+ }