@vibecheckai/cli 3.7.0 → 3.8.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 (99) hide show
  1. package/README.md +135 -63
  2. package/bin/_deprecations.js +447 -19
  3. package/bin/_router.js +1 -1
  4. package/bin/registry.js +347 -280
  5. package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
  6. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  7. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  8. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  9. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  10. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  11. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  12. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  13. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  14. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  15. package/bin/runners/lib/agent-firewall/index.js +200 -0
  16. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  17. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  18. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +622 -0
  19. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  20. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  21. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  22. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  23. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  24. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  25. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  26. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  27. package/bin/runners/lib/artifact-envelope.js +540 -0
  28. package/bin/runners/lib/auth-shared.js +977 -0
  29. package/bin/runners/lib/checkpoint.js +941 -0
  30. package/bin/runners/lib/cleanup/engine.js +571 -0
  31. package/bin/runners/lib/cleanup/index.js +53 -0
  32. package/bin/runners/lib/cleanup/output.js +375 -0
  33. package/bin/runners/lib/cleanup/rules.js +1060 -0
  34. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  35. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  36. package/bin/runners/lib/doctor/fix-script.js +336 -0
  37. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  38. package/bin/runners/lib/doctor/modules/index.js +62 -3
  39. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  40. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  41. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  42. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  43. package/bin/runners/lib/entitlements-v2.js +2 -2
  44. package/bin/runners/lib/missions/briefing.js +427 -0
  45. package/bin/runners/lib/missions/checkpoint.js +753 -0
  46. package/bin/runners/lib/missions/hardening.js +851 -0
  47. package/bin/runners/lib/missions/plan.js +421 -32
  48. package/bin/runners/lib/missions/safety-gates.js +645 -0
  49. package/bin/runners/lib/missions/schema.js +478 -0
  50. package/bin/runners/lib/packs/bundle.js +675 -0
  51. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  52. package/bin/runners/lib/packs/pack-factory.js +837 -0
  53. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  54. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  55. package/bin/runners/lib/safelist/index.js +96 -0
  56. package/bin/runners/lib/safelist/integration.js +334 -0
  57. package/bin/runners/lib/safelist/matcher.js +696 -0
  58. package/bin/runners/lib/safelist/schema.js +948 -0
  59. package/bin/runners/lib/safelist/store.js +438 -0
  60. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  61. package/bin/runners/lib/ship-gate.js +832 -0
  62. package/bin/runners/lib/ship-manifest.js +1153 -0
  63. package/bin/runners/lib/ship-output.js +1 -1
  64. package/bin/runners/lib/unified-cli-output.js +710 -383
  65. package/bin/runners/lib/upsell.js +3 -3
  66. package/bin/runners/lib/why-tree.js +650 -0
  67. package/bin/runners/runAllowlist.js +33 -4
  68. package/bin/runners/runApprove.js +240 -1122
  69. package/bin/runners/runAudit.js +692 -0
  70. package/bin/runners/runAuth.js +325 -29
  71. package/bin/runners/runCheckpoint.js +442 -494
  72. package/bin/runners/runCleanup.js +343 -0
  73. package/bin/runners/runDoctor.js +269 -19
  74. package/bin/runners/runFix.js +411 -32
  75. package/bin/runners/runForge.js +411 -0
  76. package/bin/runners/runIntent.js +906 -0
  77. package/bin/runners/runKickoff.js +878 -0
  78. package/bin/runners/runLaunch.js +2000 -0
  79. package/bin/runners/runLink.js +785 -0
  80. package/bin/runners/runMcp.js +1741 -837
  81. package/bin/runners/runPacks.js +2089 -0
  82. package/bin/runners/runPolish.js +41 -0
  83. package/bin/runners/runSafelist.js +1190 -0
  84. package/bin/runners/runScan.js +21 -9
  85. package/bin/runners/runShield.js +1282 -0
  86. package/bin/runners/runShip.js +395 -16
  87. package/bin/vibecheck.js +34 -6
  88. package/mcp-server/README.md +117 -158
  89. package/mcp-server/handlers/tool-handler.ts +3 -3
  90. package/mcp-server/index.js +16 -0
  91. package/mcp-server/intent-firewall-interceptor.js +529 -0
  92. package/mcp-server/manifest.json +473 -0
  93. package/mcp-server/package.json +1 -1
  94. package/mcp-server/registry/tool-registry.js +315 -523
  95. package/mcp-server/registry/tools.json +442 -428
  96. package/mcp-server/tier-auth.js +68 -11
  97. package/mcp-server/tools-v3.js +70 -16
  98. package/package.json +1 -1
  99. package/bin/runners/runProof.zip +0 -0
@@ -1,37 +1,45 @@
1
1
  /**
2
- * Unified CLI Output - Enterprise-Grade Terminal Experience
2
+ * Unified CLI Output - World-Class Terminal Experience
3
3
  *
4
4
  * ═══════════════════════════════════════════════════════════════════════════════
5
- * VIBECHECK DESIGN SYSTEM
5
+ * THE VIBECHECK DESIGN SYSTEM v2.0
6
6
  * ═══════════════════════════════════════════════════════════════════════════════
7
7
  *
8
- * This module provides consistent, professional output formatting for all CLI
9
- * commands. Use these functions to ensure a unified enterprise-grade experience.
8
+ * Provides consistent, professional terminal UI across all CLI commands:
9
+ * ASCII art banners (main + command-specific)
10
+ * • Box-drawing layouts with split columns
11
+ * • Progress bars and status indicators
12
+ * • Verdicts and action sections
13
+ *
14
+ * @version 2.0.0
10
15
  */
11
16
 
12
17
  "use strict";
13
18
 
14
- const { getApiKey } = require("./auth");
15
-
16
19
  // ═══════════════════════════════════════════════════════════════════════════════
17
20
  // CONSTANTS
18
21
  // ═══════════════════════════════════════════════════════════════════════════════
19
22
 
20
- const WIDTH = 72;
21
- const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
22
- const SUPPORTS_UNICODE = !process.env.NO_UNICODE && process.platform !== "win32" || process.env.WT_SESSION;
23
+ const WIDTH = 80; // Standard terminal width
24
+ const INNER_WIDTH = WIDTH - 4; // Inside borders
23
25
 
24
26
  // ═══════════════════════════════════════════════════════════════════════════════
25
27
  // ANSI CODES
26
28
  // ═══════════════════════════════════════════════════════════════════════════════
27
29
 
28
30
  const ESC = "\x1b";
