@nclamvn/vibecode-cli 2.2.1 → 3.0.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,473 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Feedback Command
3
+ // Interactive feedback mode for incremental changes
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
+ import net from 'net';
12
+ import { BackupManager } from '../core/backup.js';
13
+ import { spawnClaudeCode, buildPromptWithContext } from '../providers/index.js';
14
+
15
+ /**
16
+ * Feedback command entry point
17
+ */
18
+ export async function feedbackCommand(options = {}) {
19
+ const cwd = process.cwd();
20
+
21
+ // Check if valid project
22
+ const isValid = await isValidProject(cwd);
23
+ if (!isValid) {
24
+ console.log(chalk.red(`
25
+ ╭────────────────────────────────────────────────────────────────────╮
26
+ │ ❌ NOT A VALID PROJECT │
27
+ │ │
28
+ │ Run this command inside a project with package.json, or use: │
29
+ │ vibecode go "description" --feedback │
30
+ │ │
31
+ ╰────────────────────────────────────────────────────────────────────╯
32
+ `));
33
+ return;
34
+ }
35
+
36
+ // Initialize
37
+ const projectName = path.basename(cwd);
38
+ const backup = new BackupManager(cwd);
39
+ const changeHistory = [];
40
+ let devProcess = null;
41
+ let changeCount = 0;
42
+ const port = parseInt(options.port) || 3000;
43
+
44
+ console.log(chalk.cyan(`
45
+ ╭────────────────────────────────────────────────────────────────────╮
46
+ │ 💬 VIBECODE FEEDBACK MODE │
47
+ │ │
48
+ │ Project: ${projectName.padEnd(52)}│
49
+ │ │
50
+ │ Commands: │
51
+ │ • Type your changes in natural language │
52
+ │ • 'undo' - Revert last change │
53
+ │ • 'history' - Show change history │
54
+ │ • 'preview' - Open/refresh preview │
55
+ │ • 'status' - Show current status │
56
+ │ • 'files' - List recently changed files │
57
+ │ • 'done' or 'exit' - End session │
58
+ │ │
59
+ ╰────────────────────────────────────────────────────────────────────╯
60
+ `));
61
+
62
+ // Start dev server if preview mode
63
+ if (options.preview) {
64
+ console.log(chalk.yellow(' 🚀 Starting preview server...\n'));
65
+ devProcess = await startDevServer(cwd, port);
66
+ const serverReady = await waitForServer(port);
67
+
68
+ if (serverReady) {
69
+ await openBrowser(`http://localhost:${port}`);
70
+ console.log(chalk.green(` ✅ Preview ready at http://localhost:${port}\n`));
71
+ } else {
72
+ console.log(chalk.yellow(` ⚠️ Server may still be starting...\n`));
73
+ }
74
+ }
75
+
76
+ // Create readline interface
77
+ const rl = readline.createInterface({
78
+ input: process.stdin,
79
+ output: process.stdout,
80
+ prompt: chalk.green('feedback> ')
81
+ });
82
+
83
+ rl.prompt();
84
+
85
+ rl.on('line', async (line) => {
86
+ const input = line.trim();
87
+ const inputLower = input.toLowerCase();
88
+
89
+ // Exit commands
90
+ if (inputLower === 'done' || inputLower === 'exit' || inputLower === 'quit' || inputLower === 'q') {
91
+ await endSession(devProcess, changeCount, rl);
92
+ return;
93
+ }
94
+
95
+ // Undo command
96
+ if (inputLower === 'undo') {
97
+ if (changeHistory.length === 0) {
98
+ console.log(chalk.yellow(' No changes to undo.\n'));
99
+ } else {
100
+ const lastChange = changeHistory.pop();
101
+ try {
102
+ await backup.restoreBackup(lastChange.backupId);
103
+ changeCount--;
104
+ console.log(chalk.green(` ↩️ Reverted: "${lastChange.description}"\n`));
105
+
106
+ if (options.preview) {
107
+ console.log(chalk.gray(' 🔄 Preview will refresh automatically.\n'));
108
+ }
109
+ } catch (error) {
110
+ console.log(chalk.red(` ❌ Undo failed: ${error.message}\n`));
111
+ }
112
+ }
113
+ rl.prompt();
114
+ return;
115
+ }
116
+
117
+ // History command
118
+ if (inputLower === 'history') {
119
+ if (changeHistory.length === 0) {
120
+ console.log(chalk.gray(' No changes yet.\n'));
121
+ } else {
122
+ console.log(chalk.cyan('\n 📜 Change History:\n'));
123
+ changeHistory.forEach((change, i) => {
124
+ const time = new Date(change.timestamp).toLocaleTimeString();
125
+ console.log(chalk.gray(` ${i + 1}. [${time}] ${change.description}`));
126
+ });
127
+ console.log('');
128
+ }
129
+ rl.prompt();
130
+ return;
131
+ }
132
+
133
+ // Preview command
134
+ if (inputLower === 'preview') {
135
+ if (!devProcess) {
136
+ console.log(chalk.yellow(' 🚀 Starting preview server...\n'));
137
+ devProcess = await startDevServer(cwd, port);
138
+ await waitForServer(port);
139
+ }
140
+ await openBrowser(`http://localhost:${port}`);
141
+ console.log(chalk.green(` ✅ Preview opened at http://localhost:${port}\n`));
142
+ rl.prompt();
143
+ return;
144
+ }
145
+
146
+ // Status command
147
+ if (inputLower === 'status') {
148
+ console.log(chalk.cyan(`\n 📊 Session Status:`));
149
+ console.log(chalk.gray(` ─────────────────────────────────────`));
150
+ console.log(chalk.white(` Project: ${projectName}`));
151
+ console.log(chalk.white(` Changes: ${changeCount}`));
152
+ console.log(chalk.white(` Preview: ${devProcess ? chalk.green('Running') : chalk.gray('Not running')}`));
153
+ console.log(chalk.white(` Undoable: ${changeHistory.length}`));
154
+ console.log('');
155
+ rl.prompt();
156
+ return;
157
+ }
158
+
159
+ // Files command
160
+ if (inputLower === 'files') {
161
+ await showRecentFiles(cwd);
162
+ rl.prompt();
163
+ return;
164
+ }
165
+
166
+ // Help command
167
+ if (inputLower === 'help' || inputLower === '?') {
168
+ showHelp();
169
+ rl.prompt();
170
+ return;
171
+ }
172
+
173
+ // Clear command
174
+ if (inputLower === 'clear') {
175
+ console.clear();
176
+ console.log(chalk.cyan(` 💬 Feedback Mode - ${changeCount} changes applied\n`));
177
+ rl.prompt();
178
+ return;
179
+ }
180
+
181
+ // Empty or too short input
182
+ if (!input || input.length < 3) {
183
+ rl.prompt();
184
+ return;
185
+ }
186
+
187
+ // Process change request
188
+ console.log(chalk.yellow('\n 🔄 Processing change...\n'));
189
+
190
+ try {
191
+ // Create backup before change
192
+ const backupId = await backup.createBackup(`feedback-${changeCount + 1}`);
193
+
194
+ // Build prompt for Claude
195
+ const prompt = buildChangePrompt(cwd, input);
196
+
197
+ // Build full prompt with context
198
+ const fullPrompt = await buildPromptWithContext(prompt, cwd);
199
+
200
+ // Execute change with Claude
201
+ const result = await spawnClaudeCode(fullPrompt, { cwd });
202
+
203
+ // Record change
204
+ const description = input.substring(0, 50) + (input.length > 50 ? '...' : '');
205
+ changeHistory.push({
206
+ description,
207
+ backupId,
208
+ timestamp: new Date().toISOString(),
209
+ files: result.filesChanged || []
210
+ });
211
+ changeCount++;
212
+
213
+ console.log(chalk.green(`\n ✅ Change #${changeCount} applied: "${description}"`));
214
+
215
+ if (options.preview) {
216
+ console.log(chalk.gray(' 🔄 Preview refreshing...\n'));
217
+ } else {
218
+ console.log('');
219
+ }
220
+
221
+ } catch (error) {
222
+ console.log(chalk.red(`\n ❌ Error: ${error.message}`));
223
+ console.log(chalk.gray(' Try rephrasing your request or be more specific.\n'));
224
+ }
225
+
226
+ rl.prompt();
227
+ });
228
+
229
+ rl.on('close', () => {
230
+ if (devProcess) {
231
+ devProcess.kill();
232
+ }
233
+ process.exit(0);
234
+ });
235
+
236
+ // Handle Ctrl+C
237
+ process.on('SIGINT', async () => {
238
+ await endSession(devProcess, changeCount, rl);
239
+ });
240
+ }
241
+
242
+ /**
243
+ * End feedback session
244
+ */
245
+ async function endSession(devProcess, changeCount, rl) {
246
+ console.log(chalk.cyan(`\n
247
+ ╭────────────────────────────────────────────────────────────────────╮
248
+ │ 👋 FEEDBACK SESSION ENDED │
249
+ │ │
250
+ │ Total changes applied: ${String(changeCount).padEnd(38)}│
251
+ │ │
252
+ │ Your changes have been saved. Use 'vibecode undo' to rollback. │
253
+ │ │
254
+ ╰────────────────────────────────────────────────────────────────────╯
255
+ `));
256
+
257
+ if (devProcess) {
258
+ devProcess.kill();
259
+ }
260
+ rl.close();
261
+ process.exit(0);
262
+ }
263
+
264
+ /**
265
+ * Build change prompt for Claude
266
+ */
267
+ function buildChangePrompt(cwd, request) {
268
+ return `
269
+ # Incremental Change Request
270
+
271
+ ## Project: ${path.basename(cwd)}
272
+
273
+ ## User Request:
274
+ ${request}
275
+
276
+ ## Instructions:
277
+ 1. Make ONLY the requested change - nothing more, nothing less
278
+ 2. Preserve ALL existing functionality and code
279
+ 3. Keep changes minimal and surgically precise
280
+ 4. Update any related files if absolutely necessary
281
+ 5. Do NOT remove, modify, or refactor unrelated code
282
+ 6. Do NOT add comments explaining changes
283
+ 7. Do NOT create backup files
284
+
285
+ ## Critical Rules:
286
+ - This is an INCREMENTAL change, not a rebuild
287
+ - Make the smallest possible change to fulfill the request
288
+ - Maintain exact code style and formatting of existing code
289
+ - If the request is unclear, make a reasonable interpretation
290
+ - Do NOT add extra features or "improvements"
291
+
292
+ ## Apply the change now.
293
+ `;
294
+ }
295
+
296
+ /**
297
+ * Check if directory is a valid project
298
+ */
299
+ async function isValidProject(cwd) {
300
+ try {
301
+ await fs.access(path.join(cwd, 'package.json'));
302
+ return true;
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Start development server
310
+ */
311
+ async function startDevServer(cwd, port = 3000) {
312
+ // Detect project type and appropriate dev command
313
+ let devCmd = 'npm run dev';
314
+
315
+ try {
316
+ const pkgPath = path.join(cwd, 'package.json');
317
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
318
+ const scripts = pkg.scripts || {};
319
+
320
+ if (scripts.dev) {
321
+ devCmd = 'npm run dev';
322
+ } else if (scripts.start) {
323
+ devCmd = 'npm run start';
324
+ }
325
+ } catch {}
326
+
327
+ return new Promise((resolve) => {
328
+ const child = spawn('sh', ['-c', devCmd], {
329
+ cwd,
330
+ stdio: ['ignore', 'pipe', 'pipe'],
331
+ env: { ...process.env, PORT: String(port) },
332
+ detached: false
333
+ });
334
+
335
+ child.stdout.on('data', () => {});
336
+ child.stderr.on('data', () => {});
337
+
338
+ // Give it time to start
339
+ setTimeout(() => resolve(child), 2000);
340
+ });
341
+ }
342
+
343
+ /**
344
+ * Wait for server to be ready
345
+ */
346
+ async function waitForServer(port, maxAttempts = 30) {
347
+ for (let i = 0; i < maxAttempts; i++) {
348
+ const isReady = await checkPort(port);
349
+ if (isReady) return true;
350
+ await sleep(500);
351
+ }
352
+ return false;
353
+ }
354
+
355
+ /**
356
+ * Check if port is in use (server running)
357
+ */
358
+ function checkPort(port) {
359
+ return new Promise((resolve) => {
360
+ const socket = new net.Socket();
361
+ socket.setTimeout(500);
362
+ socket.on('connect', () => { socket.destroy(); resolve(true); });
363
+ socket.on('timeout', () => { socket.destroy(); resolve(false); });
364
+ socket.on('error', () => { socket.destroy(); resolve(false); });
365
+ socket.connect(port, '127.0.0.1');
366
+ });
367
+ }
368
+
369
+ /**
370
+ * Open URL in browser
371
+ */
372
+ async function openBrowser(url) {
373
+ try {
374
+ const open = (await import('open')).default;
375
+ await open(url);
376
+ } catch {
377
+ // Fallback
378
+ const { exec } = await import('child_process');
379
+ const { promisify } = await import('util');
380
+ const execAsync = promisify(exec);
381
+
382
+ const platform = process.platform;
383
+ const commands = {
384
+ darwin: `open "${url}"`,
385
+ win32: `start "" "${url}"`,
386
+ linux: `xdg-open "${url}"`
387
+ };
388
+
389
+ if (commands[platform]) {
390
+ try {
391
+ await execAsync(commands[platform]);
392
+ } catch {}
393
+ }
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Show recently changed files
399
+ */
400
+ async function showRecentFiles(cwd) {
401
+ console.log(chalk.cyan('\n 📁 Recent Files:\n'));
402
+
403
+ try {
404
+ const { exec } = await import('child_process');
405
+ const { promisify } = await import('util');
406
+ const execAsync = promisify(exec);
407
+
408
+ // Get recently modified files (last 5 minutes)
409
+ const { stdout } = await execAsync(
410
+ `find . -type f -mmin -5 -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/.next/*" -not -path "*/dist/*" 2>/dev/null | head -20`,
411
+ { cwd }
412
+ );
413
+
414
+ const files = stdout.trim().split('\n').filter(f => f);
415
+
416
+ if (files.length === 0) {
417
+ console.log(chalk.gray(' No recently modified files.\n'));
418
+ } else {
419
+ files.forEach(file => {
420
+ console.log(chalk.gray(` ${file}`));
421
+ });
422
+ console.log('');
423
+ }
424
+ } catch {
425
+ console.log(chalk.gray(' Could not list files.\n'));
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Show help
431
+ */
432
+ function showHelp() {
433
+ console.log(chalk.cyan(`
434
+ 📖 Feedback Mode Commands:
435
+ ─────────────────────────────────────────────────────────────────
436
+
437
+ ${chalk.white('Natural language')} Describe the change you want
438
+ ${chalk.green('undo')} Revert the last change
439
+ ${chalk.green('history')} Show all changes made this session
440
+ ${chalk.green('preview')} Open preview in browser
441
+ ${chalk.green('status')} Show session status
442
+ ${chalk.green('files')} Show recently modified files
443
+ ${chalk.green('clear')} Clear the screen
444
+ ${chalk.green('done')} / ${chalk.green('exit')} End the session
445
+
446
+ ${chalk.gray('Examples:')}
447
+ ${chalk.gray('> Change the header color to blue')}
448
+ ${chalk.gray('> Add a contact form section')}
449
+ ${chalk.gray('> Remove the pricing table')}
450
+ ${chalk.gray('> Make the logo bigger')}
451
+ `));
452
+ }
453
+
454
+ /**
455
+ * Sleep helper
456
+ */
457
+ function sleep(ms) {
458
+ return new Promise(resolve => setTimeout(resolve, ms));
459
+ }
460
+
461
+ /**
462
+ * Start feedback mode for use in go.js
463
+ */
464
+ export async function startFeedbackMode(projectPath, options = {}) {
465
+ const originalCwd = process.cwd();
466
+
467
+ try {
468
+ process.chdir(projectPath);
469
+ await feedbackCommand(options);
470
+ } finally {
471
+ process.chdir(originalCwd);
472
+ }
473
+ }
@@ -42,6 +42,11 @@ 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
44
  import { getTemplate, getCategoryIcon } from '../templates/index.js';
45
+ import { autoGenerateImages } from './images.js';
46
+ import { autoDeploy } from './deploy.js';
47
+ import { startFeedbackMode } from './feedback.js';
48
+ import { notifyBuildComplete, notifyDeployComplete } from '../utils/notifications.js';
49
+ import { addToHistory } from '../utils/history.js';
45
50
 
46
51
  const execAsync = promisify(exec);
47
52
 
@@ -255,10 +260,39 @@ export async function goCommand(description, options = {}) {
255
260
  console.log(renderProgressBar(100));
256
261
  console.log();
257
262
 
263
+ // Generate images if requested
264
+ if (options.withImages) {
265
+ console.log(chalk.cyan('\n 📸 Generating project images...\n'));
266
+ try {
267
+ const imageResults = await autoGenerateImages(projectPath, {
268
+ template: null,
269
+ theme: 'tech'
270
+ });
271
+ results.imagesGenerated = imageResults.downloaded.length;
272
+ console.log(chalk.green(` ✅ ${results.imagesGenerated} images generated\n`));
273
+ } catch (error) {
274
+ console.log(chalk.yellow(` ⚠️ Image generation failed: ${error.message}\n`));
275
+ }
276
+ }
277
+
258
278
  // Show summary
259
279
  const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
260
280
  showMagicSummary(projectName, projectPath, duration, results, options);
261
281
 
282
+ // Add to history
283
+ await addToHistory(`vibecode go "${desc}"`, desc, {
284
+ projectName,
285
+ projectPath,
286
+ duration,
287
+ filesCreated: results.filesCreated,
288
+ testsPassed: results.allPassed
289
+ });
290
+
291
+ // Send notification if enabled
292
+ if (options.notify) {
293
+ notifyBuildComplete(results.allPassed, projectName);
294
+ }
295
+
262
296
  // Auto preview if requested
263
297
  if (options.preview) {
264
298
  console.log(chalk.cyan('\n 🚀 Starting preview...\n'));
@@ -281,6 +315,38 @@ export async function goCommand(description, options = {}) {
281
315
  console.log(chalk.gray(` 💡 Quick preview: ${chalk.cyan(`cd ${projectName} && vibecode preview`)}\n`));
282
316
  }
283
317
 
318
+ // Auto deploy if requested
319
+ if (options.deploy) {
320
+ console.log(chalk.cyan('\n 🚀 Deploying to cloud...\n'));
321
+ try {
322
+ const deployResult = await autoDeploy(projectPath, {
323
+ platform: options.deployPlatform || 'vercel',
324
+ preview: false
325
+ });
326
+ if (deployResult?.url) {
327
+ results.deployUrl = deployResult.url;
328
+ if (options.notify) {
329
+ notifyDeployComplete(true, options.deployPlatform || 'Vercel', deployResult.url);
330
+ }
331
+ }
332
+ } catch (error) {
333
+ console.log(chalk.yellow(` ⚠️ Deploy failed: ${error.message}`));
334
+ console.log(chalk.gray(` Run manually: cd ${projectName} && vibecode deploy\n`));
335
+ if (options.notify) {
336
+ notifyDeployComplete(false, options.deployPlatform || 'Vercel');
337
+ }
338
+ }
339
+ }
340
+
341
+ // Enter feedback mode if requested
342
+ if (options.feedback) {
343
+ console.log(chalk.cyan('\n 💬 Entering feedback mode...\n'));
344
+ await startFeedbackMode(projectPath, {
345
+ preview: options.preview || true,
346
+ port: options.port || '3000'
347
+ });
348
+ }
349
+
284
350
  } catch (error) {
285
351
  console.log();
286
352
  printError(`Magic mode failed: ${error.message}`);
@@ -585,10 +651,40 @@ ${prompt}
585
651
  console.log(renderProgressBar(100));
586
652
  console.log();
587
653
 
654
+ // Generate images if requested
655
+ if (options.withImages) {
656
+ console.log(chalk.cyan('\n 📸 Generating project images...\n'));
657
+ try {
658
+ const imageResults = await autoGenerateImages(projectPath, {
659
+ template: templateId,
660
+ theme: 'tech'
661
+ });
662
+ results.imagesGenerated = imageResults.downloaded.length;
663
+ console.log(chalk.green(` ✅ ${results.imagesGenerated} images generated\n`));
664
+ } catch (error) {
665
+ console.log(chalk.yellow(` ⚠️ Image generation failed: ${error.message}\n`));
666
+ }
667
+ }
668
+
588
669
  // Show summary
589
670
  const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
590
671
  showTemplateSummary(template, projectName, projectPath, duration, results, options);
591
672
 
673
+ // Add to history
674
+ await addToHistory(`vibecode go --template ${templateId}`, template.name, {
675
+ template: templateId,
676
+ projectName,
677
+ projectPath,
678
+ duration,
679
+ filesCreated: results.filesCreated,
680
+ testsPassed: results.allPassed
681
+ });
682
+
683
+ // Send notification if enabled
684
+ if (options.notify) {
685
+ notifyBuildComplete(results.allPassed, projectName);
686
+ }
687
+
592
688
  // Auto preview if requested
593
689
  if (options.preview) {
594
690
  console.log(chalk.cyan('\n 🚀 Starting preview...\n'));
@@ -611,6 +707,38 @@ ${prompt}
611
707
  console.log(chalk.gray(` 💡 Quick preview: ${chalk.cyan(`cd ${projectName} && vibecode preview`)}\n`));
612
708
  }
613
709
 
710
+ // Auto deploy if requested
711
+ if (options.deploy) {
712
+ console.log(chalk.cyan('\n 🚀 Deploying to cloud...\n'));
713
+ try {
714
+ const deployResult = await autoDeploy(projectPath, {
715
+ platform: options.deployPlatform || 'vercel',
716
+ preview: false
717
+ });
718
+ if (deployResult?.url) {
719
+ results.deployUrl = deployResult.url;
720
+ if (options.notify) {
721
+ notifyDeployComplete(true, options.deployPlatform || 'Vercel', deployResult.url);
722
+ }
723
+ }
724
+ } catch (error) {
725
+ console.log(chalk.yellow(` ⚠️ Deploy failed: ${error.message}`));
726
+ console.log(chalk.gray(` Run manually: cd ${projectName} && vibecode deploy\n`));
727
+ if (options.notify) {
728
+ notifyDeployComplete(false, options.deployPlatform || 'Vercel');
729
+ }
730
+ }
731
+ }
732
+
733
+ // Enter feedback mode if requested
734
+ if (options.feedback) {
735
+ console.log(chalk.cyan('\n 💬 Entering feedback mode...\n'));
736
+ await startFeedbackMode(projectPath, {
737
+ preview: options.preview || true,
738
+ port: options.port || '3000'
739
+ });
740
+ }
741
+
614
742
  } catch (error) {
615
743
  console.log();
616
744
  printError(`Template build failed: ${error.message}`);