@vibecheckai/cli 3.2.1 → 3.2.2
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/cli-output.js +236 -210
- package/bin/runners/lib/scan-output.js +144 -822
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/terminal-ui.js +188 -770
- package/bin/runners/lib/unified-output.js +162 -158
- package/bin/runners/runShip.js +7 -8
- package/package.json +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Ship Output - Fixed Alignment
|
|
3
|
+
* * fixes: Table border misalignment and logo centering.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
10
|
+
// ASCII ART LOGOS
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
12
|
+
|
|
13
|
+
const LOGO_VIBECHECK = `
|
|
14
|
+
█████████ █████ █████ █████ ███████████
|
|
15
|
+
███░░░░░███░░███ ░░███ ░░███ ░░███░░░░░███
|
|
16
|
+
░███ ░░░ ░███ ░███ ░███ ░███ ░███ VIBECHECK CLI
|
|
17
|
+
░░█████████ ░███████████ ░███ ░██████████ ENTERPRISE SUITE
|
|
18
|
+
░░░░░░░░███ ░███░░░░░███ ░███ ░███░░░░░░ v2.4.0-stable
|
|
19
|
+
███ ░███ ░███ ░███ ░███ ░███
|
|
20
|
+
░░█████████ █████ █████ █████ █████ © 2026 Vibecheck Inc.
|
|
21
|
+
░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const LOGO_NOT_SHIP_READY = `
|
|
25
|
+
███ ███ █████████ █████████ █████ █████ █████ ███████
|
|
26
|
+
████ ███ ███░░░░░███ ███░░░░░███░░███ ░░███ ░░███ ░░███░░█
|
|
27
|
+
███░██ ██████ ███ ░███ ░░░ ░███ ░███ ░███ ░███ ░█
|
|
28
|
+
███ ░██ ██████ ███ ░█████████ ░███████████ ░███ ░██████
|
|
29
|
+
███ ░████████ ███ ░░░░░░░░███ ░███░░░░░███ ░███ ░███░░
|
|
30
|
+
███ ░███████ ███ ███ ░███ ░███ ░███ ░███ ░███
|
|
31
|
+
███ ░████░█████████ ░░█████████ █████ █████ █████ █████
|
|
32
|
+
░░░ ░░░ ░░░░░░░░░ ░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
36
|
+
// CONFIGURATION
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
38
|
+
|
|
39
|
+
const BOX = {
|
|
40
|
+
topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
|
|
41
|
+
horizontal: '═', vertical: '║',
|
|
42
|
+
teeRight: '╠', teeLeft: '╣',
|
|
43
|
+
|
|
44
|
+
// Table Borders (Light)
|
|
45
|
+
tTopLeft: '┌', tTopRight: '┐', tBottomLeft: '└', tBottomRight: '┘',
|
|
46
|
+
tHorizontal: '─', tVertical: '│',
|
|
47
|
+
tTeeTop: '┬', tTeeBottom: '┴', tTee: '┼', tTeeLeft: '├', tTeeRight: '┤'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const WIDTH = 76;
|
|
51
|
+
// Column widths for the findings table (Must equal WIDTH - 4 when summed + separators)
|
|
52
|
+
// 10 + 13 + 47 + 2 borders + 2 internal separators = 76? Let's calibrate:
|
|
53
|
+
// Total internal width = WIDTH - 2 (borders) - 2 (padding) = 72
|
|
54
|
+
// Let's use: Col1: 10, Col2: 13, Col3: 45. 10+13+45 = 68. + 4 padding spaces + 2 internal bars = 74.
|
|
55
|
+
// We need exact pixel perfect match.
|
|
56
|
+
// Border: 76. Inner: 74.
|
|
57
|
+
// Row: "│ " + COL1 + " │ " + COL2 + " │ " + COL3 + " │"
|
|
58
|
+
// chars: 1+1+10 + 1+1+1 + 13 + 1+1+1 + 41 + 1+1 = 74.
|
|
59
|
+
// Let's stick to the previous column sizes but FIX the padding math.
|
|
60
|
+
const COL_1 = 10;
|
|
61
|
+
const COL_2 = 13;
|
|
62
|
+
const COL_3 = 41;
|
|
63
|
+
|
|
64
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
65
|
+
// UTILITIES
|
|
66
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
function padCenter(str, width) {
|
|
69
|
+
const visibleLen = str.length; // Simplified for no-ansi
|
|
70
|
+
const padding = Math.max(0, width - visibleLen);
|
|
71
|
+
const left = Math.floor(padding / 2);
|
|
72
|
+
const right = padding - left;
|
|
73
|
+
return ' '.repeat(left) + str + ' '.repeat(right);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function padRight(str, len) {
|
|
77
|
+
// Ensure we don't overflow if string is too long
|
|
78
|
+
const truncated = str.length > len ? str.substring(0, len - 3) + '...' : str;
|
|
79
|
+
return truncated + ' '.repeat(Math.max(0, len - truncated.length));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Fixes the "Wobbly Logo" by padding lines to a block first
|
|
83
|
+
function padLogoBlock(ascii, width) {
|
|
84
|
+
const lines = ascii.trim().split('\n');
|
|
85
|
+
const maxContentWidth = Math.max(...lines.map(l => l.length));
|
|
86
|
+
|
|
87
|
+
return lines.map(line => {
|
|
88
|
+
// 1. Make the line itself consistent width
|
|
89
|
+
const solidLine = line + ' '.repeat(maxContentWidth - line.length);
|
|
90
|
+
// 2. Center that solid block
|
|
91
|
+
return padCenter(solidLine, width);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function renderProgressBar(score, width = 20) {
|
|
96
|
+
const filled = Math.round((score / 100) * width);
|
|
97
|
+
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getMemoryUsage() {
|
|
101
|
+
const usage = process.memoryUsage();
|
|
102
|
+
return Math.round(usage.heapUsed / 1024 / 1024);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
106
|
+
// MAIN OUTPUT FORMATTER
|
|
107
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
function formatShipOutput(result) {
|
|
110
|
+
const { verdict, score = 0, blockers = [], warnings = [], duration = 0 } = result;
|
|
111
|
+
const canShip = verdict === 'SHIP';
|
|
112
|
+
const heapMB = getMemoryUsage();
|
|
113
|
+
|
|
114
|
+
const lines = [];
|
|
115
|
+
|
|
116
|
+
// 1. OUTER FRAME TOP
|
|
117
|
+
lines.push(BOX.topLeft + BOX.horizontal.repeat(WIDTH - 2) + BOX.topRight);
|
|
118
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
119
|
+
|
|
120
|
+
// 2. LOGO
|
|
121
|
+
const logoSrc = canShip ? LOGO_VIBECHECK : LOGO_NOT_SHIP_READY;
|
|
122
|
+
padLogoBlock(logoSrc, WIDTH - 2).forEach(line => {
|
|
123
|
+
lines.push(BOX.vertical + line + BOX.vertical);
|
|
124
|
+
});
|
|
125
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
126
|
+
|
|
127
|
+
// 3. TELEMETRY
|
|
128
|
+
lines.push(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft);
|
|
129
|
+
const exitTxt = canShip ? '✅ Exit: 0' : '❌ Exit: 1';
|
|
130
|
+
const abortTxt = canShip ? '' : ' 🛑 ABORTED';
|
|
131
|
+
const telemetry = `📡 TELEMETRY │ ⏱ Duration: ${duration}ms │ 📦 Heap: ${heapMB}MB │ ${exitTxt}${abortTxt}`;
|
|
132
|
+
lines.push(BOX.vertical + padCenter(telemetry, WIDTH - 2) + BOX.vertical);
|
|
133
|
+
lines.push(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft);
|
|
134
|
+
|
|
135
|
+
// 4. STATUS & SCORES
|
|
136
|
+
if (canShip) {
|
|
137
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
138
|
+
lines.push(BOX.vertical + padCenter('✅ SHIPMENT CLEARED: INTEGRITY VERIFIED', WIDTH - 2) + BOX.vertical);
|
|
139
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
140
|
+
} else {
|
|
141
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
142
|
+
lines.push(BOX.vertical + padCenter('⛔ SHIPMENT ABORTED: INTEGRITY FAILURE', WIDTH - 2) + BOX.vertical);
|
|
143
|
+
lines.push(BOX.vertical + padCenter('────────────────────────────────────────────────────────────────────', WIDTH - 2) + BOX.vertical);
|
|
144
|
+
|
|
145
|
+
const scoreBar = renderProgressBar(score, 20);
|
|
146
|
+
lines.push(BOX.vertical + padCenter(`VIOLATION SCORE [${scoreBar}] ${score} / 100`, WIDTH - 2) + BOX.vertical);
|
|
147
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
148
|
+
|
|
149
|
+
// 5. THE TABLE (FIXED ALIGNMENT)
|
|
150
|
+
lines.push(BOX.vertical + padCenter('CRITICAL BLOCKERS (VISIBLE TIER)', WIDTH - 2) + BOX.vertical);
|
|
151
|
+
|
|
152
|
+
// Construct Table Borders
|
|
153
|
+
// We need to match: " │ COL1 │ COL2 │ COL3 │ "
|
|
154
|
+
// The padding inside the outer box is 2 spaces.
|
|
155
|
+
// Inner Width = WIDTH - 2 (borders) - 4 (padding) = 70.
|
|
156
|
+
// Our columns: 10 + 13 + 41 = 64.
|
|
157
|
+
// We have 3 separators (3 chars) + 2 outer table borders (2 chars) = 5 chars.
|
|
158
|
+
// Total table width = 69. We have 1 char slack. Let's adjust COL_3 to 42.
|
|
159
|
+
|
|
160
|
+
const C1 = 10, C2 = 13, C3 = 41;
|
|
161
|
+
|
|
162
|
+
const tTop = ` ${BOX.tTopLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C3)}${BOX.tTopRight} `;
|
|
163
|
+
const tMid = ` ${BOX.tTeeLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTee}${BOX.tHorizontal.repeat(C2)}${BOX.tTee}${BOX.tHorizontal.repeat(C3)}${BOX.tTeeRight} `;
|
|
164
|
+
const tBot = ` ${BOX.tBottomLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C3)}${BOX.tBottomRight} `;
|
|
165
|
+
|
|
166
|
+
lines.push(BOX.vertical + tTop + BOX.vertical);
|
|
167
|
+
|
|
168
|
+
// Table Header
|
|
169
|
+
const header = ` ${BOX.tVertical}${padRight(' SEVERITY', C1)}${BOX.tVertical}${padRight(' COMPONENT', C2)}${BOX.tVertical}${padRight(' FAILURE CONDITION', C3)}${BOX.tVertical} `;
|
|
170
|
+
lines.push(BOX.vertical + header + BOX.vertical);
|
|
171
|
+
lines.push(BOX.vertical + tMid + BOX.vertical);
|
|
172
|
+
|
|
173
|
+
// Table Rows
|
|
174
|
+
const items = [...blockers, ...warnings].slice(0, 5);
|
|
175
|
+
items.forEach(item => {
|
|
176
|
+
// Fix Emoji Length: '🛑 FATAL' is 8 chars.
|
|
177
|
+
// We manually construct the string to ensure it fits the 10 char column.
|
|
178
|
+
const severity = item.severity === 'block' ? '🛑 FATAL ' : '🟡 WARN '; // 9 visual chars?
|
|
179
|
+
// Note: 🛑 is 2 chars wide visually. " FATAL " is 7. Total 9.
|
|
180
|
+
// If column is 10, we add 1 space.
|
|
181
|
+
|
|
182
|
+
const comp = padRight(' ' + (getComponentName(item.category)), C2);
|
|
183
|
+
const desc = padRight(' ' + (item.title || item.message), C3);
|
|
184
|
+
|
|
185
|
+
// Manual row construction
|
|
186
|
+
const row = ` ${BOX.tVertical}${padRight(severity, C1)}${BOX.tVertical}${comp}${BOX.tVertical}${desc}${BOX.tVertical} `;
|
|
187
|
+
lines.push(BOX.vertical + row + BOX.vertical);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
lines.push(BOX.vertical + tBot + BOX.vertical);
|
|
191
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
192
|
+
|
|
193
|
+
// 6. LOCKED SECTION
|
|
194
|
+
lines.push(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft);
|
|
195
|
+
lines.push(BOX.vertical + padCenter('🔒 ROOT CAUSE ANALYSIS: ACCESS DENIED', WIDTH - 2) + BOX.vertical);
|
|
196
|
+
lines.push(BOX.vertical + padCenter('────────────────────────────────────────────────────────────────────', WIDTH - 2) + BOX.vertical);
|
|
197
|
+
lines.push(BOX.vertical + padCenter('The specific lines of code causing these breaks are deeper in the', WIDTH - 2) + BOX.vertical);
|
|
198
|
+
lines.push(BOX.vertical + padCenter('dependency graph. Starter/Pro context is required to trace them.', WIDTH - 2) + BOX.vertical);
|
|
199
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
200
|
+
|
|
201
|
+
// Redacted bars
|
|
202
|
+
const bar = '░'.repeat(60);
|
|
203
|
+
lines.push(BOX.vertical + padCenter(bar, WIDTH - 2) + BOX.vertical);
|
|
204
|
+
|
|
205
|
+
const redacted = [
|
|
206
|
+
'░░ [REDACTED] 🔒 Schema definition mismatch in node_modules/@api...',
|
|
207
|
+
'░░ [REDACTED] 🔒 Environment leakage detected in webpack.config...',
|
|
208
|
+
'░░ [REDACTED] 🔒 Route /checkout defined in backend but not exp...'
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
redacted.forEach(r => {
|
|
212
|
+
lines.push(BOX.vertical + padCenter(r, WIDTH - 2) + BOX.vertical);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
lines.push(BOX.vertical + padCenter(bar, WIDTH - 2) + BOX.vertical);
|
|
216
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
217
|
+
|
|
218
|
+
lines.push(BOX.vertical + padCenter('👉 RESOLUTION:', WIDTH - 2) + BOX.vertical);
|
|
219
|
+
lines.push(BOX.vertical + padCenter('Run `vibecheck upgrade starter` to reveal root causes.', WIDTH - 2) + BOX.vertical);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 7. BOTTOM FRAME
|
|
223
|
+
lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
|
|
224
|
+
lines.push(BOX.bottomLeft + BOX.horizontal.repeat(WIDTH - 2) + BOX.bottomRight);
|
|
225
|
+
|
|
226
|
+
return lines.join('\n');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getComponentName(category) {
|
|
230
|
+
const map = {
|
|
231
|
+
'MissingRoute': 'routes',
|
|
232
|
+
'EnvContract': 'env_sec',
|
|
233
|
+
'FakeSuccess': 'ui_flow',
|
|
234
|
+
'GhostAuth': 'auth',
|
|
235
|
+
};
|
|
236
|
+
return map[category] || 'unknown';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = { formatShipOutput };
|