@vibecheckai/cli 3.2.4 → 3.2.6
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/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/api-client.js +269 -0
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runAgent.d.ts +5 -0
- package/bin/runners/runFirewall.d.ts +5 -0
- package/bin/runners/runFirewallHook.d.ts +5 -0
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runScan.js +82 -0
- package/bin/runners/runTruth.d.ts +5 -0
- package/bin/vibecheck.js +45 -20
- package/mcp-server/index.js +85 -0
- package/mcp-server/lib/api-client.js +269 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/tier-auth.js +173 -113
- package/mcp-server/tools/index.js +72 -72
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/package.json +1 -1
|
@@ -1,540 +1,540 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI UI Library — Beautiful Interactive Experience
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Stunning ASCII art headers
|
|
6
|
-
* - Beautiful bordered tables
|
|
7
|
-
* - Interactive menus
|
|
8
|
-
* - Progress bars and spinners
|
|
9
|
-
* - Color gradients
|
|
10
|
-
* - Box drawing characters
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const readline = require("readline");
|
|
14
|
-
|
|
15
|
-
// Extended ANSI color palette
|
|
16
|
-
const colors = {
|
|
17
|
-
reset: "\x1b[0m",
|
|
18
|
-
bold: "\x1b[1m",
|
|
19
|
-
dim: "\x1b[2m",
|
|
20
|
-
italic: "\x1b[3m",
|
|
21
|
-
underline: "\x1b[4m",
|
|
22
|
-
blink: "\x1b[5m",
|
|
23
|
-
inverse: "\x1b[7m",
|
|
24
|
-
hidden: "\x1b[8m",
|
|
25
|
-
strikethrough: "\x1b[9m",
|
|
26
|
-
|
|
27
|
-
// Foreground colors
|
|
28
|
-
black: "\x1b[30m",
|
|
29
|
-
red: "\x1b[31m",
|
|
30
|
-
green: "\x1b[32m",
|
|
31
|
-
yellow: "\x1b[33m",
|
|
32
|
-
blue: "\x1b[34m",
|
|
33
|
-
magenta: "\x1b[35m",
|
|
34
|
-
cyan: "\x1b[36m",
|
|
35
|
-
white: "\x1b[37m",
|
|
36
|
-
|
|
37
|
-
// Bright foreground
|
|
38
|
-
brightBlack: "\x1b[90m",
|
|
39
|
-
brightRed: "\x1b[91m",
|
|
40
|
-
brightGreen: "\x1b[92m",
|
|
41
|
-
brightYellow: "\x1b[93m",
|
|
42
|
-
brightBlue: "\x1b[94m",
|
|
43
|
-
brightMagenta: "\x1b[95m",
|
|
44
|
-
brightCyan: "\x1b[96m",
|
|
45
|
-
brightWhite: "\x1b[97m",
|
|
46
|
-
|
|
47
|
-
// Background colors
|
|
48
|
-
bgBlack: "\x1b[40m",
|
|
49
|
-
bgRed: "\x1b[41m",
|
|
50
|
-
bgGreen: "\x1b[42m",
|
|
51
|
-
bgYellow: "\x1b[43m",
|
|
52
|
-
bgBlue: "\x1b[44m",
|
|
53
|
-
bgMagenta: "\x1b[45m",
|
|
54
|
-
bgCyan: "\x1b[46m",
|
|
55
|
-
bgWhite: "\x1b[47m",
|
|
56
|
-
|
|
57
|
-
// Bright background
|
|
58
|
-
bgBrightBlack: "\x1b[100m",
|
|
59
|
-
bgBrightRed: "\x1b[101m",
|
|
60
|
-
bgBrightGreen: "\x1b[102m",
|
|
61
|
-
bgBrightYellow: "\x1b[103m",
|
|
62
|
-
bgBrightBlue: "\x1b[104m",
|
|
63
|
-
bgBrightMagenta: "\x1b[105m",
|
|
64
|
-
bgBrightCyan: "\x1b[106m",
|
|
65
|
-
bgBrightWhite: "\x1b[107m",
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const c = colors;
|
|
69
|
-
|
|
70
|
-
// Box drawing characters
|
|
71
|
-
const box = {
|
|
72
|
-
// Single line
|
|
73
|
-
topLeft: "┌",
|
|
74
|
-
topRight: "┐",
|
|
75
|
-
bottomLeft: "└",
|
|
76
|
-
bottomRight: "┘",
|
|
77
|
-
horizontal: "─",
|
|
78
|
-
vertical: "│",
|
|
79
|
-
leftT: "├",
|
|
80
|
-
rightT: "┤",
|
|
81
|
-
topT: "┬",
|
|
82
|
-
bottomT: "┴",
|
|
83
|
-
cross: "┼",
|
|
84
|
-
|
|
85
|
-
// Double line
|
|
86
|
-
dTopLeft: "╔",
|
|
87
|
-
dTopRight: "╗",
|
|
88
|
-
dBottomLeft: "╚",
|
|
89
|
-
dBottomRight: "╝",
|
|
90
|
-
dHorizontal: "═",
|
|
91
|
-
dVertical: "║",
|
|
92
|
-
dLeftT: "╠",
|
|
93
|
-
dRightT: "╣",
|
|
94
|
-
dTopT: "╦",
|
|
95
|
-
dBottomT: "╩",
|
|
96
|
-
dCross: "╬",
|
|
97
|
-
|
|
98
|
-
// Rounded
|
|
99
|
-
rTopLeft: "╭",
|
|
100
|
-
rTopRight: "╮",
|
|
101
|
-
rBottomLeft: "╰",
|
|
102
|
-
rBottomRight: "╯",
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// Spinner frames
|
|
106
|
-
const spinners = {
|
|
107
|
-
dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
108
|
-
line: ["-", "\\", "|", "/"],
|
|
109
|
-
circle: ["◐", "◓", "◑", "◒"],
|
|
110
|
-
arc: ["◜", "◠", "◝", "◞", "◡", "◟"],
|
|
111
|
-
box: ["▖", "▘", "▝", "▗"],
|
|
112
|
-
bounce: ["⠁", "⠂", "⠄", "⠂"],
|
|
113
|
-
pulse: ["█", "▓", "▒", "░", "▒", "▓"],
|
|
114
|
-
arrows: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
|
|
115
|
-
star: ["✶", "✷", "✸", "✹", "✺", "✹", "✸", "✷"],
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// Progress bar characters
|
|
119
|
-
const progressChars = {
|
|
120
|
-
filled: "█",
|
|
121
|
-
empty: "░",
|
|
122
|
-
gradient: ["░", "▒", "▓", "█"],
|
|
123
|
-
smooth: ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"],
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Create a beautiful header with ASCII art border
|
|
128
|
-
*/
|
|
129
|
-
function createHeader(title, subtitle = "", options = {}) {
|
|
130
|
-
const width = options.width || 70;
|
|
131
|
-
const style = options.style || "double"; // single, double, rounded
|
|
132
|
-
const color = options.color || c.cyan;
|
|
133
|
-
|
|
134
|
-
const chars = style === "double" ? {
|
|
135
|
-
tl: box.dTopLeft, tr: box.dTopRight, bl: box.dBottomLeft, br: box.dBottomRight,
|
|
136
|
-
h: box.dHorizontal, v: box.dVertical
|
|
137
|
-
} : style === "rounded" ? {
|
|
138
|
-
tl: box.rTopLeft, tr: box.rTopRight, bl: box.rBottomLeft, br: box.rBottomRight,
|
|
139
|
-
h: box.horizontal, v: box.vertical
|
|
140
|
-
} : {
|
|
141
|
-
tl: box.topLeft, tr: box.topRight, bl: box.bottomLeft, br: box.bottomRight,
|
|
142
|
-
h: box.horizontal, v: box.vertical
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const innerWidth = width - 2;
|
|
146
|
-
const titlePad = Math.floor((innerWidth - title.length) / 2);
|
|
147
|
-
const subtitlePad = Math.floor((innerWidth - subtitle.length) / 2);
|
|
148
|
-
|
|
149
|
-
let output = "";
|
|
150
|
-
output += `${color}${chars.tl}${chars.h.repeat(innerWidth)}${chars.tr}${c.reset}\n`;
|
|
151
|
-
output += `${color}${chars.v}${c.reset}${" ".repeat(innerWidth)}${color}${chars.v}${c.reset}\n`;
|
|
152
|
-
output += `${color}${chars.v}${c.reset}${" ".repeat(titlePad)}${c.bold}${c.brightWhite}${title}${c.reset}${" ".repeat(innerWidth - titlePad - title.length)}${color}${chars.v}${c.reset}\n`;
|
|
153
|
-
|
|
154
|
-
if (subtitle) {
|
|
155
|
-
output += `${color}${chars.v}${c.reset}${" ".repeat(subtitlePad)}${c.dim}${subtitle}${c.reset}${" ".repeat(innerWidth - subtitlePad - subtitle.length)}${color}${chars.v}${c.reset}\n`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
output += `${color}${chars.v}${c.reset}${" ".repeat(innerWidth)}${color}${chars.v}${c.reset}\n`;
|
|
159
|
-
output += `${color}${chars.bl}${chars.h.repeat(innerWidth)}${chars.br}${c.reset}`;
|
|
160
|
-
|
|
161
|
-
return output;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Create a beautiful table
|
|
166
|
-
*/
|
|
167
|
-
function createTable(headers, rows, options = {}) {
|
|
168
|
-
const style = options.style || "rounded";
|
|
169
|
-
const headerColor = options.headerColor || c.cyan;
|
|
170
|
-
const borderColor = options.borderColor || c.dim;
|
|
171
|
-
|
|
172
|
-
// Calculate column widths
|
|
173
|
-
const colWidths = headers.map((h, i) => {
|
|
174
|
-
const headerLen = stripAnsi(h).length;
|
|
175
|
-
const maxRowLen = Math.max(...rows.map(r => stripAnsi(String(r[i] || "")).length));
|
|
176
|
-
return Math.max(headerLen, maxRowLen) + 2;
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const chars = style === "rounded" ? {
|
|
180
|
-
tl: box.rTopLeft, tr: box.rTopRight, bl: box.rBottomLeft, br: box.rBottomRight,
|
|
181
|
-
h: box.horizontal, v: box.vertical, lt: box.leftT, rt: box.rightT,
|
|
182
|
-
tt: box.topT, bt: box.bottomT, cr: box.cross
|
|
183
|
-
} : {
|
|
184
|
-
tl: box.topLeft, tr: box.topRight, bl: box.bottomLeft, br: box.bottomRight,
|
|
185
|
-
h: box.horizontal, v: box.vertical, lt: box.leftT, rt: box.rightT,
|
|
186
|
-
tt: box.topT, bt: box.bottomT, cr: box.cross
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
let output = "";
|
|
190
|
-
|
|
191
|
-
// Top border
|
|
192
|
-
output += borderColor + chars.tl;
|
|
193
|
-
output += colWidths.map(w => chars.h.repeat(w)).join(chars.tt);
|
|
194
|
-
output += chars.tr + c.reset + "\n";
|
|
195
|
-
|
|
196
|
-
// Header row
|
|
197
|
-
output += borderColor + chars.v + c.reset;
|
|
198
|
-
headers.forEach((h, i) => {
|
|
199
|
-
const padded = padCenter(h, colWidths[i]);
|
|
200
|
-
output += `${headerColor}${c.bold}${padded}${c.reset}${borderColor}${chars.v}${c.reset}`;
|
|
201
|
-
});
|
|
202
|
-
output += "\n";
|
|
203
|
-
|
|
204
|
-
// Header separator
|
|
205
|
-
output += borderColor + chars.lt;
|
|
206
|
-
output += colWidths.map(w => chars.h.repeat(w)).join(chars.cr);
|
|
207
|
-
output += chars.rt + c.reset + "\n";
|
|
208
|
-
|
|
209
|
-
// Data rows
|
|
210
|
-
rows.forEach((row, rowIdx) => {
|
|
211
|
-
output += borderColor + chars.v + c.reset;
|
|
212
|
-
row.forEach((cell, i) => {
|
|
213
|
-
const cellStr = String(cell || "");
|
|
214
|
-
const padded = padCenter(cellStr, colWidths[i]);
|
|
215
|
-
output += `${padded}${borderColor}${chars.v}${c.reset}`;
|
|
216
|
-
});
|
|
217
|
-
output += "\n";
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Bottom border
|
|
221
|
-
output += borderColor + chars.bl;
|
|
222
|
-
output += colWidths.map(w => chars.h.repeat(w)).join(chars.bt);
|
|
223
|
-
output += chars.br + c.reset;
|
|
224
|
-
|
|
225
|
-
return output;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Create a beautiful progress bar
|
|
230
|
-
*/
|
|
231
|
-
function createProgressBar(percent, options = {}) {
|
|
232
|
-
const width = options.width || 40;
|
|
233
|
-
const showPercent = options.showPercent !== false;
|
|
234
|
-
const showLabel = options.label || "";
|
|
235
|
-
const color = options.color || c.green;
|
|
236
|
-
|
|
237
|
-
const filled = Math.round((percent / 100) * width);
|
|
238
|
-
const empty = width - filled;
|
|
239
|
-
|
|
240
|
-
let bar = "";
|
|
241
|
-
|
|
242
|
-
// Gradient effect
|
|
243
|
-
for (let i = 0; i < filled; i++) {
|
|
244
|
-
const charIdx = Math.min(3, Math.floor((i / width) * 4));
|
|
245
|
-
bar += color + progressChars.gradient[charIdx];
|
|
246
|
-
}
|
|
247
|
-
bar += c.dim;
|
|
248
|
-
for (let i = 0; i < empty; i++) {
|
|
249
|
-
bar += progressChars.empty;
|
|
250
|
-
}
|
|
251
|
-
bar += c.reset;
|
|
252
|
-
|
|
253
|
-
let output = "";
|
|
254
|
-
if (showLabel) {
|
|
255
|
-
output += `${showLabel} `;
|
|
256
|
-
}
|
|
257
|
-
output += `${c.dim}[${c.reset}${bar}${c.dim}]${c.reset}`;
|
|
258
|
-
if (showPercent) {
|
|
259
|
-
output += ` ${color}${percent.toFixed(0)}%${c.reset}`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return output;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Create a category score display with visual bars
|
|
267
|
-
*/
|
|
268
|
-
function createScoreCard(categories, options = {}) {
|
|
269
|
-
const width = options.width || 50;
|
|
270
|
-
const barWidth = options.barWidth || 25;
|
|
271
|
-
|
|
272
|
-
let output = "";
|
|
273
|
-
|
|
274
|
-
for (const [name, score] of Object.entries(categories)) {
|
|
275
|
-
const color = score >= 80 ? c.green : score >= 60 ? c.yellow : c.red;
|
|
276
|
-
const icon = score >= 80 ? "✓" : score >= 60 ? "!" : "✗";
|
|
277
|
-
|
|
278
|
-
const filled = Math.round((score / 100) * barWidth);
|
|
279
|
-
const empty = barWidth - filled;
|
|
280
|
-
const bar = color + progressChars.filled.repeat(filled) + c.dim + progressChars.empty.repeat(empty) + c.reset;
|
|
281
|
-
|
|
282
|
-
const nameStr = name.padEnd(15);
|
|
283
|
-
const scoreStr = `${score}%`.padStart(4);
|
|
284
|
-
|
|
285
|
-
output += ` ${c.dim}${icon}${c.reset} ${nameStr} ${bar} ${color}${scoreStr}${c.reset}\n`;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return output;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Create an interactive menu
|
|
293
|
-
*/
|
|
294
|
-
async function createMenu(title, options, config = {}) {
|
|
295
|
-
const menuColor = config.color || c.cyan;
|
|
296
|
-
|
|
297
|
-
return new Promise((resolve) => {
|
|
298
|
-
console.log("");
|
|
299
|
-
console.log(createHeader(title, config.subtitle || "", { color: menuColor, style: "rounded" }));
|
|
300
|
-
console.log("");
|
|
301
|
-
|
|
302
|
-
options.forEach((opt, i) => {
|
|
303
|
-
const num = `${i + 1}`.padStart(2);
|
|
304
|
-
const icon = opt.icon || "›";
|
|
305
|
-
console.log(` ${menuColor}${num}${c.reset} ${icon} ${c.bold}${opt.label}${c.reset}`);
|
|
306
|
-
if (opt.description) {
|
|
307
|
-
console.log(` ${c.dim}${opt.description}${c.reset}`);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
console.log("");
|
|
312
|
-
console.log(` ${c.dim}${options.length + 1}. Exit${c.reset}`);
|
|
313
|
-
console.log("");
|
|
314
|
-
|
|
315
|
-
const rl = readline.createInterface({
|
|
316
|
-
input: process.stdin,
|
|
317
|
-
output: process.stdout,
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
rl.question(` ${menuColor}›${c.reset} Select an option: `, (answer) => {
|
|
321
|
-
rl.close();
|
|
322
|
-
const choice = parseInt(answer, 10);
|
|
323
|
-
|
|
324
|
-
if (choice >= 1 && choice <= options.length) {
|
|
325
|
-
resolve(options[choice - 1]);
|
|
326
|
-
} else {
|
|
327
|
-
resolve(null);
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Create a spinner
|
|
335
|
-
*/
|
|
336
|
-
function createSpinner(message, type = "dots") {
|
|
337
|
-
const frames = spinners[type] || spinners.dots;
|
|
338
|
-
let frameIdx = 0;
|
|
339
|
-
let interval = null;
|
|
340
|
-
|
|
341
|
-
const spinner = {
|
|
342
|
-
start() {
|
|
343
|
-
process.stdout.write(`\n ${frames[0]} ${message}`);
|
|
344
|
-
interval = setInterval(() => {
|
|
345
|
-
frameIdx = (frameIdx + 1) % frames.length;
|
|
346
|
-
process.stdout.write(`\r ${c.cyan}${frames[frameIdx]}${c.reset} ${message}`);
|
|
347
|
-
}, 80);
|
|
348
|
-
return this;
|
|
349
|
-
},
|
|
350
|
-
|
|
351
|
-
update(newMessage) {
|
|
352
|
-
message = newMessage;
|
|
353
|
-
return this;
|
|
354
|
-
},
|
|
355
|
-
|
|
356
|
-
success(finalMessage) {
|
|
357
|
-
if (interval) clearInterval(interval);
|
|
358
|
-
process.stdout.write(`\r ${c.green}✓${c.reset} ${finalMessage || message}\n`);
|
|
359
|
-
return this;
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
error(finalMessage) {
|
|
363
|
-
if (interval) clearInterval(interval);
|
|
364
|
-
process.stdout.write(`\r ${c.red}✗${c.reset} ${finalMessage || message}\n`);
|
|
365
|
-
return this;
|
|
366
|
-
},
|
|
367
|
-
|
|
368
|
-
stop() {
|
|
369
|
-
if (interval) clearInterval(interval);
|
|
370
|
-
process.stdout.write("\r" + " ".repeat(message.length + 10) + "\r");
|
|
371
|
-
return this;
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
return spinner;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Create a beautiful verdict display
|
|
380
|
-
*/
|
|
381
|
-
function createVerdictDisplay(verdict, score, options = {}) {
|
|
382
|
-
const width = options.width || 60;
|
|
383
|
-
|
|
384
|
-
const configs = {
|
|
385
|
-
SHIP: {
|
|
386
|
-
color: c.green,
|
|
387
|
-
bgColor: c.bgGreen,
|
|
388
|
-
icon: "🚀",
|
|
389
|
-
ascii: [
|
|
390
|
-
" ███████╗██╗ ██╗██╗██████╗ ",
|
|
391
|
-
" ██╔════╝██║ ██║██║██╔══██╗",
|
|
392
|
-
" ███████╗███████║██║██████╔╝",
|
|
393
|
-
" ╚════██║██╔══██║██║██╔═══╝ ",
|
|
394
|
-
" ███████║██║ ██║██║██║ ",
|
|
395
|
-
" ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ",
|
|
396
|
-
],
|
|
397
|
-
message: "Ready to ship!",
|
|
398
|
-
tagline: "All systems go. Deploy with confidence.",
|
|
399
|
-
},
|
|
400
|
-
WARN: {
|
|
401
|
-
color: c.yellow,
|
|
402
|
-
bgColor: c.bgYellow,
|
|
403
|
-
icon: "⚠️",
|
|
404
|
-
ascii: [
|
|
405
|
-
" ██╗ ██╗ █████╗ ██████╗ ███╗ ██╗",
|
|
406
|
-
" ██║ ██║██╔══██╗██╔══██╗████╗ ██║",
|
|
407
|
-
" ██║ █╗ ██║███████║██████╔╝██╔██╗ ██║",
|
|
408
|
-
" ██║███╗██║██╔══██║██╔══██╗██║╚██╗██║",
|
|
409
|
-
" ╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║",
|
|
410
|
-
" ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
|
|
411
|
-
],
|
|
412
|
-
message: "Ship with caution",
|
|
413
|
-
tagline: "Minor issues found. Review before deploy.",
|
|
414
|
-
},
|
|
415
|
-
BLOCK: {
|
|
416
|
-
color: c.red,
|
|
417
|
-
bgColor: c.bgRed,
|
|
418
|
-
icon: "🚫",
|
|
419
|
-
ascii: [
|
|
420
|
-
" ██████╗ ██╗ ██████╗ ██████╗██╗ ██╗",
|
|
421
|
-
" ██╔══██╗██║ ██╔═══██╗██╔════╝██║ ██╔╝",
|
|
422
|
-
" ██████╔╝██║ ██║ ██║██║ █████╔╝ ",
|
|
423
|
-
" ██╔══██╗██║ ██║ ██║██║ ██╔═██╗ ",
|
|
424
|
-
" ██████╔╝███████╗╚██████╔╝╚██████╗██║ ██╗",
|
|
425
|
-
" ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝",
|
|
426
|
-
],
|
|
427
|
-
message: "Do not ship!",
|
|
428
|
-
tagline: "Critical issues must be fixed first.",
|
|
429
|
-
},
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
const config = configs[verdict] || configs.BLOCK;
|
|
433
|
-
|
|
434
|
-
let output = "\n";
|
|
435
|
-
|
|
436
|
-
// ASCII art verdict
|
|
437
|
-
for (const line of config.ascii) {
|
|
438
|
-
output += `${config.color}${line}${c.reset}\n`;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
output += "\n";
|
|
442
|
-
|
|
443
|
-
// Score display
|
|
444
|
-
const scoreDisplay = `SCORE: ${score}`;
|
|
445
|
-
output += ` ${config.color}${c.bold}${config.icon} ${scoreDisplay}${c.reset}\n`;
|
|
446
|
-
output += ` ${c.dim}${config.tagline}${c.reset}\n`;
|
|
447
|
-
|
|
448
|
-
return output;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Create a badge display (the stunning one)
|
|
453
|
-
*/
|
|
454
|
-
function createBadgeDisplay(projectId, score, verdict) {
|
|
455
|
-
const config = {
|
|
456
|
-
SHIP: { color: c.green, bg: c.bgGreen, icon: "✓", glow: "🌟" },
|
|
457
|
-
WARN: { color: c.yellow, bg: c.bgYellow, icon: "!", glow: "⚡" },
|
|
458
|
-
BLOCK: { color: c.red, bg: c.bgRed, icon: "✗", glow: "🔥" },
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
const cfg = config[verdict] || config.BLOCK;
|
|
462
|
-
|
|
463
|
-
const badge = `
|
|
464
|
-
${cfg.color}${c.bold}
|
|
465
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
466
|
-
║ ║
|
|
467
|
-
║ ${cfg.glow} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ${cfg.glow} ║
|
|
468
|
-
║ ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝ ║
|
|
469
|
-
║ ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ║
|
|
470
|
-
║ ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ║
|
|
471
|
-
║ ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗ ║
|
|
472
|
-
║ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ║
|
|
473
|
-
║ ║
|
|
474
|
-
║ ┌────────────────────────────────────────────────────────┐ ║
|
|
475
|
-
║ │ │ ║
|
|
476
|
-
║ │ ${cfg.icon} ${verdict} │ SCORE: ${String(score).padStart(3)} │ ${projectId.substring(0, 20).padEnd(20)} │ ║
|
|
477
|
-
║ │ │ ║
|
|
478
|
-
║ └────────────────────────────────────────────────────────┘ ║
|
|
479
|
-
║ ║
|
|
480
|
-
║ ════════════════════════════════════════════ ║
|
|
481
|
-
║ VERIFIED BY VIBECHECK ║
|
|
482
|
-
║ ════════════════════════════════════════════ ║
|
|
483
|
-
║ ║
|
|
484
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
485
|
-
${c.reset}`;
|
|
486
|
-
|
|
487
|
-
return badge;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Strip ANSI codes from string
|
|
492
|
-
*/
|
|
493
|
-
function stripAnsi(str) {
|
|
494
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Pad string to center
|
|
499
|
-
*/
|
|
500
|
-
function padCenter(str, width) {
|
|
501
|
-
const len = stripAnsi(str).length;
|
|
502
|
-
const pad = Math.max(0, width - len);
|
|
503
|
-
const padLeft = Math.floor(pad / 2);
|
|
504
|
-
const padRight = pad - padLeft;
|
|
505
|
-
return " ".repeat(padLeft) + str + " ".repeat(padRight);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Create a divider line
|
|
510
|
-
*/
|
|
511
|
-
function divider(width = 60, char = "─", color = c.dim) {
|
|
512
|
-
return `${color}${char.repeat(width)}${c.reset}`;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Create a section header
|
|
517
|
-
*/
|
|
518
|
-
function sectionHeader(title, icon = "›") {
|
|
519
|
-
return `\n${c.cyan}${icon}${c.reset} ${c.bold}${title}${c.reset}\n${divider(40)}`;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
module.exports = {
|
|
523
|
-
colors,
|
|
524
|
-
c,
|
|
525
|
-
box,
|
|
526
|
-
spinners,
|
|
527
|
-
progressChars,
|
|
528
|
-
createHeader,
|
|
529
|
-
createTable,
|
|
530
|
-
createProgressBar,
|
|
531
|
-
createScoreCard,
|
|
532
|
-
createMenu,
|
|
533
|
-
createSpinner,
|
|
534
|
-
createVerdictDisplay,
|
|
535
|
-
createBadgeDisplay,
|
|
536
|
-
stripAnsi,
|
|
537
|
-
padCenter,
|
|
538
|
-
divider,
|
|
539
|
-
sectionHeader,
|
|
540
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* CLI UI Library — Beautiful Interactive Experience
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Stunning ASCII art headers
|
|
6
|
+
* - Beautiful bordered tables
|
|
7
|
+
* - Interactive menus
|
|
8
|
+
* - Progress bars and spinners
|
|
9
|
+
* - Color gradients
|
|
10
|
+
* - Box drawing characters
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const readline = require("readline");
|
|
14
|
+
|
|
15
|
+
// Extended ANSI color palette
|
|
16
|
+
const colors = {
|
|
17
|
+
reset: "\x1b[0m",
|
|
18
|
+
bold: "\x1b[1m",
|
|
19
|
+
dim: "\x1b[2m",
|
|
20
|
+
italic: "\x1b[3m",
|
|
21
|
+
underline: "\x1b[4m",
|
|
22
|
+
blink: "\x1b[5m",
|
|
23
|
+
inverse: "\x1b[7m",
|
|
24
|
+
hidden: "\x1b[8m",
|
|
25
|
+
strikethrough: "\x1b[9m",
|
|
26
|
+
|
|
27
|
+
// Foreground colors
|
|
28
|
+
black: "\x1b[30m",
|
|
29
|
+
red: "\x1b[31m",
|
|
30
|
+
green: "\x1b[32m",
|
|
31
|
+
yellow: "\x1b[33m",
|
|
32
|
+
blue: "\x1b[34m",
|
|
33
|
+
magenta: "\x1b[35m",
|
|
34
|
+
cyan: "\x1b[36m",
|
|
35
|
+
white: "\x1b[37m",
|
|
36
|
+
|
|
37
|
+
// Bright foreground
|
|
38
|
+
brightBlack: "\x1b[90m",
|
|
39
|
+
brightRed: "\x1b[91m",
|
|
40
|
+
brightGreen: "\x1b[92m",
|
|
41
|
+
brightYellow: "\x1b[93m",
|
|
42
|
+
brightBlue: "\x1b[94m",
|
|
43
|
+
brightMagenta: "\x1b[95m",
|
|
44
|
+
brightCyan: "\x1b[96m",
|
|
45
|
+
brightWhite: "\x1b[97m",
|
|
46
|
+
|
|
47
|
+
// Background colors
|
|
48
|
+
bgBlack: "\x1b[40m",
|
|
49
|
+
bgRed: "\x1b[41m",
|
|
50
|
+
bgGreen: "\x1b[42m",
|
|
51
|
+
bgYellow: "\x1b[43m",
|
|
52
|
+
bgBlue: "\x1b[44m",
|
|
53
|
+
bgMagenta: "\x1b[45m",
|
|
54
|
+
bgCyan: "\x1b[46m",
|
|
55
|
+
bgWhite: "\x1b[47m",
|
|
56
|
+
|
|
57
|
+
// Bright background
|
|
58
|
+
bgBrightBlack: "\x1b[100m",
|
|
59
|
+
bgBrightRed: "\x1b[101m",
|
|
60
|
+
bgBrightGreen: "\x1b[102m",
|
|
61
|
+
bgBrightYellow: "\x1b[103m",
|
|
62
|
+
bgBrightBlue: "\x1b[104m",
|
|
63
|
+
bgBrightMagenta: "\x1b[105m",
|
|
64
|
+
bgBrightCyan: "\x1b[106m",
|
|
65
|
+
bgBrightWhite: "\x1b[107m",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const c = colors;
|
|
69
|
+
|
|
70
|
+
// Box drawing characters
|
|
71
|
+
const box = {
|
|
72
|
+
// Single line
|
|
73
|
+
topLeft: "┌",
|
|
74
|
+
topRight: "┐",
|
|
75
|
+
bottomLeft: "└",
|
|
76
|
+
bottomRight: "┘",
|
|
77
|
+
horizontal: "─",
|
|
78
|
+
vertical: "│",
|
|
79
|
+
leftT: "├",
|
|
80
|
+
rightT: "┤",
|
|
81
|
+
topT: "┬",
|
|
82
|
+
bottomT: "┴",
|
|
83
|
+
cross: "┼",
|
|
84
|
+
|
|
85
|
+
// Double line
|
|
86
|
+
dTopLeft: "╔",
|
|
87
|
+
dTopRight: "╗",
|
|
88
|
+
dBottomLeft: "╚",
|
|
89
|
+
dBottomRight: "╝",
|
|
90
|
+
dHorizontal: "═",
|
|
91
|
+
dVertical: "║",
|
|
92
|
+
dLeftT: "╠",
|
|
93
|
+
dRightT: "╣",
|
|
94
|
+
dTopT: "╦",
|
|
95
|
+
dBottomT: "╩",
|
|
96
|
+
dCross: "╬",
|
|
97
|
+
|
|
98
|
+
// Rounded
|
|
99
|
+
rTopLeft: "╭",
|
|
100
|
+
rTopRight: "╮",
|
|
101
|
+
rBottomLeft: "╰",
|
|
102
|
+
rBottomRight: "╯",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Spinner frames
|
|
106
|
+
const spinners = {
|
|
107
|
+
dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
108
|
+
line: ["-", "\\", "|", "/"],
|
|
109
|
+
circle: ["◐", "◓", "◑", "◒"],
|
|
110
|
+
arc: ["◜", "◠", "◝", "◞", "◡", "◟"],
|
|
111
|
+
box: ["▖", "▘", "▝", "▗"],
|
|
112
|
+
bounce: ["⠁", "⠂", "⠄", "⠂"],
|
|
113
|
+
pulse: ["█", "▓", "▒", "░", "▒", "▓"],
|
|
114
|
+
arrows: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
|
|
115
|
+
star: ["✶", "✷", "✸", "✹", "✺", "✹", "✸", "✷"],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Progress bar characters
|
|
119
|
+
const progressChars = {
|
|
120
|
+
filled: "█",
|
|
121
|
+
empty: "░",
|
|
122
|
+
gradient: ["░", "▒", "▓", "█"],
|
|
123
|
+
smooth: ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"],
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create a beautiful header with ASCII art border
|
|
128
|
+
*/
|
|
129
|
+
function createHeader(title, subtitle = "", options = {}) {
|
|
130
|
+
const width = options.width || 70;
|
|
131
|
+
const style = options.style || "double"; // single, double, rounded
|
|
132
|
+
const color = options.color || c.cyan;
|
|
133
|
+
|
|
134
|
+
const chars = style === "double" ? {
|
|
135
|
+
tl: box.dTopLeft, tr: box.dTopRight, bl: box.dBottomLeft, br: box.dBottomRight,
|
|
136
|
+
h: box.dHorizontal, v: box.dVertical
|
|
137
|
+
} : style === "rounded" ? {
|
|
138
|
+
tl: box.rTopLeft, tr: box.rTopRight, bl: box.rBottomLeft, br: box.rBottomRight,
|
|
139
|
+
h: box.horizontal, v: box.vertical
|
|
140
|
+
} : {
|
|
141
|
+
tl: box.topLeft, tr: box.topRight, bl: box.bottomLeft, br: box.bottomRight,
|
|
142
|
+
h: box.horizontal, v: box.vertical
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const innerWidth = width - 2;
|
|
146
|
+
const titlePad = Math.floor((innerWidth - title.length) / 2);
|
|
147
|
+
const subtitlePad = Math.floor((innerWidth - subtitle.length) / 2);
|
|
148
|
+
|
|
149
|
+
let output = "";
|
|
150
|
+
output += `${color}${chars.tl}${chars.h.repeat(innerWidth)}${chars.tr}${c.reset}\n`;
|
|
151
|
+
output += `${color}${chars.v}${c.reset}${" ".repeat(innerWidth)}${color}${chars.v}${c.reset}\n`;
|
|
152
|
+
output += `${color}${chars.v}${c.reset}${" ".repeat(titlePad)}${c.bold}${c.brightWhite}${title}${c.reset}${" ".repeat(innerWidth - titlePad - title.length)}${color}${chars.v}${c.reset}\n`;
|
|
153
|
+
|
|
154
|
+
if (subtitle) {
|
|
155
|
+
output += `${color}${chars.v}${c.reset}${" ".repeat(subtitlePad)}${c.dim}${subtitle}${c.reset}${" ".repeat(innerWidth - subtitlePad - subtitle.length)}${color}${chars.v}${c.reset}\n`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
output += `${color}${chars.v}${c.reset}${" ".repeat(innerWidth)}${color}${chars.v}${c.reset}\n`;
|
|
159
|
+
output += `${color}${chars.bl}${chars.h.repeat(innerWidth)}${chars.br}${c.reset}`;
|
|
160
|
+
|
|
161
|
+
return output;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a beautiful table
|
|
166
|
+
*/
|
|
167
|
+
function createTable(headers, rows, options = {}) {
|
|
168
|
+
const style = options.style || "rounded";
|
|
169
|
+
const headerColor = options.headerColor || c.cyan;
|
|
170
|
+
const borderColor = options.borderColor || c.dim;
|
|
171
|
+
|
|
172
|
+
// Calculate column widths
|
|
173
|
+
const colWidths = headers.map((h, i) => {
|
|
174
|
+
const headerLen = stripAnsi(h).length;
|
|
175
|
+
const maxRowLen = Math.max(...rows.map(r => stripAnsi(String(r[i] || "")).length));
|
|
176
|
+
return Math.max(headerLen, maxRowLen) + 2;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const chars = style === "rounded" ? {
|
|
180
|
+
tl: box.rTopLeft, tr: box.rTopRight, bl: box.rBottomLeft, br: box.rBottomRight,
|
|
181
|
+
h: box.horizontal, v: box.vertical, lt: box.leftT, rt: box.rightT,
|
|
182
|
+
tt: box.topT, bt: box.bottomT, cr: box.cross
|
|
183
|
+
} : {
|
|
184
|
+
tl: box.topLeft, tr: box.topRight, bl: box.bottomLeft, br: box.bottomRight,
|
|
185
|
+
h: box.horizontal, v: box.vertical, lt: box.leftT, rt: box.rightT,
|
|
186
|
+
tt: box.topT, bt: box.bottomT, cr: box.cross
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
let output = "";
|
|
190
|
+
|
|
191
|
+
// Top border
|
|
192
|
+
output += borderColor + chars.tl;
|
|
193
|
+
output += colWidths.map(w => chars.h.repeat(w)).join(chars.tt);
|
|
194
|
+
output += chars.tr + c.reset + "\n";
|
|
195
|
+
|
|
196
|
+
// Header row
|
|
197
|
+
output += borderColor + chars.v + c.reset;
|
|
198
|
+
headers.forEach((h, i) => {
|
|
199
|
+
const padded = padCenter(h, colWidths[i]);
|
|
200
|
+
output += `${headerColor}${c.bold}${padded}${c.reset}${borderColor}${chars.v}${c.reset}`;
|
|
201
|
+
});
|
|
202
|
+
output += "\n";
|
|
203
|
+
|
|
204
|
+
// Header separator
|
|
205
|
+
output += borderColor + chars.lt;
|
|
206
|
+
output += colWidths.map(w => chars.h.repeat(w)).join(chars.cr);
|
|
207
|
+
output += chars.rt + c.reset + "\n";
|
|
208
|
+
|
|
209
|
+
// Data rows
|
|
210
|
+
rows.forEach((row, rowIdx) => {
|
|
211
|
+
output += borderColor + chars.v + c.reset;
|
|
212
|
+
row.forEach((cell, i) => {
|
|
213
|
+
const cellStr = String(cell || "");
|
|
214
|
+
const padded = padCenter(cellStr, colWidths[i]);
|
|
215
|
+
output += `${padded}${borderColor}${chars.v}${c.reset}`;
|
|
216
|
+
});
|
|
217
|
+
output += "\n";
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Bottom border
|
|
221
|
+
output += borderColor + chars.bl;
|
|
222
|
+
output += colWidths.map(w => chars.h.repeat(w)).join(chars.bt);
|
|
223
|
+
output += chars.br + c.reset;
|
|
224
|
+
|
|
225
|
+
return output;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Create a beautiful progress bar
|
|
230
|
+
*/
|
|
231
|
+
function createProgressBar(percent, options = {}) {
|
|
232
|
+
const width = options.width || 40;
|
|
233
|
+
const showPercent = options.showPercent !== false;
|
|
234
|
+
const showLabel = options.label || "";
|
|
235
|
+
const color = options.color || c.green;
|
|
236
|
+
|
|
237
|
+
const filled = Math.round((percent / 100) * width);
|
|
238
|
+
const empty = width - filled;
|
|
239
|
+
|
|
240
|
+
let bar = "";
|
|
241
|
+
|
|
242
|
+
// Gradient effect
|
|
243
|
+
for (let i = 0; i < filled; i++) {
|
|
244
|
+
const charIdx = Math.min(3, Math.floor((i / width) * 4));
|
|
245
|
+
bar += color + progressChars.gradient[charIdx];
|
|
246
|
+
}
|
|
247
|
+
bar += c.dim;
|
|
248
|
+
for (let i = 0; i < empty; i++) {
|
|
249
|
+
bar += progressChars.empty;
|
|
250
|
+
}
|
|
251
|
+
bar += c.reset;
|
|
252
|
+
|
|
253
|
+
let output = "";
|
|
254
|
+
if (showLabel) {
|
|
255
|
+
output += `${showLabel} `;
|
|
256
|
+
}
|
|
257
|
+
output += `${c.dim}[${c.reset}${bar}${c.dim}]${c.reset}`;
|
|
258
|
+
if (showPercent) {
|
|
259
|
+
output += ` ${color}${percent.toFixed(0)}%${c.reset}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return output;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create a category score display with visual bars
|
|
267
|
+
*/
|
|
268
|
+
function createScoreCard(categories, options = {}) {
|
|
269
|
+
const width = options.width || 50;
|
|
270
|
+
const barWidth = options.barWidth || 25;
|
|
271
|
+
|
|
272
|
+
let output = "";
|
|
273
|
+
|
|
274
|
+
for (const [name, score] of Object.entries(categories)) {
|
|
275
|
+
const color = score >= 80 ? c.green : score >= 60 ? c.yellow : c.red;
|
|
276
|
+
const icon = score >= 80 ? "✓" : score >= 60 ? "!" : "✗";
|
|
277
|
+
|
|
278
|
+
const filled = Math.round((score / 100) * barWidth);
|
|
279
|
+
const empty = barWidth - filled;
|
|
280
|
+
const bar = color + progressChars.filled.repeat(filled) + c.dim + progressChars.empty.repeat(empty) + c.reset;
|
|
281
|
+
|
|
282
|
+
const nameStr = name.padEnd(15);
|
|
283
|
+
const scoreStr = `${score}%`.padStart(4);
|
|
284
|
+
|
|
285
|
+
output += ` ${c.dim}${icon}${c.reset} ${nameStr} ${bar} ${color}${scoreStr}${c.reset}\n`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return output;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Create an interactive menu
|
|
293
|
+
*/
|
|
294
|
+
async function createMenu(title, options, config = {}) {
|
|
295
|
+
const menuColor = config.color || c.cyan;
|
|
296
|
+
|
|
297
|
+
return new Promise((resolve) => {
|
|
298
|
+
console.log("");
|
|
299
|
+
console.log(createHeader(title, config.subtitle || "", { color: menuColor, style: "rounded" }));
|
|
300
|
+
console.log("");
|
|
301
|
+
|
|
302
|
+
options.forEach((opt, i) => {
|
|
303
|
+
const num = `${i + 1}`.padStart(2);
|
|
304
|
+
const icon = opt.icon || "›";
|
|
305
|
+
console.log(` ${menuColor}${num}${c.reset} ${icon} ${c.bold}${opt.label}${c.reset}`);
|
|
306
|
+
if (opt.description) {
|
|
307
|
+
console.log(` ${c.dim}${opt.description}${c.reset}`);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
console.log("");
|
|
312
|
+
console.log(` ${c.dim}${options.length + 1}. Exit${c.reset}`);
|
|
313
|
+
console.log("");
|
|
314
|
+
|
|
315
|
+
const rl = readline.createInterface({
|
|
316
|
+
input: process.stdin,
|
|
317
|
+
output: process.stdout,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
rl.question(` ${menuColor}›${c.reset} Select an option: `, (answer) => {
|
|
321
|
+
rl.close();
|
|
322
|
+
const choice = parseInt(answer, 10);
|
|
323
|
+
|
|
324
|
+
if (choice >= 1 && choice <= options.length) {
|
|
325
|
+
resolve(options[choice - 1]);
|
|
326
|
+
} else {
|
|
327
|
+
resolve(null);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Create a spinner
|
|
335
|
+
*/
|
|
336
|
+
function createSpinner(message, type = "dots") {
|
|
337
|
+
const frames = spinners[type] || spinners.dots;
|
|
338
|
+
let frameIdx = 0;
|
|
339
|
+
let interval = null;
|
|
340
|
+
|
|
341
|
+
const spinner = {
|
|
342
|
+
start() {
|
|
343
|
+
process.stdout.write(`\n ${frames[0]} ${message}`);
|
|
344
|
+
interval = setInterval(() => {
|
|
345
|
+
frameIdx = (frameIdx + 1) % frames.length;
|
|
346
|
+
process.stdout.write(`\r ${c.cyan}${frames[frameIdx]}${c.reset} ${message}`);
|
|
347
|
+
}, 80);
|
|
348
|
+
return this;
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
update(newMessage) {
|
|
352
|
+
message = newMessage;
|
|
353
|
+
return this;
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
success(finalMessage) {
|
|
357
|
+
if (interval) clearInterval(interval);
|
|
358
|
+
process.stdout.write(`\r ${c.green}✓${c.reset} ${finalMessage || message}\n`);
|
|
359
|
+
return this;
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
error(finalMessage) {
|
|
363
|
+
if (interval) clearInterval(interval);
|
|
364
|
+
process.stdout.write(`\r ${c.red}✗${c.reset} ${finalMessage || message}\n`);
|
|
365
|
+
return this;
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
stop() {
|
|
369
|
+
if (interval) clearInterval(interval);
|
|
370
|
+
process.stdout.write("\r" + " ".repeat(message.length + 10) + "\r");
|
|
371
|
+
return this;
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
return spinner;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create a beautiful verdict display
|
|
380
|
+
*/
|
|
381
|
+
function createVerdictDisplay(verdict, score, options = {}) {
|
|
382
|
+
const width = options.width || 60;
|
|
383
|
+
|
|
384
|
+
const configs = {
|
|
385
|
+
SHIP: {
|
|
386
|
+
color: c.green,
|
|
387
|
+
bgColor: c.bgGreen,
|
|
388
|
+
icon: "🚀",
|
|
389
|
+
ascii: [
|
|
390
|
+
" ███████╗██╗ ██╗██╗██████╗ ",
|
|
391
|
+
" ██╔════╝██║ ██║██║██╔══██╗",
|
|
392
|
+
" ███████╗███████║██║██████╔╝",
|
|
393
|
+
" ╚════██║██╔══██║██║██╔═══╝ ",
|
|
394
|
+
" ███████║██║ ██║██║██║ ",
|
|
395
|
+
" ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ",
|
|
396
|
+
],
|
|
397
|
+
message: "Ready to ship!",
|
|
398
|
+
tagline: "All systems go. Deploy with confidence.",
|
|
399
|
+
},
|
|
400
|
+
WARN: {
|
|
401
|
+
color: c.yellow,
|
|
402
|
+
bgColor: c.bgYellow,
|
|
403
|
+
icon: "⚠️",
|
|
404
|
+
ascii: [
|
|
405
|
+
" ██╗ ██╗ █████╗ ██████╗ ███╗ ██╗",
|
|
406
|
+
" ██║ ██║██╔══██╗██╔══██╗████╗ ██║",
|
|
407
|
+
" ██║ █╗ ██║███████║██████╔╝██╔██╗ ██║",
|
|
408
|
+
" ██║███╗██║██╔══██║██╔══██╗██║╚██╗██║",
|
|
409
|
+
" ╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║",
|
|
410
|
+
" ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
|
|
411
|
+
],
|
|
412
|
+
message: "Ship with caution",
|
|
413
|
+
tagline: "Minor issues found. Review before deploy.",
|
|
414
|
+
},
|
|
415
|
+
BLOCK: {
|
|
416
|
+
color: c.red,
|
|
417
|
+
bgColor: c.bgRed,
|
|
418
|
+
icon: "🚫",
|
|
419
|
+
ascii: [
|
|
420
|
+
" ██████╗ ██╗ ██████╗ ██████╗██╗ ██╗",
|
|
421
|
+
" ██╔══██╗██║ ██╔═══██╗██╔════╝██║ ██╔╝",
|
|
422
|
+
" ██████╔╝██║ ██║ ██║██║ █████╔╝ ",
|
|
423
|
+
" ██╔══██╗██║ ██║ ██║██║ ██╔═██╗ ",
|
|
424
|
+
" ██████╔╝███████╗╚██████╔╝╚██████╗██║ ██╗",
|
|
425
|
+
" ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝",
|
|
426
|
+
],
|
|
427
|
+
message: "Do not ship!",
|
|
428
|
+
tagline: "Critical issues must be fixed first.",
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const config = configs[verdict] || configs.BLOCK;
|
|
433
|
+
|
|
434
|
+
let output = "\n";
|
|
435
|
+
|
|
436
|
+
// ASCII art verdict
|
|
437
|
+
for (const line of config.ascii) {
|
|
438
|
+
output += `${config.color}${line}${c.reset}\n`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
output += "\n";
|
|
442
|
+
|
|
443
|
+
// Score display
|
|
444
|
+
const scoreDisplay = `SCORE: ${score}`;
|
|
445
|
+
output += ` ${config.color}${c.bold}${config.icon} ${scoreDisplay}${c.reset}\n`;
|
|
446
|
+
output += ` ${c.dim}${config.tagline}${c.reset}\n`;
|
|
447
|
+
|
|
448
|
+
return output;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Create a badge display (the stunning one)
|
|
453
|
+
*/
|
|
454
|
+
function createBadgeDisplay(projectId, score, verdict) {
|
|
455
|
+
const config = {
|
|
456
|
+
SHIP: { color: c.green, bg: c.bgGreen, icon: "✓", glow: "🌟" },
|
|
457
|
+
WARN: { color: c.yellow, bg: c.bgYellow, icon: "!", glow: "⚡" },
|
|
458
|
+
BLOCK: { color: c.red, bg: c.bgRed, icon: "✗", glow: "🔥" },
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const cfg = config[verdict] || config.BLOCK;
|
|
462
|
+
|
|
463
|
+
const badge = `
|
|
464
|
+
${cfg.color}${c.bold}
|
|
465
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
466
|
+
║ ║
|
|
467
|
+
║ ${cfg.glow} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ${cfg.glow} ║
|
|
468
|
+
║ ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝ ║
|
|
469
|
+
║ ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ║
|
|
470
|
+
║ ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ║
|
|
471
|
+
║ ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗ ║
|
|
472
|
+
║ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ║
|
|
473
|
+
║ ║
|
|
474
|
+
║ ┌────────────────────────────────────────────────────────┐ ║
|
|
475
|
+
║ │ │ ║
|
|
476
|
+
║ │ ${cfg.icon} ${verdict} │ SCORE: ${String(score).padStart(3)} │ ${projectId.substring(0, 20).padEnd(20)} │ ║
|
|
477
|
+
║ │ │ ║
|
|
478
|
+
║ └────────────────────────────────────────────────────────┘ ║
|
|
479
|
+
║ ║
|
|
480
|
+
║ ════════════════════════════════════════════ ║
|
|
481
|
+
║ VERIFIED BY VIBECHECK ║
|
|
482
|
+
║ ════════════════════════════════════════════ ║
|
|
483
|
+
║ ║
|
|
484
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
485
|
+
${c.reset}`;
|
|
486
|
+
|
|
487
|
+
return badge;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Strip ANSI codes from string
|
|
492
|
+
*/
|
|
493
|
+
function stripAnsi(str) {
|
|
494
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Pad string to center
|
|
499
|
+
*/
|
|
500
|
+
function padCenter(str, width) {
|
|
501
|
+
const len = stripAnsi(str).length;
|
|
502
|
+
const pad = Math.max(0, width - len);
|
|
503
|
+
const padLeft = Math.floor(pad / 2);
|
|
504
|
+
const padRight = pad - padLeft;
|
|
505
|
+
return " ".repeat(padLeft) + str + " ".repeat(padRight);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Create a divider line
|
|
510
|
+
*/
|
|
511
|
+
function divider(width = 60, char = "─", color = c.dim) {
|
|
512
|
+
return `${color}${char.repeat(width)}${c.reset}`;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Create a section header
|
|
517
|
+
*/
|
|
518
|
+
function sectionHeader(title, icon = "›") {
|
|
519
|
+
return `\n${c.cyan}${icon}${c.reset} ${c.bold}${title}${c.reset}\n${divider(40)}`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
module.exports = {
|
|
523
|
+
colors,
|
|
524
|
+
c,
|
|
525
|
+
box,
|
|
526
|
+
spinners,
|
|
527
|
+
progressChars,
|
|
528
|
+
createHeader,
|
|
529
|
+
createTable,
|
|
530
|
+
createProgressBar,
|
|
531
|
+
createScoreCard,
|
|
532
|
+
createMenu,
|
|
533
|
+
createSpinner,
|
|
534
|
+
createVerdictDisplay,
|
|
535
|
+
createBadgeDisplay,
|
|
536
|
+
stripAnsi,
|
|
537
|
+
padCenter,
|
|
538
|
+
divider,
|
|
539
|
+
sectionHeader,
|
|
540
|
+
};
|