31
+ const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
32
+ const SUPPORTS_TRUECOLOR = process.env.COLORTERM === "truecolor" ||
33
+ process.env.TERM_PROGRAM === "iTerm.app" ||
34
+ process.env.WT_SESSION;
35
+
29
36
  const ansi = SUPPORTS_COLOR ? {
30
37
  reset: `${ESC}[0m`,
31
38
  bold: `${ESC}[1m`,
32
39
  dim: `${ESC}[2m`,
33
40
  italic: `${ESC}[3m`,
34
41
  underline: `${ESC}[4m`,
42
+ // Standard colors
35
43
  red: `${ESC}[31m`,
36
44
  green: `${ESC}[32m`,
37
45
  yellow: `${ESC}[33m`,
@@ -40,514 +48,830 @@ const ansi = SUPPORTS_COLOR ? {
40
48
  cyan: `${ESC}[36m`,
41
49
  white: `${ESC}[37m`,
42
50
  gray: `${ESC}[90m`,
43
- bgGreen: `${ESC}[42m`,
51
+ // Bright colors
52
+ brightRed: `${ESC}[91m`,
53
+ brightGreen: `${ESC}[92m`,
54
+ brightYellow: `${ESC}[93m`,
55
+ brightCyan: `${ESC}[96m`,
56
+ // Backgrounds
44
57
  bgRed: `${ESC}[41m`,
58
+ bgGreen: `${ESC}[42m`,
45
59
  bgYellow: `${ESC}[43m`,
46
- bgCyan: `${ESC}[46m`,
60
+ bgBlue: `${ESC}[44m`,
47
61
  bgMagenta: `${ESC}[45m`,
62
+ bgCyan: `${ESC}[46m`,
63
+ // RGB (truecolor)
64
+ rgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `${ESC}[38;2;${r};${g};${b}m` : "",
65
+ bgRgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `${ESC}[48;2;${r};${g};${b}m` : "",
66
+ // Cursor control
67
+ hideCursor: `${ESC}[?25l`,
68
+ showCursor: `${ESC}[?25h`,
69
+ clearLine: `${ESC}[2K`,
70
+ cursorUp: (n = 1) => `${ESC}[${n}A`,
48
71
  } : Object.fromEntries([
49
72
  "reset", "bold", "dim", "italic", "underline",
50
73
  "red", "green", "yellow", "blue", "magenta", "cyan", "white", "gray",
51
- "bgGreen", "bgRed", "bgYellow", "bgCyan", "bgMagenta"
74
+ "brightRed", "brightGreen", "brightYellow", "brightCyan",
75
+ "bgRed", "bgGreen", "bgYellow", "bgBlue", "bgMagenta", "bgCyan",
76
+ "hideCursor", "showCursor", "clearLine"
52
77
  ].map(k => [k, ""]));
53
78
 
79
+ // Add stub functions if colors disabled
80
+ if (!SUPPORTS_COLOR) {
81
+ ansi.rgb = () => "";
82
+ ansi.bgRgb = () => "";
83
+ ansi.cursorUp = () => "";
84
+ }
85
+
54
86
  // ═══════════════════════════════════════════════════════════════════════════════
55
87
  // SYMBOLS
56
88
  // ═══════════════════════════════════════════════════════════════════════════════
57
89
 
58
- const sym = SUPPORTS_UNICODE ? {
90
+ const sym = {
91
+ // Status
59
92
  check: "✓",
60
93
  cross: "✗",
61
94
  warning: "⚠",
62
95
  info: "ℹ",
63
- arrow: "→",
64
96
  bullet: "•",
97
+ arrow: "→",
98
+ arrowRight: "▸",
65
99
  star: "★",
100
+ // Progress
101
+ blockFull: "█",
102
+ blockMed: "▓",
103
+ blockLight: "▒",
104
+ blockEmpty: "░",
105
+ // Box drawing (double)
106
+ boxTL: "╔",
107
+ boxTR: "╗",
108
+ boxBL: "╚",
109
+ boxBR: "╝",
110
+ boxH: "═",
111
+ boxV: "║",
112
+ boxTeeR: "╠",
113
+ boxTeeL: "╣",
114
+ boxTeeD: "╦",
115
+ boxTeeU: "╩",
116
+ boxCross: "╬",
117
+ // Box drawing (single)
118
+ lineTL: "┌",
119
+ lineTR: "┐",
120
+ lineBL: "└",
121
+ lineBR: "┘",
122
+ lineH: "─",
123
+ lineV: "│",
124
+ lineTeeR: "├",
125
+ lineTeeL: "┤",
126
+ lineTeeD: "┬",
127
+ lineTeeU: "┴",
128
+ lineCross: "┼",
129
+ // Emoji
66
130
  rocket: "🚀",
131
+ fire: "🔥",
132
+ brain: "🧠",
67
133
  shield: "🛡️",
68
- lock: "🔒",
69
- key: "🔑",
70
- gear: "⚙",
71
134
  search: "🔍",
72
- chart: "📊",
73
- file: "📄",
74
- folder: "📁",
135
+ lock: "🔐",
136
+ unlock: "🔓",
137
+ link: "🔗",
75
138
  clock: "⏱",
76
- lightning: "⚡",
77
- sparkle: "✨",
78
- fire: "🔥",
79
139
  target: "🎯",
80
- trophy: "🏆",
81
- box: {
82
- tl: "", tr: "╗", bl: "╚", br: "╝", h: "═", v: "║",
83
- ltl: "", ltr: "┐", lbl: "└", lbr: "┘", lh: "─", lv: "│",
84
- lt: "", lb: "┴", lx: "┼", ltrT: "├", ltlT: "┤",
85
- },
86
- } : {
87
- check: "+",
88
- cross: "x",
89
- warning: "!",
90
- info: "i",
91
- arrow: "->",
92
- bullet: "*",
93
- star: "*",
94
- rocket: ">",
95
- shield: "[S]",
96
- lock: "[L]",
97
- key: "[K]",
98
- gear: "[G]",
99
- search: "[?]",
100
- chart: "[C]",
101
- file: "[F]",
102
- folder: "[D]",
103
- clock: "[T]",
104
- lightning: "[!]",
105
- sparkle: "*",
106
- fire: "(!)",
107
- target: "(>)",
108
- trophy: "[W]",
109
- box: {
110
- tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|",
111
- ltl: "+", ltr: "+", lbl: "+", lbr: "+", lh: "-", lv: "|",
112
- lt: "+", lb: "+", lx: "+", ltrT: "+", ltlT: "+",
113
- },
140
+ party: "🎉",
141
+ skull: "💀",
142
+ bomb: "💣",
143
+ ghost: "👻",
144
+ money: "💰",
145
+ doc: "📄",
146
+ box: "📦",
147
+ sparkle: "",
148
+ lightning: "",
114
149
  };
115
150
 
116
151
  // ═══════════════════════════════════════════════════════════════════════════════
117
- // TIER BADGES
118
- // ═══════════════════════════════════════════════════════════════════════════════
119
-
120
- const TIER = {
121
- free: { label: "FREE", color: ansi.green, bg: ansi.bgGreen },
122
- starter: { label: "STARTER", color: ansi.cyan, bg: ansi.bgCyan },
123
- pro: { label: "PRO", color: ansi.magenta, bg: ansi.bgMagenta },
124
- complete: { label: "COMPLETE", color: ansi.yellow, bg: ansi.bgYellow },
152
+ // ASCII ART BANNERS
153
+ // ═══════════════════════════════════════════════════════════════════════════════
154
+
155
+ const VIBECHECK_BANNER = `
156
+ ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗
157
+ ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝
158
+ ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝
159
+ ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗
160
+ ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗
161
+ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝`.trim();
162
+
163
+ const COMMAND_BANNERS = {
164
+ launch: `
165
+ ██╗ █████╗ ██╗ ██╗███╗ ██╗ ██████╗██╗ ██╗
166
+ ██║ ██╔══██╗██║ ██║████╗ ██║██╔════╝██║ ██║
167
+ ██║ ███████║██║ ██║██╔██╗ ██║██║ ███████║
168
+ ██║ ██╔══██║██║ ██║██║╚██╗██║██║ ██╔══██║
169
+ ███████╗██║ ██║╚██████╔╝██║ ╚████║╚██████╗██║ ██║
170
+ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝`.trim(),
171
+
172
+ kickoff: `
173
+ ██╗ ██╗██╗ ██████╗██╗ ██╗ ██████╗ ███████╗███████╗
174
+ ██║ ██╔╝██║██╔════╝██║ ██╔╝██╔═══██╗██╔════╝██╔════╝
175
+ █████╔╝ ██║██║ █████╔╝ ██║ ██║█████╗ █████╗
176
+ ██╔═██╗ ██║██║ ██╔═██╗ ██║ ██║██╔══╝ ██╔══╝
177
+ ██║ ██╗██║╚██████╗██║ ██╗╚██████╔╝██║ ██║
178
+ ╚═╝ ╚═╝╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝`.trim(),
179
+
180
+ scan: `
181
+ ███████╗ ██████╗ █████╗ ███╗ ██╗
182
+ ██╔════╝██╔════╝██╔══██╗████╗ ██║
183
+ ███████╗██║ ███████║██╔██╗ ██║
184
+ ╚════██║██║ ██╔══██║██║╚██╗██║
185
+ ███████║╚██████╗██║ ██║██║ ╚████║
186
+ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝`.trim(),
187
+
188
+ audit: `
189
+ █████╗ ██╗ ██╗██████╗ ██╗████████╗
190
+ ██╔══██╗██║ ██║██╔══██╗██║╚══██╔══╝
191
+ ███████║██║ ██║██║ ██║██║ ██║
192
+ ██╔══██║██║ ██║██║ ██║██║ ██║
193
+ ██║ ██║╚██████╔╝██████╔╝██║ ██║
194
+ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝`.trim(),
195
+
196
+ packs: `
197
+ ██████╗ █████╗ ██████╗██╗ ██╗███████╗
198
+ ██╔══██╗██╔══██╗██╔════╝██║ ██╔╝██╔════╝
199
+ ██████╔╝███████║██║ █████╔╝ ███████╗
200
+ ██╔═══╝ ██╔══██║██║ ██╔═██╗ ╚════██║
201
+ ██║ ██║ ██║╚██████╗██║ ██╗███████║
202
+ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝`.trim(),
203
+
204
+ safelist: `
205
+ ███████╗ █████╗ ███████╗███████╗██╗ ██╗███████╗████████╗
206
+ ██╔════╝██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝╚══██╔══╝
207
+ ███████╗███████║█████╗ █████╗ ██║ ██║███████╗ ██║
208
+ ╚════██║██╔══██║██╔══╝ ██╔══╝ ██║ ██║╚════██║ ██║
209
+ ███████║██║ ██║██║ ███████╗███████╗██║███████║ ██║
210
+ ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝╚═╝╚══════╝ ╚═╝`.trim(),
211
+
212
+ doctor: `
213
+ ██████╗ ██████╗ ██████╗████████╗ ██████╗ ██████╗
214
+ ██╔══██╗██╔═══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
215
+ ██║ ██║██║ ██║██║ ██║ ██║ ██║██████╔╝
216
+ ██║ ██║██║ ██║██║ ██║ ██║ ██║██╔══██╗
217
+ ██████╔╝╚██████╔╝╚██████╗ ██║ ╚██████╔╝██║ ██║
218
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝`.trim(),
219
+
220
+ watch: `
221
+ ██╗ ██╗ █████╗ ████████╗ ██████╗██╗ ██╗
222
+ ██║ ██║██╔══██╗╚══██╔══╝██╔════╝██║ ██║
223
+ ██║ █╗ ██║███████║ ██║ ██║ ███████║
224
+ ██║███╗██║██╔══██║ ██║ ██║ ██╔══██║
225
+ ╚███╔███╔╝██║ ██║ ██║ ╚██████╗██║ ██║
226
+ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝`.trim(),
227
+
228
+ init: `
229
+ ██╗███╗ ██╗██╗████████╗
230
+ ██║████╗ ██║██║╚══██╔══╝
231
+ ██║██╔██╗ ██║██║ ██║
232
+ ██║██║╚██╗██║██║ ██║
233
+ ██║██║ ╚████║██║ ██║
234
+ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝`.trim(),
235
+
236
+ ship: `
237
+ ███████╗██╗ ██╗██╗██████╗
238
+ ██╔════╝██║ ██║██║██╔══██╗
239
+ ███████╗███████║██║██████╔╝
240
+ ╚════██║██╔══██║██║██╔═══╝
241
+ ███████║██║ ██║██║██║
242
+ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝`.trim(),
243
+
244
+ forge: `
245
+ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
246
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝
247
+ █████╗ ██║ ██║██████╔╝██║ ███╗█████╗
248
+ ██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝
249
+ ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗
250
+ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝`.trim(),
251
+
252
+ shield: `
253
+ ███████╗██╗ ██╗██╗███████╗██╗ ██████╗
254
+ ██╔════╝██║ ██║██║██╔════╝██║ ██╔══██╗
255
+ ███████╗███████║██║█████╗ ██║ ██║ ██║
256
+ ╚════██║██╔══██║██║██╔══╝ ██║ ██║ ██║
257
+ ███████║██║ ██║██║███████╗███████╗██████╔╝
258
+ ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═════╝`.trim(),
259
+
260
+ fix: `
261
+ ███████╗██╗██╗ ██╗
262
+ ██╔════╝██║╚██╗██╔╝
263
+ █████╗ ██║ ╚███╔╝
264
+ ██╔══╝ ██║ ██╔██╗
265
+ ██║ ██║██╔╝ ██╗
266
+ ╚═╝ ╚═╝╚═╝ ╚═╝`.trim(),
267
+
268
+ link: `
269
+ ██╗ ██╗███╗ ██╗██╗ ██╗
270
+ ██║ ██║████╗ ██║██║ ██╔╝
271
+ ██║ ██║██╔██╗ ██║█████╔╝
272
+ ██║ ██║██║╚██╗██║██╔═██╗
273
+ ███████╗██║██║ ╚████║██║ ██╗
274
+ ╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝`.trim(),
275
+
276
+ prove: `
277
+ ██████╗ ██████╗ ██████╗ ██╗ ██╗███████╗
278
+ ██╔══██╗██╔══██╗██╔═══██╗██║ ██║██╔════╝
279
+ ██████╔╝██████╔╝██║ ██║██║ ██║█████╗
280
+ ██╔═══╝ ██╔══██╗██║ ██║╚██╗ ██╔╝██╔══╝
281
+ ██║ ██║ ██║╚██████╔╝ ╚████╔╝ ███████╗
282
+ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚══════╝`.trim(),
283
+
284
+ reality: `
285
+ ██████╗ ███████╗ █████╗ ██╗ ██╗████████╗██╗ ██╗
286
+ ██╔══██╗██╔════╝██╔══██╗██║ ██║╚══██╔══╝╚██╗ ██╔╝
287
+ ██████╔╝█████╗ ███████║██║ ██║ ██║ ╚████╔╝
288
+ ██╔══██╗██╔══╝ ██╔══██║██║ ██║ ██║ ╚██╔╝
289
+ ██║ ██║███████╗██║ ██║███████╗██║ ██║ ██║
290
+ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝`.trim(),
125
291
  };
126
292
 
127
- function getTierBadge(tier) {
128
- const t = TIER[tier] || TIER.free;
129
- return `${t.color}[${t.label}]${ansi.reset}`;
130
- }
131
-
132
- function getTierFromKey() {
133
- const { key } = getApiKey();
134
- if (!key) return "free";
135
- // Simplified tier detection - in real app would check entitlements
136
- return "starter";
137
- }
138
-
139
293
  // ═══════════════════════════════════════════════════════════════════════════════
140
294
  // UTILITY FUNCTIONS
141
295
  // ═══════════════════════════════════════════════════════════════════════════════
142
296
 
297
+ /**
298
+ * Strip ANSI codes for length calculation
299
+ */
143
300
  function stripAnsi(str) {
144
- return str.replace(/\x1b\[\d+m/g, "").replace(/\x1b\[38;2;\d+;\d+;\d+m/g, "");
301
+ return str.replace(/\u001b\[[0-9;]*m/g, "");
145
302
  }
146
303
 
147
- function padCenter(str, width = WIDTH) {
148
- const len = stripAnsi(str).length;
149
- const padding = Math.max(0, width - len);
150
- const left = Math.floor(padding / 2);
151
- return " ".repeat(left) + str + " ".repeat(padding - left);
304
+ /**
305
+ * Get visible length of string (excluding ANSI codes)
306
+ */
307
+ function visibleLength(str) {
308
+ return stripAnsi(str).length;
309
+ }
310
+
311
+ /**
312
+ * Pad string to right with spaces
313
+ */
314
+ function padRight(str, len) {
315
+ const visible = visibleLength(str);
316
+ if (visible >= len) return str;
317
+ return str + " ".repeat(len - visible);
152
318
  }
153
319
 
154
- function padRight(str, width) {
155
- const len = stripAnsi(str).length;
156
- return str + " ".repeat(Math.max(0, width - len));
320
+ /**
321
+ * Pad string to left with spaces
322
+ */
323
+ function padLeft(str, len) {
324
+ const visible = visibleLength(str);
325
+ if (visible >= len) return str;
326
+ return " ".repeat(len - visible) + str;
327
+ }
328
+
329
+ /**
330
+ * Center string within given width
331
+ */
332
+ function padCenter(str, width) {
333
+ const visible = visibleLength(str);
334
+ if (visible >= width) return str;
335
+ const padding = width - visible;
336
+ const left = Math.floor(padding / 2);
337
+ return " ".repeat(left) + str + " ".repeat(padding - left);
157
338
  }
158
339
 
340
+ /**
341
+ * Truncate string with ellipsis
342
+ */
159
343
  function truncate(str, maxLen) {
160
- const clean = stripAnsi(str);
161
- if (clean.length <= maxLen) return str;
162
- return clean.slice(0, maxLen - 3) + "...";
344
+ const visible = visibleLength(str);
345
+ if (visible <= maxLen) return str;
346
+ return stripAnsi(str).substring(0, maxLen - 3) + "...";
163
347
  }
164
348
 
349
+ /**
350
+ * Format duration
351
+ */
165
352
  function formatDuration(ms) {
166
353
  if (ms < 1000) return `${ms}ms`;
167
354
  if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
168
- return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
355
+ const minutes = Math.floor(ms / 60000);
356
+ const seconds = Math.floor((ms % 60000) / 1000);
357
+ return `${minutes}m ${seconds}s`;
358
+ }
359
+
360
+ /**
361
+ * Format number with commas
362
+ */
363
+ function formatNumber(num) {
364
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
169
365
  }
170
366
 
171
367
  // ═══════════════════════════════════════════════════════════════════════════════
172
- // HEADER / BANNER
368
+ // PROGRESS BAR
173
369
  // ═══════════════════════════════════════════════════════════════════════════════
174
370
 
175
371
  /**
176
- * Render a compact enterprise header for any command
177
- * @param {object} opts - Header options
178
- * @param {string} opts.command - Command name (e.g., "scan", "ship")
179
- * @param {string} opts.title - Title text
180
- * @param {string} opts.tier - Tier requirement (free, starter, pro)
181
- * @param {string} [opts.subtitle] - Optional subtitle
372
+ * Render a progress bar
373
+ * @param {number} percent - 0-100
374
+ * @param {number} width - Character width
375
+ * @param {object} opts - Options { color, showPercent, label }
182
376
  */
183
- function renderHeader(opts) {
184
- const { command, title, tier = "free", subtitle } = opts;
185
- const tierBadge = getTierBadge(tier);
186
- const b = sym.box;
187
-
188
- const lines = [];
189
-
190
- // Top border
191
- lines.push(`${ansi.gray}${b.tl}${b.h.repeat(WIDTH - 2)}${b.tr}${ansi.reset}`);
377
+ function renderProgressBar(percent, width = 25, opts = {}) {
378
+ const { color = ansi.green, bgColor = ansi.gray, showPercent = false, label = "" } = opts;
379
+ const filled = Math.round((percent / 100) * width);
380
+ const empty = width - filled;
192
381
 
193
- // Title line
194
- const titleStr = `${ansi.bold}${title}${ansi.reset} ${tierBadge}`;
195
- lines.push(`${ansi.gray}${b.v}${ansi.reset}${padCenter(titleStr, WIDTH - 2)}${ansi.gray}${b.v}${ansi.reset}`);
382
+ let bar = `${color}${sym.blockFull.repeat(filled)}${bgColor}${sym.blockEmpty.repeat(empty)}${ansi.reset}`;
196
383
 
197
- // Subtitle if provided
198
- if (subtitle) {
199
- const subStr = `${ansi.dim}${subtitle}${ansi.reset}`;
200
- lines.push(`${ansi.gray}${b.v}${ansi.reset}${padCenter(subStr, WIDTH - 2)}${ansi.gray}${b.v}${ansi.reset}`);
384
+ if (showPercent) {
385
+ bar += ` ${percent}%`;
201
386
  }
202
387
 
203
- // Bottom border
204
- lines.push(`${ansi.gray}${b.bl}${b.h.repeat(WIDTH - 2)}${b.br}${ansi.reset}`);
388
+ if (label) {
389
+ return `${label} [${bar}]`;
390
+ }
205
391
 
206
- console.log(lines.join("\n"));
392
+ return `[${bar}]`;
207
393
  }
208
394
 
209
395
  /**
210
- * Render a minimal header (single line)
396
+ * Render a status indicator with label
211
397
  */
212
- function renderMinimalHeader(command, tier = "free") {
213
- const tierBadge = getTierBadge(tier);
214
- console.log(`\n ${ansi.bold}vibecheck ${command}${ansi.reset} ${tierBadge}\n`);
398
+ function renderStatus(label, status, width = 20) {
399
+ const colors = {
400
+ OPTIMAL: ansi.green,
401
+ STABLE: ansi.green,
402
+ GOOD: ansi.green,
403
+ OK: ansi.green,
404
+ PASS: ansi.green,
405
+ WARNING: ansi.yellow,
406
+ WARN: ansi.yellow,
407
+ HEAVY: ansi.yellow,
408
+ DEGRADED: ansi.yellow,
409
+ CRITICAL: ansi.red,
410
+ FAIL: ansi.red,
411
+ ERROR: ansi.red,
412
+ LOW: ansi.red,
413
+ };
414
+
415
+ const color = colors[status.toUpperCase()] || ansi.gray;
416
+ return `${padRight(label, width)} ${color}[${status}]${ansi.reset}`;
215
417
  }
216
418
 
217
419
  // ═══════════════════════════════════════════════════════════════════════════════
218
- // SECTION DIVIDERS
420
+ // HEADER RENDERING
219
421
  // ═══════════════════════════════════════════════════════════════════════════════
220
422
 
221
- function renderDivider(style = "single") {
222
- const char = style === "double" ? sym.box.h : sym.box.lh;
223
- console.log(` ${ansi.gray}${char.repeat(WIDTH - 4)}${ansi.reset}`);
224
- }
225
-
226
- function renderSectionHeader(title, icon = sym.bullet) {
227
- console.log(`\n ${ansi.cyan}${icon}${ansi.reset} ${ansi.bold}${title}${ansi.reset}`);
228
- console.log(` ${ansi.gray}${sym.box.lh.repeat(WIDTH - 4)}${ansi.reset}`);
423
+ /**
424
+ * Render the full header with VIBECHECK banner and command banner
425
+ */
426
+ function renderFullHeader(command, opts = {}) {
427
+ const {
428
+ version = "4.0.0",
429
+ tier = "FREE",
430
+ integrity = null,
431
+ target = process.cwd(),
432
+ sessionId = generateSessionId(),
433
+ duration = null,
434
+ } = opts;
435
+
436
+ const lines = [];
437
+
438
+ // Main VIBECHECK banner with gradient colors
439
+ const vibeLines = VIBECHECK_BANNER.split("\n");
440
+ const gradientColors = [
441
+ ansi.rgb(0, 255, 200),
442
+ ansi.rgb(0, 230, 220),
443
+ ansi.rgb(0, 200, 255),
444
+ ansi.rgb(50, 150, 255),
445
+ ansi.rgb(100, 100, 255),
446
+ ansi.rgb(150, 50, 255),
447
+ ];
448
+
449
+ lines.push("");
450
+ vibeLines.forEach((line, i) => {
451
+ const color = gradientColors[i % gradientColors.length] || ansi.cyan;
452
+ lines.push(` ${color}${line}${ansi.reset}`);
453
+ });
454
+
455
+ // Main box
456
+ lines.push("");
457
+ lines.push(`${ansi.cyan}${sym.boxTL}${sym.boxH.repeat(WIDTH - 2)}${sym.boxTR}${ansi.reset}`);
458
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
459
+
460
+ // Command banner
461
+ const cmdBanner = COMMAND_BANNERS[command];
462
+ if (cmdBanner) {
463
+ const cmdLines = cmdBanner.split("\n");
464
+ cmdLines.forEach(line => {
465
+ const centered = padCenter(line, WIDTH - 4);
466
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.bold}${ansi.white}${centered}${ansi.reset} ${ansi.cyan}${sym.boxV}${ansi.reset}`);
467
+ });
468
+ }
469
+
470
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
471
+
472
+ // Info bar
473
+ const tierBadge = tier === "PRO" ? `${ansi.magenta}PRO${ansi.reset}` : `${ansi.gray}FREE${ansi.reset}`;
474
+ const integrityStr = integrity ? ` :: SYSTEM INTEGRITY: ${getIntegrityColor(integrity)}${integrity}${ansi.reset}` : "";
475
+ const infoLine = `v${version} ${tierBadge}${integrityStr}`;
476
+
477
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${infoLine}${" ".repeat(Math.max(0, WIDTH - 4 - visibleLength(infoLine)))}${ansi.cyan}${sym.boxV}${ansi.reset}`);
478
+
479
+ // Target and session line
480
+ const shortTarget = truncate(target, 40);
481
+ const durationStr = duration ? ` | Time: ${formatDuration(duration)}` : "";
482
+ const targetLine = `Target: ${shortTarget}${" ".repeat(6)}Session: #${sessionId}${durationStr}`;
483
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.dim}${truncate(targetLine, WIDTH - 6)}${ansi.reset}${" ".repeat(Math.max(0, WIDTH - 4 - visibleLength(targetLine)))}${ansi.cyan}${sym.boxV}${ansi.reset}`);
484
+
485
+ // Separator
486
+ lines.push(`${ansi.cyan}${sym.boxTeeR}${sym.boxH.repeat(WIDTH - 2)}${sym.boxTeeL}${ansi.reset}`);
487
+
488
+ return lines.join("\n");
229
489
  }
230
490
 
231
- // ═══════════════════════════════════════════════════════════════════════════════
232
- // STATUS INDICATORS
233
- // ═══════════════════════════════════════════════════════════════════════════════
234
-
235
- function renderSuccess(message) {
236
- console.log(` ${ansi.green}${sym.check}${ansi.reset} ${message}`);
491
+ /**
492
+ * Get color for integrity level
493
+ */
494
+ function getIntegrityColor(integrity) {
495
+ const upper = integrity.toUpperCase();
496
+ if (upper === "HIGH" || upper === "OPTIMAL") return ansi.green;
497
+ if (upper === "MEDIUM" || upper === "STABLE") return ansi.yellow;
498
+ return ansi.red;
237
499
  }
238
500
 
239
- function renderError(message) {
240
- console.log(` ${ansi.red}${sym.cross}${ansi.reset} ${message}`);
501
+ /**
502
+ * Generate a short session ID
503
+ */
504
+ function generateSessionId() {
505
+ const chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ";
506
+ let id = "";
507
+ for (let i = 0; i < 6; i++) {
508
+ id += chars[Math.floor(Math.random() * chars.length)];
509
+ }
510
+ return id;
241
511
  }
242
512
 
243
- function renderWarning(message) {
244
- console.log(` ${ansi.yellow}${sym.warning}${ansi.reset} ${message}`);
245
- }
513
+ // ═══════════════════════════════════════════════════════════════════════════════
514
+ // MINIMAL HEADER (for simpler commands)
515
+ // ═══════════════════════════════════════════════════════════════════════════════
246
516
 
247
- function renderInfo(message) {
248
- console.log(` ${ansi.cyan}${sym.info}${ansi.reset} ${message}`);
517
+ /**
518
+ * Render a minimal header (no full banner, just command name)
519
+ */
520
+ function renderMinimalHeader(command, tier = "free") {
521
+ const tierBadge = tier === "pro" ? `${ansi.magenta}PRO${ansi.reset}` : `${ansi.dim}free${ansi.reset}`;
522
+ console.log();
523
+ console.log(` ${ansi.cyan}${sym.boxTL}${sym.boxH.repeat(60)}${sym.boxTR}${ansi.reset}`);
524
+ console.log(` ${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.bold}vibecheck ${command}${ansi.reset} ${tierBadge}${" ".repeat(60 - 15 - command.length - (tier === "pro" ? 3 : 4))}${ansi.cyan}${sym.boxV}${ansi.reset}`);
525
+ console.log(` ${ansi.cyan}${sym.boxBL}${sym.boxH.repeat(60)}${sym.boxBR}${ansi.reset}`);
526
+ console.log();
249
527
  }
250
528
 
251
- function renderBullet(message, indent = 2) {
252
- console.log(`${" ".repeat(indent)}${ansi.gray}${sym.bullet}${ansi.reset} ${message}`);
253
- }
529
+ // ═══════════════════════════════════════════════════════════════════════════════
530
+ // SPLIT COLUMN LAYOUT
531
+ // ═══════════════════════════════════════════════════════════════════════════════
254
532
 
255
- function renderStep(number, message, status = "pending") {
256
- const statusIcon = status === "done" ? `${ansi.green}${sym.check}${ansi.reset}` :
257
- status === "error" ? `${ansi.red}${sym.cross}${ansi.reset}` :
258
- status === "running" ? `${ansi.cyan}${sym.gear}${ansi.reset}` :
259
- `${ansi.gray}${number}${ansi.reset}`;
260
- console.log(` ${statusIcon} ${message}`);
533
+ /**
534
+ * Render a split-column section
535
+ */
536
+ function renderSplitColumns(leftTitle, rightTitle, leftContent, rightContent, opts = {}) {
537
+ const { leftWidth = 44, rightWidth = 33 } = opts;
538
+ const lines = [];
539
+
540
+ // Header row
541
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.bold}${padRight(leftTitle, leftWidth)}${ansi.reset}${ansi.dim}│${ansi.reset} ${ansi.bold}${padRight(rightTitle, rightWidth)}${ansi.reset}${ansi.cyan}${sym.boxV}${ansi.reset}`);
542
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${sym.lineH.repeat(leftWidth)}${ansi.dim}┼${ansi.reset}${sym.lineH.repeat(rightWidth + 1)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
543
+
544
+ // Content rows
545
+ const maxRows = Math.max(leftContent.length, rightContent.length);
546
+ for (let i = 0; i < maxRows; i++) {
547
+ const left = leftContent[i] || "";
548
+ const right = rightContent[i] || "";
549
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${padRight(left, leftWidth)}${ansi.dim}│${ansi.reset} ${padRight(right, rightWidth)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
550
+ }
551
+
552
+ return lines.join("\n");
261
553
  }
262
554
 
263
555
  // ═══════════════════════════════════════════════════════════════════════════════
264
- // VERDICT / RESULT BOX
556
+ // VITALS SECTION
265
557
  // ═══════════════════════════════════════════════════════════════════════════════
266
558
 
267
559
  /**
268
- * Render a verdict box (SHIP/WARN/BLOCK)
269
- * @param {string} verdict - SHIP, WARN, or BLOCK
270
- * @param {object} [stats] - Optional statistics
560
+ * Render system vitals with progress bars
271
561
  */
272
- function renderVerdict(verdict, stats = {}) {
273
- const b = sym.box;
274
- const v = String(verdict).toUpperCase();
275
-
276
- let bg, icon, label;
277
- switch (v) {
278
- case "SHIP":
279
- case "PASS":
280
- case "SUCCESS":
281
- bg = ansi.bgGreen;
282
- icon = sym.rocket;
283
- label = "SHIP";
284
- break;
285
- case "WARN":
286
- case "WARNING":
287
- bg = ansi.bgYellow;
288
- icon = sym.warning;
289
- label = "WARN";
290
- break;
291
- case "BLOCK":
292
- case "FAIL":
293
- case "ERROR":
294
- bg = ansi.bgRed;
295
- icon = sym.cross;
296
- label = "BLOCK";
297
- break;
298
- default:
299
- bg = ansi.bgCyan;
300
- icon = sym.info;
301
- label = v;
302
- }
562
+ function renderVitals(vitals) {
563
+ const lines = [];
303
564
 
304
- console.log();
305
- console.log(` ${ansi.gray}${b.tl}${b.h.repeat(WIDTH - 6)}${b.tr}${ansi.reset}`);
306
-
307
- // Verdict line
308
- const verdictStr = `${bg}${ansi.bold}${ansi.white} ${icon} ${label} ${ansi.reset}`;
309
- console.log(` ${ansi.gray}${b.v}${ansi.reset}${padCenter(verdictStr, WIDTH - 4)}${ansi.gray}${b.v}${ansi.reset}`);
310
-
311
- // Stats line if provided
312
- if (stats.findings !== undefined || stats.duration !== undefined) {
313
- const parts = [];
314
- if (stats.critical !== undefined) parts.push(`${ansi.red}${stats.critical} critical${ansi.reset}`);
315
- if (stats.warnings !== undefined) parts.push(`${ansi.yellow}${stats.warnings} warnings${ansi.reset}`);
316
- if (stats.findings !== undefined && stats.critical === undefined) parts.push(`${stats.findings} findings`);
317
- if (stats.duration !== undefined) parts.push(`${ansi.dim}${formatDuration(stats.duration)}${ansi.reset}`);
565
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.bold}SYSTEM VITALS${ansi.reset}${" ".repeat(WIDTH - 17)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
566
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${sym.lineH.repeat(WIDTH - 4)} ${ansi.cyan}${sym.boxV}${ansi.reset}`);
567
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
568
+
569
+ for (const vital of vitals) {
570
+ const { label, value, status, percent } = vital;
571
+ const statusColor = getStatusColor(status);
572
+
573
+ let line = ` ${padRight(label, 18)} ${statusColor}[${status}]${ansi.reset}`;
574
+ if (percent !== undefined) {
575
+ const bar = renderProgressBar(percent, 25);
576
+ line += `\n${ansi.cyan}${sym.boxV}${ansi.reset} ${bar} ${percent}%`;
577
+ } else if (value) {
578
+ line += ` ${ansi.dim}${value}${ansi.reset}`;
579
+ }
318
580
 
319
- const statsStr = parts.join(` ${ansi.gray}${sym.bullet}${ansi.reset} `);
320
- console.log(` ${ansi.gray}${b.v}${ansi.reset}${padCenter(statsStr, WIDTH - 4)}${ansi.gray}${b.v}${ansi.reset}`);
581
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${line}${" ".repeat(Math.max(0, WIDTH - 4 - visibleLength(line)))}${ansi.cyan}${sym.boxV}${ansi.reset}`);
321
582
  }
322
583
 
323
- console.log(` ${ansi.gray}${b.bl}${b.h.repeat(WIDTH - 6)}${b.br}${ansi.reset}`);
324
- console.log();
584
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
585
+
586
+ return lines.join("\n");
587
+ }
588
+
589
+ /**
590
+ * Get color for status
591
+ */
592
+ function getStatusColor(status) {
593
+ const upper = String(status).toUpperCase();
594
+ if (["OPTIMAL", "STABLE", "GOOD", "PASS", "HIGH", "SHIP", "LAUNCH"].includes(upper)) return ansi.green;
595
+ if (["WARNING", "WARN", "HEAVY", "DEGRADED", "MEDIUM"].includes(upper)) return ansi.yellow;
596
+ return ansi.red;
325
597
  }
326
598
 
327
599
  // ═══════════════════════════════════════════════════════════════════════════════
328
- // FINDINGS TABLE
600
+ // FINDINGS/DIAGNOSTIC SECTION
329
601
  // ═══════════════════════════════════════════════════════════════════════════════
330
602
 
331
603
  /**
332
- * Render a findings table
333
- * @param {Array} findings - Array of finding objects
334
- * @param {object} opts - Options
335
- * @param {number} [opts.limit=5] - Max findings to show
604
+ * Render diagnostic log / findings
336
605
  */
337
- function renderFindingsTable(findings, opts = {}) {
338
- const { limit = 5 } = opts;
339
- const b = sym.box;
606
+ function renderDiagnostics(findings, opts = {}) {
607
+ const { maxItems = 5, title = "DIAGNOSTIC LOG" } = opts;
608
+ const lines = [];
609
+
610
+ lines.push(`${ansi.bold}${title}${ansi.reset}`);
611
+ lines.push(`${sym.lineH.repeat(30)}`);
340
612
 
341
- if (!findings || findings.length === 0) {
342
- console.log(` ${ansi.green}${sym.check}${ansi.reset} ${ansi.dim}No issues found${ansi.reset}`);
343
- return;
613
+ if (findings.length === 0) {
614
+ lines.push("");
615
+ lines.push(`${ansi.green}${sym.check} No issues found${ansi.reset}`);
616
+ return lines;
344
617
  }
345
618
 
346
- const COL1 = 10; // Severity
347
- const COL2 = 18; // Category
348
- const COL3 = WIDTH - COL1 - COL2 - 12; // Message
349
-
350
- // Header
351
- console.log(` ${ansi.gray}${b.ltl}${b.lh.repeat(COL1)}${b.lt}${b.lh.repeat(COL2)}${b.lt}${b.lh.repeat(COL3)}${b.ltr}${ansi.reset}`);
352
- console.log(` ${ansi.gray}${b.lv}${ansi.reset}${ansi.bold}${padRight(" SEVERITY", COL1)}${ansi.reset}${ansi.gray}${b.lv}${ansi.reset}${ansi.bold}${padRight(" CATEGORY", COL2)}${ansi.reset}${ansi.gray}${b.lv}${ansi.reset}${ansi.bold}${padRight(" MESSAGE", COL3)}${ansi.reset}${ansi.gray}${b.lv}${ansi.reset}`);
353
- console.log(` ${ansi.gray}${b.ltrT}${b.lh.repeat(COL1)}${b.lx}${b.lh.repeat(COL2)}${b.lx}${b.lh.repeat(COL3)}${b.ltlT}${ansi.reset}`);
354
-
355
- // Rows
356
- const shown = findings.slice(0, limit);
357
- for (const f of shown) {
358
- const sev = f.severity?.toUpperCase() || "INFO";
359
- let sevStr;
360
- switch (sev) {
361
- case "BLOCK":
362
- case "CRITICAL":
363
- sevStr = `${ansi.red} ${sym.cross} BLOCK${ansi.reset}`;
364
- break;
365
- case "WARN":
366
- case "WARNING":
367
- case "HIGH":
368
- sevStr = `${ansi.yellow} ${sym.warning} WARN${ansi.reset}`;
369
- break;
370
- default:
371
- sevStr = `${ansi.gray} ${sym.info} INFO${ansi.reset}`;
372
- }
619
+ findings.slice(0, maxItems).forEach((f, i) => {
620
+ const severityIcon = getSeverityIcon(f.severity);
621
+ const type = f.type || f.rule || "ISSUE";
373
622
 
374
- const cat = truncate(f.category || f.type || "General", COL2 - 1);
375
- const msg = truncate(f.message || f.title || f.id || "", COL3 - 1);
623
+ lines.push("");
624
+ lines.push(`${severityIcon} ${type}${f.count ? ` (x${f.count})` : ""}`);
625
+ lines.push(`${sym.lineH.repeat(30)}`);
626
+ lines.push(`Severity: ${getSeverityLabel(f.severity)}`);
627
+ if (f.impact) lines.push(`Impact: ${f.impact}`);
376
628
 
377
- console.log(` ${ansi.gray}${b.lv}${ansi.reset}${padRight(sevStr, COL1 + 9)}${ansi.gray}${b.lv}${ansi.reset}${padRight(" " + cat, COL2)}${ansi.gray}${b.lv}${ansi.reset}${padRight(" " + msg, COL3)}${ansi.gray}${b.lv}${ansi.reset}`);
378
- }
379
-
380
- // Footer
381
- console.log(` ${ansi.gray}${b.lbl}${b.lh.repeat(COL1)}${b.lb}${b.lh.repeat(COL2)}${b.lb}${b.lh.repeat(COL3)}${b.lbr}${ansi.reset}`);
629
+ if (f.instances && f.instances.length > 0) {
630
+ lines.push("");
631
+ lines.push("DETECTED INSTANCES:");
632
+ lines.push("");
633
+ f.instances.slice(0, 3).forEach((inst, j) => {
634
+ const file = inst.file ? truncate(inst.file, 28) : "Unknown";
635
+ lines.push(`${j + 1}. ${file}`);
636
+ if (inst.message) lines.push(` ${ansi.dim}> ${truncate(inst.message, 25)}${ansi.reset}`);
637
+ });
638
+ } else if (f.file) {
639
+ lines.push("");
640
+ lines.push(`File: ${truncate(f.file, 25)}`);
641
+ if (f.message) lines.push(`${ansi.dim}> ${truncate(f.message, 25)}${ansi.reset}`);
642
+ }
643
+ });
382
644
 
383
- // More indicator
384
- if (findings.length > limit) {
385
- console.log(` ${ansi.dim}... and ${findings.length - limit} more findings${ansi.reset}`);
645
+ if (findings.length > maxItems) {
646
+ lines.push("");
647
+ lines.push(`${ansi.dim}... and ${findings.length - maxItems} more${ansi.reset}`);
386
648
  }
649
+
650
+ return lines;
651
+ }
652
+
653
+ /**
654
+ * Get icon for severity
655
+ */
656
+ function getSeverityIcon(severity) {
657
+ const upper = String(severity).toUpperCase();
658
+ if (["CRITICAL", "BLOCK", "HIGH"].includes(upper)) return `${ansi.red}[FAIL]${ansi.reset}`;
659
+ if (["WARNING", "WARN", "MEDIUM"].includes(upper)) return `${ansi.yellow}[WARN]${ansi.reset}`;
660
+ return `${ansi.blue}[INFO]${ansi.reset}`;
661
+ }
662
+
663
+ /**
664
+ * Get label for severity
665
+ */
666
+ function getSeverityLabel(severity) {
667
+ const upper = String(severity).toUpperCase();
668
+ if (["CRITICAL", "BLOCK"].includes(upper)) return `${ansi.red}CRITICAL${ansi.reset}`;
669
+ if (upper === "HIGH") return `${ansi.red}HIGH${ansi.reset}`;
670
+ if (["WARNING", "WARN", "MEDIUM"].includes(upper)) return `${ansi.yellow}MEDIUM${ansi.reset}`;
671
+ return `${ansi.blue}LOW${ansi.reset}`;
387
672
  }
388
673
 
389
674
  // ═══════════════════════════════════════════════════════════════════════════════
390
- // KEY-VALUE LIST
675
+ // SECURITY AUDIT SECTION
391
676
  // ═══════════════════════════════════════════════════════════════════════════════
392
677
 
393
678
  /**
394
- * Render a key-value list
395
- * @param {Array<{label: string, value: string}>} items
679
+ * Render security audit checklist
396
680
  */
397
- function renderKeyValue(items) {
398
- const maxLabel = Math.max(...items.map(i => stripAnsi(i.label).length));
681
+ function renderSecurityAudit(checks) {
682
+ const lines = [];
399
683
 
400
- for (const item of items) {
401
- const label = padRight(item.label, maxLabel);
402
- console.log(` ${ansi.dim}${label}${ansi.reset} ${item.value}`);
684
+ lines.push(`${ansi.bold}SECURITY AUDIT${ansi.reset}`);
685
+ lines.push("");
686
+
687
+ for (const check of checks) {
688
+ const icon = check.pass ? `${ansi.green}${sym.check}${ansi.reset}` : `${ansi.red}${sym.cross}${ansi.reset}`;
689
+ const status = check.pass ? `${ansi.green}PASS${ansi.reset}` : `${ansi.red}FAIL${ansi.reset}`;
690
+ const dots = ".".repeat(Math.max(0, 20 - check.name.length));
691
+ lines.push(` ${icon} ${check.name}${ansi.dim}${dots}${ansi.reset}${status}`);
403
692
  }
693
+
694
+ return lines;
404
695
  }
405
696
 
406
697
  // ═══════════════════════════════════════════════════════════════════════════════
407
- // PROGRESS BAR
698
+ // ACTION REQUIRED SECTION
408
699
  // ═══════════════════════════════════════════════════════════════════════════════
409
700
 
410
- function renderProgressBar(percent, width = 20) {
411
- const filled = Math.round((percent / 100) * width);
412
- const empty = width - filled;
701
+ /**
702
+ * Render action required section
703
+ */
704
+ function renderActionRequired(actions, opts = {}) {
705
+ const { blocking = false, title = "ACTION REQUIRED" } = opts;
706
+ const lines = [];
707
+
708
+ lines.push(`${ansi.cyan}${sym.boxTeeR}${sym.boxH.repeat(WIDTH - 2)}${sym.boxTeeL}${ansi.reset}`);
709
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.bold}${title}${ansi.reset}${" ".repeat(WIDTH - 4 - title.length)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
710
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${sym.lineH.repeat(WIDTH - 4)} ${ansi.cyan}${sym.boxV}${ansi.reset}`);
711
+
712
+ if (blocking) {
713
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.red}[!] BLOCKING ISSUES DETECTED. DEPLOYMENT HALTED.${ansi.reset}${" ".repeat(WIDTH - 54)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
714
+ }
413
715
 
414
- let color = ansi.green;
415
- if (percent < 50) color = ansi.red;
416
- else if (percent < 80) color = ansi.yellow;
716
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
417
717
 
418
- return `${color}${"█".repeat(filled)}${ansi.gray}${"░".repeat(empty)}${ansi.reset}`;
718
+ for (const action of actions) {
719
+ const cmd = `${ansi.cyan}> ${action.command}${ansi.reset}`;
720
+ const label = action.label ? `${ansi.dim}[${action.label}]${ansi.reset}` : "";
721
+ const desc = action.description || "";
722
+
723
+ const line = ` ${cmd}${" ".repeat(Math.max(2, 26 - action.command.length))}${label} ${desc}`;
724
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${padRight(line, WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
725
+ }
726
+
727
+ return lines.join("\n");
419
728
  }
420
729
 
421
- function renderScore(score, label = "Score") {
422
- const bar = renderProgressBar(score);
423
- console.log(` ${ansi.dim}${label}:${ansi.reset} [${bar}] ${score}/100`);
730
+ // ═══════════════════════════════════════════════════════════════════════════════
731
+ // FOOTER
732
+ // ═══════════════════════════════════════════════════════════════════════════════
733
+
734
+ /**
735
+ * Render standard footer
736
+ */
737
+ function renderFooter() {
738
+ return `${ansi.cyan}${sym.boxBL}${sym.boxH.repeat(WIDTH - 2)}${sym.boxBR}${ansi.reset}`;
424
739
  }
425
740
 
426
741
  // ═══════════════════════════════════════════════════════════════════════════════
427
- // FOOTER / NEXT STEPS
742
+ // VERDICT BOX
428
743
  // ═══════════════════════════════════════════════════════════════════════════════
429
744
 
430
745
  /**
431
- * Render footer with next steps and optional upsell
432
- * @param {object} opts
433
- * @param {Array<{cmd: string, desc: string}>} [opts.nextSteps]
434
- * @param {string} [opts.docsUrl]
435
- * @param {boolean} [opts.showUpsell]
746
+ * Render a verdict box
436
747
  */
437
- function renderFooter(opts = {}) {
438
- const { nextSteps = [], docsUrl, showUpsell = true } = opts;
439
- const currentTier = getTierFromKey();
748
+ function renderVerdict(verdict, score, stats = {}) {
749
+ const { blockers = 0, warnings = 0, passed = 0, duration = 0 } = stats;
750
+
751
+ const verdictColors = {
752
+ SHIP: ansi.green,
753
+ LAUNCH: ansi.green,
754
+ PASS: ansi.green,
755
+ WARN: ansi.yellow,
756
+ WARNING: ansi.yellow,
757
+ BLOCK: ansi.red,
758
+ FAIL: ansi.red,
759
+ };
760
+
761
+ const verdictIcons = {
762
+ SHIP: sym.rocket,
763
+ LAUNCH: sym.rocket,
764
+ PASS: sym.check,
765
+ WARN: sym.warning,
766
+ WARNING: sym.warning,
767
+ BLOCK: sym.cross,
768
+ FAIL: sym.cross,
769
+ };
770
+
771
+ const color = verdictColors[verdict.toUpperCase()] || ansi.gray;
772
+ const icon = verdictIcons[verdict.toUpperCase()] || sym.bullet;
440
773
 
441
- console.log();
442
- renderDivider();
774
+ const lines = [];
443
775
 
444
- // Next steps
445
- if (nextSteps.length > 0) {
446
- console.log(`\n ${ansi.bold}${sym.arrow} Next steps${ansi.reset}`);
447
- for (const step of nextSteps) {
448
- console.log(` ${ansi.cyan}${step.cmd}${ansi.reset} ${ansi.dim}${step.desc}${ansi.reset}`);
449
- }
450
- }
776
+ lines.push(`${ansi.cyan}${sym.boxTeeR}${sym.boxH.repeat(WIDTH - 2)}${sym.boxTeeL}${ansi.reset}`);
777
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
778
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${icon} ${ansi.bold}VERDICT: ${color}${verdict}${ansi.reset}${" ".repeat(WIDTH - 16 - verdict.length)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
779
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
451
780
 
452
- // Upsell for free tier
453
- if (showUpsell && currentTier === "free") {
454
- console.log();
455
- console.log(` ${ansi.dim}${sym.star}${ansi.reset} ${ansi.cyan}STARTER${ansi.reset}${ansi.dim}: dashboard sync + auto-fix + team features${ansi.reset}`);
456
- console.log(` ${ansi.dim} Upgrade ${sym.arrow} https://vibecheckai.dev${ansi.reset}`);
457
- }
781
+ // Score bar
782
+ const bar = renderProgressBar(score, 30, { color });
783
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} Score: ${bar} ${score}/100${" ".repeat(WIDTH - 54)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
458
784
 
459
- // Docs link
460
- if (docsUrl) {
461
- console.log();
462
- console.log(` ${ansi.dim}Docs: ${docsUrl}${ansi.reset}`);
785
+ // Stats
786
+ const statsLine = `Blockers: ${blockers} Warnings: ${warnings} Passed: ${passed}`;
787
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${statsLine}${" ".repeat(WIDTH - 4 - statsLine.length)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
788
+
789
+ if (duration) {
790
+ const durationLine = `Duration: ${formatDuration(duration)}`;
791
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset} ${ansi.dim}${durationLine}${ansi.reset}${" ".repeat(WIDTH - 4 - durationLine.length)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
463
792
  }
464
793
 
465
- console.log();
794
+ lines.push(`${ansi.cyan}${sym.boxV}${ansi.reset}${" ".repeat(WIDTH - 2)}${ansi.cyan}${sym.boxV}${ansi.reset}`);
795
+
796
+ return lines.join("\n");
797
+ }
798
+
799
+ // ═══════════════════════════════════════════════════════════════════════════════
800
+ // SECTION HEADER
801
+ // ═══════════════════════════════════════════════════════════════════════════════
802
+
803
+ /**
804
+ * Render a section header within the box
805
+ */
806
+ function renderSectionHeader(title, icon = sym.bullet) {
807
+ console.log(` ${ansi.cyan}${icon}${ansi.reset} ${ansi.bold}${title}${ansi.reset}`);
808
+ console.log(` ${ansi.dim}${sym.lineH.repeat(60)}${ansi.reset}`);
466
809
  }
467
810
 
468
811
  // ═══════════════════════════════════════════════════════════════════════════════
469
- // SPINNER
812
+ // SUCCESS/ERROR/WARNING MESSAGES
813
+ // ═══════════════════════════════════════════════════════════════════════════════
814
+
815
+ function renderSuccess(message) {
816
+ console.log(` ${ansi.green}${sym.check}${ansi.reset} ${message}`);
817
+ }
818
+
819
+ function renderError(message) {
820
+ console.log(` ${ansi.red}${sym.cross}${ansi.reset} ${message}`);
821
+ }
822
+
823
+ function renderWarning(message) {
824
+ console.log(` ${ansi.yellow}${sym.warning}${ansi.reset} ${message}`);
825
+ }
826
+
827
+ function renderInfo(message) {
828
+ console.log(` ${ansi.cyan}${sym.info}${ansi.reset} ${message}`);
829
+ }
830
+
831
+ // ═══════════════════════════════════════════════════════════════════════════════
832
+ // SPINNER CLASS
470
833
  // ═══════════════════════════════════════════════════════════════════════════════
471
834
 
472
835
  const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
473
836
 
474
837
  class Spinner {
475
- constructor(text = "") {
838
+ constructor(text = "", color = ansi.cyan) {
476
839
  this.text = text;
840
+ this.color = color;
477
841
  this.timer = null;
478
- this.frame = 0;
842
+ this.frameIndex = 0;
479
843
  }
480
844
 
481
845
  start(text) {
482
846
  if (text) this.text = text;
483
- if (!process.stdout.isTTY) {
484
- console.log(` ${ansi.cyan}${sym.gear}${ansi.reset} ${this.text}...`);
485
- return this;
486
- }
487
-
488
- process.stdout.write("\x1b[?25l"); // Hide cursor
847
+ process.stdout.write(ansi.hideCursor);
489
848
  this.timer = setInterval(() => {
490
- const f = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
491
- process.stdout.write(`\r ${ansi.cyan}${f}${ansi.reset} ${this.text}`);
492
- this.frame++;
849
+ const frame = SPINNER_FRAMES[this.frameIndex];
850
+ this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
851
+ process.stdout.write(`\r ${this.color}${frame}${ansi.reset} ${this.text}`);
493
852
  }, 80);
494
853
  return this;
495
854
  }
496
855
 
497
- stop(finalIcon, finalColor, finalText) {
498
- if (this.timer) {
499
- clearInterval(this.timer);
500
- this.timer = null;
501
- }
502
-
503
- if (process.stdout.isTTY) {
504
- process.stdout.write("\r\x1b[2K"); // Clear line
505
- process.stdout.write("\x1b[?25h"); // Show cursor
506
- }
507
-
856
+ update(text) {
857
+ this.text = text;
858
+ return this;
859
+ }
860
+
861
+ stop(icon = sym.check, color = ansi.green, finalText) {
862
+ if (this.timer) clearInterval(this.timer);
863
+ process.stdout.write(`\r${ansi.clearLine}`);
508
864
  if (finalText !== null) {
509
- const icon = finalIcon || sym.check;
510
- const color = finalColor || ansi.green;
511
- console.log(` ${color}${icon}${ansi.reset} ${finalText || this.text}`);
865
+ const msg = finalText || this.text;
866
+ console.log(` ${color}${icon}${ansi.reset} ${msg}`);
512
867
  }
868
+ process.stdout.write(ansi.showCursor);
513
869
  return this;
514
870
  }
515
871
 
516
872
  succeed(text) { return this.stop(sym.check, ansi.green, text); }
517
873
  fail(text) { return this.stop(sym.cross, ansi.red, text); }
518
874
  warn(text) { return this.stop(sym.warning, ansi.yellow, text); }
519
- info(text) { return this.stop(sym.info, ansi.cyan, text); }
520
- }
521
-
522
- // ═══════════════════════════════════════════════════════════════════════════════
523
- // COMMAND TEMPLATES
524
- // ═══════════════════════════════════════════════════════════════════════════════
525
-
526
- /**
527
- * Standard command wrapper for consistent output
528
- * @param {object} opts
529
- * @param {string} opts.command - Command name
530
- * @param {string} opts.title - Title for header
531
- * @param {string} [opts.tier] - Required tier
532
- * @param {boolean} [opts.quiet] - Suppress non-essential output
533
- * @param {boolean} [opts.json] - JSON output mode
534
- * @param {Function} opts.run - The command's main logic
535
- */
536
- async function runCommand(opts) {
537
- const { command, title, tier = "free", quiet = false, json = false, run } = opts;
538
-
539
- if (json) {
540
- // JSON mode - run silently, output only JSON
541
- return await run();
542
- }
543
-
544
- if (!quiet) {
545
- renderMinimalHeader(command, tier);
546
- }
547
-
548
- const result = await run();
549
-
550
- return result;
551
875
  }
552
876
 
553
877
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -557,48 +881,51 @@ async function runCommand(opts) {
557
881
  module.exports = {
558
882
  // Constants
559
883
  WIDTH,
884
+ INNER_WIDTH,
885
+
886
+ // ANSI & Symbols
560
887
  ansi,
561
888
  sym,
562
- TIER,
889
+
890
+ // Banners
891
+ VIBECHECK_BANNER,
892
+ COMMAND_BANNERS,
563
893
 
564
894
  // Utilities
565
895
  stripAnsi,
566
- padCenter,
896
+ visibleLength,
567
897
  padRight,
898
+ padLeft,
899
+ padCenter,
568
900
  truncate,
569
901
  formatDuration,
570
- getTierBadge,
571
- getTierFromKey,
902
+ formatNumber,
903
+
904
+ // Progress & Status
905
+ renderProgressBar,
906
+ renderStatus,
572
907
 
573
908
  // Headers
574
- renderHeader,
909
+ renderFullHeader,
575
910
  renderMinimalHeader,
576
-
577
- // Dividers
578
- renderDivider,
911
+ generateSessionId,
912
+
913
+ // Layouts
914
+ renderSplitColumns,
915
+ renderVitals,
916
+ renderDiagnostics,
917
+ renderSecurityAudit,
918
+ renderActionRequired,
919
+ renderVerdict,
920
+ renderFooter,
579
921
  renderSectionHeader,
580
922
 
581
- // Status
923
+ // Messages
582
924
  renderSuccess,
583
925
  renderError,
584
926
  renderWarning,
585
927
  renderInfo,
586
- renderBullet,
587
- renderStep,
588
-
589
- // Results
590
- renderVerdict,
591
- renderFindingsTable,
592
- renderKeyValue,
593
- renderProgressBar,
594
- renderScore,
595
928
 
596
- // Footer
597
- renderFooter,
598
-
599
- // Components
929
+ // Spinner
600
930
  Spinner,
601
-
602
- // Templates
603
- runCommand,
604
931
  };