@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.
- package/bin/vibecode.js +48 -1
- package/docs-site/README.md +41 -0
- package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
- package/docs-site/blog/authors.yml +25 -0
- package/docs-site/blog/tags.yml +19 -0
- package/docs-site/docs/commands/agent.md +162 -0
- package/docs-site/docs/commands/assist.md +71 -0
- package/docs-site/docs/commands/build.md +53 -0
- package/docs-site/docs/commands/config.md +30 -0
- package/docs-site/docs/commands/debug.md +173 -0
- package/docs-site/docs/commands/doctor.md +34 -0
- package/docs-site/docs/commands/go.md +128 -0
- package/docs-site/docs/commands/index.md +79 -0
- package/docs-site/docs/commands/init.md +42 -0
- package/docs-site/docs/commands/learn.md +82 -0
- package/docs-site/docs/commands/lock.md +33 -0
- package/docs-site/docs/commands/plan.md +29 -0
- package/docs-site/docs/commands/review.md +31 -0
- package/docs-site/docs/commands/snapshot.md +34 -0
- package/docs-site/docs/commands/start.md +32 -0
- package/docs-site/docs/commands/status.md +37 -0
- package/docs-site/docs/commands/undo.md +83 -0
- package/docs-site/docs/configuration.md +72 -0
- package/docs-site/docs/faq.md +83 -0
- package/docs-site/docs/getting-started.md +119 -0
- package/docs-site/docs/guides/agent-mode.md +94 -0
- package/docs-site/docs/guides/debug-mode.md +83 -0
- package/docs-site/docs/guides/magic-mode.md +107 -0
- package/docs-site/docs/installation.md +98 -0
- package/docs-site/docs/intro.md +67 -0
- package/docs-site/docusaurus.config.ts +141 -0
- package/docs-site/package-lock.json +18039 -0
- package/docs-site/package.json +48 -0
- package/docs-site/sidebars.ts +70 -0
- package/docs-site/src/components/HomepageFeatures/index.tsx +72 -0
- package/docs-site/src/components/HomepageFeatures/styles.module.css +16 -0
- package/docs-site/src/css/custom.css +30 -0
- package/docs-site/src/pages/index.module.css +23 -0
- package/docs-site/src/pages/index.tsx +44 -0
- package/docs-site/src/pages/markdown-page.md +7 -0
- package/docs-site/src/theme/Footer/index.tsx +127 -0
- package/docs-site/src/theme/Footer/styles.module.css +285 -0
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
- package/docs-site/static/img/docusaurus.png +0 -0
- package/docs-site/static/img/favicon.ico +0 -0
- package/docs-site/static/img/logo.svg +1 -0
- package/docs-site/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs-site/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs-site/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs-site/tsconfig.json +8 -0
- package/package.json +2 -1
- package/src/commands/debug.js +109 -1
- package/src/commands/git.js +923 -0
- package/src/commands/shell.js +486 -0
- package/src/commands/watch.js +556 -0
- package/src/debug/image-analyzer.js +304 -0
- package/src/index.js +19 -0
- 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
|
+
};
|