@vibecheckai/cli 3.2.2 → 3.2.3
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/runners/lib/terminal-ui.js +125 -45
- package/package.json +1 -1
|
@@ -7,16 +7,21 @@
|
|
|
7
7
|
* - Components: Spinners, Progress Bars, Tables, Headers
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
"use strict";
|
|
11
11
|
|
|
12
12
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
13
13
|
// 1. CORE CONSTANTS & ANSI
|
|
14
14
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const WIDTH = 76;
|
|
17
17
|
|
|
18
18
|
const ESC = '\x1b';
|
|
19
|
-
|
|
19
|
+
const SUPPORTS_TRUECOLOR = process.env.COLORTERM === 'truecolor' ||
|
|
20
|
+
process.env.TERM_PROGRAM === 'iTerm.app' ||
|
|
21
|
+
process.env.TERM_PROGRAM === 'Apple_Terminal' ||
|
|
22
|
+
process.env.WT_SESSION;
|
|
23
|
+
|
|
24
|
+
const ansi = {
|
|
20
25
|
reset: `${ESC}[0m`,
|
|
21
26
|
bold: `${ESC}[1m`,
|
|
22
27
|
dim: `${ESC}[2m`,
|
|
@@ -31,6 +36,9 @@ export const ansi = {
|
|
|
31
36
|
cyan: `${ESC}[36m`,
|
|
32
37
|
white: `${ESC}[37m`,
|
|
33
38
|
gray: `${ESC}[90m`,
|
|
39
|
+
// RGB colors (truecolor support)
|
|
40
|
+
rgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `${ESC}[38;2;${r};${g};${b}m` : '',
|
|
41
|
+
bgRgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `${ESC}[48;2;${r};${g};${b}m` : '',
|
|
34
42
|
// Backgrounds
|
|
35
43
|
bgRed: `${ESC}[41m`,
|
|
36
44
|
bgGreen: `${ESC}[42m`,
|
|
@@ -46,21 +54,21 @@ export const ansi = {
|
|
|
46
54
|
};
|
|
47
55
|
|
|
48
56
|
// Semantic Palette
|
|
49
|
-
|
|
50
|
-
success: (t
|
|
51
|
-
error: (t
|
|
52
|
-
warning: (t
|
|
53
|
-
info: (t
|
|
54
|
-
subtle: (t
|
|
55
|
-
highlight: (t
|
|
56
|
-
label: (t
|
|
57
|
+
const style = {
|
|
58
|
+
success: (t) => `${ansi.green}${t}${ansi.reset}`,
|
|
59
|
+
error: (t) => `${ansi.red}${t}${ansi.reset}`,
|
|
60
|
+
warning: (t) => `${ansi.yellow}${t}${ansi.reset}`,
|
|
61
|
+
info: (t) => `${ansi.cyan}${t}${ansi.reset}`,
|
|
62
|
+
subtle: (t) => `${ansi.gray}${t}${ansi.reset}`,
|
|
63
|
+
highlight: (t) => `${ansi.bold}${ansi.white}${t}${ansi.reset}`,
|
|
64
|
+
label: (t) => `${ansi.blue}${t}${ansi.reset}`,
|
|
57
65
|
// Backgrounds for badges
|
|
58
|
-
bgSuccess: (t
|
|
59
|
-
bgError: (t
|
|
60
|
-
bgWarn: (t
|
|
66
|
+
bgSuccess: (t) => `${ansi.bgGreen}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
|
|
67
|
+
bgError: (t) => `${ansi.bgRed}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
|
|
68
|
+
bgWarn: (t) => `${ansi.bgYellow}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
|
|
61
69
|
};
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
const icons = {
|
|
64
72
|
success: '✓',
|
|
65
73
|
error: '✗',
|
|
66
74
|
warning: '⚠',
|
|
@@ -78,7 +86,7 @@ export const icons = {
|
|
|
78
86
|
// 2. LAYOUT PRIMITIVES (The "Grid")
|
|
79
87
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
80
88
|
|
|
81
|
-
|
|
89
|
+
const BOX = {
|
|
82
90
|
// Outer Frame (Double)
|
|
83
91
|
tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║',
|
|
84
92
|
trT: '╠', tlT: '╣',
|
|
@@ -90,7 +98,7 @@ export const BOX = {
|
|
|
90
98
|
/**
|
|
91
99
|
* Centers text within the standard width, accounting for ANSI codes
|
|
92
100
|
*/
|
|
93
|
-
|
|
101
|
+
function padCenter(str, width = WIDTH - 2) {
|
|
94
102
|
const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
|
|
95
103
|
const padding = Math.max(0, width - visibleLen);
|
|
96
104
|
const left = Math.floor(padding / 2);
|
|
@@ -100,7 +108,7 @@ export function padCenter(str: string, width: number = WIDTH - 2): string {
|
|
|
100
108
|
/**
|
|
101
109
|
* Pads text to the right, accounting for ANSI codes
|
|
102
110
|
*/
|
|
103
|
-
|
|
111
|
+
function padRight(str, len) {
|
|
104
112
|
const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
|
|
105
113
|
const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
|
|
106
114
|
const finalLen = truncated.replace(/\u001b\[\d+m/g, '').length;
|
|
@@ -110,7 +118,7 @@ export function padRight(str: string, len: number): string {
|
|
|
110
118
|
/**
|
|
111
119
|
* Truncates text with ellipsis
|
|
112
120
|
*/
|
|
113
|
-
|
|
121
|
+
function truncate(str, len) {
|
|
114
122
|
if (!str) return '';
|
|
115
123
|
const clean = str.replace(/\u001b\[\d+m/g, '');
|
|
116
124
|
if (clean.length <= len) return str;
|
|
@@ -123,13 +131,15 @@ export function truncate(str: string, len: number): string {
|
|
|
123
131
|
|
|
124
132
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
class Spinner {
|
|
135
|
+
constructor(text = '', colorStr = ansi.cyan) {
|
|
136
|
+
this.text = text;
|
|
137
|
+
this.colorStr = colorStr;
|
|
138
|
+
this.timer = null;
|
|
139
|
+
this.frameIndex = 0;
|
|
140
|
+
}
|
|
131
141
|
|
|
132
|
-
start(text
|
|
142
|
+
start(text) {
|
|
133
143
|
if (text) this.text = text;
|
|
134
144
|
process.stdout.write(ansi.hideCursor);
|
|
135
145
|
this.timer = setInterval(() => {
|
|
@@ -140,7 +150,7 @@ export class Spinner {
|
|
|
140
150
|
return this;
|
|
141
151
|
}
|
|
142
152
|
|
|
143
|
-
stop(symbol = icons.success, color = ansi.green, finalMsg
|
|
153
|
+
stop(symbol = icons.success, color = ansi.green, finalMsg) {
|
|
144
154
|
if (this.timer) clearInterval(this.timer);
|
|
145
155
|
process.stdout.write(`\r${ansi.clearLine}`);
|
|
146
156
|
if (finalMsg !== null) { // Pass null to clear completely
|
|
@@ -151,16 +161,16 @@ export class Spinner {
|
|
|
151
161
|
return this;
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
succeed(text
|
|
155
|
-
fail(text
|
|
156
|
-
warn(text
|
|
164
|
+
succeed(text) { return this.stop(icons.success, ansi.green, text); }
|
|
165
|
+
fail(text) { return this.stop(icons.error, ansi.red, text); }
|
|
166
|
+
warn(text) { return this.stop(icons.warning, ansi.yellow, text); }
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
160
170
|
// 4. COMPONENT: PROGRESS BAR
|
|
161
171
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
162
172
|
|
|
163
|
-
|
|
173
|
+
function renderProgressBar(percentage, width = 20, color = ansi.green) {
|
|
164
174
|
const filled = Math.round((percentage / 100) * width);
|
|
165
175
|
return `${color}${'█'.repeat(filled)}${ansi.gray}${'░'.repeat(width - filled)}${ansi.reset}`;
|
|
166
176
|
}
|
|
@@ -172,13 +182,8 @@ export function renderProgressBar(percentage: number, width = 20, color = ansi.g
|
|
|
172
182
|
/**
|
|
173
183
|
* Renders the Standard Double-Border Header with Logo
|
|
174
184
|
*/
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
title: string,
|
|
178
|
-
logoColor: (t: string) => string = (t) => t,
|
|
179
|
-
context?: { label: string, value: string }
|
|
180
|
-
) {
|
|
181
|
-
const lines: string[] = [];
|
|
185
|
+
function renderScreenHeader(logoAscii, title, logoColor = (t) => t, context) {
|
|
186
|
+
const lines = [];
|
|
182
187
|
|
|
183
188
|
// Top Frame
|
|
184
189
|
lines.push(ansi.gray + BOX.tl + BOX.h.repeat(WIDTH - 2) + BOX.tr + ansi.reset);
|
|
@@ -215,19 +220,15 @@ export function renderScreenHeader(
|
|
|
215
220
|
/**
|
|
216
221
|
* Renders the Standard Footer
|
|
217
222
|
*/
|
|
218
|
-
|
|
223
|
+
function renderScreenFooter() {
|
|
219
224
|
console.log(ansi.gray + BOX.bl + BOX.h.repeat(WIDTH - 2) + BOX.br + ansi.reset);
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
/**
|
|
223
228
|
* Renders a Standard Verdict/Telemetry Table
|
|
224
229
|
*/
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
score: number,
|
|
228
|
-
findings: any[]
|
|
229
|
-
) {
|
|
230
|
-
const lines: string[] = [];
|
|
230
|
+
function renderVerdictTable(stats, score, findings) {
|
|
231
|
+
const lines = [];
|
|
231
232
|
|
|
232
233
|
// Telemetry Row
|
|
233
234
|
const heapMB = Math.round(stats.heap / 1024 / 1024);
|
|
@@ -268,4 +269,83 @@ export function renderVerdictTable(
|
|
|
268
269
|
|
|
269
270
|
lines.push(ansi.gray + BOX.v + ansi.reset + ' '.repeat(WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
|
|
270
271
|
console.log(lines.join('\n'));
|
|
271
|
-
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
275
|
+
// COLORS OBJECT (for compatibility)
|
|
276
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
277
|
+
|
|
278
|
+
const colors = {
|
|
279
|
+
success: ansi.green,
|
|
280
|
+
error: ansi.red,
|
|
281
|
+
warning: ansi.yellow,
|
|
282
|
+
info: ansi.cyan,
|
|
283
|
+
accent: ansi.cyan,
|
|
284
|
+
muted: ansi.gray,
|
|
285
|
+
highlight: ansi.white,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
289
|
+
// UTILITY FUNCTIONS
|
|
290
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Format duration in milliseconds to human-readable string
|
|
294
|
+
*/
|
|
295
|
+
function formatDuration(ms) {
|
|
296
|
+
if (ms < 1000) return `${ms}ms`;
|
|
297
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
298
|
+
const minutes = Math.floor(ms / 60000);
|
|
299
|
+
const seconds = Math.floor((ms % 60000) / 1000);
|
|
300
|
+
return `${minutes}m ${seconds}s`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Render a section header
|
|
305
|
+
*/
|
|
306
|
+
function renderSection(title, icon = '•') {
|
|
307
|
+
return `\n ${ansi.cyan}${icon}${ansi.reset} ${ansi.bold}${title}${ansi.reset}\n ${ansi.gray}${BOX.lh.repeat(WIDTH - 4)}${ansi.reset}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Render banner (placeholder - actual banner is in runScan.js)
|
|
312
|
+
*/
|
|
313
|
+
function renderBanner() {
|
|
314
|
+
// Banner is handled in individual command files
|
|
315
|
+
return '';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* PhaseProgress class (placeholder)
|
|
320
|
+
*/
|
|
321
|
+
class PhaseProgress {
|
|
322
|
+
constructor() {}
|
|
323
|
+
start() { return this; }
|
|
324
|
+
update() { return this; }
|
|
325
|
+
stop() { return this; }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
329
|
+
// EXPORTS
|
|
330
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
331
|
+
|
|
332
|
+
module.exports = {
|
|
333
|
+
WIDTH,
|
|
334
|
+
ansi,
|
|
335
|
+
colors,
|
|
336
|
+
style,
|
|
337
|
+
icons,
|
|
338
|
+
BOX,
|
|
339
|
+
padCenter,
|
|
340
|
+
padRight,
|
|
341
|
+
truncate,
|
|
342
|
+
Spinner,
|
|
343
|
+
PhaseProgress,
|
|
344
|
+
renderProgressBar,
|
|
345
|
+
renderScreenHeader,
|
|
346
|
+
renderScreenFooter,
|
|
347
|
+
renderVerdictTable,
|
|
348
|
+
renderSection,
|
|
349
|
+
renderBanner,
|
|
350
|
+
formatDuration,
|
|
351
|
+
};
|