@nclamvn/vibecode-cli 2.1.0 → 2.2.1

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,397 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Templates Command
3
+ // Browse and use project templates
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import {
9
+ TEMPLATES,
10
+ getCategories,
11
+ getTemplatesByCategory,
12
+ searchTemplates,
13
+ getTemplate,
14
+ getCategoryIcon
15
+ } from '../templates/index.js';
16
+
17
+ /**
18
+ * Templates command entry point
19
+ */
20
+ export async function templatesCommand(options = {}) {
21
+ // Show template info
22
+ if (options.info) {
23
+ return showTemplateInfo(options.info);
24
+ }
25
+
26
+ // Preview template
27
+ if (options.preview) {
28
+ return previewTemplate(options.preview);
29
+ }
30
+
31
+ // Search templates
32
+ if (options.search) {
33
+ return searchAndShow(options.search);
34
+ }
35
+
36
+ // Default: List all templates
37
+ return listTemplates(options);
38
+ }
39
+
40
+ /**
41
+ * List all templates grouped by category
42
+ */
43
+ async function listTemplates(options) {
44
+ const categories = getCategories();
45
+ const totalTemplates = Object.keys(TEMPLATES).length;
46
+
47
+ console.log(chalk.cyan(`
48
+ ╭────────────────────────────────────────────────────────────────────╮
49
+ │ 📦 VIBECODE TEMPLATE GALLERY │
50
+ │ │
51
+ │ ${String(totalTemplates).padEnd(2)} professional templates ready to use │
52
+ ╰────────────────────────────────────────────────────────────────────╯
53
+ `));
54
+
55
+ // Group by category
56
+ for (const category of categories) {
57
+ const templates = getTemplatesByCategory(category.id);
58
+ const icon = getCategoryIcon(category.id);
59
+
60
+ console.log(chalk.white.bold(`\n ${icon} ${category.name.toUpperCase()} (${category.count})`));
61
+ console.log(chalk.gray(' ' + '─'.repeat(60)));
62
+
63
+ for (const template of templates) {
64
+ const id = chalk.green(template.id.padEnd(22));
65
+ const desc = chalk.gray(truncate(template.description, 40));
66
+ console.log(` ${id} ${desc}`);
67
+ }
68
+ }
69
+
70
+ console.log(chalk.gray(`
71
+ ─────────────────────────────────────────────────────────────────────
72
+
73
+ Usage:
74
+ ${chalk.cyan('vibecode templates --info <id>')} View template details
75
+ ${chalk.cyan('vibecode templates --search <query>')} Search templates
76
+ ${chalk.cyan('vibecode go --template <id>')} Use a template
77
+ ${chalk.cyan('vibecode go -t <id> --name "X"')} With customization
78
+
79
+ Example:
80
+ ${chalk.cyan('vibecode go --template landing-saas')}
81
+ ${chalk.cyan('vibecode go -t dashboard-admin --name "My Dashboard"')}
82
+ `));
83
+
84
+ // Interactive mode unless quiet
85
+ if (!options.quiet) {
86
+ await interactiveMode();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Interactive template browser
92
+ */
93
+ async function interactiveMode() {
94
+ const { action } = await inquirer.prompt([{
95
+ type: 'list',
96
+ name: 'action',
97
+ message: 'What would you like to do?',
98
+ choices: [
99
+ { name: '📋 View template details', value: 'info' },
100
+ { name: '🚀 Use a template', value: 'use' },
101
+ { name: '🔍 Search templates', value: 'search' },
102
+ { name: '👋 Exit', value: 'exit' }
103
+ ]
104
+ }]);
105
+
106
+ if (action === 'exit') {
107
+ return;
108
+ }
109
+
110
+ if (action === 'info') {
111
+ const { templateId } = await inquirer.prompt([{
112
+ type: 'list',
113
+ name: 'templateId',
114
+ message: 'Select template to view:',
115
+ choices: Object.values(TEMPLATES).map(t => ({
116
+ name: `${getCategoryIcon(t.category)} ${t.name} - ${truncate(t.description, 35)}`,
117
+ value: t.id
118
+ })),
119
+ pageSize: 15
120
+ }]);
121
+ return showTemplateInfo(templateId);
122
+ }
123
+
124
+ if (action === 'use') {
125
+ const { templateId } = await inquirer.prompt([{
126
+ type: 'list',
127
+ name: 'templateId',
128
+ message: 'Select template to use:',
129
+ choices: Object.values(TEMPLATES).map(t => ({
130
+ name: `${getCategoryIcon(t.category)} ${t.name}`,
131
+ value: t.id
132
+ })),
133
+ pageSize: 15
134
+ }]);
135
+
136
+ const template = getTemplate(templateId);
137
+
138
+ // Ask for customization if template has variables
139
+ let customOptions = {};
140
+ if (Object.keys(template.variables).length > 0) {
141
+ const { customize } = await inquirer.prompt([{
142
+ type: 'confirm',
143
+ name: 'customize',
144
+ message: 'Customize template options?',
145
+ default: false
146
+ }]);
147
+
148
+ if (customize) {
149
+ customOptions = await promptForVariables(template);
150
+ }
151
+ }
152
+
153
+ console.log(chalk.cyan(`
154
+ ─────────────────────────────────────────────────────────────────────
155
+
156
+ Run this command to create your project:
157
+
158
+ ${chalk.white.bold(`vibecode go --template ${templateId}${formatCustomOptions(customOptions)}`)}
159
+
160
+ `));
161
+ }
162
+
163
+ if (action === 'search') {
164
+ const { query } = await inquirer.prompt([{
165
+ type: 'input',
166
+ name: 'query',
167
+ message: 'Search:'
168
+ }]);
169
+
170
+ const results = searchTemplates(query);
171
+ if (results.length === 0) {
172
+ console.log(chalk.yellow('\n No templates found matching your search.\n'));
173
+ } else {
174
+ console.log(chalk.green(`\n Found ${results.length} template(s):\n`));
175
+ for (const t of results) {
176
+ const icon = getCategoryIcon(t.category);
177
+ console.log(` ${icon} ${chalk.green(t.id.padEnd(22))} ${chalk.gray(truncate(t.description, 40))}`);
178
+ }
179
+ console.log('');
180
+
181
+ // Offer to view details
182
+ const { viewDetails } = await inquirer.prompt([{
183
+ type: 'confirm',
184
+ name: 'viewDetails',
185
+ message: 'View template details?',
186
+ default: true
187
+ }]);
188
+
189
+ if (viewDetails && results.length > 0) {
190
+ const { templateId } = await inquirer.prompt([{
191
+ type: 'list',
192
+ name: 'templateId',
193
+ message: 'Select template:',
194
+ choices: results.map(t => ({ name: t.name, value: t.id }))
195
+ }]);
196
+ return showTemplateInfo(templateId);
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Show detailed template information
204
+ */
205
+ async function showTemplateInfo(templateId) {
206
+ const template = getTemplate(templateId);
207
+
208
+ if (!template) {
209
+ console.log(chalk.red(`\n ❌ Template "${templateId}" not found.\n`));
210
+ console.log(chalk.gray(` Run ${chalk.cyan('vibecode templates')} to see available templates.\n`));
211
+ return;
212
+ }
213
+
214
+ const icon = getCategoryIcon(template.category);
215
+
216
+ console.log(chalk.cyan(`
217
+ ╭────────────────────────────────────────────────────────────────────╮
218
+ │ ${icon} ${template.name.padEnd(56)}│
219
+ ╰────────────────────────────────────────────────────────────────────╯
220
+ `));
221
+
222
+ console.log(chalk.white(` ${template.description}\n`));
223
+
224
+ console.log(chalk.gray(' Category: ') + chalk.white(template.category));
225
+ console.log(chalk.gray(' Tags: ') + chalk.cyan(template.tags.join(', ')));
226
+ console.log(chalk.gray(' Stack: ') + chalk.yellow(template.stack.join(', ')));
227
+
228
+ console.log(chalk.gray('\n Features:'));
229
+ for (const feature of template.features) {
230
+ console.log(chalk.green(` ✓ ${feature}`));
231
+ }
232
+
233
+ // Show customization options
234
+ if (Object.keys(template.variables).length > 0) {
235
+ console.log(chalk.gray('\n Customization Options:'));
236
+ for (const [key, config] of Object.entries(template.variables)) {
237
+ const typeInfo = config.type === 'select'
238
+ ? `[${config.options.join('|')}]`
239
+ : `(${config.type})`;
240
+ console.log(chalk.yellow(` --${key}`) + chalk.gray(` ${typeInfo}`));
241
+ console.log(chalk.gray(` ${config.description}`));
242
+ console.log(chalk.gray(` Default: "${config.default}"`));
243
+ }
244
+ }
245
+
246
+ console.log(chalk.gray(`
247
+ ─────────────────────────────────────────────────────────────────────
248
+
249
+ Use this template:
250
+ ${chalk.cyan(`vibecode go --template ${templateId}`)}
251
+
252
+ With customization:
253
+ ${chalk.cyan(`vibecode go -t ${templateId} --name "MyProject"`)}
254
+ `));
255
+
256
+ // Prompt to use
257
+ const { useNow } = await inquirer.prompt([{
258
+ type: 'confirm',
259
+ name: 'useNow',
260
+ message: 'Use this template now?',
261
+ default: false
262
+ }]);
263
+
264
+ if (useNow) {
265
+ // Ask for customization
266
+ let customOptions = {};
267
+ if (Object.keys(template.variables).length > 0) {
268
+ customOptions = await promptForVariables(template);
269
+ }
270
+
271
+ // Import and call go command
272
+ const { goCommand } = await import('./go.js');
273
+ await goCommand('', { template: templateId, ...customOptions });
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Prompt user for template variables
279
+ */
280
+ async function promptForVariables(template) {
281
+ const questions = [];
282
+
283
+ for (const [key, config] of Object.entries(template.variables)) {
284
+ if (config.type === 'select') {
285
+ questions.push({
286
+ type: 'list',
287
+ name: key,
288
+ message: config.description,
289
+ choices: config.options,
290
+ default: config.default
291
+ });
292
+ } else if (config.type === 'boolean') {
293
+ questions.push({
294
+ type: 'confirm',
295
+ name: key,
296
+ message: config.description,
297
+ default: config.default
298
+ });
299
+ } else if (config.type === 'number') {
300
+ questions.push({
301
+ type: 'number',
302
+ name: key,
303
+ message: config.description,
304
+ default: config.default
305
+ });
306
+ } else {
307
+ questions.push({
308
+ type: 'input',
309
+ name: key,
310
+ message: config.description,
311
+ default: config.default
312
+ });
313
+ }
314
+ }
315
+
316
+ return inquirer.prompt(questions);
317
+ }
318
+
319
+ /**
320
+ * Search and display results
321
+ */
322
+ async function searchAndShow(query) {
323
+ const results = searchTemplates(query);
324
+
325
+ if (results.length === 0) {
326
+ console.log(chalk.yellow(`\n No templates found for "${query}".\n`));
327
+ console.log(chalk.gray(` Try: landing, dashboard, ecommerce, blog, portfolio, app\n`));
328
+ return;
329
+ }
330
+
331
+ console.log(chalk.green(`\n Found ${results.length} template(s) matching "${query}":\n`));
332
+
333
+ for (const t of results) {
334
+ const icon = getCategoryIcon(t.category);
335
+ console.log(` ${icon} ${chalk.green(t.id.padEnd(22))} ${chalk.gray(truncate(t.description, 40))}`);
336
+ }
337
+
338
+ console.log(chalk.gray(`
339
+ ─────────────────────────────────────────────────────────────────────
340
+
341
+ View details:
342
+ ${chalk.cyan(`vibecode templates --info ${results[0].id}`)}
343
+
344
+ Use template:
345
+ ${chalk.cyan(`vibecode go --template ${results[0].id}`)}
346
+ `));
347
+ }
348
+
349
+ /**
350
+ * Preview template (open in browser)
351
+ */
352
+ async function previewTemplate(templateId) {
353
+ const template = getTemplate(templateId);
354
+
355
+ if (!template) {
356
+ console.log(chalk.red(`\n ❌ Template "${templateId}" not found.\n`));
357
+ return;
358
+ }
359
+
360
+ console.log(chalk.cyan(`\n 🖼️ Preview: ${template.name}`));
361
+ console.log(chalk.gray(` URL: ${template.preview}\n`));
362
+
363
+ if (template.preview.startsWith('http')) {
364
+ try {
365
+ const open = (await import('open')).default;
366
+ await open(template.preview);
367
+ console.log(chalk.green(' ✓ Opened preview in browser.\n'));
368
+ } catch (e) {
369
+ console.log(chalk.yellow(' ⚠ Could not open browser. Visit URL manually.\n'));
370
+ }
371
+ } else {
372
+ console.log(chalk.gray(' (Preview not available yet)\n'));
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Truncate string to max length
378
+ */
379
+ function truncate(str, maxLen) {
380
+ if (str.length <= maxLen) return str;
381
+ return str.substring(0, maxLen - 3) + '...';
382
+ }
383
+
384
+ /**
385
+ * Format custom options for display
386
+ */
387
+ function formatCustomOptions(options) {
388
+ const parts = [];
389
+ for (const [key, value] of Object.entries(options)) {
390
+ if (typeof value === 'string') {
391
+ parts.push(`--${key} "${value}"`);
392
+ } else {
393
+ parts.push(`--${key} ${value}`);
394
+ }
395
+ }
396
+ return parts.length > 0 ? ' ' + parts.join(' ') : '';
397
+ }
package/src/index.js CHANGED
@@ -58,6 +58,21 @@ export { securityCommand } from './commands/security.js';
58
58
  export { askCommand } from './commands/ask.js';
59
59
  export { migrateCommand } from './commands/migrate.js';
60
60
 
61
+ // Phase M Commands - Templates & Preview
62
+ export { templatesCommand } from './commands/templates.js';
63
+ export {
64
+ TEMPLATES,
65
+ getTemplate,
66
+ getTemplatesByCategory,
67
+ getCategories,
68
+ searchTemplates,
69
+ getCategoryIcon,
70
+ getTemplateIds,
71
+ isValidTemplate
72
+ } from './templates/index.js';
73
+
74
+ export { previewCommand, autoPreview } from './commands/preview.js';
75
+
61
76
  // UI exports (Phase H2: Dashboard)
62
77
  export {
63
78
  ProgressDashboard,