@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
|
@@ -1,189 +1,193 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified Output System
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* This is what makes vibecheck feel "enterprise-grade".
|
|
2
|
+
* Unified Output System - World Class
|
|
3
|
+
* * The central nervous system for Vibecheck's CLI output.
|
|
4
|
+
* Delivers consistent, high-fidelity visualization and strict exit code compliance.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
let EXIT_CODES, formatVerdictOutput;
|
|
7
|
+
"use strict";
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
13
|
+
// CONFIGURATION & CONSTANTS
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
const EXIT_CODES = {
|
|
16
17
|
SHIP: 0, // Ready to ship, no blockers
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
MISCONFIG: 2, // Configuration error = block
|
|
22
|
-
INTERNAL: 2, // Internal error = block (safe default)
|
|
18
|
+
WARN: 1, // Warnings found (allowed via flag)
|
|
19
|
+
BLOCK: 2, // Blockers found (hard stop)
|
|
20
|
+
INTERNAL: 2, // Internal error (hard stop)
|
|
21
|
+
MISCONFIG: 2, // Configuration error (hard stop)
|
|
23
22
|
};
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
verdict.verdict === 'FAIL' ? `${c.red}✗${c.reset}` :
|
|
45
|
-
`${c.yellow}⚠${c.reset}`;
|
|
46
|
-
|
|
47
|
-
let output = `\n${icon} ${c.bold}${verdict.verdict}${c.reset}\n`;
|
|
48
|
-
|
|
49
|
-
if (verdict.summary) {
|
|
50
|
-
output += `\n${c.dim}Summary:${c.reset}\n`;
|
|
51
|
-
output += ` Blockers: ${verdict.summary.blockers || 0}\n`;
|
|
52
|
-
output += ` Warnings: ${verdict.summary.warnings || 0}\n`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (verdict.findings && verdict.findings.length > 0) {
|
|
56
|
-
output += `\n${c.dim}Findings:${c.reset}\n`;
|
|
57
|
-
for (const finding of verdict.findings.slice(0, 10)) {
|
|
58
|
-
const sevColor = finding.severity === 'critical' ? c.red :
|
|
59
|
-
finding.severity === 'high' ? c.red :
|
|
60
|
-
finding.severity === 'medium' ? c.yellow : c.dim;
|
|
61
|
-
output += ` ${sevColor}[${finding.severity?.toUpperCase() || 'INFO'}]${c.reset} ${finding.title || finding.message || 'Unknown issue'}\n`;
|
|
62
|
-
if (finding.file) {
|
|
63
|
-
output += ` ${c.cyan}${finding.file}${finding.line ? `:${finding.line}` : ''}${c.reset}\n`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (verdict.findings.length > 10) {
|
|
67
|
-
output += ` ${c.dim}... and ${verdict.findings.length - 10} more${c.reset}\n`;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return output;
|
|
72
|
-
};
|
|
24
|
+
const WIDTH = 76;
|
|
25
|
+
|
|
26
|
+
const BOX = {
|
|
27
|
+
tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║',
|
|
28
|
+
trT: '╠', tlT: '╣',
|
|
29
|
+
// Light (Inner)
|
|
30
|
+
ltl: '┌', ltr: '┐', lbl: '└', lbr: '┘', lh: '─', lv: '│',
|
|
31
|
+
lt: '┬', lb: '┴', lx: '┼', ltrT: '├', ltlT: '┤'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
// VISUAL RENDERING ENGINE
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
37
|
+
|
|
38
|
+
function padCenter(str, width) {
|
|
39
|
+
const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
|
|
40
|
+
const padding = Math.max(0, width - visibleLen);
|
|
41
|
+
const left = Math.floor(padding / 2);
|
|
42
|
+
return ' '.repeat(left) + str + ' '.repeat(padding - left);
|
|
73
43
|
}
|
|
74
44
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const { verbose = false, json = false } = options;
|
|
80
|
-
|
|
81
|
-
if (json) {
|
|
82
|
-
return JSON.stringify(result, null, 2);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return formatVerdictOutput(result.verdict, { verbose, json });
|
|
45
|
+
function padRight(str, len) {
|
|
46
|
+
const visibleLen = str.length;
|
|
47
|
+
const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
|
|
48
|
+
return truncated + ' '.repeat(Math.max(0, len - truncated.length));
|
|
86
49
|
}
|
|
87
50
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
*/
|
|
92
|
-
function getExitCode(verdict) {
|
|
93
|
-
if (!verdict) return EXIT_CODES.BLOCK; // Safe default
|
|
94
|
-
|
|
95
|
-
switch (verdict.verdict) {
|
|
96
|
-
case 'SHIP':
|
|
97
|
-
case 'PASS':
|
|
98
|
-
return EXIT_CODES.SHIP; // 0
|
|
99
|
-
case 'WARN':
|
|
100
|
-
return EXIT_CODES.WARN; // 1
|
|
101
|
-
case 'BLOCK':
|
|
102
|
-
case 'FAIL':
|
|
103
|
-
return EXIT_CODES.BLOCK; // 2
|
|
104
|
-
case 'ERROR':
|
|
105
|
-
case 'MISCONFIG':
|
|
106
|
-
return EXIT_CODES.BLOCK; // 2 (safe default)
|
|
107
|
-
default:
|
|
108
|
-
return EXIT_CODES.BLOCK; // 2 (safe default)
|
|
109
|
-
}
|
|
51
|
+
function renderProgressBar(score, width = 20) {
|
|
52
|
+
const filled = Math.round((score / 100) * width);
|
|
53
|
+
return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(width - filled));
|
|
110
54
|
}
|
|
111
55
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
56
|
+
// --- The Core Formatter ---
|
|
57
|
+
|
|
58
|
+
function renderVerdict(verdictData, options = {}) {
|
|
59
|
+
const {
|
|
60
|
+
score = 0,
|
|
61
|
+
findings = [],
|
|
62
|
+
duration = 0,
|
|
63
|
+
scannedFiles = 0
|
|
64
|
+
} = verdictData;
|
|
65
|
+
|
|
66
|
+
const lines = [];
|
|
67
|
+
const hasIssues = findings.length > 0;
|
|
117
68
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
69
|
+
// 1. FRAME HEADER
|
|
70
|
+
lines.push(chalk.gray(BOX.tl + BOX.h.repeat(WIDTH - 2) + BOX.tr));
|
|
71
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
72
|
+
|
|
73
|
+
// 2. CLEAN HEADER (No ASCII Logo)
|
|
74
|
+
const titleColor = hasIssues ? chalk.red.bold : chalk.cyan.bold;
|
|
75
|
+
const subTitle = hasIssues ? 'INTEGRITY SCAN • ISSUES DETECTED' : 'INTEGRITY SCAN • CLEAN';
|
|
121
76
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
77
|
+
// Add some vertical padding for elegance
|
|
78
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
79
|
+
lines.push(chalk.gray(BOX.v) + padCenter(titleColor(subTitle), WIDTH - 2) + chalk.gray(BOX.v));
|
|
80
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
81
|
+
|
|
82
|
+
// 3. TELEMETRY
|
|
83
|
+
lines.push(chalk.gray(BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT));
|
|
84
|
+
const heapMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
85
|
+
const stats = `📡 TELEMETRY │ ⏱ ${duration}ms │ 📂 ${scannedFiles || '?'} Files │ 📦 ${heapMB}MB`;
|
|
86
|
+
lines.push(chalk.gray(BOX.v) + padCenter(stats, WIDTH - 2) + chalk.gray(BOX.v));
|
|
87
|
+
lines.push(chalk.gray(BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT));
|
|
88
|
+
|
|
89
|
+
if (!hasIssues) {
|
|
90
|
+
// CLEAN STATE
|
|
91
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
92
|
+
lines.push(chalk.gray(BOX.v) + padCenter(chalk.green('✅ NO STATIC ISSUES FOUND'), WIDTH - 2) + chalk.gray(BOX.v));
|
|
93
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
94
|
+
} else {
|
|
95
|
+
// ISSUES STATE
|
|
96
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
97
|
+
const scoreBar = renderProgressBar(score, 20);
|
|
98
|
+
lines.push(chalk.gray(BOX.v) + padCenter(`HEALTH SCORE [${scoreBar}] ${score} / 100`, WIDTH + 18) + chalk.gray(BOX.v));
|
|
99
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
100
|
+
|
|
101
|
+
// TABLE HEADER
|
|
102
|
+
lines.push(chalk.gray(BOX.v) + padCenter('DETECTED VULNERABILITIES', WIDTH - 2) + chalk.gray(BOX.v));
|
|
103
|
+
|
|
104
|
+
// Hardcoded column widths for perfect alignment
|
|
105
|
+
const C1=10, C2=13, C3=41;
|
|
106
|
+
|
|
107
|
+
const tTop = ` ${BOX.ltl}${BOX.lh.repeat(C1)}${BOX.lt}${BOX.lh.repeat(C2)}${BOX.lt}${BOX.lh.repeat(C3)}${BOX.ltr} `;
|
|
108
|
+
const tMid = ` ${BOX.ltrT}${BOX.lh.repeat(C1)}${BOX.lx}${BOX.lh.repeat(C2)}${BOX.lx}${BOX.lh.repeat(C3)}${BOX.ltlT} `;
|
|
109
|
+
const tBot = ` ${BOX.lbl}${BOX.lh.repeat(C1)}${BOX.lb}${BOX.lh.repeat(C2)}${BOX.lb}${BOX.lh.repeat(C3)}${BOX.lbr} `;
|
|
110
|
+
|
|
111
|
+
lines.push(chalk.gray(BOX.v) + chalk.gray(tTop) + chalk.gray(BOX.v));
|
|
112
|
+
const header = ` ${BOX.lv}${padRight(' SEVERITY', C1)}${BOX.lv}${padRight(' TYPE', C2)}${BOX.lv}${padRight(' FINDING', C3)}${BOX.lv} `;
|
|
113
|
+
lines.push(chalk.gray(BOX.v) + chalk.bold(header) + chalk.gray(BOX.v));
|
|
114
|
+
lines.push(chalk.gray(BOX.v) + chalk.gray(tMid) + chalk.gray(BOX.v));
|
|
115
|
+
|
|
116
|
+
// ROWS
|
|
117
|
+
findings.slice(0, 5).forEach(f => {
|
|
118
|
+
const sev = (f.severity === 'critical' || f.severity === 'BLOCK') ? chalk.red('🛑 CRIT ') : chalk.yellow('🟡 WARN ');
|
|
119
|
+
const row = ` ${chalk.gray(BOX.lv)}${sev}${chalk.gray(BOX.lv)}${padRight(' '+(f.category||'General'), C2)}${chalk.gray(BOX.lv)}${padRight(' '+(f.message||f.title), C3)}${chalk.gray(BOX.lv)} `;
|
|
120
|
+
lines.push(chalk.gray(BOX.v) + row + chalk.gray(BOX.v));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
lines.push(chalk.gray(BOX.v) + chalk.gray(tBot) + chalk.gray(BOX.v));
|
|
124
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
125
|
+
|
|
126
|
+
// UPSELL (REDACTED STYLE)
|
|
127
|
+
lines.push(chalk.gray(BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT));
|
|
128
|
+
const lockTitle = chalk.white.bold('🔒 DEEP SCAN ANALYSIS: ') + chalk.gray('UNAVAILABLE');
|
|
129
|
+
lines.push(chalk.gray(BOX.v) + padCenter(lockTitle, WIDTH + 9) + chalk.gray(BOX.v));
|
|
130
|
+
lines.push(chalk.gray(BOX.v) + padCenter(chalk.gray('─'.repeat(60)), WIDTH + 9) + chalk.gray(BOX.v));
|
|
131
|
+
|
|
132
|
+
// Redacted lines
|
|
133
|
+
['Runtime API Schema Validation', 'Live Auth Boundary Verification', 'Env Config Resolution'].forEach(txt => {
|
|
134
|
+
const line = chalk.dim(`░░ [REDACTED] 🔒 ${txt}`);
|
|
135
|
+
lines.push(chalk.gray(BOX.v) + padCenter(line, WIDTH + 9) + chalk.gray(BOX.v));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
139
|
+
lines.push(chalk.gray(BOX.v) + padCenter('Run ' + chalk.cyan('vibecheck upgrade pro') + ' to unlock.', WIDTH + 9) + chalk.gray(BOX.v));
|
|
142
140
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
};
|
|
141
|
+
|
|
142
|
+
// BOTTOM FRAME
|
|
143
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
144
|
+
lines.push(chalk.gray(BOX.bl + BOX.h.repeat(WIDTH - 2) + BOX.br));
|
|
145
|
+
|
|
146
|
+
return lines.join('\n');
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return 'INTERNAL';
|
|
149
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
150
|
+
// PUBLIC API
|
|
151
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
152
|
+
|
|
153
|
+
function formatScanOutput(result, options = {}) {
|
|
154
|
+
const { json = false } = options;
|
|
155
|
+
if (json) return JSON.stringify(result, null, 2);
|
|
156
|
+
return renderVerdict(result, options);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getExitCode(verdict) {
|
|
160
|
+
if (!verdict) return EXIT_CODES.BLOCK;
|
|
161
|
+
const v = (typeof verdict === 'string' ? verdict : verdict.verdict).toUpperCase();
|
|
162
|
+
if (v === 'SHIP' || v === 'PASS') return EXIT_CODES.SHIP;
|
|
163
|
+
if (v === 'WARN') return EXIT_CODES.WARN;
|
|
164
|
+
return EXIT_CODES.BLOCK;
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
/**
|
|
172
|
-
* Print error with proper formatting
|
|
173
|
-
*/
|
|
174
167
|
function printError(error, context = '') {
|
|
175
|
-
const
|
|
168
|
+
const isConfig = error.message.toLowerCase().includes('config') || error.code === 'ENOENT';
|
|
169
|
+
const exitCode = isConfig ? EXIT_CODES.MISCONFIG : EXIT_CODES.INTERNAL;
|
|
176
170
|
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
const lines = [];
|
|
172
|
+
lines.push('');
|
|
173
|
+
lines.push(chalk.bgRed.white.bold(` 🛑 SYSTEM ALERT: ${context || 'INTERNAL_ERROR'} `));
|
|
174
|
+
lines.push(chalk.red('─'.repeat(WIDTH)));
|
|
175
|
+
lines.push(`${chalk.bold('Error:')} ${error.message}`);
|
|
176
|
+
if (error.code) lines.push(`${chalk.bold('Code:')} ${error.code}`);
|
|
177
|
+
lines.push('');
|
|
179
178
|
|
|
180
|
-
|
|
179
|
+
const fixCmd = isConfig ? 'vibecheck doctor --fix' : 'vibecheck doctor';
|
|
180
|
+
lines.push(chalk.gray('Recovery Step:'));
|
|
181
|
+
lines.push(`$ ${chalk.cyan(fixCmd)}`);
|
|
182
|
+
lines.push('');
|
|
183
|
+
|
|
184
|
+
console.error(lines.join('\n'));
|
|
185
|
+
return exitCode;
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
module.exports = {
|
|
184
189
|
formatScanOutput,
|
|
185
190
|
getExitCode,
|
|
186
|
-
handleError,
|
|
187
191
|
printError,
|
|
188
192
|
EXIT_CODES,
|
|
189
193
|
};
|
package/bin/runners/runShip.js
CHANGED
|
@@ -54,7 +54,7 @@ const {
|
|
|
54
54
|
} = require("./lib/terminal-ui");
|
|
55
55
|
|
|
56
56
|
const {
|
|
57
|
-
formatShipOutput,
|
|
57
|
+
formatShipOutput: formatShipOutputLegacy,
|
|
58
58
|
renderVerdictCard,
|
|
59
59
|
renderFixModeHeader,
|
|
60
60
|
renderFixResults,
|
|
@@ -64,6 +64,10 @@ const {
|
|
|
64
64
|
shipIcons,
|
|
65
65
|
} = require("./lib/ship-output");
|
|
66
66
|
|
|
67
|
+
const {
|
|
68
|
+
formatShipOutput,
|
|
69
|
+
} = require("./lib/ship-output-enterprise");
|
|
70
|
+
|
|
67
71
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
68
72
|
// PREMIUM BANNER
|
|
69
73
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1031,7 +1035,7 @@ async function runShip(args, context = {}) {
|
|
|
1031
1035
|
if (spinner) spinner.succeed('Safe fixes applied');
|
|
1032
1036
|
}
|
|
1033
1037
|
|
|
1034
|
-
// Human-readable output using ship-output module
|
|
1038
|
+
// Human-readable output using enterprise ship-output module
|
|
1035
1039
|
const result = {
|
|
1036
1040
|
verdict,
|
|
1037
1041
|
score: results.score,
|
|
@@ -1047,14 +1051,9 @@ async function runShip(args, context = {}) {
|
|
|
1047
1051
|
// Get current tier for output formatting
|
|
1048
1052
|
const currentTier = context?.authInfo?.access?.tier || getCurrentTier() || "free";
|
|
1049
1053
|
|
|
1054
|
+
// Use enterprise format
|
|
1050
1055
|
console.log(formatShipOutput(result, {
|
|
1051
|
-
verbose: opts.verbose,
|
|
1052
|
-
showFix: opts.fix,
|
|
1053
|
-
showBadge: opts.badge,
|
|
1054
|
-
outputDir,
|
|
1055
|
-
projectPath,
|
|
1056
1056
|
tier: currentTier,
|
|
1057
|
-
isVerified: opts.withRuntime || false, // Reality testing = verified
|
|
1058
1057
|
}));
|
|
1059
1058
|
|
|
1060
1059
|
// Badge file generation (STARTER+ only)
|