@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,349 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Agent Command
3
+ // Autonomous multi-module builder
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import chalk from 'chalk';
7
+ import path from 'path';
8
+ import fs from 'fs-extra';
9
+
10
+ import { createAgent, loadProgress } from '../agent/index.js';
11
+ import { printError, printSuccess } from '../ui/output.js';
12
+ import { isClaudeCodeAvailable } from '../providers/index.js';
13
+
14
+ /**
15
+ * Agent command entry point
16
+ * vibecode agent "description" [options]
17
+ */
18
+ export async function agentCommand(description, options = {}) {
19
+ // Handle description as array (from commander variadic)
20
+ const desc = Array.isArray(description) ? description.join(' ') : description;
21
+
22
+ // Handle sub-commands that don't need Claude Code check first
23
+ if (options.status) {
24
+ return showAgentStatus(options);
25
+ }
26
+
27
+ // Check for Claude Code
28
+ const claudeAvailable = await isClaudeCodeAvailable();
29
+ if (!claudeAvailable) {
30
+ printError('Claude Code CLI not found.');
31
+ console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
32
+ process.exit(1);
33
+ }
34
+
35
+ // Handle resume
36
+ if (options.resume) {
37
+ return resumeCommand(options);
38
+ }
39
+
40
+ // Validate description for non-resume commands
41
+ if (!desc || desc.trim().length < 5) {
42
+ // Check if there's a session to resume
43
+ const progress = await loadProgress(process.cwd());
44
+ if (progress) {
45
+ console.log(chalk.cyan('\n📦 Found existing session:'));
46
+ console.log(chalk.white(` Project: ${progress.projectName}`));
47
+ console.log(chalk.white(` Progress: ${progress.completedModules?.length || progress.currentModule}/${progress.totalModules} modules\n`));
48
+ console.log(chalk.gray(' Resume: vibecode agent --resume'));
49
+ console.log(chalk.gray(' Status: vibecode agent --status\n'));
50
+ return;
51
+ }
52
+
53
+ printError('Description too short. Please provide more details.');
54
+ console.log(chalk.gray('Example: vibecode agent "SaaS dashboard with auth, billing, and analytics"'));
55
+ process.exit(1);
56
+ }
57
+
58
+ // Handle sub-commands
59
+ if (options.analyze) {
60
+ return analyzeCommand(desc, options);
61
+ }
62
+
63
+ if (options.report) {
64
+ return reportCommand(options);
65
+ }
66
+
67
+ if (options.clear) {
68
+ return clearCommand(options);
69
+ }
70
+
71
+ // Main build flow
72
+ return buildCommand(desc, options);
73
+ }
74
+
75
+ /**
76
+ * Main agent build command
77
+ */
78
+ async function buildCommand(description, options) {
79
+ // Determine project path
80
+ let projectPath = process.cwd();
81
+
82
+ // If --new flag, create new directory
83
+ if (options.new) {
84
+ const projectName = generateProjectName(description);
85
+ projectPath = path.join(process.cwd(), projectName);
86
+
87
+ if (await fs.pathExists(projectPath)) {
88
+ printError(`Directory already exists: ${projectName}`);
89
+ console.log(chalk.gray('Choose a different name or delete the existing directory.'));
90
+ process.exit(1);
91
+ }
92
+
93
+ await fs.ensureDir(projectPath);
94
+ await fs.ensureDir(path.join(projectPath, '.vibecode'));
95
+ process.chdir(projectPath);
96
+
97
+ console.log(chalk.gray(`Created project: ${projectName}`));
98
+ }
99
+
100
+ // Create and initialize agent
101
+ const agent = createAgent({
102
+ projectPath,
103
+ verbose: options.verbose || false
104
+ });
105
+
106
+ try {
107
+ await agent.initialize();
108
+
109
+ // Build with options
110
+ const buildOptions = {
111
+ maxModuleRetries: options.maxRetries || 3,
112
+ testAfterEachModule: !options.skipTests,
113
+ continueOnFailure: options.continue || false
114
+ };
115
+
116
+ const result = await agent.build(description, buildOptions);
117
+
118
+ // Exit with appropriate code
119
+ if (!result.success) {
120
+ process.exit(1);
121
+ }
122
+
123
+ } catch (error) {
124
+ printError(`Agent failed: ${error.message}`);
125
+ console.log(chalk.gray('Check .vibecode/agent/orchestrator.log for details.'));
126
+ process.exit(1);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Analyze project without building
132
+ */
133
+ async function analyzeCommand(description, options) {
134
+ const agent = createAgent({
135
+ projectPath: process.cwd(),
136
+ verbose: options.verbose || false
137
+ });
138
+
139
+ try {
140
+ await agent.initialize();
141
+ const analysis = await agent.analyze(description);
142
+
143
+ // Output JSON if requested
144
+ if (options.json) {
145
+ console.log(JSON.stringify(analysis, null, 2));
146
+ }
147
+
148
+ return analysis;
149
+
150
+ } catch (error) {
151
+ printError(`Analysis failed: ${error.message}`);
152
+ process.exit(1);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Show agent status with resume info
158
+ */
159
+ async function showAgentStatus(options) {
160
+ const cwd = process.cwd();
161
+ const progress = await loadProgress(cwd);
162
+
163
+ if (!progress) {
164
+ console.log(chalk.yellow('\n📭 No active agent session.\n'));
165
+ console.log(chalk.gray(' Start new: vibecode agent "description" --new\n'));
166
+ return;
167
+ }
168
+
169
+ console.log();
170
+ console.log(chalk.cyan('╭' + '─'.repeat(68) + '╮'));
171
+ console.log(chalk.cyan('│') + chalk.bold.white(' 🤖 AGENT STATUS') + ' '.repeat(50) + chalk.cyan('│'));
172
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
173
+
174
+ const projectLine = ` Project: ${progress.projectName}`;
175
+ console.log(chalk.cyan('│') + chalk.white(projectLine) + ' '.repeat(Math.max(0, 66 - projectLine.length)) + chalk.cyan('│'));
176
+
177
+ const completed = progress.completedModules?.length || progress.currentModule || 0;
178
+ const progressLine = ` Progress: ${completed}/${progress.totalModules} modules`;
179
+ console.log(chalk.cyan('│') + chalk.white(progressLine) + ' '.repeat(Math.max(0, 66 - progressLine.length)) + chalk.cyan('│'));
180
+
181
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
182
+
183
+ // Show modules with status
184
+ for (const mod of progress.modules || []) {
185
+ let icon, color;
186
+ switch (mod.status) {
187
+ case 'done':
188
+ icon = chalk.green('✓');
189
+ color = chalk.green;
190
+ break;
191
+ case 'building':
192
+ icon = chalk.yellow('◐');
193
+ color = chalk.yellow;
194
+ break;
195
+ case 'failed':
196
+ icon = chalk.red('✗');
197
+ color = chalk.red;
198
+ break;
199
+ default:
200
+ icon = chalk.gray('○');
201
+ color = chalk.gray;
202
+ }
203
+ const modLine = ` ${icon} ${color(mod.name)}`;
204
+ // Approximate length without ANSI codes
205
+ const approxLen = 5 + mod.name.length;
206
+ console.log(chalk.cyan('│') + modLine + ' '.repeat(Math.max(0, 66 - approxLen)) + chalk.cyan('│'));
207
+ }
208
+
209
+ if (progress.error) {
210
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
211
+ const errorLine = ` Error: ${progress.error.substring(0, 55)}`;
212
+ console.log(chalk.cyan('│') + chalk.red(errorLine) + ' '.repeat(Math.max(0, 66 - errorLine.length)) + chalk.cyan('│'));
213
+ }
214
+
215
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
216
+
217
+ const timeLine = ` Last updated: ${new Date(progress.lastUpdated).toLocaleString()}`;
218
+ console.log(chalk.cyan('│') + chalk.gray(timeLine) + ' '.repeat(Math.max(0, 66 - timeLine.length)) + chalk.cyan('│'));
219
+
220
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
221
+ console.log(chalk.cyan('│') + chalk.yellow(' 💡 Resume: vibecode agent --resume') + ' '.repeat(29) + chalk.cyan('│'));
222
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
223
+ console.log(chalk.cyan('╰' + '─'.repeat(68) + '╯'));
224
+ console.log();
225
+
226
+ // Output JSON if requested
227
+ if (options.json) {
228
+ console.log(JSON.stringify(progress, null, 2));
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Resume agent from last stopped module
234
+ */
235
+ async function resumeCommand(options) {
236
+ const cwd = process.cwd();
237
+
238
+ // Check for progress
239
+ const progress = await loadProgress(cwd);
240
+ if (!progress) {
241
+ console.log(chalk.yellow('\n📭 No session to resume.\n'));
242
+ console.log(chalk.gray(' Start new: vibecode agent "description" --new\n'));
243
+ return;
244
+ }
245
+
246
+ // Create agent and resume
247
+ const agent = createAgent({
248
+ projectPath: cwd,
249
+ verbose: options.verbose || false
250
+ });
251
+
252
+ try {
253
+ // Determine from module
254
+ const fromModule = options.from ? parseInt(options.from) - 1 : undefined;
255
+
256
+ const resumeOptions = {
257
+ maxModuleRetries: options.maxRetries || 3,
258
+ testAfterEachModule: !options.skipTests,
259
+ continueOnFailure: options.continue || false,
260
+ fromModule
261
+ };
262
+
263
+ const result = await agent.resume(resumeOptions);
264
+
265
+ // Exit with appropriate code
266
+ if (result && !result.success) {
267
+ process.exit(1);
268
+ }
269
+
270
+ } catch (error) {
271
+ printError(`Resume failed: ${error.message}`);
272
+ console.log(chalk.gray('Check .vibecode/agent/orchestrator.log for details.'));
273
+ process.exit(1);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Legacy status command (for compatibility)
279
+ */
280
+ async function statusCommand(options) {
281
+ return showAgentStatus(options);
282
+ }
283
+
284
+ /**
285
+ * Export memory report
286
+ */
287
+ async function reportCommand(options) {
288
+ const agent = createAgent({
289
+ projectPath: process.cwd()
290
+ });
291
+
292
+ try {
293
+ await agent.initialize();
294
+ const report = await agent.exportReport();
295
+
296
+ if (options.stdout) {
297
+ console.log(report);
298
+ }
299
+
300
+ } catch (error) {
301
+ printError(`Report failed: ${error.message}`);
302
+ process.exit(1);
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Clear agent memory
308
+ */
309
+ async function clearCommand(options) {
310
+ if (!options.force) {
311
+ console.log(chalk.yellow('This will clear all agent memory.'));
312
+ console.log(chalk.gray('Use --force to confirm.'));
313
+ return;
314
+ }
315
+
316
+ const agent = createAgent({
317
+ projectPath: process.cwd()
318
+ });
319
+
320
+ try {
321
+ await agent.initialize();
322
+ await agent.clearMemory();
323
+ printSuccess('Agent memory cleared');
324
+
325
+ } catch (error) {
326
+ printError(`Clear failed: ${error.message}`);
327
+ process.exit(1);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Generate project name from description
333
+ */
334
+ function generateProjectName(description) {
335
+ const stopWords = ['a', 'an', 'the', 'for', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'with', 'my', 'our'];
336
+
337
+ const words = description
338
+ .toLowerCase()
339
+ .replace(/[^a-z0-9\s]/g, '')
340
+ .split(/\s+/)
341
+ .filter(w => w.length > 2 && !stopWords.includes(w))
342
+ .slice(0, 3);
343
+
344
+ if (words.length === 0) {
345
+ return `vibecode-agent-${Date.now().toString(36)}`;
346
+ }
347
+
348
+ return words.join('-') + '-agent';
349
+ }
@@ -0,0 +1,230 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Ask Command
3
+ // Phase K6: Codebase Q&A
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 readline from 'readline';
11
+
12
+ export async function askCommand(question, options = {}) {
13
+ const cwd = process.cwd();
14
+
15
+ // If no question, enter interactive mode
16
+ if (!question || question.length === 0) {
17
+ return interactiveAsk(cwd);
18
+ }
19
+
20
+ const questionText = Array.isArray(question) ? question.join(' ') : question;
21
+ return answerQuestion(cwd, questionText);
22
+ }
23
+
24
+ async function interactiveAsk(cwd) {
25
+ console.log(chalk.cyan(`
26
+ ╭────────────────────────────────────────────────────────────────────╮
27
+ │ 💬 CODEBASE Q&A │
28
+ │ │
29
+ │ Ask anything about your codebase. │
30
+ │ Type 'exit' to quit. │
31
+ │ │
32
+ ╰────────────────────────────────────────────────────────────────────╯
33
+ `));
34
+
35
+ const rl = readline.createInterface({
36
+ input: process.stdin,
37
+ output: process.stdout,
38
+ prompt: chalk.green('ask> ')
39
+ });
40
+
41
+ rl.prompt();
42
+
43
+ rl.on('line', async (line) => {
44
+ const input = line.trim();
45
+
46
+ if (!input) {
47
+ rl.prompt();
48
+ return;
49
+ }
50
+
51
+ if (input === 'exit' || input === 'quit' || input === 'q') {
52
+ console.log(chalk.cyan('\n👋 Goodbye!\n'));
53
+ rl.close();
54
+ return;
55
+ }
56
+
57
+ await answerQuestion(cwd, input);
58
+ console.log('');
59
+ rl.prompt();
60
+ });
61
+
62
+ rl.on('close', () => process.exit(0));
63
+ }
64
+
65
+ async function answerQuestion(cwd, question) {
66
+ console.log(chalk.gray('\n Analyzing codebase...\n'));
67
+
68
+ // Build context
69
+ const projectInfo = await getProjectContext(cwd);
70
+
71
+ const prompt = `
72
+ # Codebase Question
73
+
74
+ ## Project: ${path.basename(cwd)}
75
+ ## Type: ${projectInfo.type}
76
+
77
+ ## Project Structure:
78
+ ${projectInfo.structure}
79
+
80
+ ## Key Files:
81
+ ${projectInfo.keyFiles.join('\n')}
82
+
83
+ ## Question:
84
+ ${question}
85
+
86
+ ## Instructions:
87
+ 1. Analyze the codebase to answer the question
88
+ 2. Reference specific files and line numbers when applicable
89
+ 3. Provide code examples if helpful
90
+ 4. Be concise but thorough
91
+ 5. If you need to look at specific files, do so
92
+
93
+ Answer the question now.
94
+ `;
95
+
96
+ await runClaudeCode(prompt, cwd);
97
+ }
98
+
99
+ async function getProjectContext(cwd) {
100
+ const context = {
101
+ type: 'unknown',
102
+ structure: '',
103
+ keyFiles: []
104
+ };
105
+
106
+ // Detect project type
107
+ try {
108
+ const pkgPath = path.join(cwd, 'package.json');
109
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
110
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
111
+
112
+ if (deps.next) context.type = 'Next.js';
113
+ else if (deps.nuxt) context.type = 'Nuxt';
114
+ else if (deps['@angular/core']) context.type = 'Angular';
115
+ else if (deps.react) context.type = 'React';
116
+ else if (deps.vue) context.type = 'Vue';
117
+ else if (deps.svelte) context.type = 'Svelte';
118
+ else if (deps.express) context.type = 'Express';
119
+ else if (deps.fastify) context.type = 'Fastify';
120
+ else if (deps.koa) context.type = 'Koa';
121
+ else if (deps.nestjs || deps['@nestjs/core']) context.type = 'NestJS';
122
+ else context.type = 'Node.js';
123
+ } catch {
124
+ // Check for other project types
125
+ try {
126
+ await fs.access(path.join(cwd, 'Cargo.toml'));
127
+ context.type = 'Rust';
128
+ } catch {}
129
+ try {
130
+ await fs.access(path.join(cwd, 'go.mod'));
131
+ context.type = 'Go';
132
+ } catch {}
133
+ try {
134
+ await fs.access(path.join(cwd, 'requirements.txt'));
135
+ context.type = 'Python';
136
+ } catch {}
137
+ }
138
+
139
+ // Get directory structure
140
+ context.structure = await getDirectoryTree(cwd, 3);
141
+
142
+ // Get key files
143
+ context.keyFiles = await findKeyFiles(cwd);
144
+
145
+ return context;
146
+ }
147
+
148
+ async function getDirectoryTree(dir, depth, prefix = '') {
149
+ if (depth === 0) return '';
150
+
151
+ let result = '';
152
+
153
+ try {
154
+ const entries = await fs.readdir(dir, { withFileTypes: true });
155
+ const filtered = entries.filter(e =>
156
+ !e.name.startsWith('.') &&
157
+ e.name !== 'node_modules' &&
158
+ e.name !== 'dist' &&
159
+ e.name !== 'build' &&
160
+ e.name !== '.next' &&
161
+ e.name !== 'coverage' &&
162
+ e.name !== '__pycache__'
163
+ );
164
+
165
+ for (const entry of filtered.slice(0, 15)) {
166
+ const icon = entry.isDirectory() ? '📁' : '📄';
167
+ result += `${prefix}${icon} ${entry.name}\n`;
168
+
169
+ if (entry.isDirectory() && depth > 1) {
170
+ result += await getDirectoryTree(
171
+ path.join(dir, entry.name),
172
+ depth - 1,
173
+ prefix + ' '
174
+ );
175
+ }
176
+ }
177
+
178
+ if (filtered.length > 15) {
179
+ result += `${prefix}... and ${filtered.length - 15} more\n`;
180
+ }
181
+ } catch {}
182
+
183
+ return result;
184
+ }
185
+
186
+ async function findKeyFiles(cwd) {
187
+ const keyFiles = [];
188
+ const importantFiles = [
189
+ 'package.json',
190
+ 'tsconfig.json',
191
+ 'README.md',
192
+ 'CLAUDE.md',
193
+ '.env.example',
194
+ 'src/index.ts',
195
+ 'src/index.js',
196
+ 'src/main.ts',
197
+ 'src/main.js',
198
+ 'src/app.ts',
199
+ 'src/app.js',
200
+ 'app/page.tsx',
201
+ 'app/layout.tsx',
202
+ 'pages/index.tsx',
203
+ 'pages/_app.tsx',
204
+ 'prisma/schema.prisma',
205
+ 'drizzle.config.ts',
206
+ 'Dockerfile',
207
+ 'docker-compose.yml'
208
+ ];
209
+
210
+ for (const file of importantFiles) {
211
+ try {
212
+ await fs.access(path.join(cwd, file));
213
+ keyFiles.push(file);
214
+ } catch {}
215
+ }
216
+
217
+ return keyFiles;
218
+ }
219
+
220
+ async function runClaudeCode(prompt, cwd) {
221
+ return new Promise((resolve) => {
222
+ const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
223
+ cwd,
224
+ stdio: 'inherit'
225
+ });
226
+
227
+ child.on('close', resolve);
228
+ child.on('error', () => resolve());
229
+ });
230
+ }