@nclamvn/vibecode-cli 1.3.0 → 1.5.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.
@@ -0,0 +1,255 @@
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 } 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
+ // Check for Claude Code
20
+ const claudeAvailable = await isClaudeCodeAvailable();
21
+ if (!claudeAvailable) {
22
+ printError('Claude Code CLI not found.');
23
+ console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
24
+ process.exit(1);
25
+ }
26
+
27
+ // Validate description
28
+ if (!description || description.trim().length < 5) {
29
+ printError('Description too short. Please provide more details.');
30
+ console.log(chalk.gray('Example: vibecode agent "SaaS dashboard with auth, billing, and analytics"'));
31
+ process.exit(1);
32
+ }
33
+
34
+ // Handle sub-commands
35
+ if (options.analyze) {
36
+ return analyzeCommand(description, options);
37
+ }
38
+
39
+ if (options.status) {
40
+ return statusCommand(options);
41
+ }
42
+
43
+ if (options.report) {
44
+ return reportCommand(options);
45
+ }
46
+
47
+ if (options.clear) {
48
+ return clearCommand(options);
49
+ }
50
+
51
+ // Main build flow
52
+ return buildCommand(description, options);
53
+ }
54
+
55
+ /**
56
+ * Main agent build command
57
+ */
58
+ async function buildCommand(description, options) {
59
+ // Determine project path
60
+ let projectPath = process.cwd();
61
+
62
+ // If --new flag, create new directory
63
+ if (options.new) {
64
+ const projectName = generateProjectName(description);
65
+ projectPath = path.join(process.cwd(), projectName);
66
+
67
+ if (await fs.pathExists(projectPath)) {
68
+ printError(`Directory already exists: ${projectName}`);
69
+ console.log(chalk.gray('Choose a different name or delete the existing directory.'));
70
+ process.exit(1);
71
+ }
72
+
73
+ await fs.ensureDir(projectPath);
74
+ await fs.ensureDir(path.join(projectPath, '.vibecode'));
75
+ process.chdir(projectPath);
76
+
77
+ console.log(chalk.gray(`Created project: ${projectName}`));
78
+ }
79
+
80
+ // Create and initialize agent
81
+ const agent = createAgent({
82
+ projectPath,
83
+ verbose: options.verbose || false
84
+ });
85
+
86
+ try {
87
+ await agent.initialize();
88
+
89
+ // Build with options
90
+ const buildOptions = {
91
+ maxModuleRetries: options.maxRetries || 3,
92
+ testAfterEachModule: !options.skipTests,
93
+ continueOnFailure: options.continue || false
94
+ };
95
+
96
+ const result = await agent.build(description, buildOptions);
97
+
98
+ // Exit with appropriate code
99
+ if (!result.success) {
100
+ process.exit(1);
101
+ }
102
+
103
+ } catch (error) {
104
+ printError(`Agent failed: ${error.message}`);
105
+ console.log(chalk.gray('Check .vibecode/agent/orchestrator.log for details.'));
106
+ process.exit(1);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Analyze project without building
112
+ */
113
+ async function analyzeCommand(description, options) {
114
+ const agent = createAgent({
115
+ projectPath: process.cwd(),
116
+ verbose: options.verbose || false
117
+ });
118
+
119
+ try {
120
+ await agent.initialize();
121
+ const analysis = await agent.analyze(description);
122
+
123
+ // Output JSON if requested
124
+ if (options.json) {
125
+ console.log(JSON.stringify(analysis, null, 2));
126
+ }
127
+
128
+ return analysis;
129
+
130
+ } catch (error) {
131
+ printError(`Analysis failed: ${error.message}`);
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Show agent status
138
+ */
139
+ async function statusCommand(options) {
140
+ const agent = createAgent({
141
+ projectPath: process.cwd()
142
+ });
143
+
144
+ try {
145
+ await agent.initialize();
146
+ const status = agent.getStatus();
147
+
148
+ console.log();
149
+ console.log(chalk.cyan('Agent Status'));
150
+ console.log(chalk.gray('─'.repeat(40)));
151
+ console.log();
152
+
153
+ console.log(` Initialized: ${status.initialized ? chalk.green('Yes') : chalk.red('No')}`);
154
+ console.log(` Project: ${status.projectPath}`);
155
+
156
+ if (status.memoryStats) {
157
+ console.log();
158
+ console.log(chalk.cyan('Memory Stats'));
159
+ console.log(chalk.gray('─'.repeat(40)));
160
+ console.log(` Modules: ${status.memoryStats.modulesCompleted}/${status.memoryStats.modulesTotal} completed`);
161
+ console.log(` Decisions: ${status.memoryStats.decisionsCount}`);
162
+ console.log(` Patterns: ${status.memoryStats.patternsCount}`);
163
+ console.log(` Errors: ${status.memoryStats.errorsFixed}/${status.memoryStats.errorsTotal} fixed`);
164
+ console.log(` Files: ${status.memoryStats.filesCreated}`);
165
+ }
166
+
167
+ if (status.healingStats) {
168
+ console.log();
169
+ console.log(chalk.cyan('Self-Healing Stats'));
170
+ console.log(chalk.gray('─'.repeat(40)));
171
+ console.log(` Attempts: ${status.healingStats.total}`);
172
+ console.log(` Success: ${status.healingStats.successful}`);
173
+ console.log(` Failed: ${status.healingStats.failed}`);
174
+ console.log(` Rate: ${status.healingStats.successRate}`);
175
+ }
176
+
177
+ console.log();
178
+
179
+ // Output JSON if requested
180
+ if (options.json) {
181
+ console.log(JSON.stringify(status, null, 2));
182
+ }
183
+
184
+ } catch (error) {
185
+ printError(`Status failed: ${error.message}`);
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Export memory report
192
+ */
193
+ async function reportCommand(options) {
194
+ const agent = createAgent({
195
+ projectPath: process.cwd()
196
+ });
197
+
198
+ try {
199
+ await agent.initialize();
200
+ const report = await agent.exportReport();
201
+
202
+ if (options.stdout) {
203
+ console.log(report);
204
+ }
205
+
206
+ } catch (error) {
207
+ printError(`Report failed: ${error.message}`);
208
+ process.exit(1);
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Clear agent memory
214
+ */
215
+ async function clearCommand(options) {
216
+ if (!options.force) {
217
+ console.log(chalk.yellow('This will clear all agent memory.'));
218
+ console.log(chalk.gray('Use --force to confirm.'));
219
+ return;
220
+ }
221
+
222
+ const agent = createAgent({
223
+ projectPath: process.cwd()
224
+ });
225
+
226
+ try {
227
+ await agent.initialize();
228
+ await agent.clearMemory();
229
+ printSuccess('Agent memory cleared');
230
+
231
+ } catch (error) {
232
+ printError(`Clear failed: ${error.message}`);
233
+ process.exit(1);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Generate project name from description
239
+ */
240
+ function generateProjectName(description) {
241
+ const stopWords = ['a', 'an', 'the', 'for', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'with', 'my', 'our'];
242
+
243
+ const words = description
244
+ .toLowerCase()
245
+ .replace(/[^a-z0-9\s]/g, '')
246
+ .split(/\s+/)
247
+ .filter(w => w.length > 2 && !stopWords.includes(w))
248
+ .slice(0, 3);
249
+
250
+ if (words.length === 0) {
251
+ return `vibecode-agent-${Date.now().toString(36)}`;
252
+ }
253
+
254
+ return words.join('-') + '-agent';
255
+ }
@@ -0,0 +1,413 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Assist Command
3
+ // AI Expert Mode - Direct Claude Code Access with Full Project Context
4
+ // "User NEVER bế tắc" - The Ultimate Escape Hatch
5
+ // ═══════════════════════════════════════════════════════════════════════════════
6
+
7
+ import { spawn } from 'child_process';
8
+ import readline from 'readline';
9
+ import chalk from 'chalk';
10
+ import path from 'path';
11
+ import fs from 'fs-extra';
12
+ import os from 'os';
13
+
14
+ /**
15
+ * Assist Command - AI Expert Mode
16
+ *
17
+ * Usage:
18
+ * vibecode assist - Interactive mode
19
+ * vibecode assist "fix the error" - One-shot with prompt
20
+ * vibecode expert "help me debug" - Alias
21
+ */
22
+ export async function assistCommand(initialPrompt = [], options = {}) {
23
+ const projectPath = process.cwd();
24
+
25
+ // Render welcome header
26
+ console.log(renderHeader());
27
+
28
+ // Gather all context
29
+ const context = await gatherContext(projectPath);
30
+ console.log(renderContextSummary(context));
31
+
32
+ // If initial prompt provided, run once
33
+ if (initialPrompt && initialPrompt.length > 0) {
34
+ const prompt = initialPrompt.join(' ');
35
+ await runClaudeCode(prompt, context, projectPath, options);
36
+ return;
37
+ }
38
+
39
+ // Interactive mode
40
+ await interactiveAssist(context, projectPath, options);
41
+ }
42
+
43
+ /**
44
+ * Gather all available project context
45
+ */
46
+ async function gatherContext(projectPath) {
47
+ const context = {
48
+ project: path.basename(projectPath),
49
+ cwd: projectPath,
50
+ state: null,
51
+ memory: null,
52
+ debugHistory: null,
53
+ claudeMd: null,
54
+ packageJson: null,
55
+ files: [],
56
+ gitBranch: null
57
+ };
58
+
59
+ // Load vibecode state
60
+ try {
61
+ const statePath = path.join(projectPath, '.vibecode', 'state.json');
62
+ if (await fs.pathExists(statePath)) {
63
+ context.state = await fs.readJson(statePath);
64
+ }
65
+ } catch {}
66
+
67
+ // Load agent memory
68
+ try {
69
+ const memoryPath = path.join(projectPath, '.vibecode', 'agent', 'memory.json');
70
+ if (await fs.pathExists(memoryPath)) {
71
+ context.memory = await fs.readJson(memoryPath);
72
+ }
73
+ } catch {}
74
+
75
+ // Load debug history
76
+ try {
77
+ const fixesPath = path.join(projectPath, '.vibecode', 'debug', 'fixes.md');
78
+ if (await fs.pathExists(fixesPath)) {
79
+ context.debugHistory = await fs.readFile(fixesPath, 'utf-8');
80
+ }
81
+ } catch {}
82
+
83
+ // Load CLAUDE.md
84
+ try {
85
+ const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
86
+ if (await fs.pathExists(claudeMdPath)) {
87
+ context.claudeMd = await fs.readFile(claudeMdPath, 'utf-8');
88
+ }
89
+ } catch {}
90
+
91
+ // Load package.json
92
+ try {
93
+ const pkgPath = path.join(projectPath, 'package.json');
94
+ if (await fs.pathExists(pkgPath)) {
95
+ context.packageJson = await fs.readJson(pkgPath);
96
+ }
97
+ } catch {}
98
+
99
+ // List project files
100
+ try {
101
+ const files = await fs.readdir(projectPath);
102
+ context.files = files.filter(f =>
103
+ !f.startsWith('.') &&
104
+ f !== 'node_modules' &&
105
+ f !== 'dist' &&
106
+ f !== 'build'
107
+ ).slice(0, 25);
108
+ } catch {}
109
+
110
+ // Get git branch
111
+ try {
112
+ const { execSync } = await import('child_process');
113
+ context.gitBranch = execSync('git branch --show-current', {
114
+ cwd: projectPath,
115
+ encoding: 'utf-8'
116
+ }).trim();
117
+ } catch {}
118
+
119
+ return context;
120
+ }
121
+
122
+ /**
123
+ * Build context prompt for Claude Code
124
+ */
125
+ function buildContextPrompt(context) {
126
+ const parts = [];
127
+
128
+ parts.push(`# VIBECODE PROJECT CONTEXT`);
129
+ parts.push('');
130
+ parts.push(`## Project: ${context.project}`);
131
+ parts.push(`## Path: ${context.cwd}`);
132
+ if (context.gitBranch) {
133
+ parts.push(`## Branch: ${context.gitBranch}`);
134
+ }
135
+ parts.push('');
136
+
137
+ // CLAUDE.md rules
138
+ if (context.claudeMd) {
139
+ parts.push('## Project Rules (CLAUDE.md)');
140
+ parts.push(context.claudeMd.substring(0, 2000));
141
+ parts.push('');
142
+ }
143
+
144
+ // Vibecode state
145
+ if (context.state) {
146
+ parts.push('## Vibecode State');
147
+ parts.push(`- Current State: ${context.state.current_state || 'unknown'}`);
148
+ parts.push(`- Session: ${context.state.session_id || 'none'}`);
149
+ if (context.state.build_started) {
150
+ parts.push(`- Build Started: ${context.state.build_started}`);
151
+ }
152
+ parts.push('');
153
+ }
154
+
155
+ // Agent memory
156
+ if (context.memory) {
157
+ parts.push('## Agent Memory');
158
+ if (context.memory.decisions?.length) {
159
+ parts.push(`### Decisions (${context.memory.decisions.length})`);
160
+ context.memory.decisions.slice(-5).forEach(d => {
161
+ parts.push(`- ${d.decision}: ${d.rationale?.substring(0, 100) || ''}`);
162
+ });
163
+ }
164
+ if (context.memory.learnings?.length) {
165
+ parts.push(`### Learnings (${context.memory.learnings.length})`);
166
+ context.memory.learnings.slice(-5).forEach(l => {
167
+ parts.push(`- ${l.description?.substring(0, 100) || l}`);
168
+ });
169
+ }
170
+ if (context.memory.moduleStates) {
171
+ const completed = Object.entries(context.memory.moduleStates)
172
+ .filter(([_, v]) => v.status === 'completed')
173
+ .map(([k, _]) => k);
174
+ const failed = Object.entries(context.memory.moduleStates)
175
+ .filter(([_, v]) => v.status === 'failed')
176
+ .map(([k, _]) => k);
177
+ if (completed.length) parts.push(`### Completed Modules: ${completed.join(', ')}`);
178
+ if (failed.length) parts.push(`### Failed Modules: ${failed.join(', ')}`);
179
+ }
180
+ parts.push('');
181
+ }
182
+
183
+ // Debug history (condensed)
184
+ if (context.debugHistory) {
185
+ parts.push('## Recent Debug Attempts');
186
+ // Take last 1000 chars of debug history
187
+ const condensed = context.debugHistory.slice(-1000);
188
+ parts.push(condensed);
189
+ parts.push('');
190
+ }
191
+
192
+ // Package info
193
+ if (context.packageJson) {
194
+ parts.push('## Package');
195
+ parts.push(`- Name: ${context.packageJson.name || 'unknown'}`);
196
+ if (context.packageJson.scripts) {
197
+ parts.push(`- Scripts: ${Object.keys(context.packageJson.scripts).join(', ')}`);
198
+ }
199
+ parts.push('');
200
+ }
201
+
202
+ // Project files
203
+ if (context.files.length > 0) {
204
+ parts.push('## Project Files');
205
+ parts.push(context.files.join(', '));
206
+ parts.push('');
207
+ }
208
+
209
+ parts.push('---');
210
+ parts.push('');
211
+ parts.push('You have full access to this project. Help the user with whatever they need.');
212
+ parts.push('Use --dangerously-skip-permissions mode - you can modify files directly.');
213
+ parts.push('');
214
+
215
+ return parts.join('\n');
216
+ }
217
+
218
+ /**
219
+ * Run Claude Code with prompt
220
+ */
221
+ async function runClaudeCode(prompt, context, projectPath, options) {
222
+ // Build full prompt with context
223
+ let fullPrompt;
224
+ if (options.noContext) {
225
+ fullPrompt = prompt;
226
+ } else {
227
+ const contextPrompt = buildContextPrompt(context);
228
+ fullPrompt = `${contextPrompt}\n## USER REQUEST:\n${prompt}`;
229
+ }
230
+
231
+ // Write to temp file
232
+ const tempFile = path.join(os.tmpdir(), `vibecode-summon-${Date.now()}.md`);
233
+ await fs.writeFile(tempFile, fullPrompt);
234
+
235
+ // Also save to project for reference
236
+ const summonDir = path.join(projectPath, '.vibecode', 'summon');
237
+ await fs.ensureDir(summonDir);
238
+ await fs.writeFile(path.join(summonDir, 'last-prompt.md'), fullPrompt);
239
+
240
+ return new Promise((resolve, reject) => {
241
+ console.log(chalk.blue('\n🤝 AI Assist responding...\n'));
242
+ console.log(chalk.gray('─'.repeat(60)));
243
+ console.log();
244
+
245
+ const claude = spawn('claude', [
246
+ '--dangerously-skip-permissions',
247
+ '--print',
248
+ '-p', tempFile
249
+ ], {
250
+ cwd: projectPath,
251
+ stdio: ['inherit', 'inherit', 'inherit']
252
+ });
253
+
254
+ claude.on('close', async (code) => {
255
+ console.log();
256
+ console.log(chalk.gray('─'.repeat(60)));
257
+ console.log(chalk.green('\n✓ Claude Code finished\n'));
258
+
259
+ // Cleanup temp file
260
+ await fs.remove(tempFile).catch(() => {});
261
+
262
+ resolve(code);
263
+ });
264
+
265
+ claude.on('error', async (err) => {
266
+ console.log(chalk.red(`\nError: ${err.message}`));
267
+ await fs.remove(tempFile).catch(() => {});
268
+ reject(err);
269
+ });
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Interactive assist REPL
275
+ */
276
+ async function interactiveAssist(context, projectPath, options) {
277
+ const rl = readline.createInterface({
278
+ input: process.stdin,
279
+ output: process.stdout,
280
+ prompt: chalk.blue('assist> ')
281
+ });
282
+
283
+ console.log(chalk.cyan(`
284
+ Commands:
285
+ • Type any request → Claude Code responds
286
+ • /context - Show injected context
287
+ • /refresh - Reload project context
288
+ • /back - Return to vibecode
289
+ • /quit - Exit
290
+ `));
291
+
292
+ rl.prompt();
293
+
294
+ rl.on('line', async (line) => {
295
+ const input = line.trim();
296
+
297
+ if (!input) {
298
+ rl.prompt();
299
+ return;
300
+ }
301
+
302
+ // Handle commands
303
+ if (input === '/back' || input === '/quit' || input === '/exit' || input === '/q') {
304
+ console.log(chalk.blue('\n👋 AI Assist session ended.\n'));
305
+ rl.close();
306
+ return;
307
+ }
308
+
309
+ if (input === '/context') {
310
+ console.log(chalk.gray('\n' + buildContextPrompt(context) + '\n'));
311
+ rl.prompt();
312
+ return;
313
+ }
314
+
315
+ if (input === '/refresh') {
316
+ context = await gatherContext(projectPath);
317
+ console.log(chalk.green('\n✓ Context refreshed\n'));
318
+ console.log(renderContextSummary(context));
319
+ rl.prompt();
320
+ return;
321
+ }
322
+
323
+ if (input === '/help') {
324
+ console.log(chalk.cyan(`
325
+ 🤝 VIBECODE ASSIST - Help
326
+
327
+ AI Expert Mode with full project context.
328
+ Everything you type will be sent to Claude Code.
329
+
330
+ Commands:
331
+ /context Show the context being injected
332
+ /refresh Reload project context (state, memory, etc.)
333
+ /back Return to normal vibecode
334
+ /quit Exit assist mode
335
+ /help Show this help
336
+
337
+ Tips:
338
+ • Be specific: "Fix the type error in prisma/seed.ts line 54"
339
+ • Reference files: "Look at src/app/api/auth and fix the session issue"
340
+ • Ask questions: "Why is this component not rendering?"
341
+ `));
342
+ rl.prompt();
343
+ return;
344
+ }
345
+
346
+ // Run Claude Code with the input
347
+ try {
348
+ await runClaudeCode(input, context, projectPath, options);
349
+ } catch (error) {
350
+ console.log(chalk.red(`Error: ${error.message}`));
351
+ }
352
+
353
+ rl.prompt();
354
+ });
355
+
356
+ rl.on('close', () => {
357
+ process.exit(0);
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Render welcome header
363
+ */
364
+ function renderHeader() {
365
+ return chalk.blue(`
366
+ ╭────────────────────────────────────────────────────────────────────╮
367
+ │ │
368
+ │ 🤝 VIBECODE ASSIST │
369
+ │ AI Expert Mode - Direct Claude Code Access │
370
+ │ │
371
+ │ Full project context injected automatically. │
372
+ │ Claude Code has permission to modify files. │
373
+ │ │
374
+ ╰────────────────────────────────────────────────────────────────────╯
375
+ `);
376
+ }
377
+
378
+ /**
379
+ * Render context summary
380
+ */
381
+ function renderContextSummary(context) {
382
+ const items = [];
383
+ items.push(` Project: ${context.project}`);
384
+
385
+ if (context.state) {
386
+ items.push(` State: ${context.state.current_state || 'unknown'}`);
387
+ }
388
+
389
+ if (context.memory) {
390
+ const decisions = context.memory.decisions?.length || 0;
391
+ const learnings = context.memory.learnings?.length || 0;
392
+ items.push(` Memory: ${decisions} decisions, ${learnings} learnings`);
393
+ }
394
+
395
+ if (context.debugHistory) {
396
+ items.push(` Debug history: available`);
397
+ }
398
+
399
+ if (context.claudeMd) {
400
+ items.push(` CLAUDE.md: loaded`);
401
+ }
402
+
403
+ items.push(` Files: ${context.files.length} visible`);
404
+
405
+ if (context.gitBranch) {
406
+ items.push(` Branch: ${context.gitBranch}`);
407
+ }
408
+
409
+ return chalk.gray(`
410
+ Context loaded:
411
+ ${items.join('\n')}
412
+ `);
413
+ }