@vibecheckai/cli 3.1.6 → 3.2.0

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