@vibecheckai/cli 3.0.8 → 3.0.10
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/README.md +77 -484
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
- package/bin/runners/lib/entitlements-v2.js +409 -299
- package/bin/runners/lib/report-html.js +378 -1
- package/bin/runners/runBadge.js +823 -116
- package/bin/runners/runCtx.js +602 -119
- package/bin/runners/runDoctor.js +329 -42
- package/bin/runners/runFix.js +562 -83
- package/bin/runners/runGraph.js +231 -74
- package/bin/runners/runInit.js +647 -88
- package/bin/runners/runInstall.js +207 -46
- package/bin/runners/runMcp.js +58 -0
- package/bin/runners/runPR.js +172 -13
- package/bin/runners/runProve.js +818 -97
- package/bin/runners/runReality.js +831 -65
- package/bin/runners/runReport.js +108 -2
- package/bin/runners/runShare.js +156 -38
- package/bin/runners/runShip.js +919 -792
- package/bin/runners/runWatch.js +215 -38
- package/bin/vibecheck.js +158 -59
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
package/bin/runners/runBadge.js
CHANGED
|
@@ -1,121 +1,731 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibecheck badge - Generate
|
|
2
|
+
* vibecheck badge - Generate Premium Ship Badges
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* ENTERPRISE EDITION - World-Class Badge Generation
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Generate stunning, modern badges for your project README.
|
|
9
|
+
* Multiple styles: Classic, Gradient, Cyberpunk, Minimal, Full Stats
|
|
3
10
|
*/
|
|
4
11
|
|
|
5
12
|
const fs = require("fs");
|
|
6
13
|
const path = require("path");
|
|
7
14
|
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
16
|
+
// ADVANCED TERMINAL - ANSI CODES & UTILITIES
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
|
|
8
19
|
const c = {
|
|
9
|
-
reset:
|
|
10
|
-
bold:
|
|
11
|
-
dim:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
bold: '\x1b[1m',
|
|
22
|
+
dim: '\x1b[2m',
|
|
23
|
+
italic: '\x1b[3m',
|
|
24
|
+
underline: '\x1b[4m',
|
|
25
|
+
blink: '\x1b[5m',
|
|
26
|
+
inverse: '\x1b[7m',
|
|
27
|
+
hidden: '\x1b[8m',
|
|
28
|
+
strike: '\x1b[9m',
|
|
29
|
+
black: '\x1b[30m',
|
|
30
|
+
red: '\x1b[31m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
blue: '\x1b[34m',
|
|
34
|
+
magenta: '\x1b[35m',
|
|
35
|
+
cyan: '\x1b[36m',
|
|
36
|
+
white: '\x1b[37m',
|
|
37
|
+
gray: '\x1b[90m',
|
|
38
|
+
brightRed: '\x1b[91m',
|
|
39
|
+
brightGreen: '\x1b[92m',
|
|
40
|
+
brightYellow: '\x1b[93m',
|
|
41
|
+
brightBlue: '\x1b[94m',
|
|
42
|
+
brightMagenta: '\x1b[95m',
|
|
43
|
+
brightCyan: '\x1b[96m',
|
|
44
|
+
brightWhite: '\x1b[97m',
|
|
45
|
+
bgBlack: '\x1b[40m',
|
|
46
|
+
bgRed: '\x1b[41m',
|
|
47
|
+
bgGreen: '\x1b[42m',
|
|
48
|
+
bgYellow: '\x1b[43m',
|
|
49
|
+
bgBlue: '\x1b[44m',
|
|
50
|
+
bgMagenta: '\x1b[45m',
|
|
51
|
+
bgCyan: '\x1b[46m',
|
|
52
|
+
bgWhite: '\x1b[47m',
|
|
53
|
+
clearLine: '\x1b[2K',
|
|
54
|
+
hideCursor: '\x1b[?25l',
|
|
55
|
+
showCursor: '\x1b[?25h',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// True color support
|
|
59
|
+
const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
|
|
60
|
+
const bgRgb = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
|
|
61
|
+
|
|
62
|
+
// Premium color palette (emerald/teal theme for badges)
|
|
63
|
+
const colors = {
|
|
64
|
+
// Gradient for banner
|
|
65
|
+
gradient1: rgb(0, 255, 200),
|
|
66
|
+
gradient2: rgb(0, 230, 180),
|
|
67
|
+
gradient3: rgb(0, 210, 160),
|
|
68
|
+
gradient4: rgb(0, 190, 140),
|
|
69
|
+
gradient5: rgb(0, 170, 120),
|
|
70
|
+
gradient6: rgb(0, 150, 100),
|
|
71
|
+
|
|
72
|
+
// Verdict colors
|
|
73
|
+
ship: rgb(0, 255, 150),
|
|
74
|
+
warn: rgb(255, 200, 0),
|
|
75
|
+
block: rgb(255, 80, 80),
|
|
76
|
+
|
|
77
|
+
// UI colors
|
|
78
|
+
accent: rgb(0, 255, 200),
|
|
79
|
+
muted: rgb(120, 140, 130),
|
|
80
|
+
success: rgb(0, 255, 150),
|
|
81
|
+
info: rgb(100, 200, 255),
|
|
82
|
+
highlight: rgb(255, 255, 255),
|
|
83
|
+
|
|
84
|
+
// Badge colors
|
|
85
|
+
neonCyan: rgb(0, 255, 255),
|
|
86
|
+
neonGreen: rgb(0, 255, 128),
|
|
87
|
+
neonPink: rgb(255, 0, 128),
|
|
88
|
+
neonPurple: rgb(180, 0, 255),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
92
|
+
// PREMIUM BANNER
|
|
93
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
94
|
+
|
|
95
|
+
const BADGE_BANNER = `
|
|
96
|
+
${rgb(0, 255, 200)} ██████╗ █████╗ ██████╗ ██████╗ ███████╗${c.reset}
|
|
97
|
+
${rgb(0, 230, 180)} ██╔══██╗██╔══██╗██╔══██╗██╔════╝ ██╔════╝${c.reset}
|
|
98
|
+
${rgb(0, 210, 160)} ██████╔╝███████║██║ ██║██║ ███╗█████╗ ${c.reset}
|
|
99
|
+
${rgb(0, 190, 140)} ██╔══██╗██╔══██║██║ ██║██║ ██║██╔══╝ ${c.reset}
|
|
100
|
+
${rgb(0, 170, 120)} ██████╔╝██║ ██║██████╔╝╚██████╔╝███████╗${c.reset}
|
|
101
|
+
${rgb(0, 150, 100)} ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝${c.reset}
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
const BANNER_FULL = `
|
|
105
|
+
${rgb(0, 255, 200)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
|
|
106
|
+
${rgb(0, 230, 180)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
|
|
107
|
+
${rgb(0, 210, 160)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
|
|
108
|
+
${rgb(0, 190, 140)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
|
|
109
|
+
${rgb(0, 170, 120)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
|
|
110
|
+
${rgb(0, 150, 100)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
|
|
111
|
+
|
|
112
|
+
${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
|
|
113
|
+
${c.dim} │${c.reset} ${rgb(0, 255, 200)}🏆${c.reset} ${c.bold}BADGE${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Premium Badges${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Show Off Your Ship Status${c.reset} ${c.dim}│${c.reset}
|
|
114
|
+
${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
118
|
+
// ICONS & BOX DRAWING
|
|
119
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
120
|
+
|
|
121
|
+
const ICONS = {
|
|
122
|
+
badge: '🏆',
|
|
123
|
+
ship: '🚀',
|
|
124
|
+
warn: '⚠️',
|
|
125
|
+
block: '🛑',
|
|
126
|
+
check: '✓',
|
|
127
|
+
cross: '✗',
|
|
128
|
+
arrow: '→',
|
|
129
|
+
sparkle: '✨',
|
|
130
|
+
star: '★',
|
|
131
|
+
copy: '📋',
|
|
132
|
+
file: '📄',
|
|
133
|
+
paint: '🎨',
|
|
134
|
+
lightning: '⚡',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const BOX = {
|
|
138
|
+
topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯',
|
|
139
|
+
horizontal: '─', vertical: '│',
|
|
140
|
+
dTopLeft: '╔', dTopRight: '╗', dBottomLeft: '╚', dBottomRight: '╝',
|
|
141
|
+
dHorizontal: '═', dVertical: '║',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
145
|
+
// UTILITY FUNCTIONS
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
147
|
+
|
|
148
|
+
function padCenter(str, width) {
|
|
149
|
+
const padding = Math.max(0, width - str.length);
|
|
150
|
+
const left = Math.floor(padding / 2);
|
|
151
|
+
const right = padding - left;
|
|
152
|
+
return ' '.repeat(left) + str + ' '.repeat(right);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function printDivider(char = '─', width = 69, color = c.dim) {
|
|
156
|
+
console.log(`${color} ${char.repeat(width)}${c.reset}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function printSection(title, icon = '◆') {
|
|
160
|
+
console.log();
|
|
161
|
+
console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
|
|
162
|
+
printDivider();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
166
|
+
// BADGE GENERATION - PREMIUM SVG BADGES
|
|
167
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Style: Classic (shields.io compatible)
|
|
171
|
+
*/
|
|
172
|
+
function generateClassicBadge(verdict, score) {
|
|
173
|
+
const colorMap = {
|
|
174
|
+
SHIP: 'brightgreen',
|
|
175
|
+
WARN: 'yellow',
|
|
176
|
+
BLOCK: 'red',
|
|
177
|
+
};
|
|
178
|
+
const color = colorMap[verdict] || 'lightgrey';
|
|
179
|
+
const message = verdict === 'SHIP' ? 'SHIP%20✓' : verdict;
|
|
180
|
+
return {
|
|
181
|
+
markdown: `[](https://vibecheckai.dev)`,
|
|
182
|
+
html: `<a href="https://vibecheckai.dev"><img src="https://img.shields.io/badge/vibecheck-${message}-${color}?style=flat-square" alt="vibecheck"></a>`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Style: Gradient - Modern gradient badge with glow
|
|
188
|
+
*/
|
|
189
|
+
function generateGradientBadge(verdict, score) {
|
|
190
|
+
const configs = {
|
|
191
|
+
SHIP: {
|
|
192
|
+
gradient: ['#00FF96', '#00D4AA'],
|
|
193
|
+
glow: '#00FF96',
|
|
194
|
+
text: 'SHIP READY',
|
|
195
|
+
icon: '✓',
|
|
196
|
+
},
|
|
197
|
+
WARN: {
|
|
198
|
+
gradient: ['#FFD700', '#FFA500'],
|
|
199
|
+
glow: '#FFD700',
|
|
200
|
+
text: 'WARNINGS',
|
|
201
|
+
icon: '⚠',
|
|
202
|
+
},
|
|
203
|
+
BLOCK: {
|
|
204
|
+
gradient: ['#FF6B6B', '#FF4757'],
|
|
205
|
+
glow: '#FF6B6B',
|
|
206
|
+
text: 'BLOCKED',
|
|
207
|
+
icon: '✗',
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const config = configs[verdict] || configs.SHIP;
|
|
212
|
+
|
|
213
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="140" height="28" viewBox="0 0 140 28">
|
|
214
|
+
<defs>
|
|
215
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
216
|
+
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
|
217
|
+
<stop offset="100%" style="stop-color:#16213e"/>
|
|
218
|
+
</linearGradient>
|
|
219
|
+
<linearGradient id="accent" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
220
|
+
<stop offset="0%" style="stop-color:${config.gradient[0]}"/>
|
|
221
|
+
<stop offset="100%" style="stop-color:${config.gradient[1]}"/>
|
|
222
|
+
</linearGradient>
|
|
223
|
+
<filter id="glow">
|
|
224
|
+
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
|
225
|
+
<feMerge>
|
|
226
|
+
<feMergeNode in="coloredBlur"/>
|
|
227
|
+
<feMergeNode in="SourceGraphic"/>
|
|
228
|
+
</feMerge>
|
|
229
|
+
</filter>
|
|
230
|
+
</defs>
|
|
231
|
+
<rect width="140" height="28" rx="6" fill="url(#bg)"/>
|
|
232
|
+
<rect x="1" y="1" width="138" height="26" rx="5" fill="none" stroke="url(#accent)" stroke-width="1" opacity="0.5"/>
|
|
233
|
+
<text x="12" y="18" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600" fill="url(#accent)" filter="url(#glow)">⚡</text>
|
|
234
|
+
<text x="28" y="18" font-family="system-ui, -apple-system, sans-serif" font-size="10" font-weight="500" fill="#ffffff">vibecheck</text>
|
|
235
|
+
<rect x="88" y="6" width="46" height="16" rx="4" fill="url(#accent)"/>
|
|
236
|
+
<text x="111" y="18" font-family="system-ui, -apple-system, sans-serif" font-size="9" font-weight="700" fill="#000" text-anchor="middle">${config.icon} ${verdict}</text>
|
|
237
|
+
</svg>`;
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
svg,
|
|
241
|
+
markdown: `.toString('base64')})`,
|
|
242
|
+
html: svg,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Style: Cyberpunk - Neon glow effect with tech aesthetic
|
|
248
|
+
*/
|
|
249
|
+
function generateCyberpunkBadge(verdict, score) {
|
|
250
|
+
const configs = {
|
|
251
|
+
SHIP: {
|
|
252
|
+
primary: '#00FFFF',
|
|
253
|
+
secondary: '#00FF88',
|
|
254
|
+
text: 'SHIP',
|
|
255
|
+
scanline: '#00FFFF',
|
|
256
|
+
},
|
|
257
|
+
WARN: {
|
|
258
|
+
primary: '#FFD700',
|
|
259
|
+
secondary: '#FF8800',
|
|
260
|
+
text: 'WARN',
|
|
261
|
+
scanline: '#FFD700',
|
|
262
|
+
},
|
|
263
|
+
BLOCK: {
|
|
264
|
+
primary: '#FF0066',
|
|
265
|
+
secondary: '#FF4444',
|
|
266
|
+
text: 'BLOCK',
|
|
267
|
+
scanline: '#FF0066',
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const config = configs[verdict] || configs.SHIP;
|
|
272
|
+
|
|
273
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="160" height="32" viewBox="0 0 160 32">
|
|
274
|
+
<defs>
|
|
275
|
+
<linearGradient id="cyberbg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
276
|
+
<stop offset="0%" style="stop-color:#0a0a0f"/>
|
|
277
|
+
<stop offset="50%" style="stop-color:#1a1a2e"/>
|
|
278
|
+
<stop offset="100%" style="stop-color:#0a0a0f"/>
|
|
279
|
+
</linearGradient>
|
|
280
|
+
<linearGradient id="cyberglow" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
281
|
+
<stop offset="0%" style="stop-color:${config.primary};stop-opacity:0"/>
|
|
282
|
+
<stop offset="50%" style="stop-color:${config.primary};stop-opacity:0.3"/>
|
|
283
|
+
<stop offset="100%" style="stop-color:${config.primary};stop-opacity:0"/>
|
|
284
|
+
</linearGradient>
|
|
285
|
+
<filter id="neon">
|
|
286
|
+
<feGaussianBlur stdDeviation="1.5" result="blur"/>
|
|
287
|
+
<feMerge>
|
|
288
|
+
<feMergeNode in="blur"/>
|
|
289
|
+
<feMergeNode in="blur"/>
|
|
290
|
+
<feMergeNode in="SourceGraphic"/>
|
|
291
|
+
</feMerge>
|
|
292
|
+
</filter>
|
|
293
|
+
<clipPath id="clip">
|
|
294
|
+
<rect width="160" height="32" rx="4"/>
|
|
295
|
+
</clipPath>
|
|
296
|
+
</defs>
|
|
297
|
+
<g clip-path="url(#clip)">
|
|
298
|
+
<rect width="160" height="32" fill="url(#cyberbg)"/>
|
|
299
|
+
<rect width="160" height="1" y="8" fill="url(#cyberglow)"/>
|
|
300
|
+
<rect width="160" height="1" y="23" fill="url(#cyberglow)"/>
|
|
301
|
+
<rect x="0" y="0" width="2" height="32" fill="${config.primary}" opacity="0.8"/>
|
|
302
|
+
<rect x="158" y="0" width="2" height="32" fill="${config.secondary}" opacity="0.8"/>
|
|
303
|
+
</g>
|
|
304
|
+
<text x="14" y="21" font-family="'Courier New', monospace" font-size="11" font-weight="bold" fill="${config.primary}" filter="url(#neon)">VIBE</text>
|
|
305
|
+
<text x="48" y="21" font-family="'Courier New', monospace" font-size="11" font-weight="bold" fill="#ffffff">CHECK</text>
|
|
306
|
+
<rect x="98" y="7" width="54" height="18" rx="2" fill="none" stroke="${config.primary}" stroke-width="1"/>
|
|
307
|
+
<text x="125" y="20" font-family="'Courier New', monospace" font-size="10" font-weight="bold" fill="${config.primary}" text-anchor="middle" filter="url(#neon)">${config.text}</text>
|
|
308
|
+
</svg>`;
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
svg,
|
|
312
|
+
markdown: `.toString('base64')})`,
|
|
313
|
+
html: svg,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Style: Minimal - Clean, modern, Apple-inspired
|
|
319
|
+
*/
|
|
320
|
+
function generateMinimalBadge(verdict, score) {
|
|
321
|
+
const configs = {
|
|
322
|
+
SHIP: { color: '#34C759', symbol: '●' },
|
|
323
|
+
WARN: { color: '#FF9500', symbol: '●' },
|
|
324
|
+
BLOCK: { color: '#FF3B30', symbol: '●' },
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const config = configs[verdict] || configs.SHIP;
|
|
328
|
+
|
|
329
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="90" height="24" viewBox="0 0 90 24">
|
|
330
|
+
<rect width="90" height="24" rx="12" fill="#f5f5f7"/>
|
|
331
|
+
<circle cx="14" cy="12" r="4" fill="${config.color}"/>
|
|
332
|
+
<text x="26" y="16" font-family="system-ui, -apple-system, SF Pro Text, sans-serif" font-size="11" font-weight="500" fill="#1d1d1f">vibecheck</text>
|
|
333
|
+
</svg>`;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
svg,
|
|
337
|
+
markdown: `.toString('base64')})`,
|
|
338
|
+
html: svg,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Style: Full Stats - Complete badge with score and details
|
|
344
|
+
*/
|
|
345
|
+
function generateFullStatsBadge(verdict, score, stats = {}) {
|
|
346
|
+
const configs = {
|
|
347
|
+
SHIP: {
|
|
348
|
+
gradient: ['#00C853', '#00E676'],
|
|
349
|
+
icon: '🚀',
|
|
350
|
+
label: 'SHIP READY',
|
|
351
|
+
},
|
|
352
|
+
WARN: {
|
|
353
|
+
gradient: ['#FF8F00', '#FFB300'],
|
|
354
|
+
icon: '⚠️',
|
|
355
|
+
label: 'WARNINGS',
|
|
356
|
+
},
|
|
357
|
+
BLOCK: {
|
|
358
|
+
gradient: ['#D50000', '#FF1744'],
|
|
359
|
+
icon: '🛑',
|
|
360
|
+
label: 'BLOCKED',
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const config = configs[verdict] || configs.SHIP;
|
|
365
|
+
const blockers = stats.blockers || 0;
|
|
366
|
+
const warnings = stats.warnings || 0;
|
|
367
|
+
|
|
368
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="200" height="60" viewBox="0 0 200 60">
|
|
369
|
+
<defs>
|
|
370
|
+
<linearGradient id="fullbg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
371
|
+
<stop offset="0%" style="stop-color:#1e1e2e"/>
|
|
372
|
+
<stop offset="100%" style="stop-color:#2d2d44"/>
|
|
373
|
+
</linearGradient>
|
|
374
|
+
<linearGradient id="statusgrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
375
|
+
<stop offset="0%" style="stop-color:${config.gradient[0]}"/>
|
|
376
|
+
<stop offset="100%" style="stop-color:${config.gradient[1]}"/>
|
|
377
|
+
</linearGradient>
|
|
378
|
+
</defs>
|
|
379
|
+
<rect width="200" height="60" rx="8" fill="url(#fullbg)"/>
|
|
380
|
+
<rect x="1" y="1" width="198" height="58" rx="7" fill="none" stroke="#3d3d5c" stroke-width="1"/>
|
|
381
|
+
|
|
382
|
+
<!-- Header -->
|
|
383
|
+
<text x="16" y="22" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="600" fill="#ffffff">⚡ vibecheck</text>
|
|
384
|
+
|
|
385
|
+
<!-- Status pill -->
|
|
386
|
+
<rect x="100" y="8" width="90" height="20" rx="10" fill="url(#statusgrad)"/>
|
|
387
|
+
<text x="145" y="22" font-family="system-ui, -apple-system, sans-serif" font-size="10" font-weight="700" fill="#000" text-anchor="middle">${config.label}</text>
|
|
388
|
+
|
|
389
|
+
<!-- Stats row -->
|
|
390
|
+
<text x="16" y="45" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#888">Score</text>
|
|
391
|
+
<text x="16" y="55" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600" fill="#fff">${score}%</text>
|
|
392
|
+
|
|
393
|
+
<text x="70" y="45" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#888">Blockers</text>
|
|
394
|
+
<text x="70" y="55" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600" fill="${blockers > 0 ? '#FF5252' : '#4CAF50'}">${blockers}</text>
|
|
395
|
+
|
|
396
|
+
<text x="130" y="45" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#888">Warnings</text>
|
|
397
|
+
<text x="130" y="55" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="600" fill="${warnings > 0 ? '#FFB300' : '#4CAF50'}">${warnings}</text>
|
|
398
|
+
</svg>`;
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
svg,
|
|
402
|
+
markdown: `.toString('base64')})`,
|
|
403
|
+
html: svg,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Style: Animated - CSS animation for web use
|
|
409
|
+
*/
|
|
410
|
+
function generateAnimatedBadge(verdict, score) {
|
|
411
|
+
const configs = {
|
|
412
|
+
SHIP: {
|
|
413
|
+
gradient: ['#00FF96', '#00D4AA', '#00B8D4'],
|
|
414
|
+
text: 'SHIP ✓',
|
|
415
|
+
},
|
|
416
|
+
WARN: {
|
|
417
|
+
gradient: ['#FFD700', '#FFA500', '#FF8C00'],
|
|
418
|
+
text: 'WARN ⚠',
|
|
419
|
+
},
|
|
420
|
+
BLOCK: {
|
|
421
|
+
gradient: ['#FF6B6B', '#FF4757', '#FF3838'],
|
|
422
|
+
text: 'BLOCK ✗',
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const config = configs[verdict] || configs.SHIP;
|
|
427
|
+
|
|
428
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="150" height="30" viewBox="0 0 150 30">
|
|
429
|
+
<defs>
|
|
430
|
+
<linearGradient id="animbg" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
431
|
+
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
|
432
|
+
<stop offset="100%" style="stop-color:#16213e"/>
|
|
433
|
+
</linearGradient>
|
|
434
|
+
<linearGradient id="animgrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
435
|
+
<stop offset="0%" style="stop-color:${config.gradient[0]}">
|
|
436
|
+
<animate attributeName="stop-color" values="${config.gradient.join(';')};${config.gradient[0]}" dur="3s" repeatCount="indefinite"/>
|
|
437
|
+
</stop>
|
|
438
|
+
<stop offset="100%" style="stop-color:${config.gradient[1]}">
|
|
439
|
+
<animate attributeName="stop-color" values="${config.gradient.slice(1).join(';')};${config.gradient[0]};${config.gradient[1]}" dur="3s" repeatCount="indefinite"/>
|
|
440
|
+
</stop>
|
|
441
|
+
</linearGradient>
|
|
442
|
+
<filter id="animglow">
|
|
443
|
+
<feGaussianBlur stdDeviation="1" result="blur"/>
|
|
444
|
+
<feMerge>
|
|
445
|
+
<feMergeNode in="blur"/>
|
|
446
|
+
<feMergeNode in="SourceGraphic"/>
|
|
447
|
+
</feMerge>
|
|
448
|
+
</filter>
|
|
449
|
+
</defs>
|
|
450
|
+
<rect width="150" height="30" rx="6" fill="url(#animbg)"/>
|
|
451
|
+
<rect x="1" y="1" width="148" height="28" rx="5" fill="none" stroke="url(#animgrad)" stroke-width="1.5">
|
|
452
|
+
<animate attributeName="stroke-opacity" values="0.5;1;0.5" dur="2s" repeatCount="indefinite"/>
|
|
453
|
+
</rect>
|
|
454
|
+
<text x="12" y="20" font-family="system-ui, sans-serif" font-size="11" font-weight="600" fill="url(#animgrad)" filter="url(#animglow)">⚡</text>
|
|
455
|
+
<text x="28" y="20" font-family="system-ui, sans-serif" font-size="11" font-weight="500" fill="#fff">vibecheck</text>
|
|
456
|
+
<rect x="95" y="6" width="48" height="18" rx="4" fill="url(#animgrad)"/>
|
|
457
|
+
<text x="119" y="19" font-family="system-ui, sans-serif" font-size="9" font-weight="700" fill="#000" text-anchor="middle">${config.text}</text>
|
|
458
|
+
</svg>`;
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
svg,
|
|
462
|
+
markdown: `.toString('base64')})`,
|
|
463
|
+
html: svg,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Style: Holographic - Premium iridescent effect
|
|
469
|
+
*/
|
|
470
|
+
function generateHolographicBadge(verdict, score) {
|
|
471
|
+
const configs = {
|
|
472
|
+
SHIP: { text: 'SHIP', icon: '✓' },
|
|
473
|
+
WARN: { text: 'WARN', icon: '⚠' },
|
|
474
|
+
BLOCK: { text: 'BLOCK', icon: '✗' },
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const config = configs[verdict] || configs.SHIP;
|
|
478
|
+
|
|
479
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="160" height="36" viewBox="0 0 160 36">
|
|
480
|
+
<defs>
|
|
481
|
+
<linearGradient id="holobg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
482
|
+
<stop offset="0%" style="stop-color:#0f0f1a"/>
|
|
483
|
+
<stop offset="50%" style="stop-color:#1a1a2e"/>
|
|
484
|
+
<stop offset="100%" style="stop-color:#0f0f1a"/>
|
|
485
|
+
</linearGradient>
|
|
486
|
+
<linearGradient id="hologram" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
487
|
+
<stop offset="0%" style="stop-color:#00FFFF">
|
|
488
|
+
<animate attributeName="stop-color" values="#00FFFF;#FF00FF;#FFFF00;#00FF00;#00FFFF" dur="4s" repeatCount="indefinite"/>
|
|
489
|
+
</stop>
|
|
490
|
+
<stop offset="25%" style="stop-color:#FF00FF">
|
|
491
|
+
<animate attributeName="stop-color" values="#FF00FF;#FFFF00;#00FF00;#00FFFF;#FF00FF" dur="4s" repeatCount="indefinite"/>
|
|
492
|
+
</stop>
|
|
493
|
+
<stop offset="50%" style="stop-color:#FFFF00">
|
|
494
|
+
<animate attributeName="stop-color" values="#FFFF00;#00FF00;#00FFFF;#FF00FF;#FFFF00" dur="4s" repeatCount="indefinite"/>
|
|
495
|
+
</stop>
|
|
496
|
+
<stop offset="75%" style="stop-color:#00FF00">
|
|
497
|
+
<animate attributeName="stop-color" values="#00FF00;#00FFFF;#FF00FF;#FFFF00;#00FF00" dur="4s" repeatCount="indefinite"/>
|
|
498
|
+
</stop>
|
|
499
|
+
<stop offset="100%" style="stop-color:#00FFFF">
|
|
500
|
+
<animate attributeName="stop-color" values="#00FFFF;#FF00FF;#FFFF00;#00FF00;#00FFFF" dur="4s" repeatCount="indefinite"/>
|
|
501
|
+
</stop>
|
|
502
|
+
</linearGradient>
|
|
503
|
+
<filter id="hologlow">
|
|
504
|
+
<feGaussianBlur stdDeviation="2" result="blur"/>
|
|
505
|
+
<feMerge>
|
|
506
|
+
<feMergeNode in="blur"/>
|
|
507
|
+
<feMergeNode in="blur"/>
|
|
508
|
+
<feMergeNode in="SourceGraphic"/>
|
|
509
|
+
</feMerge>
|
|
510
|
+
</filter>
|
|
511
|
+
<mask id="holomask">
|
|
512
|
+
<rect width="160" height="36" rx="8" fill="white"/>
|
|
513
|
+
</mask>
|
|
514
|
+
</defs>
|
|
515
|
+
<g mask="url(#holomask)">
|
|
516
|
+
<rect width="160" height="36" fill="url(#holobg)"/>
|
|
517
|
+
<rect x="0" y="0" width="160" height="36" fill="url(#hologram)" opacity="0.1">
|
|
518
|
+
<animate attributeName="opacity" values="0.05;0.15;0.05" dur="2s" repeatCount="indefinite"/>
|
|
519
|
+
</rect>
|
|
520
|
+
</g>
|
|
521
|
+
<rect x="1" y="1" width="158" height="34" rx="7" fill="none" stroke="url(#hologram)" stroke-width="1.5"/>
|
|
522
|
+
<text x="16" y="24" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="700" fill="url(#hologram)" filter="url(#hologlow)">⚡ vibecheck</text>
|
|
523
|
+
<rect x="108" y="8" width="44" height="20" rx="4" fill="url(#hologram)" opacity="0.9"/>
|
|
524
|
+
<text x="130" y="22" font-family="system-ui, -apple-system, sans-serif" font-size="10" font-weight="800" fill="#000" text-anchor="middle">${config.icon} ${config.text}</text>
|
|
525
|
+
</svg>`;
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
svg,
|
|
529
|
+
markdown: `.toString('base64')})`,
|
|
530
|
+
html: svg,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
535
|
+
// BADGE STYLE REGISTRY
|
|
536
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
537
|
+
|
|
538
|
+
const BADGE_STYLES = {
|
|
539
|
+
classic: {
|
|
540
|
+
name: 'Classic',
|
|
541
|
+
description: 'shields.io compatible',
|
|
542
|
+
generator: generateClassicBadge,
|
|
543
|
+
},
|
|
544
|
+
gradient: {
|
|
545
|
+
name: 'Gradient',
|
|
546
|
+
description: 'Modern gradient with glow',
|
|
547
|
+
generator: generateGradientBadge,
|
|
548
|
+
},
|
|
549
|
+
cyberpunk: {
|
|
550
|
+
name: 'Cyberpunk',
|
|
551
|
+
description: 'Neon tech aesthetic',
|
|
552
|
+
generator: generateCyberpunkBadge,
|
|
553
|
+
},
|
|
554
|
+
minimal: {
|
|
555
|
+
name: 'Minimal',
|
|
556
|
+
description: 'Clean Apple-inspired',
|
|
557
|
+
generator: generateMinimalBadge,
|
|
558
|
+
},
|
|
559
|
+
full: {
|
|
560
|
+
name: 'Full Stats',
|
|
561
|
+
description: 'Complete with score & stats',
|
|
562
|
+
generator: generateFullStatsBadge,
|
|
563
|
+
},
|
|
564
|
+
animated: {
|
|
565
|
+
name: 'Animated',
|
|
566
|
+
description: 'CSS animation for web',
|
|
567
|
+
generator: generateAnimatedBadge,
|
|
568
|
+
},
|
|
569
|
+
holographic: {
|
|
570
|
+
name: 'Holographic',
|
|
571
|
+
description: 'Premium iridescent effect',
|
|
572
|
+
generator: generateHolographicBadge,
|
|
573
|
+
},
|
|
16
574
|
};
|
|
17
575
|
|
|
576
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
577
|
+
// TERMINAL PREVIEW
|
|
578
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
579
|
+
|
|
580
|
+
function printBadgePreview(verdict, score, style) {
|
|
581
|
+
const verdictColors = {
|
|
582
|
+
SHIP: colors.ship,
|
|
583
|
+
WARN: colors.warn,
|
|
584
|
+
BLOCK: colors.block,
|
|
585
|
+
};
|
|
586
|
+
const vColor = verdictColors[verdict] || colors.accent;
|
|
587
|
+
const vIcon = verdict === 'SHIP' ? ICONS.ship : verdict === 'WARN' ? ICONS.warn : ICONS.block;
|
|
588
|
+
|
|
589
|
+
console.log();
|
|
590
|
+
console.log(` ${c.dim}┌${'─'.repeat(50)}┐${c.reset}`);
|
|
591
|
+
console.log(` ${c.dim}│${c.reset} ${ICONS.paint} ${c.bold}Badge Preview${c.reset}${' '.repeat(33)}${c.dim}│${c.reset}`);
|
|
592
|
+
console.log(` ${c.dim}├${'─'.repeat(50)}┤${c.reset}`);
|
|
593
|
+
console.log(` ${c.dim}│${c.reset} ${c.dim}│${c.reset}`);
|
|
594
|
+
|
|
595
|
+
// Terminal representation of badge
|
|
596
|
+
const styleInfo = BADGE_STYLES[style] || BADGE_STYLES.gradient;
|
|
597
|
+
console.log(` ${c.dim}│${c.reset} ${bgRgb(26, 26, 46)} ${colors.accent}⚡${c.reset}${bgRgb(26, 26, 46)} vibecheck ${c.reset}${vColor}${bgRgb(0, 50, 40)} ${vIcon} ${verdict} ${c.reset} ${c.dim}│${c.reset}`);
|
|
598
|
+
|
|
599
|
+
console.log(` ${c.dim}│${c.reset} ${c.dim}│${c.reset}`);
|
|
600
|
+
console.log(` ${c.dim}│${c.reset} ${c.dim}Style:${c.reset} ${colors.accent}${styleInfo.name}${c.reset} ${c.dim}(${styleInfo.description})${c.reset}${' '.repeat(Math.max(0, 20 - styleInfo.name.length - styleInfo.description.length))}${c.dim}│${c.reset}`);
|
|
601
|
+
console.log(` ${c.dim}│${c.reset} ${c.dim}Score:${c.reset} ${score >= 80 ? colors.success : score >= 50 ? colors.warn : colors.block}${score}%${c.reset}${' '.repeat(39)}${c.dim}│${c.reset}`);
|
|
602
|
+
console.log(` ${c.dim}│${c.reset} ${c.dim}│${c.reset}`);
|
|
603
|
+
console.log(` ${c.dim}└${'─'.repeat(50)}┘${c.reset}`);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function printStyleGallery() {
|
|
607
|
+
printSection('AVAILABLE STYLES', ICONS.paint);
|
|
608
|
+
console.log();
|
|
609
|
+
|
|
610
|
+
for (const [key, style] of Object.entries(BADGE_STYLES)) {
|
|
611
|
+
const isAnimated = key === 'animated' || key === 'holographic';
|
|
612
|
+
const animTag = isAnimated ? ` ${c.dim}(animated)${c.reset}` : '';
|
|
613
|
+
console.log(` ${colors.accent}${key.padEnd(12)}${c.reset} ${c.bold}${style.name}${c.reset}${animTag}`);
|
|
614
|
+
console.log(` ${' '.repeat(12)} ${c.dim}${style.description}${c.reset}`);
|
|
615
|
+
console.log();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
620
|
+
// ARGS PARSER
|
|
621
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
622
|
+
|
|
18
623
|
function parseArgs(args) {
|
|
19
624
|
const opts = {
|
|
20
625
|
help: false,
|
|
21
626
|
output: null,
|
|
22
|
-
format:
|
|
627
|
+
format: 'markdown',
|
|
628
|
+
style: 'gradient',
|
|
23
629
|
verdict: null,
|
|
24
630
|
score: null,
|
|
631
|
+
list: false,
|
|
632
|
+
all: false,
|
|
25
633
|
};
|
|
26
634
|
|
|
27
635
|
for (let i = 0; i < args.length; i++) {
|
|
28
636
|
const arg = args[i];
|
|
29
637
|
switch (arg) {
|
|
30
|
-
case
|
|
31
|
-
case
|
|
638
|
+
case '--help':
|
|
639
|
+
case '-h':
|
|
32
640
|
opts.help = true;
|
|
33
641
|
break;
|
|
34
|
-
case
|
|
35
|
-
case
|
|
642
|
+
case '--output':
|
|
643
|
+
case '-o':
|
|
36
644
|
opts.output = args[++i];
|
|
37
645
|
break;
|
|
38
|
-
case
|
|
39
|
-
case
|
|
646
|
+
case '--format':
|
|
647
|
+
case '-f':
|
|
40
648
|
opts.format = args[++i];
|
|
41
649
|
break;
|
|
42
|
-
case
|
|
43
|
-
|
|
650
|
+
case '--style':
|
|
651
|
+
case '-s':
|
|
652
|
+
opts.style = args[++i];
|
|
44
653
|
break;
|
|
45
|
-
case
|
|
654
|
+
case '--verdict':
|
|
655
|
+
opts.verdict = args[++i]?.toUpperCase();
|
|
656
|
+
break;
|
|
657
|
+
case '--score':
|
|
46
658
|
opts.score = parseInt(args[++i]);
|
|
47
659
|
break;
|
|
660
|
+
case '--list':
|
|
661
|
+
case '-l':
|
|
662
|
+
opts.list = true;
|
|
663
|
+
break;
|
|
664
|
+
case '--all':
|
|
665
|
+
case '-a':
|
|
666
|
+
opts.all = true;
|
|
667
|
+
break;
|
|
48
668
|
}
|
|
49
669
|
}
|
|
50
670
|
|
|
51
671
|
return opts;
|
|
52
672
|
}
|
|
53
673
|
|
|
674
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
675
|
+
// HELP DISPLAY
|
|
676
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
677
|
+
|
|
54
678
|
function printHelp() {
|
|
679
|
+
console.log(BANNER_FULL);
|
|
55
680
|
console.log(`
|
|
56
|
-
${c.bold}
|
|
57
|
-
|
|
58
|
-
${c.bold}USAGE${c.reset}
|
|
59
|
-
vibecheck badge [options]
|
|
60
|
-
|
|
61
|
-
${c.bold}OPTIONS${c.reset}
|
|
62
|
-
--help, -h Show this help
|
|
63
|
-
--output, -o <file> Output file (default: stdout)
|
|
64
|
-
--format, -f <fmt> Format: markdown, html, svg (default: markdown)
|
|
65
|
-
--verdict <v> Override verdict: SHIP, WARN, BLOCK
|
|
66
|
-
--score <n> Override score (0-100)
|
|
67
|
-
|
|
68
|
-
${c.bold}EXAMPLES${c.reset}
|
|
69
|
-
vibecheck badge # Generate from last scan
|
|
70
|
-
vibecheck badge -o README.md # Append to README
|
|
71
|
-
vibecheck badge --format svg # SVG badge
|
|
72
|
-
vibecheck badge --verdict SHIP # Force SHIP badge
|
|
73
|
-
`);
|
|
74
|
-
}
|
|
681
|
+
${c.bold}Usage:${c.reset} vibecheck badge [options]
|
|
75
682
|
|
|
76
|
-
|
|
77
|
-
switch (verdict) {
|
|
78
|
-
case "SHIP": return "brightgreen";
|
|
79
|
-
case "WARN": return "yellow";
|
|
80
|
-
case "BLOCK": return "red";
|
|
81
|
-
default: return "lightgrey";
|
|
82
|
-
}
|
|
83
|
-
}
|
|
683
|
+
${c.bold}Premium Badge Generator${c.reset} — Show off your ship status in style.
|
|
84
684
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
685
|
+
${c.bold}Options:${c.reset}
|
|
686
|
+
${colors.accent}--help, -h${c.reset} Show this help
|
|
687
|
+
${colors.accent}--output, -o <file>${c.reset} Output file ${c.dim}(default: stdout)${c.reset}
|
|
688
|
+
${colors.accent}--format, -f <fmt>${c.reset} Format: markdown, html, svg ${c.dim}(default: markdown)${c.reset}
|
|
689
|
+
${colors.accent}--style, -s <style>${c.reset} Badge style ${c.dim}(default: gradient)${c.reset}
|
|
690
|
+
${colors.accent}--verdict <v>${c.reset} Override: SHIP, WARN, BLOCK
|
|
691
|
+
${colors.accent}--score <n>${c.reset} Override score (0-100)
|
|
692
|
+
${colors.accent}--list, -l${c.reset} Show all available styles
|
|
693
|
+
${colors.accent}--all, -a${c.reset} Generate all badge styles
|
|
91
694
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
695
|
+
${c.bold}Badge Styles:${c.reset}
|
|
696
|
+
${colors.accent}classic${c.reset} shields.io compatible
|
|
697
|
+
${colors.accent}gradient${c.reset} Modern gradient with glow ${c.dim}(default)${c.reset}
|
|
698
|
+
${colors.accent}cyberpunk${c.reset} Neon tech aesthetic
|
|
699
|
+
${colors.accent}minimal${c.reset} Clean Apple-inspired
|
|
700
|
+
${colors.accent}full${c.reset} Complete with score & stats
|
|
701
|
+
${colors.accent}animated${c.reset} CSS animation for web
|
|
702
|
+
${colors.accent}holographic${c.reset} Premium iridescent effect
|
|
96
703
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</svg>`;
|
|
704
|
+
${c.bold}Examples:${c.reset}
|
|
705
|
+
${c.dim}# Generate default gradient badge${c.reset}
|
|
706
|
+
vibecheck badge
|
|
707
|
+
|
|
708
|
+
${c.dim}# Cyberpunk style badge${c.reset}
|
|
709
|
+
vibecheck badge --style cyberpunk
|
|
710
|
+
|
|
711
|
+
${c.dim}# Add to README${c.reset}
|
|
712
|
+
vibecheck badge -o README.md
|
|
713
|
+
|
|
714
|
+
${c.dim}# Force SHIP badge${c.reset}
|
|
715
|
+
vibecheck badge --verdict SHIP
|
|
716
|
+
|
|
717
|
+
${c.dim}# SVG output${c.reset}
|
|
718
|
+
vibecheck badge --format svg -o badge.svg
|
|
719
|
+
|
|
720
|
+
${c.dim}# Generate all styles${c.reset}
|
|
721
|
+
vibecheck badge --all
|
|
722
|
+
`);
|
|
117
723
|
}
|
|
118
724
|
|
|
725
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
726
|
+
// MAIN BADGE FUNCTION
|
|
727
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
728
|
+
|
|
119
729
|
async function runBadge(args) {
|
|
120
730
|
const opts = parseArgs(args);
|
|
121
731
|
|
|
@@ -124,59 +734,156 @@ async function runBadge(args) {
|
|
|
124
734
|
return 0;
|
|
125
735
|
}
|
|
126
736
|
|
|
127
|
-
|
|
128
|
-
|
|
737
|
+
if (opts.list) {
|
|
738
|
+
console.log(BANNER_FULL);
|
|
739
|
+
printStyleGallery();
|
|
740
|
+
return 0;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Load last scan result
|
|
744
|
+
let verdict = opts.verdict || 'SHIP';
|
|
129
745
|
let score = opts.score || 100;
|
|
746
|
+
let stats = { blockers: 0, warnings: 0 };
|
|
130
747
|
|
|
131
|
-
const lastScanPath = path.join(process.cwd(),
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
748
|
+
const lastScanPath = path.join(process.cwd(), '.vibecheck', 'last_scan.json');
|
|
749
|
+
const lastShipPath = path.join(process.cwd(), '.vibecheck', 'last_ship.json');
|
|
750
|
+
|
|
751
|
+
if (!opts.verdict) {
|
|
752
|
+
// Try ship report first, then scan
|
|
753
|
+
const reportPath = fs.existsSync(lastShipPath) ? lastShipPath : lastScanPath;
|
|
754
|
+
|
|
755
|
+
if (fs.existsSync(reportPath)) {
|
|
756
|
+
try {
|
|
757
|
+
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
|
758
|
+
verdict = report.verdict || 'SHIP';
|
|
759
|
+
score = report.score ?? report.vibeScore ?? 100;
|
|
760
|
+
stats = {
|
|
761
|
+
blockers: report.counts?.BLOCK || report.findings?.filter(f => f.severity === 'BLOCK').length || 0,
|
|
762
|
+
warnings: report.counts?.WARN || report.findings?.filter(f => f.severity === 'WARN').length || 0,
|
|
763
|
+
};
|
|
764
|
+
} catch (e) {
|
|
765
|
+
// Use defaults
|
|
766
|
+
}
|
|
139
767
|
}
|
|
140
768
|
}
|
|
141
769
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
770
|
+
const projectName = path.basename(process.cwd());
|
|
771
|
+
|
|
772
|
+
// Print banner
|
|
773
|
+
console.log(BANNER_FULL);
|
|
774
|
+
|
|
775
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
776
|
+
console.log(` ${c.dim}Verdict:${c.reset} ${verdict === 'SHIP' ? colors.ship : verdict === 'WARN' ? colors.warn : colors.block}${verdict}${c.reset}`);
|
|
777
|
+
console.log(` ${c.dim}Score:${c.reset} ${score}%`);
|
|
778
|
+
|
|
779
|
+
// Generate badge(s)
|
|
780
|
+
if (opts.all) {
|
|
781
|
+
// Generate all styles
|
|
782
|
+
printSection('ALL BADGE STYLES', ICONS.sparkle);
|
|
783
|
+
console.log();
|
|
784
|
+
|
|
785
|
+
for (const [key, styleInfo] of Object.entries(BADGE_STYLES)) {
|
|
786
|
+
const badge = styleInfo.generator(verdict, score, stats);
|
|
787
|
+
console.log(` ${colors.accent}${c.bold}${styleInfo.name}${c.reset} ${c.dim}(--style ${key})${c.reset}`);
|
|
788
|
+
console.log();
|
|
789
|
+
console.log(` ${c.dim}Markdown:${c.reset}`);
|
|
790
|
+
console.log(` ${badge.markdown || badge}`);
|
|
791
|
+
console.log();
|
|
792
|
+
printDivider('─', 50);
|
|
793
|
+
console.log();
|
|
794
|
+
}
|
|
795
|
+
} else {
|
|
796
|
+
// Generate single style
|
|
797
|
+
const styleKey = opts.style || 'gradient';
|
|
798
|
+
const styleInfo = BADGE_STYLES[styleKey];
|
|
799
|
+
|
|
800
|
+
if (!styleInfo) {
|
|
801
|
+
console.log();
|
|
802
|
+
console.log(` ${colors.block}${ICONS.cross}${c.reset} Unknown style: ${styleKey}`);
|
|
803
|
+
console.log(` ${c.dim}Run 'vibecheck badge --list' to see available styles${c.reset}`);
|
|
804
|
+
return 1;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const badge = styleInfo.generator(verdict, score, stats);
|
|
808
|
+
|
|
809
|
+
// Preview
|
|
810
|
+
printBadgePreview(verdict, score, styleKey);
|
|
154
811
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
812
|
+
// Output
|
|
813
|
+
printSection('GENERATED BADGE', ICONS.badge);
|
|
814
|
+
console.log();
|
|
815
|
+
|
|
816
|
+
if (opts.output) {
|
|
817
|
+
const outputPath = path.resolve(opts.output);
|
|
818
|
+
const content = opts.format === 'svg' ? badge.svg :
|
|
819
|
+
opts.format === 'html' ? badge.html :
|
|
820
|
+
badge.markdown || badge;
|
|
821
|
+
|
|
822
|
+
if (fs.existsSync(outputPath)) {
|
|
823
|
+
let existing = fs.readFileSync(outputPath, 'utf8');
|
|
824
|
+
|
|
825
|
+
// Replace existing badge or append
|
|
826
|
+
if (existing.includes('vibecheck')) {
|
|
827
|
+
const updated = existing.replace(/\[!\[vibecheck\].*?\]\(https:\/\/vibecheckai\.dev\)/g, content);
|
|
828
|
+
fs.writeFileSync(outputPath, updated);
|
|
829
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Updated badge in ${c.bold}${outputPath}${c.reset}`);
|
|
830
|
+
} else {
|
|
831
|
+
fs.writeFileSync(outputPath, existing + '\n\n' + content + '\n');
|
|
832
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Added badge to ${c.bold}${outputPath}${c.reset}`);
|
|
833
|
+
}
|
|
165
834
|
} else {
|
|
166
|
-
fs.writeFileSync(outputPath, content +
|
|
167
|
-
console.log(
|
|
835
|
+
fs.writeFileSync(outputPath, content + '\n');
|
|
836
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Created ${c.bold}${outputPath}${c.reset}`);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Also save SVG to .vibecheck
|
|
840
|
+
const badgeDir = path.join(process.cwd(), '.vibecheck', 'badges');
|
|
841
|
+
if (!fs.existsSync(badgeDir)) {
|
|
842
|
+
fs.mkdirSync(badgeDir, { recursive: true });
|
|
843
|
+
}
|
|
844
|
+
if (badge.svg) {
|
|
845
|
+
fs.writeFileSync(path.join(badgeDir, `badge-${styleKey}.svg`), badge.svg);
|
|
846
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Saved ${c.dim}.vibecheck/badges/badge-${styleKey}.svg${c.reset}`);
|
|
168
847
|
}
|
|
169
848
|
} else {
|
|
170
|
-
|
|
171
|
-
console.log(
|
|
849
|
+
// Output to console
|
|
850
|
+
console.log(` ${c.dim}Format: ${opts.format}${c.reset}`);
|
|
851
|
+
console.log();
|
|
852
|
+
|
|
853
|
+
const output = opts.format === 'svg' ? badge.svg :
|
|
854
|
+
opts.format === 'html' ? badge.html :
|
|
855
|
+
badge.markdown || badge;
|
|
856
|
+
|
|
857
|
+
console.log(` ${colors.accent}${BOX.topLeft}${'─'.repeat(60)}${BOX.topRight}${c.reset}`);
|
|
858
|
+
console.log(` ${colors.accent}${BOX.vertical}${c.reset} ${ICONS.copy} ${c.bold}Copy this to your README:${c.reset}${' '.repeat(30)}${colors.accent}${BOX.vertical}${c.reset}`);
|
|
859
|
+
console.log(` ${colors.accent}${BOX.vertical}${c.reset}${' '.repeat(60)}${colors.accent}${BOX.vertical}${c.reset}`);
|
|
860
|
+
|
|
861
|
+
// Word wrap for long content
|
|
862
|
+
const maxWidth = 56;
|
|
863
|
+
if (output.length > maxWidth) {
|
|
864
|
+
const lines = [];
|
|
865
|
+
for (let i = 0; i < output.length; i += maxWidth) {
|
|
866
|
+
lines.push(output.slice(i, i + maxWidth));
|
|
867
|
+
}
|
|
868
|
+
for (const line of lines) {
|
|
869
|
+
console.log(` ${colors.accent}${BOX.vertical}${c.reset} ${c.dim}${line}${c.reset}${' '.repeat(Math.max(0, maxWidth - line.length + 2))}${colors.accent}${BOX.vertical}${c.reset}`);
|
|
870
|
+
}
|
|
871
|
+
} else {
|
|
872
|
+
console.log(` ${colors.accent}${BOX.vertical}${c.reset} ${c.dim}${output}${c.reset}${' '.repeat(Math.max(0, maxWidth - output.length + 2))}${colors.accent}${BOX.vertical}${c.reset}`);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
console.log(` ${colors.accent}${BOX.vertical}${c.reset}${' '.repeat(60)}${colors.accent}${BOX.vertical}${c.reset}`);
|
|
876
|
+
console.log(` ${colors.accent}${BOX.bottomLeft}${'─'.repeat(60)}${BOX.bottomRight}${c.reset}`);
|
|
172
877
|
}
|
|
173
|
-
} else {
|
|
174
|
-
console.log(`\n${c.bold}Generated Badge:${c.reset}\n`);
|
|
175
|
-
console.log(badge);
|
|
176
|
-
console.log(`\n${c.dim}Copy and paste into your README.md${c.reset}\n`);
|
|
177
878
|
}
|
|
178
879
|
|
|
880
|
+
// Tips
|
|
881
|
+
console.log();
|
|
882
|
+
console.log(` ${c.dim}Tip: Use ${colors.accent}--style holographic${c.reset}${c.dim} for the premium animated effect${c.reset}`);
|
|
883
|
+
console.log(` ${c.dim} Use ${colors.accent}--all${c.reset}${c.dim} to see all available styles${c.reset}`);
|
|
884
|
+
console.log();
|
|
885
|
+
|
|
179
886
|
return 0;
|
|
180
887
|
}
|
|
181
888
|
|
|
182
|
-
module.exports = { runBadge }
|
|
889
|
+
module.exports = { runBadge }
|