@nclamvn/vibecode-cli 1.3.0 → 1.6.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/.vibecode/learning/fixes.json +1 -0
- package/.vibecode/learning/preferences.json +1 -0
- package/README.md +310 -49
- package/bin/vibecode.js +103 -2
- package/package.json +4 -2
- package/src/agent/decomposition.js +476 -0
- package/src/agent/index.js +325 -0
- package/src/agent/memory.js +542 -0
- package/src/agent/orchestrator.js +713 -0
- package/src/agent/self-healing.js +516 -0
- package/src/commands/agent.js +255 -0
- package/src/commands/assist.js +413 -0
- package/src/commands/build.js +13 -3
- package/src/commands/debug.js +457 -0
- package/src/commands/go.js +387 -0
- package/src/commands/learn.js +294 -0
- package/src/commands/undo.js +281 -0
- package/src/commands/wizard.js +322 -0
- package/src/core/backup.js +325 -0
- package/src/core/learning.js +295 -0
- package/src/core/test-runner.js +38 -5
- package/src/debug/analyzer.js +329 -0
- package/src/debug/evidence.js +228 -0
- package/src/debug/fixer.js +348 -0
- package/src/debug/index.js +378 -0
- package/src/debug/verifier.js +346 -0
- package/src/index.js +62 -0
- package/src/ui/__tests__/error-translator.test.js +390 -0
- package/src/ui/dashboard.js +364 -0
- package/src/ui/error-translator.js +775 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Progress Dashboard
|
|
3
|
+
// Phase H2: Real-time Visual Progress with ETA
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import readline from 'readline';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ProgressDashboard - Full-screen progress display for multi-module builds
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const dashboard = new ProgressDashboard({
|
|
14
|
+
* title: 'VIBECODE AGENT',
|
|
15
|
+
* projectName: 'my-app',
|
|
16
|
+
* mode: 'Agent (8 modules)'
|
|
17
|
+
* });
|
|
18
|
+
* dashboard.setModules(['core', 'auth', 'dashboard']);
|
|
19
|
+
* dashboard.start();
|
|
20
|
+
* dashboard.startModule('core');
|
|
21
|
+
* dashboard.completeModule('core');
|
|
22
|
+
* dashboard.stop();
|
|
23
|
+
*/
|
|
24
|
+
export class ProgressDashboard {
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.title = options.title || 'VIBECODE';
|
|
27
|
+
this.projectName = options.projectName || 'project';
|
|
28
|
+
this.mode = options.mode || 'build';
|
|
29
|
+
this.modules = [];
|
|
30
|
+
this.startTime = Date.now();
|
|
31
|
+
this.currentModule = null;
|
|
32
|
+
this.completedModules = [];
|
|
33
|
+
this.moduleTimes = {};
|
|
34
|
+
this.isRendering = false;
|
|
35
|
+
this.renderInterval = null;
|
|
36
|
+
this.logs = [];
|
|
37
|
+
this.maxLogs = options.maxLogs || 3;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Start the dashboard rendering
|
|
42
|
+
*/
|
|
43
|
+
start() {
|
|
44
|
+
this.isRendering = true;
|
|
45
|
+
this.startTime = Date.now();
|
|
46
|
+
|
|
47
|
+
// Hide cursor
|
|
48
|
+
process.stdout.write('\x1B[?25l');
|
|
49
|
+
|
|
50
|
+
// Clear screen and render
|
|
51
|
+
this.render();
|
|
52
|
+
|
|
53
|
+
// Update every 500ms for elapsed time
|
|
54
|
+
this.renderInterval = setInterval(() => {
|
|
55
|
+
if (this.isRendering) {
|
|
56
|
+
this.render();
|
|
57
|
+
}
|
|
58
|
+
}, 500);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Stop the dashboard rendering
|
|
63
|
+
*/
|
|
64
|
+
stop() {
|
|
65
|
+
this.isRendering = false;
|
|
66
|
+
|
|
67
|
+
if (this.renderInterval) {
|
|
68
|
+
clearInterval(this.renderInterval);
|
|
69
|
+
this.renderInterval = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Show cursor
|
|
73
|
+
process.stdout.write('\x1B[?25h');
|
|
74
|
+
|
|
75
|
+
// Final render
|
|
76
|
+
this.render();
|
|
77
|
+
console.log('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set modules to track
|
|
82
|
+
*/
|
|
83
|
+
setModules(modules) {
|
|
84
|
+
this.modules = modules.map(m => ({
|
|
85
|
+
name: typeof m === 'string' ? m : m.name,
|
|
86
|
+
status: 'pending',
|
|
87
|
+
time: null,
|
|
88
|
+
startTime: null
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Mark module as started
|
|
94
|
+
*/
|
|
95
|
+
startModule(moduleName) {
|
|
96
|
+
this.currentModule = moduleName;
|
|
97
|
+
const module = this.modules.find(m => m.name === moduleName);
|
|
98
|
+
if (module) {
|
|
99
|
+
module.status = 'building';
|
|
100
|
+
module.startTime = Date.now();
|
|
101
|
+
}
|
|
102
|
+
this.addLog(`Building: ${moduleName}`);
|
|
103
|
+
this.render();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Mark module as completed
|
|
108
|
+
*/
|
|
109
|
+
completeModule(moduleName, success = true) {
|
|
110
|
+
const module = this.modules.find(m => m.name === moduleName);
|
|
111
|
+
if (module) {
|
|
112
|
+
module.status = success ? 'done' : 'failed';
|
|
113
|
+
module.time = Date.now() - (module.startTime || this.startTime);
|
|
114
|
+
this.completedModules.push(moduleName);
|
|
115
|
+
this.addLog(success ? `✓ ${moduleName} complete` : `✗ ${moduleName} failed`);
|
|
116
|
+
}
|
|
117
|
+
this.currentModule = null;
|
|
118
|
+
this.render();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Mark module as failed
|
|
123
|
+
*/
|
|
124
|
+
failModule(moduleName) {
|
|
125
|
+
this.completeModule(moduleName, false);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Add a log message
|
|
130
|
+
*/
|
|
131
|
+
addLog(message) {
|
|
132
|
+
this.logs.push({
|
|
133
|
+
time: Date.now(),
|
|
134
|
+
message
|
|
135
|
+
});
|
|
136
|
+
// Keep only last N logs
|
|
137
|
+
if (this.logs.length > this.maxLogs) {
|
|
138
|
+
this.logs.shift();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get progress percentage
|
|
144
|
+
*/
|
|
145
|
+
getProgress() {
|
|
146
|
+
const total = this.modules.length;
|
|
147
|
+
const done = this.completedModules.length;
|
|
148
|
+
return total > 0 ? Math.round((done / total) * 100) : 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Calculate ETA based on average module time
|
|
153
|
+
*/
|
|
154
|
+
getETA() {
|
|
155
|
+
const done = this.completedModules.length;
|
|
156
|
+
const remaining = this.modules.length - done;
|
|
157
|
+
|
|
158
|
+
if (done === 0 || remaining === 0) return null;
|
|
159
|
+
|
|
160
|
+
const elapsed = Date.now() - this.startTime;
|
|
161
|
+
const avgPerModule = elapsed / done;
|
|
162
|
+
const etaMs = avgPerModule * remaining;
|
|
163
|
+
|
|
164
|
+
return this.formatTime(etaMs);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Format milliseconds to human readable time
|
|
169
|
+
*/
|
|
170
|
+
formatTime(ms) {
|
|
171
|
+
const seconds = Math.floor(ms / 1000);
|
|
172
|
+
const minutes = Math.floor(seconds / 60);
|
|
173
|
+
const secs = seconds % 60;
|
|
174
|
+
|
|
175
|
+
if (minutes > 0) {
|
|
176
|
+
return `${minutes}m ${secs}s`;
|
|
177
|
+
}
|
|
178
|
+
return `${secs}s`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Format elapsed time
|
|
183
|
+
*/
|
|
184
|
+
formatElapsed() {
|
|
185
|
+
return this.formatTime(Date.now() - this.startTime);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Render progress bar
|
|
190
|
+
*/
|
|
191
|
+
renderProgressBar(percent, width = 40) {
|
|
192
|
+
const filled = Math.round(width * percent / 100);
|
|
193
|
+
const empty = width - filled;
|
|
194
|
+
return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Render module status line
|
|
199
|
+
*/
|
|
200
|
+
renderModuleStatus(module) {
|
|
201
|
+
const icons = {
|
|
202
|
+
pending: chalk.gray('○'),
|
|
203
|
+
building: chalk.yellow('◐'),
|
|
204
|
+
done: chalk.green('✓'),
|
|
205
|
+
failed: chalk.red('✗')
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const icon = icons[module.status] || icons.pending;
|
|
209
|
+
const name = module.status === 'building'
|
|
210
|
+
? chalk.yellow(module.name)
|
|
211
|
+
: module.status === 'done'
|
|
212
|
+
? chalk.green(module.name)
|
|
213
|
+
: module.status === 'failed'
|
|
214
|
+
? chalk.red(module.name)
|
|
215
|
+
: chalk.gray(module.name);
|
|
216
|
+
|
|
217
|
+
let timeStr = '';
|
|
218
|
+
if (module.time) {
|
|
219
|
+
timeStr = chalk.gray(this.formatTime(module.time));
|
|
220
|
+
} else if (module.status === 'building') {
|
|
221
|
+
timeStr = chalk.yellow('building...');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Pad name to 16 chars, time to 12 chars
|
|
225
|
+
const paddedName = name + ' '.repeat(Math.max(0, 16 - module.name.length));
|
|
226
|
+
return ` ${icon} ${paddedName} ${timeStr}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Main render function
|
|
231
|
+
*/
|
|
232
|
+
render() {
|
|
233
|
+
if (!this.isRendering && this.completedModules.length === 0) return;
|
|
234
|
+
|
|
235
|
+
// Move cursor to top-left
|
|
236
|
+
readline.cursorTo(process.stdout, 0, 0);
|
|
237
|
+
readline.clearScreenDown(process.stdout);
|
|
238
|
+
|
|
239
|
+
const progress = this.getProgress();
|
|
240
|
+
const eta = this.getETA();
|
|
241
|
+
const elapsed = this.formatElapsed();
|
|
242
|
+
|
|
243
|
+
// Build output
|
|
244
|
+
const lines = [];
|
|
245
|
+
|
|
246
|
+
// Header
|
|
247
|
+
lines.push(chalk.cyan('╭────────────────────────────────────────────────────────────────────╮'));
|
|
248
|
+
lines.push(chalk.cyan('│') + ` 🏗️ ${chalk.bold(this.title)}`.padEnd(76) + chalk.cyan('│'));
|
|
249
|
+
lines.push(chalk.cyan('│') + ''.padEnd(68) + chalk.cyan('│'));
|
|
250
|
+
|
|
251
|
+
// Project info
|
|
252
|
+
lines.push(chalk.cyan('│') + ` Project: ${chalk.white(this.projectName)}`.padEnd(76) + chalk.cyan('│'));
|
|
253
|
+
lines.push(chalk.cyan('│') + ` Mode: ${chalk.white(this.mode)}`.padEnd(76) + chalk.cyan('│'));
|
|
254
|
+
lines.push(chalk.cyan('│') + ''.padEnd(68) + chalk.cyan('│'));
|
|
255
|
+
|
|
256
|
+
// Progress bar
|
|
257
|
+
const progressBar = ` [${this.renderProgressBar(progress)}] ${String(progress).padStart(3)}%`;
|
|
258
|
+
lines.push(chalk.cyan('│') + progressBar.padEnd(76) + chalk.cyan('│'));
|
|
259
|
+
lines.push(chalk.cyan('│') + ''.padEnd(68) + chalk.cyan('│'));
|
|
260
|
+
|
|
261
|
+
// Module list (max 10 visible)
|
|
262
|
+
const visibleModules = this.modules.slice(0, 10);
|
|
263
|
+
for (const module of visibleModules) {
|
|
264
|
+
const statusLine = this.renderModuleStatus(module);
|
|
265
|
+
lines.push(chalk.cyan('│') + statusLine.padEnd(76) + chalk.cyan('│'));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (this.modules.length > 10) {
|
|
269
|
+
lines.push(chalk.cyan('│') + chalk.gray(` ... and ${this.modules.length - 10} more`).padEnd(76) + chalk.cyan('│'));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
lines.push(chalk.cyan('│') + ''.padEnd(68) + chalk.cyan('│'));
|
|
273
|
+
|
|
274
|
+
// Time info
|
|
275
|
+
const etaText = eta ? `~${eta} remaining` : 'calculating...';
|
|
276
|
+
const timeInfo = ` Elapsed: ${chalk.white(elapsed.padEnd(10))} │ ETA: ${chalk.white(etaText)}`;
|
|
277
|
+
lines.push(chalk.cyan('│') + timeInfo.padEnd(76) + chalk.cyan('│'));
|
|
278
|
+
lines.push(chalk.cyan('│') + ''.padEnd(68) + chalk.cyan('│'));
|
|
279
|
+
|
|
280
|
+
// Footer
|
|
281
|
+
lines.push(chalk.cyan('╰────────────────────────────────────────────────────────────────────╯'));
|
|
282
|
+
|
|
283
|
+
// Recent logs
|
|
284
|
+
if (this.logs.length > 0) {
|
|
285
|
+
lines.push('');
|
|
286
|
+
lines.push(chalk.gray(' Recent:'));
|
|
287
|
+
for (const log of this.logs) {
|
|
288
|
+
lines.push(chalk.gray(` ${log.message}`));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
process.stdout.write(lines.join('\n'));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Simpler inline progress bar for non-dashboard use
|
|
298
|
+
*/
|
|
299
|
+
export function renderInlineProgress(current, total, label = '') {
|
|
300
|
+
const percent = Math.round((current / total) * 100);
|
|
301
|
+
const width = 30;
|
|
302
|
+
const filled = Math.round(width * percent / 100);
|
|
303
|
+
const empty = width - filled;
|
|
304
|
+
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
305
|
+
|
|
306
|
+
return `[${bar}] ${percent}% ${label}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Quick progress update (single line, overwrites previous)
|
|
311
|
+
*/
|
|
312
|
+
export function updateProgress(current, total, label = '') {
|
|
313
|
+
readline.clearLine(process.stdout, 0);
|
|
314
|
+
readline.cursorTo(process.stdout, 0);
|
|
315
|
+
process.stdout.write(renderInlineProgress(current, total, label));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Complete progress and move to next line
|
|
320
|
+
*/
|
|
321
|
+
export function completeProgress(label = 'Done') {
|
|
322
|
+
readline.clearLine(process.stdout, 0);
|
|
323
|
+
readline.cursorTo(process.stdout, 0);
|
|
324
|
+
console.log(chalk.green(`✓ ${label}`));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Simple step-by-step progress tracker
|
|
329
|
+
*/
|
|
330
|
+
export class StepProgress {
|
|
331
|
+
constructor(steps, options = {}) {
|
|
332
|
+
this.steps = steps;
|
|
333
|
+
this.currentStep = 0;
|
|
334
|
+
this.startTime = Date.now();
|
|
335
|
+
this.showTime = options.showTime !== false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
next(label) {
|
|
339
|
+
this.currentStep++;
|
|
340
|
+
const progress = renderInlineProgress(this.currentStep, this.steps.length, label || this.steps[this.currentStep - 1]);
|
|
341
|
+
|
|
342
|
+
readline.clearLine(process.stdout, 0);
|
|
343
|
+
readline.cursorTo(process.stdout, 0);
|
|
344
|
+
process.stdout.write(progress);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
complete(label = 'Complete') {
|
|
348
|
+
const elapsed = Date.now() - this.startTime;
|
|
349
|
+
readline.clearLine(process.stdout, 0);
|
|
350
|
+
readline.cursorTo(process.stdout, 0);
|
|
351
|
+
|
|
352
|
+
if (this.showTime) {
|
|
353
|
+
console.log(chalk.green(`✓ ${label} (${Math.round(elapsed / 1000)}s)`));
|
|
354
|
+
} else {
|
|
355
|
+
console.log(chalk.green(`✓ ${label}`));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fail(label = 'Failed') {
|
|
360
|
+
readline.clearLine(process.stdout, 0);
|
|
361
|
+
readline.cursorTo(process.stdout, 0);
|
|
362
|
+
console.log(chalk.red(`✗ ${label}`));
|
|
363
|
+
}
|
|
364
|
+
}
|