@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.
- package/bin/vibecode.js +86 -0
- package/package.json +1 -1
- package/src/commands/config.js +42 -4
- package/src/commands/deploy.js +728 -0
- package/src/commands/favorite.js +412 -0
- package/src/commands/feedback.js +473 -0
- package/src/commands/go.js +128 -0
- package/src/commands/history.js +249 -0
- package/src/commands/images.js +465 -0
- package/src/commands/voice.js +580 -0
- package/src/commands/watch.js +3 -20
- package/src/index.js +46 -1
- package/src/services/image-service.js +513 -0
- package/src/utils/history.js +357 -0
- package/src/utils/notifications.js +343 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Images Command
|
|
3
|
+
// AI-powered image generation and management
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import {
|
|
10
|
+
ImageService,
|
|
11
|
+
createImageService,
|
|
12
|
+
searchImages,
|
|
13
|
+
generateImages,
|
|
14
|
+
getCuratedCollection,
|
|
15
|
+
CURATED_COLLECTIONS
|
|
16
|
+
} from '../services/image-service.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Images command entry point
|
|
20
|
+
*/
|
|
21
|
+
export async function imagesCommand(query, options = {}) {
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
|
|
24
|
+
// List generated images
|
|
25
|
+
if (options.list) {
|
|
26
|
+
return listImages(cwd);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Search mode
|
|
30
|
+
if (options.search || query) {
|
|
31
|
+
return searchAndDisplay(query || options.search, options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Replace placeholders
|
|
35
|
+
if (options.replace) {
|
|
36
|
+
return replacePlaceholders(cwd, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Generate hero image
|
|
40
|
+
if (options.hero) {
|
|
41
|
+
return generateHeroImage(cwd, options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Generate product images
|
|
45
|
+
if (options.products) {
|
|
46
|
+
return generateProductImages(cwd, parseInt(options.products) || 6, options);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Generate full image set
|
|
50
|
+
if (options.generate) {
|
|
51
|
+
return generateFullSet(cwd, options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Default: interactive mode
|
|
55
|
+
return interactiveMode(cwd, options);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Search and display images
|
|
60
|
+
*/
|
|
61
|
+
async function searchAndDisplay(query, options = {}) {
|
|
62
|
+
console.log(chalk.cyan(`\n 🔍 Searching for "${query}"...\n`));
|
|
63
|
+
|
|
64
|
+
const service = createImageService({ verbose: true });
|
|
65
|
+
const images = await service.searchImages(query, {
|
|
66
|
+
count: parseInt(options.count) || 8,
|
|
67
|
+
orientation: options.orientation || 'landscape'
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (images.length === 0) {
|
|
71
|
+
console.log(chalk.yellow(' No images found.\n'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(chalk.green(` Found ${images.length} images:\n`));
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < images.length; i++) {
|
|
78
|
+
const img = images[i];
|
|
79
|
+
console.log(chalk.white(` ${i + 1}. ${img.alt || 'Image'}`));
|
|
80
|
+
console.log(chalk.gray(` ${img.url}`));
|
|
81
|
+
console.log(chalk.gray(` by ${img.photographer}\n`));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Offer to download
|
|
85
|
+
const { download } = await inquirer.prompt([{
|
|
86
|
+
type: 'confirm',
|
|
87
|
+
name: 'download',
|
|
88
|
+
message: 'Download these images?',
|
|
89
|
+
default: false
|
|
90
|
+
}]);
|
|
91
|
+
|
|
92
|
+
if (download) {
|
|
93
|
+
const downloadPath = path.join(process.cwd(), 'public', 'images');
|
|
94
|
+
console.log(chalk.cyan(`\n Downloading to ${downloadPath}...\n`));
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < images.length; i++) {
|
|
97
|
+
try {
|
|
98
|
+
const filename = `${query.replace(/\s+/g, '-')}-${i + 1}.jpg`;
|
|
99
|
+
await service.downloadImage(images[i].url, filename, { directory: downloadPath });
|
|
100
|
+
console.log(chalk.green(` ✓ ${filename}`));
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(chalk.red(` ✗ Failed: ${error.message}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(chalk.green(`\n ✅ Download complete!\n`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Replace placeholder images in project
|
|
112
|
+
*/
|
|
113
|
+
async function replacePlaceholders(projectPath, options = {}) {
|
|
114
|
+
console.log(chalk.cyan(`
|
|
115
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
116
|
+
│ 🔄 REPLACING PLACEHOLDER IMAGES │
|
|
117
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
118
|
+
`));
|
|
119
|
+
|
|
120
|
+
const service = createImageService({ verbose: true });
|
|
121
|
+
const result = await service.replacePlaceholders(projectPath);
|
|
122
|
+
|
|
123
|
+
if (result.replacedCount === 0) {
|
|
124
|
+
console.log(chalk.yellow(' No placeholder images found to replace.\n'));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.green(` ✅ Replaced ${result.replacedCount} placeholder images!\n`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generate hero image for project
|
|
132
|
+
*/
|
|
133
|
+
async function generateHeroImage(projectPath, options = {}) {
|
|
134
|
+
console.log(chalk.cyan(`
|
|
135
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
136
|
+
│ 🖼️ GENERATING HERO IMAGE │
|
|
137
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
138
|
+
`));
|
|
139
|
+
|
|
140
|
+
const theme = options.theme || 'tech';
|
|
141
|
+
const result = await generateImages(projectPath, 'web', {
|
|
142
|
+
hero: true,
|
|
143
|
+
products: 0,
|
|
144
|
+
team: 0,
|
|
145
|
+
testimonials: 0,
|
|
146
|
+
theme
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (result.downloaded.length > 0) {
|
|
150
|
+
console.log(chalk.green(` ✅ Hero image generated!`));
|
|
151
|
+
console.log(chalk.gray(` Path: public/images/${result.downloaded[0].filename}\n`));
|
|
152
|
+
} else {
|
|
153
|
+
console.log(chalk.red(` ❌ Failed to generate hero image.\n`));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generate product images
|
|
159
|
+
*/
|
|
160
|
+
async function generateProductImages(projectPath, count, options = {}) {
|
|
161
|
+
console.log(chalk.cyan(`
|
|
162
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
163
|
+
│ 📦 GENERATING PRODUCT IMAGES │
|
|
164
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
165
|
+
`));
|
|
166
|
+
|
|
167
|
+
const theme = options.theme || 'tech';
|
|
168
|
+
const result = await generateImages(projectPath, 'ecommerce', {
|
|
169
|
+
hero: false,
|
|
170
|
+
products: count,
|
|
171
|
+
team: 0,
|
|
172
|
+
testimonials: 0,
|
|
173
|
+
theme
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const productCount = result.downloaded.filter(d => d.type === 'product').length;
|
|
177
|
+
if (productCount > 0) {
|
|
178
|
+
console.log(chalk.green(` ✅ ${productCount} product images generated!`));
|
|
179
|
+
console.log(chalk.gray(` Path: public/images/\n`));
|
|
180
|
+
} else {
|
|
181
|
+
console.log(chalk.red(` ❌ Failed to generate product images.\n`));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Generate full image set
|
|
187
|
+
*/
|
|
188
|
+
async function generateFullSet(projectPath, options = {}) {
|
|
189
|
+
console.log(chalk.cyan(`
|
|
190
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
191
|
+
│ 📸 GENERATING FULL IMAGE SET │
|
|
192
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
193
|
+
`));
|
|
194
|
+
|
|
195
|
+
const theme = options.theme || 'tech';
|
|
196
|
+
|
|
197
|
+
const result = await generateImages(projectPath, 'full', {
|
|
198
|
+
hero: true,
|
|
199
|
+
products: parseInt(options.productCount) || 6,
|
|
200
|
+
team: parseInt(options.teamCount) || 4,
|
|
201
|
+
testimonials: parseInt(options.testimonialCount) || 4,
|
|
202
|
+
theme
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const total = result.downloaded.length;
|
|
206
|
+
if (total > 0) {
|
|
207
|
+
console.log(chalk.green(` ✅ Generated ${total} images!`));
|
|
208
|
+
console.log(chalk.gray(` Path: public/images/\n`));
|
|
209
|
+
|
|
210
|
+
// Summary
|
|
211
|
+
const byType = {};
|
|
212
|
+
for (const item of result.downloaded) {
|
|
213
|
+
byType[item.type] = (byType[item.type] || 0) + 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log(chalk.gray(' Summary:'));
|
|
217
|
+
for (const [type, count] of Object.entries(byType)) {
|
|
218
|
+
console.log(chalk.gray(` - ${type}: ${count}`));
|
|
219
|
+
}
|
|
220
|
+
console.log('');
|
|
221
|
+
} else {
|
|
222
|
+
console.log(chalk.red(` ❌ Failed to generate images.\n`));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Interactive mode
|
|
228
|
+
*/
|
|
229
|
+
async function interactiveMode(projectPath, options = {}) {
|
|
230
|
+
console.log(chalk.cyan(`
|
|
231
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
232
|
+
│ 📸 VIBECODE IMAGE GENERATOR │
|
|
233
|
+
│ │
|
|
234
|
+
│ Generate professional images for your project │
|
|
235
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
236
|
+
`));
|
|
237
|
+
|
|
238
|
+
const { action } = await inquirer.prompt([{
|
|
239
|
+
type: 'list',
|
|
240
|
+
name: 'action',
|
|
241
|
+
message: 'What would you like to do?',
|
|
242
|
+
choices: [
|
|
243
|
+
{ name: '🔍 Search for images', value: 'search' },
|
|
244
|
+
{ name: '🖼️ Generate hero image', value: 'hero' },
|
|
245
|
+
{ name: '📦 Generate product images', value: 'products' },
|
|
246
|
+
{ name: '👥 Generate team photos', value: 'team' },
|
|
247
|
+
{ name: '📸 Generate full image set', value: 'full' },
|
|
248
|
+
{ name: '🔄 Replace placeholder images', value: 'replace' },
|
|
249
|
+
{ name: '📋 Show curated collections', value: 'collections' },
|
|
250
|
+
{ name: '👋 Exit', value: 'exit' }
|
|
251
|
+
]
|
|
252
|
+
}]);
|
|
253
|
+
|
|
254
|
+
if (action === 'exit') {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (action === 'search') {
|
|
259
|
+
const { query } = await inquirer.prompt([{
|
|
260
|
+
type: 'input',
|
|
261
|
+
name: 'query',
|
|
262
|
+
message: 'Search query:',
|
|
263
|
+
default: 'tech hero'
|
|
264
|
+
}]);
|
|
265
|
+
return searchAndDisplay(query, options);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (action === 'hero') {
|
|
269
|
+
const { theme } = await inquirer.prompt([{
|
|
270
|
+
type: 'list',
|
|
271
|
+
name: 'theme',
|
|
272
|
+
message: 'Select theme:',
|
|
273
|
+
choices: ['tech', 'business', 'creative', 'nature', 'abstract']
|
|
274
|
+
}]);
|
|
275
|
+
return generateHeroImage(projectPath, { ...options, theme });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (action === 'products') {
|
|
279
|
+
const { count, theme } = await inquirer.prompt([
|
|
280
|
+
{
|
|
281
|
+
type: 'number',
|
|
282
|
+
name: 'count',
|
|
283
|
+
message: 'Number of product images:',
|
|
284
|
+
default: 6
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
type: 'list',
|
|
288
|
+
name: 'theme',
|
|
289
|
+
message: 'Product category:',
|
|
290
|
+
choices: ['tech', 'fashion', 'food', 'lifestyle']
|
|
291
|
+
}
|
|
292
|
+
]);
|
|
293
|
+
return generateProductImages(projectPath, count, { ...options, theme });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (action === 'team') {
|
|
297
|
+
const { count } = await inquirer.prompt([{
|
|
298
|
+
type: 'number',
|
|
299
|
+
name: 'count',
|
|
300
|
+
message: 'Number of team photos:',
|
|
301
|
+
default: 4
|
|
302
|
+
}]);
|
|
303
|
+
|
|
304
|
+
const result = await generateImages(projectPath, 'team', {
|
|
305
|
+
hero: false,
|
|
306
|
+
products: 0,
|
|
307
|
+
team: count,
|
|
308
|
+
testimonials: 0
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
console.log(chalk.green(` ✅ ${result.downloaded.length} team photos generated!\n`));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (action === 'full') {
|
|
315
|
+
const { theme } = await inquirer.prompt([{
|
|
316
|
+
type: 'list',
|
|
317
|
+
name: 'theme',
|
|
318
|
+
message: 'Select theme:',
|
|
319
|
+
choices: ['tech', 'business', 'creative']
|
|
320
|
+
}]);
|
|
321
|
+
return generateFullSet(projectPath, { ...options, theme });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (action === 'replace') {
|
|
325
|
+
return replacePlaceholders(projectPath, options);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (action === 'collections') {
|
|
329
|
+
showCollections();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Show available curated collections
|
|
335
|
+
*/
|
|
336
|
+
function showCollections() {
|
|
337
|
+
console.log(chalk.cyan(`
|
|
338
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
339
|
+
│ 📚 CURATED IMAGE COLLECTIONS │
|
|
340
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
341
|
+
`));
|
|
342
|
+
|
|
343
|
+
console.log(chalk.white.bold(' Hero Images:'));
|
|
344
|
+
console.log(chalk.gray(' tech, business, creative, nature, abstract'));
|
|
345
|
+
|
|
346
|
+
console.log(chalk.white.bold('\n Product Images:'));
|
|
347
|
+
console.log(chalk.gray(' tech, fashion, food, lifestyle'));
|
|
348
|
+
|
|
349
|
+
console.log(chalk.white.bold('\n People:'));
|
|
350
|
+
console.log(chalk.gray(' team (8 photos), testimonials (6 avatars)'));
|
|
351
|
+
|
|
352
|
+
console.log(chalk.white.bold('\n Backgrounds:'));
|
|
353
|
+
console.log(chalk.gray(' gradient, pattern'));
|
|
354
|
+
|
|
355
|
+
console.log(chalk.gray(`
|
|
356
|
+
─────────────────────────────────────────────────────────────────────
|
|
357
|
+
|
|
358
|
+
Usage:
|
|
359
|
+
${chalk.cyan('vibecode images --search "tech hero"')} Search images
|
|
360
|
+
${chalk.cyan('vibecode images --hero --theme tech')} Generate hero
|
|
361
|
+
${chalk.cyan('vibecode images --products 6')} Generate products
|
|
362
|
+
${chalk.cyan('vibecode images --generate')} Full image set
|
|
363
|
+
${chalk.cyan('vibecode images --replace')} Replace placeholders
|
|
364
|
+
|
|
365
|
+
With go command:
|
|
366
|
+
${chalk.cyan('vibecode go --template landing-saas --with-images')}
|
|
367
|
+
`));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* List generated images in project
|
|
372
|
+
*/
|
|
373
|
+
async function listImages(projectPath) {
|
|
374
|
+
const imagesDir = path.join(projectPath, 'public', 'images');
|
|
375
|
+
|
|
376
|
+
console.log(chalk.cyan(`
|
|
377
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
378
|
+
│ 🖼️ GENERATED IMAGES │
|
|
379
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
380
|
+
`));
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const fs = await import('fs/promises');
|
|
384
|
+
const files = await fs.readdir(imagesDir);
|
|
385
|
+
const images = files.filter(f => /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(f));
|
|
386
|
+
|
|
387
|
+
if (images.length === 0) {
|
|
388
|
+
console.log(chalk.gray(' No images found.\n'));
|
|
389
|
+
console.log(chalk.gray(` Run ${chalk.cyan('vibecode images --generate')} to generate images.\n`));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let totalSize = 0;
|
|
394
|
+
for (const img of images) {
|
|
395
|
+
const stats = await fs.stat(path.join(imagesDir, img));
|
|
396
|
+
const size = stats.size / 1024;
|
|
397
|
+
totalSize += size;
|
|
398
|
+
|
|
399
|
+
const sizeStr = size >= 1024
|
|
400
|
+
? `${(size / 1024).toFixed(1)} MB`
|
|
401
|
+
: `${size.toFixed(1)} KB`;
|
|
402
|
+
|
|
403
|
+
const icon = img.includes('hero') ? '🖼️ ' :
|
|
404
|
+
img.includes('product') ? '📦' :
|
|
405
|
+
img.includes('team') ? '👤' :
|
|
406
|
+
img.includes('testimonial') ? '💬' :
|
|
407
|
+
img.includes('bg') ? '🎨' : '📸';
|
|
408
|
+
|
|
409
|
+
console.log(chalk.green(` ${icon} ${img.padEnd(35)} ${sizeStr.padStart(10)}`));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const totalStr = totalSize >= 1024
|
|
413
|
+
? `${(totalSize / 1024).toFixed(1)} MB`
|
|
414
|
+
: `${totalSize.toFixed(1)} KB`;
|
|
415
|
+
|
|
416
|
+
console.log(chalk.gray(`\n ─────────────────────────────────────────────────────`));
|
|
417
|
+
console.log(chalk.white(` Total: ${images.length} images (${totalStr})`));
|
|
418
|
+
console.log(chalk.gray(` Location: ${imagesDir}\n`));
|
|
419
|
+
|
|
420
|
+
} catch (error) {
|
|
421
|
+
console.log(chalk.yellow(' No images directory found.\n'));
|
|
422
|
+
console.log(chalk.gray(` Run ${chalk.cyan('vibecode images --generate')} to generate images.\n`));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Auto-generate images for project (called from go.js)
|
|
428
|
+
*/
|
|
429
|
+
export async function autoGenerateImages(projectPath, options = {}) {
|
|
430
|
+
const {
|
|
431
|
+
template,
|
|
432
|
+
theme = 'tech'
|
|
433
|
+
} = options;
|
|
434
|
+
|
|
435
|
+
// Determine what images to generate based on template
|
|
436
|
+
let imageConfig = {
|
|
437
|
+
hero: true,
|
|
438
|
+
products: 0,
|
|
439
|
+
team: 0,
|
|
440
|
+
testimonials: 0,
|
|
441
|
+
theme
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
if (template) {
|
|
445
|
+
if (template.includes('ecommerce') || template.includes('shop')) {
|
|
446
|
+
imageConfig.products = 8;
|
|
447
|
+
}
|
|
448
|
+
if (template.includes('saas') || template.includes('landing')) {
|
|
449
|
+
imageConfig.testimonials = 4;
|
|
450
|
+
}
|
|
451
|
+
if (template.includes('agency') || template.includes('portfolio')) {
|
|
452
|
+
imageConfig.team = 4;
|
|
453
|
+
}
|
|
454
|
+
if (template.includes('dashboard')) {
|
|
455
|
+
imageConfig.hero = false; // Dashboards don't need hero images
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
return await generateImages(projectPath, template || 'web', imageConfig);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.log(chalk.yellow(` ⚠ Image generation failed: ${error.message}`));
|
|
463
|
+
return { downloaded: [], failed: [{ error: error.message }] };
|
|
464
|
+
}
|
|
465
|
+
}
|