@nclamvn/vibecode-cli 1.6.0 → 1.7.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 (63) hide show
  1. package/bin/vibecode.js +48 -1
  2. package/docs-site/README.md +41 -0
  3. package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
  4. package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
  5. package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  6. package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  7. package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
  8. package/docs-site/blog/authors.yml +25 -0
  9. package/docs-site/blog/tags.yml +19 -0
  10. package/docs-site/docs/commands/agent.md +162 -0
  11. package/docs-site/docs/commands/assist.md +71 -0
  12. package/docs-site/docs/commands/build.md +53 -0
  13. package/docs-site/docs/commands/config.md +30 -0
  14. package/docs-site/docs/commands/debug.md +173 -0
  15. package/docs-site/docs/commands/doctor.md +34 -0
  16. package/docs-site/docs/commands/go.md +128 -0
  17. package/docs-site/docs/commands/index.md +79 -0
  18. package/docs-site/docs/commands/init.md +42 -0
  19. package/docs-site/docs/commands/learn.md +82 -0
  20. package/docs-site/docs/commands/lock.md +33 -0
  21. package/docs-site/docs/commands/plan.md +29 -0
  22. package/docs-site/docs/commands/review.md +31 -0
  23. package/docs-site/docs/commands/snapshot.md +34 -0
  24. package/docs-site/docs/commands/start.md +32 -0
  25. package/docs-site/docs/commands/status.md +37 -0
  26. package/docs-site/docs/commands/undo.md +83 -0
  27. package/docs-site/docs/configuration.md +72 -0
  28. package/docs-site/docs/faq.md +83 -0
  29. package/docs-site/docs/getting-started.md +119 -0
  30. package/docs-site/docs/guides/agent-mode.md +94 -0
  31. package/docs-site/docs/guides/debug-mode.md +83 -0
  32. package/docs-site/docs/guides/magic-mode.md +107 -0
  33. package/docs-site/docs/installation.md +98 -0
  34. package/docs-site/docs/intro.md +67 -0
  35. package/docs-site/docusaurus.config.ts +141 -0
  36. package/docs-site/package-lock.json +18039 -0
  37. package/docs-site/package.json +48 -0
  38. package/docs-site/sidebars.ts +70 -0
  39. package/docs-site/src/components/HomepageFeatures/index.tsx +72 -0
  40. package/docs-site/src/components/HomepageFeatures/styles.module.css +16 -0
  41. package/docs-site/src/css/custom.css +30 -0
  42. package/docs-site/src/pages/index.module.css +23 -0
  43. package/docs-site/src/pages/index.tsx +44 -0
  44. package/docs-site/src/pages/markdown-page.md +7 -0
  45. package/docs-site/src/theme/Footer/index.tsx +127 -0
  46. package/docs-site/src/theme/Footer/styles.module.css +285 -0
  47. package/docs-site/static/.nojekyll +0 -0
  48. package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
  49. package/docs-site/static/img/docusaurus.png +0 -0
  50. package/docs-site/static/img/favicon.ico +0 -0
  51. package/docs-site/static/img/logo.svg +1 -0
  52. package/docs-site/static/img/undraw_docusaurus_mountain.svg +171 -0
  53. package/docs-site/static/img/undraw_docusaurus_react.svg +170 -0
  54. package/docs-site/static/img/undraw_docusaurus_tree.svg +40 -0
  55. package/docs-site/tsconfig.json +8 -0
  56. package/package.json +2 -1
  57. package/src/commands/debug.js +109 -1
  58. package/src/commands/git.js +923 -0
  59. package/src/commands/shell.js +486 -0
  60. package/src/commands/watch.js +556 -0
  61. package/src/debug/image-analyzer.js +304 -0
  62. package/src/index.js +19 -0
  63. package/src/utils/image.js +222 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Image Analyzer for Vibecode CLI
