@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.
@@ -7,16 +7,21 @@
7
7
  * - Components: Spinners, Progress Bars, Tables, Headers
8
8
  */
9
9
 
10
- import process from 'process';
10
+ "use strict";
11
11
 
12
12
  // ═══════════════════════════════════════════════════════════════════════════════
13
13
  // 1. CORE CONSTANTS & ANSI
14
14
  // ═══════════════════════════════════════════════════════════════════════════════
15
15
 
16
- export const WIDTH = 76;
16
+ const WIDTH = 76;
17
17
 
18
18
  const ESC = '\x1b';
19
- export const ansi = {
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
- export const style = {
50
- success: (t: string) => `${ansi.green}${t}${ansi.reset}`,
51
- error: (t: string) => `${ansi.red}${t}${ansi.reset}`,
52
- warning: (t: string) => `${ansi.yellow}${t}${ansi.reset}`,
53
- info: (t: string) => `${ansi.cyan}${t}${ansi.reset}`,
54
- subtle: (t: string) => `${ansi.gray}${t}${ansi.reset}`,
55
- highlight: (t: string) => `${ansi.bold}${ansi.white}${t}${ansi.reset}`,
56
- label: (t: string) => `${ansi.blue}${t}${ansi.reset}`,
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: string) => `${ansi.bgGreen}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
59
- bgError: (t: string) => `${ansi.bgRed}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
60
- bgWarn: (t: string) => `${ansi.bgYellow}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
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
- export const icons = {
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
- export const BOX = {
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
- export function padCenter(str: string, width: number = WIDTH - 2): string {
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
- export function padRight(str: string, len: number): string {
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
- export function truncate(str: string, len: number): string {
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
- export class Spinner {
127
- private timer: NodeJS.Timeout | null = null;
128
- private frameIndex = 0;
129
-
130
- constructor(public text: string = '', private colorStr = ansi.cyan) {}
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?: string) {
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?: string) {
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?: string) { return this.stop(icons.success, ansi.green, text); }
155
- fail(text?: string) { return this.stop(icons.error, ansi.red, text); }
156
- warn(text?: string) { return this.stop(icons.warning, ansi.yellow, 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
- export function renderProgressBar(percentage: number, width = 20, color = ansi.green): string {
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
- export function renderScreenHeader(
176
- logoAscii: string,
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
- export function renderScreenFooter() {
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
- export function renderVerdictTable(
226
- stats: { duration: number, heap: number, files?: number },
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecheckai/cli",
3
- "version": "3.2.2",
3
+ "version": "3.2.3",
4
4
  "description": "Vibecheck CLI - Ship with confidence. One verdict: SHIP | WARN | BLOCK.",
5
5
  "main": "bin/vibecheck.js",
6
6
  "bin": {