3
+ * Analyze screenshots of errors using AI vision
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import { spawn } from 'child_process';
10
+ import { imageToBase64, saveClipboardImage, formatFileSize } from '../utils/image.js';
11
+
12
+ /**
13
+ * ImageAnalyzer class for screenshot error analysis
14
+ */
15
+ export class ImageAnalyzer {
16
+ constructor(projectPath = process.cwd()) {
17
+ this.projectPath = projectPath;
18
+ }
19
+
20
+ /**
21
+ * Analyze image file for errors
22
+ */
23
+ async analyzeImage(imagePath) {
24
+ console.log(chalk.cyan('\n Analyzing screenshot...\n'));
25
+
26
+ try {
27
+ // Read and validate image
28
+ const imageInfo = await imageToBase64(imagePath);
29
+
30
+ console.log(chalk.gray(` File: ${path.basename(imageInfo.path)}`));
31
+ console.log(chalk.gray(` Size: ${formatFileSize(imageInfo.size)}\n`));
32
+
33
+ // Run AI analysis
34
+ const analysis = await this.runImageAnalysis(imageInfo);
35
+
36
+ return analysis;
37
+ } catch (error) {
38
+ throw new Error(`Failed to analyze image: ${error.message}`);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Analyze image from clipboard
44
+ */
45
+ async analyzeClipboard() {
46
+ console.log(chalk.cyan('\n Getting image from clipboard...\n'));
47
+
48
+ try {
49
+ const tempFile = await saveClipboardImage();
50
+ console.log(chalk.green(` Image captured\n`));
51
+
52
+ const result = await this.analyzeImage(tempFile);
53
+
54
+ // Cleanup temp file
55
+ try {
56
+ await fs.unlink(tempFile);
57
+ } catch {
58
+ // Ignore cleanup errors
59
+ }
60
+
61
+ return result;
62
+ } catch (error) {
63
+ throw new Error(`Clipboard error: ${error.message}`);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Run AI analysis on image
69
+ */
70
+ async runImageAnalysis(imageInfo) {
71
+ // Create analysis prompt
72
+ const prompt = this.buildAnalysisPrompt(imageInfo);
73
+
74
+ // Save prompt to temp file
75
+ const tempDir = path.join(this.projectPath, '.vibecode', 'debug');
76
+ await fs.mkdir(tempDir, { recursive: true });
77
+
78
+ const promptFile = path.join(tempDir, 'image-analysis.md');
79
+ await fs.writeFile(promptFile, prompt);
80
+
81
+ // Also save image reference
82
+ const imageRef = path.join(tempDir, 'screenshot-ref.txt');
83
+ await fs.writeFile(imageRef, imageInfo.path);
84
+
85
+ // Run Claude Code with image
86
+ const result = await this.runClaudeWithImage(imageInfo, promptFile);
87
+
88
+ return this.parseAnalysisResult(result);
89
+ }
90
+
91
+ /**
92
+ * Build analysis prompt
93
+ */
94
+ buildAnalysisPrompt(imageInfo) {
95
+ return `# Screenshot Error Analysis
96
+
97
+ Analyze this screenshot and extract any error information.
98
+
99
+ ## Instructions
100
+ 1. Look for error messages, stack traces, console errors, or warnings
101
+ 2. Identify the error type (TypeError, SyntaxError, Build Error, etc.)
102
+ 3. Extract file names and line numbers if visible
103
+ 4. Note any relevant context visible in the screenshot
104
+ 5. Provide specific fix suggestions
105
+
106
+ ## Response Format
107
+ Respond in this exact format:
108
+
109
+ **Error Type**: [type of error or "None detected"]
110
+ **Error Message**: [main error text]
111
+ **Location**: [file:line if visible]
112
+ **Root Cause**: [explanation of what's wrong]
113
+ **Suggested Fix**: [how to fix it]
114
+ **Confidence**: [High/Medium/Low]
115
+
116
+ If no error is visible, respond with:
117
+ **Error Type**: None detected
118
+ **Error Message**: No error visible in screenshot
119
+ `;
120
+ }
121
+
122
+ /**
123
+ * Run Claude Code with image input
124
+ */
125
+ async runClaudeWithImage(imageInfo, promptFile) {
126
+ return new Promise((resolve) => {
127
+ let output = '';
128
+ let errorOutput = '';
129
+
130
+ // Use claude with image path
131
+ // Claude Code can read images directly
132
+ const args = [
133
+ '--print',
134
+ '-p', `Analyze this image for errors: ${imageInfo.path}
135
+
136
+ Look for:
137
+ - Error messages
138
+ - Stack traces
139
+ - Console errors
140
+ - Type errors
141
+ - Build failures
142
+
143
+ Respond with:
144
+ **Error Type**:
145
+ **Error Message**:
146
+ **Location**:
147
+ **Root Cause**:
148
+ **Suggested Fix**:
149
+ **Confidence**:
150
+
151
+ If no error found, say "No error detected"`
152
+ ];
153
+
154
+ const child = spawn('claude', args, {
155
+ cwd: this.projectPath,
156
+ stdio: ['pipe', 'pipe', 'pipe']
157
+ });
158
+
159
+ child.stdout.on('data', (data) => {
160
+ output += data.toString();
161
+ });
162
+
163
+ child.stderr.on('data', (data) => {
164
+ errorOutput += data.toString();
165
+ });
166
+
167
+ child.on('close', (code) => {
168
+ resolve({
169
+ success: code === 0,
170
+ output: output.trim(),
171
+ error: errorOutput.trim()
172
+ });
173
+ });
174
+
175
+ child.on('error', (error) => {
176
+ resolve({
177
+ success: false,
178
+ output: '',
179
+ error: error.message
180
+ });
181
+ });
182
+
183
+ // Timeout after 60 seconds
184
+ setTimeout(() => {
185
+ child.kill();
186
+ resolve({
187
+ success: false,
188
+ output: '',
189
+ error: 'Analysis timed out'
190
+ });
191
+ }, 60000);
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Parse AI analysis result
197
+ */
198
+ parseAnalysisResult(result) {
199
+ const analysis = {
200
+ errorType: null,
201
+ errorMessage: null,
202
+ location: null,
203
+ rootCause: null,
204
+ suggestedFix: null,
205
+ confidence: 'Low',
206
+ raw: result?.output || '',
207
+ success: result?.success || false
208
+ };
209
+
210
+ const output = result?.output || '';
211
+
212
+ // Extract fields using regex
213
+ const patterns = {
214
+ errorType: /\*\*Error Type\*\*:\s*(.+?)(?:\n|$)/i,
215
+ errorMessage: /\*\*Error Message\*\*:\s*(.+?)(?:\n|$)/i,
216
+ location: /\*\*Location\*\*:\s*(.+?)(?:\n|$)/i,
217
+ rootCause: /\*\*Root Cause\*\*:\s*(.+?)(?:\n|$)/i,
218
+ suggestedFix: /\*\*Suggested Fix\*\*:\s*(.+?)(?:\n|$)/i,
219
+ confidence: /\*\*Confidence\*\*:\s*(.+?)(?:\n|$)/i
220
+ };
221
+
222
+ for (const [key, pattern] of Object.entries(patterns)) {
223
+ const match = output.match(pattern);
224
+ if (match && match[1]) {
225
+ const value = match[1].trim();
226
+ if (value && value.toLowerCase() !== 'none' && value.toLowerCase() !== 'n/a') {
227
+ analysis[key] = value;
228
+ }
229
+ }
230
+ }
231
+
232
+ // Check if error was actually detected
233
+ if (analysis.errorType?.toLowerCase().includes('none detected') ||
234
+ analysis.errorMessage?.toLowerCase().includes('no error')) {
235
+ analysis.errorType = null;
236
+ analysis.errorMessage = null;
237
+ }
238
+
239
+ return analysis;
240
+ }
241
+
242
+ /**
243
+ * Format analysis for display
244
+ */
245
+ formatAnalysis(analysis) {
246
+ // No error case
247
+ if (!analysis.errorType && !analysis.errorMessage) {
248
+ return chalk.yellow('\n No error detected in screenshot.\n');
249
+ }
250
+
251
+ let output = chalk.cyan(`
252
+ +----------------------------------------------------------------------+
253
+ | SCREENSHOT ANALYSIS |
254
+ +----------------------------------------------------------------------+
255
+ `);
256
+
257
+ if (analysis.errorType) {
258
+ output += `\n ${chalk.white('Error Type:')} ${chalk.red(analysis.errorType)}`;
259
+ }
260
+
261
+ if (analysis.errorMessage) {
262
+ output += `\n ${chalk.white('Message:')} ${chalk.yellow(analysis.errorMessage)}`;
263
+ }
264
+
265
+ if (analysis.location) {
266
+ output += `\n ${chalk.white('Location:')} ${chalk.gray(analysis.location)}`;
267
+ }
268
+
269
+ if (analysis.confidence) {
270
+ const confColor = analysis.confidence === 'High' ? chalk.green :
271
+ analysis.confidence === 'Medium' ? chalk.yellow : chalk.gray;
272
+ output += `\n ${chalk.white('Confidence:')} ${confColor(analysis.confidence)}`;
273
+ }
274
+
275
+ if (analysis.rootCause) {
276
+ output += `\n\n ${chalk.cyan('Root Cause:')}`;
277
+ output += `\n ${chalk.gray(analysis.rootCause)}`;
278
+ }
279
+
280
+ if (analysis.suggestedFix) {
281
+ output += `\n\n ${chalk.green('Suggested Fix:')}`;
282
+ output += `\n ${chalk.white(analysis.suggestedFix)}`;
283
+ }
284
+
285
+ output += '\n';
286
+
287
+ return output;
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Helper function for direct use
293
+ */
294
+ export async function analyzeScreenshot(imagePathOrClipboard, options = {}) {
295
+ const analyzer = new ImageAnalyzer(options.projectPath || process.cwd());
296
+
297
+ if (imagePathOrClipboard === 'clipboard') {
298
+ return await analyzer.analyzeClipboard();
299
+ } else {
300
+ return await analyzer.analyzeImage(imagePathOrClipboard);
301
+ }
302
+ }
303
+
304
+ export default ImageAnalyzer;
package/src/index.js CHANGED
@@ -41,6 +41,15 @@ export { learnCommand } from './commands/learn.js';
41
41
  export { askFeedback, showLearningSuggestion } from './commands/learn.js';
42
42
  export { LearningEngine, getLearningEngine, createLearningEngine } from './core/learning.js';
43
43
 
44
+ // Phase I Commands - Git Integration
45
+ export { gitCommand, autoCommit } from './commands/git.js';
46
+
47
+ // Phase I2 Commands - File Watcher
48
+ export { watchCommand } from './commands/watch.js';
49
+
50
+ // Phase I3 Commands - Shell Mode
51
+ export { shellCommand } from './commands/shell.js';
52
+
44
53
  // UI exports (Phase H2: Dashboard)
45
54
  export {
46
55
  ProgressDashboard,
@@ -70,6 +79,16 @@ export {
70
79
  FixVerifier
71
80
  } from './debug/index.js';
72
81
 
82
+ // Image Analysis exports (Phase I4)
83
+ export { ImageAnalyzer, analyzeScreenshot } from './debug/image-analyzer.js';
84
+ export {
85
+ saveClipboardImage,
86
+ imageToBase64,
87
+ getImageInfo,
88
+ isValidImage,
89
+ cleanupTempImages
90
+ } from './utils/image.js';
91
+
73
92
  // Agent exports
74
93
  export {
75
94
  VibecodeAgent,
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Image utilities for Vibecode CLI
3
+ * Handle clipboard images, file reading, and base64 conversion
4
+ */
5
+
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import { exec } from 'child_process';
9
+ import { promisify } from 'util';
10
+ import os from 'os';
11
+
12
+ const execAsync = promisify(exec);
13
+
14
+ /**
15
+ * Save clipboard image to temp file
16
+ * Supports macOS, Linux, and Windows
17
+ */
18
+ export async function saveClipboardImage() {
19
+ const platform = process.platform;
20
+ const tempDir = os.tmpdir();
21
+ const tempFile = path.join(tempDir, `vibecode-screenshot-${Date.now()}.png`);
22
+
23
+ if (platform === 'darwin') {
24
+ // macOS: Use pngpaste or osascript
25
+ try {
26
+ // Try pngpaste first (if installed via brew install pngpaste)
27
+ await execAsync(`pngpaste "${tempFile}"`);
28
+
29
+ // Verify file was created
30
+ const stats = await fs.stat(tempFile);
31
+ if (stats.size > 0) {
32
+ return tempFile;
33
+ }
34
+ throw new Error('Empty file');
35
+ } catch {
36
+ // Fallback to osascript
37
+ try {
38
+ const script = `
39
+ set theFile to POSIX file "${tempFile}"
40
+ try
41
+ set imageData to the clipboard as «class PNGf»
42
+ set fileRef to open for access theFile with write permission
43
+ write imageData to fileRef
44
+ close access fileRef
45
+ return "success"
46
+ on error errMsg
47
+ return "error: " & errMsg
48
+ end try
49
+ `;
50
+
51
+ const { stdout } = await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}'`);
52
+
53
+ if (stdout.trim().startsWith('success')) {
54
+ // Verify file was created
55
+ const stats = await fs.stat(tempFile);
56
+ if (stats.size > 0) {
57
+ return tempFile;
58
+ }
59
+ }
60
+
61
+ throw new Error('No image in clipboard');
62
+ } catch (e) {
63
+ throw new Error('No image in clipboard. Copy a screenshot first (Cmd+Shift+4).');
64
+ }
65
+ }
66
+ } else if (platform === 'linux') {
67
+ // Linux: Use xclip
68
+ try {
69
+ await execAsync(`xclip -selection clipboard -t image/png -o > "${tempFile}" 2>/dev/null`);
70
+
71
+ const stats = await fs.stat(tempFile);
72
+ if (stats.size > 0) {
73
+ return tempFile;
74
+ }
75
+ throw new Error('No image in clipboard');
76
+ } catch {
77
+ throw new Error('No image in clipboard. Make sure xclip is installed: sudo apt install xclip');
78
+ }
79
+ } else if (platform === 'win32') {
80
+ // Windows: Use PowerShell
81
+ try {
82
+ const psScript = `
83
+ Add-Type -AssemblyName System.Windows.Forms
84
+ $img = [System.Windows.Forms.Clipboard]::GetImage()
85
+ if ($img -ne $null) {
86
+ $img.Save('${tempFile.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)
87
+ Write-Output 'success'
88
+ } else {
89
+ Write-Output 'no image'
90
+ }
91
+ `.replace(/\n/g, ' ');
92
+
93
+ const { stdout } = await execAsync(`powershell -Command "${psScript}"`);
94
+
95
+ if (stdout.trim() === 'success') {
96
+ return tempFile;
97
+ }
98
+ throw new Error('No image in clipboard');
99
+ } catch {
100
+ throw new Error('No image in clipboard');
101
+ }
102
+ }
103
+
104
+ throw new Error(`Unsupported platform: ${platform}`);
105
+ }
106
+
107
+ /**
108
+ * Read image file and convert to base64
109
+ */
110
+ export async function imageToBase64(imagePath) {
111
+ const absolutePath = path.resolve(imagePath);
112
+
113
+ // Check file exists
114
+ try {
115
+ await fs.access(absolutePath);
116
+ } catch {
117
+ throw new Error(`Image file not found: ${imagePath}`);
118
+ }
119
+
120
+ // Read file
121
+ const imageBuffer = await fs.readFile(absolutePath);
122
+ const base64 = imageBuffer.toString('base64');
123
+
124
+ // Detect mime type from extension
125
+ const ext = path.extname(absolutePath).toLowerCase();
126
+ const mimeTypes = {
127
+ '.png': 'image/png',
128
+ '.jpg': 'image/jpeg',
129
+ '.jpeg': 'image/jpeg',
130
+ '.gif': 'image/gif',
131
+ '.webp': 'image/webp',
132
+ '.bmp': 'image/bmp'
133
+ };
134
+ const mimeType = mimeTypes[ext] || 'image/png';
135
+
136
+ return {
137
+ base64,
138
+ mimeType,
139
+ dataUrl: `data:${mimeType};base64,${base64}`,
140
+ path: absolutePath,
141
+ size: imageBuffer.length
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Get image information
147
+ */
148
+ export async function getImageInfo(imagePath) {
149
+ const info = await imageToBase64(imagePath);
150
+ const stats = await fs.stat(imagePath);
151
+
152
+ return {
153
+ ...info,
154
+ fileSize: stats.size,
155
+ fileName: path.basename(imagePath),
156
+ extension: path.extname(imagePath).toLowerCase()
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Check if file is a valid image
162
+ */
163
+ export async function isValidImage(imagePath) {
164
+ const validExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'];
165
+ const ext = path.extname(imagePath).toLowerCase();
166
+
167
+ if (!validExtensions.includes(ext)) {
168
+ return false;
169
+ }
170
+
171
+ try {
172
+ await fs.access(imagePath);
173
+ const stats = await fs.stat(imagePath);
174
+ return stats.size > 0;
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Cleanup temporary vibecode images
182
+ */
183
+ export async function cleanupTempImages() {
184
+ const tempDir = os.tmpdir();
185
+
186
+ try {
187
+ const files = await fs.readdir(tempDir);
188
+ const vibecodeImages = files.filter(f => f.startsWith('vibecode-screenshot-'));
189
+
190
+ let cleaned = 0;
191
+ for (const file of vibecodeImages) {
192
+ try {
193
+ await fs.unlink(path.join(tempDir, file));
194
+ cleaned++;
195
+ } catch {
196
+ // Ignore errors
197
+ }
198
+ }
199
+
200
+ return cleaned;
201
+ } catch {
202
+ return 0;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Format file size for display
208
+ */
209
+ export function formatFileSize(bytes) {
210
+ if (bytes < 1024) return `${bytes} B`;
211
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
212
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
213
+ }
214
+
215
+ export default {
216
+ saveClipboardImage,
217
+ imageToBase64,
218
+ getImageInfo,
219
+ isValidImage,
220
+ cleanupTempImages,
221
+ formatFileSize
222
+ };