@vibecheckai/cli 3.3.0 → 3.5.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 (170) hide show
  1. package/bin/registry.js +389 -269
  2. package/bin/runners/cli-utils.js +2 -33
  3. package/bin/runners/context/generators/cursor.js +49 -2
  4. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
  5. package/bin/runners/lib/analyzers.js +599 -142
  6. package/bin/runners/lib/audit-logger.js +532 -0
  7. package/bin/runners/lib/authority/authorities/architecture.js +364 -0
  8. package/bin/runners/lib/authority/authorities/compliance.js +341 -0
  9. package/bin/runners/lib/authority/authorities/human.js +343 -0
  10. package/bin/runners/lib/authority/authorities/quality.js +420 -0
  11. package/bin/runners/lib/authority/authorities/security.js +228 -0
  12. package/bin/runners/lib/authority/index.js +293 -0
  13. package/bin/runners/lib/authority-badge.js +425 -425
  14. package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
  15. package/bin/runners/lib/cli-charts.js +368 -0
  16. package/bin/runners/lib/cli-config-display.js +405 -0
  17. package/bin/runners/lib/cli-demo.js +275 -0
  18. package/bin/runners/lib/cli-errors.js +438 -0
  19. package/bin/runners/lib/cli-help-formatter.js +439 -0
  20. package/bin/runners/lib/cli-interactive-menu.js +509 -0
  21. package/bin/runners/lib/cli-prompts.js +441 -0
  22. package/bin/runners/lib/cli-scan-cards.js +362 -0
  23. package/bin/runners/lib/compliance-reporter.js +710 -0
  24. package/bin/runners/lib/conductor/index.js +671 -0
  25. package/bin/runners/lib/easy/README.md +123 -0
  26. package/bin/runners/lib/easy/index.js +140 -0
  27. package/bin/runners/lib/easy/interactive-wizard.js +788 -0
  28. package/bin/runners/lib/easy/one-click-firewall.js +564 -0
  29. package/bin/runners/lib/easy/zero-config-reality.js +714 -0
  30. package/bin/runners/lib/engines/accessibility-engine.js +218 -18
  31. package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
  32. package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
  33. package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
  34. package/bin/runners/lib/engines/confidence-scoring.js +276 -0
  35. package/bin/runners/lib/engines/context-detection.js +264 -0
  36. package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
  37. package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
  38. package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
  39. package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
  40. package/bin/runners/lib/engines/env-variables-engine.js +458 -0
  41. package/bin/runners/lib/engines/error-handling-engine.js +437 -0
  42. package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
  43. package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
  44. package/bin/runners/lib/engines/framework-detection.js +508 -0
  45. package/bin/runners/lib/engines/import-order-engine.js +429 -0
  46. package/bin/runners/lib/engines/mock-data-engine.js +53 -10
  47. package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
  48. package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
  49. package/bin/runners/lib/engines/orchestrator.js +334 -0
  50. package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
  51. package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
  52. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
  53. package/bin/runners/lib/engines/type-aware-engine.js +263 -39
  54. package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
  55. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
  56. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
  57. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
  58. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
  59. package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
  60. package/bin/runners/lib/enhanced-features/index.js +305 -0
  61. package/bin/runners/lib/enhanced-output.js +631 -0
  62. package/bin/runners/lib/enterprise.js +300 -0
  63. package/bin/runners/lib/entitlements-v2.js +161 -478
  64. package/bin/runners/lib/firewall/command-validator.js +351 -0
  65. package/bin/runners/lib/firewall/config.js +341 -0
  66. package/bin/runners/lib/firewall/content-validator.js +519 -0
  67. package/bin/runners/lib/firewall/index.js +101 -0
  68. package/bin/runners/lib/firewall/path-validator.js +256 -0
  69. package/bin/runners/lib/html-proof-report.js +350 -700
  70. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
  71. package/bin/runners/lib/mcp-utils.js +425 -0
  72. package/bin/runners/lib/missions/plan.js +46 -6
  73. package/bin/runners/lib/missions/templates.js +232 -0
  74. package/bin/runners/lib/output/index.js +1022 -0
  75. package/bin/runners/lib/policy-engine.js +652 -0
  76. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
  77. package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
  78. package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
  79. package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
  80. package/bin/runners/lib/polish/autofix/index.js +200 -0
  81. package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
  82. package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
  83. package/bin/runners/lib/polish/backend-checks.js +148 -0
  84. package/bin/runners/lib/polish/documentation-checks.js +111 -0
  85. package/bin/runners/lib/polish/frontend-checks.js +168 -0
  86. package/bin/runners/lib/polish/index.js +71 -0
  87. package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
  88. package/bin/runners/lib/polish/library-detection.js +175 -0
  89. package/bin/runners/lib/polish/performance-checks.js +100 -0
  90. package/bin/runners/lib/polish/security-checks.js +148 -0
  91. package/bin/runners/lib/polish/utils.js +203 -0
  92. package/bin/runners/lib/prompt-builder.js +540 -0
  93. package/bin/runners/lib/proof-certificate.js +634 -0
  94. package/bin/runners/lib/reality/accessibility-audit.js +946 -0
  95. package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
  96. package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
  97. package/bin/runners/lib/reality/performance-tracker.js +1077 -0
  98. package/bin/runners/lib/reality/scenario-generator.js +1404 -0
  99. package/bin/runners/lib/reality/visual-regression.js +852 -0
  100. package/bin/runners/lib/reality-profiler.js +717 -0
  101. package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
  102. package/bin/runners/lib/review/ai-code-review.js +832 -0
  103. package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
  104. package/bin/runners/lib/sbom-generator.js +641 -0
  105. package/bin/runners/lib/scan-output-enhanced.js +512 -0
  106. package/bin/runners/lib/scan-output.js +65 -19
  107. package/bin/runners/lib/security/owasp-scanner.js +939 -0
  108. package/bin/runners/lib/ship-output.js +18 -25
  109. package/bin/runners/lib/terminal-ui.js +113 -1
  110. package/bin/runners/lib/unified-cli-output.js +603 -430
  111. package/bin/runners/lib/upsell.js +90 -338
  112. package/bin/runners/lib/validators/contract-validator.js +283 -0
  113. package/bin/runners/lib/validators/dead-export-detector.js +279 -0
  114. package/bin/runners/lib/validators/dep-audit.js +245 -0
  115. package/bin/runners/lib/validators/env-validator.js +319 -0
  116. package/bin/runners/lib/validators/index.js +120 -0
  117. package/bin/runners/lib/validators/license-checker.js +252 -0
  118. package/bin/runners/lib/validators/route-validator.js +290 -0
  119. package/bin/runners/runAIAgent.js +5 -10
  120. package/bin/runners/runAgent.js +3 -0
  121. package/bin/runners/runApprove.js +1233 -1200
  122. package/bin/runners/runAuth.js +22 -1
  123. package/bin/runners/runAuthority.js +528 -0
  124. package/bin/runners/runCheckpoint.js +4 -24
  125. package/bin/runners/runClassify.js +862 -859
  126. package/bin/runners/runConductor.js +772 -0
  127. package/bin/runners/runContainer.js +366 -0
  128. package/bin/runners/runContext.js +3 -0
  129. package/bin/runners/runDoctor.js +28 -41
  130. package/bin/runners/runEasy.js +410 -0
  131. package/bin/runners/runFirewall.js +3 -0
  132. package/bin/runners/runFirewallHook.js +3 -0
  133. package/bin/runners/runFix.js +76 -66
  134. package/bin/runners/runGuard.js +411 -18
  135. package/bin/runners/runIaC.js +372 -0
  136. package/bin/runners/runInit.js +10 -60
  137. package/bin/runners/runMcp.js +11 -12
  138. package/bin/runners/runPolish.js +240 -64
  139. package/bin/runners/runPromptFirewall.js +5 -12
  140. package/bin/runners/runProve.js +20 -55
  141. package/bin/runners/runReality.js +68 -59
  142. package/bin/runners/runReport.js +31 -5
  143. package/bin/runners/runRuntime.js +5 -8
  144. package/bin/runners/runScan.js +194 -1273
  145. package/bin/runners/runShip.js +695 -47
  146. package/bin/runners/runTruth.js +3 -0
  147. package/bin/runners/runValidate.js +7 -11
  148. package/bin/runners/runVibe.js +791 -0
  149. package/bin/runners/runWatch.js +14 -23
  150. package/bin/vibecheck.js +179 -65
  151. package/mcp-server/index.js +202 -636
  152. package/mcp-server/lib/api-client.cjs +7 -299
  153. package/mcp-server/package.json +1 -1
  154. package/mcp-server/tier-auth.js +175 -574
  155. package/mcp-server/tools-v3.js +800 -505
  156. package/mcp-server/tools.js +495 -0
  157. package/package.json +1 -1
  158. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  159. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  160. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  161. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  162. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  163. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  166. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  167. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  168. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  169. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  170. package/mcp-server/index-v1.js +0 -698
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Container Security Scanner CLI
3
+ *
4
+ * Scans Dockerfiles for security issues and best practices
5
+ */
6
+
7
+ "use strict";
8
+
9
+ const path = require("path");
10
+ const fs = require("fs");
11
+ const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
12
+ const { EXIT } = require("./lib/exit-codes");
13
+
14
+ // Terminal UI
15
+ let terminalUI;
16
+ try {
17
+ terminalUI = require("./lib/terminal-ui");
18
+ } catch {
19
+ terminalUI = {
20
+ c: { reset: "", bold: "", dim: "", red: "", yellow: "", green: "", cyan: "" },
21
+ rgb: () => "",
22
+ icons: { check: "✓", cross: "✗", warning: "⚠", info: "ℹ" },
23
+ Spinner: class { start() {} succeed() {} fail() {} },
24
+ };
25
+ }
26
+
27
+ const { c, rgb, icons, Spinner } = terminalUI;
28
+
29
+ // Unified Output System
30
+ const { output } = require("./lib/output/index.js");
31
+
32
+ // Colors
33
+ const colors = {
34
+ critical: rgb(255, 80, 80),
35
+ high: rgb(255, 150, 50),
36
+ medium: rgb(255, 200, 0),
37
+ low: rgb(100, 200, 255),
38
+ info: rgb(150, 150, 150),
39
+ success: rgb(0, 255, 150),
40
+ accent: rgb(0, 200, 255),
41
+ };
42
+
43
+ const BANNER = `
44
+ ${rgb(0, 200, 255)} ╔═══════════════════════════════════════════════════════════════╗${c.reset}
45
+ ${rgb(0, 180, 255)} ║ ${c.bold}CONTAINER SECURITY${c.reset}${rgb(0, 180, 255)} • Dockerfile Scanning ║${c.reset}
46
+ ${rgb(0, 160, 255)} ╚═══════════════════════════════════════════════════════════════╝${c.reset}
47
+ `;
48
+
49
+ function printBanner() {
50
+ console.log(BANNER);
51
+ }
52
+
53
+ function printHelp() {
54
+ console.log(`
55
+ ${c.bold}Usage:${c.reset} vibecheck container [options] [path]
56
+
57
+ ${c.bold}Description:${c.reset}
58
+ Scan Dockerfiles for security issues and best practices.
59
+
60
+ ${c.bold}Options:${c.reset}
61
+ ${colors.accent}--path, -p${c.reset} Path to Dockerfile or directory (default: .)
62
+ ${colors.accent}--json${c.reset} Output as JSON
63
+ ${colors.accent}--sarif${c.reset} Output as SARIF for GitHub
64
+ ${colors.accent}--ignore${c.reset} Ignore specific rule IDs (comma-separated)
65
+ ${colors.accent}--min-severity${c.reset} Minimum severity to report (critical/high/medium/low)
66
+ ${colors.accent}--verbose, -v${c.reset} Show detailed output
67
+ ${colors.accent}--help, -h${c.reset} Show this help
68
+
69
+ ${c.bold}Examples:${c.reset}
70
+ ${c.dim}# Scan current directory${c.reset}
71
+ vibecheck container
72
+
73
+ ${c.dim}# Scan specific Dockerfile${c.reset}
74
+ vibecheck container Dockerfile.prod
75
+
76
+ ${c.dim}# JSON output for CI${c.reset}
77
+ vibecheck container --json
78
+
79
+ ${c.dim}# Ignore specific rules${c.reset}
80
+ vibecheck container --ignore CIS-DI-0001,BP-003
81
+ `);
82
+ }
83
+
84
+ function parseArgs(args) {
85
+ const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
86
+
87
+ const opts = {
88
+ path: globalFlags.path || ".",
89
+ json: globalFlags.json || false,
90
+ sarif: false,
91
+ verbose: globalFlags.verbose || false,
92
+ help: globalFlags.help || false,
93
+ ignore: [],
94
+ minSeverity: null,
95
+ noBanner: globalFlags.noBanner || false,
96
+ ci: globalFlags.ci || false,
97
+ quiet: globalFlags.quiet || false,
98
+ };
99
+
100
+ for (let i = 0; i < cleanArgs.length; i++) {
101
+ const arg = cleanArgs[i];
102
+
103
+ if (arg === "--sarif") opts.sarif = true;
104
+ else if (arg === "--ignore") {
105
+ opts.ignore = (cleanArgs[++i] || "").split(",").filter(Boolean);
106
+ }
107
+ else if (arg.startsWith("--ignore=")) {
108
+ opts.ignore = arg.split("=")[1].split(",").filter(Boolean);
109
+ }
110
+ else if (arg === "--min-severity") {
111
+ opts.minSeverity = cleanArgs[++i];
112
+ }
113
+ else if (arg.startsWith("--min-severity=")) {
114
+ opts.minSeverity = arg.split("=")[1];
115
+ }
116
+ else if (!arg.startsWith("-")) {
117
+ opts.path = arg;
118
+ }
119
+ }
120
+
121
+ return opts;
122
+ }
123
+
124
+ function formatFinding(finding, verbose = false) {
125
+ const severityColors = {
126
+ critical: colors.critical,
127
+ high: colors.high,
128
+ medium: colors.medium,
129
+ low: colors.low,
130
+ info: colors.info,
131
+ };
132
+
133
+ const severityIcons = {
134
+ critical: "🔴",
135
+ high: "🟠",
136
+ medium: "🟡",
137
+ low: "🔵",
138
+ info: "⚪",
139
+ };
140
+
141
+ const color = severityColors[finding.severity] || colors.info;
142
+ const icon = severityIcons[finding.severity] || "⚪";
143
+
144
+ let output = ` ${icon} ${color}${finding.severity.toUpperCase()}${c.reset} ${finding.title}\n`;
145
+ output += ` ${c.dim}${finding.id}${c.reset}`;
146
+
147
+ if (finding.line) {
148
+ output += ` ${c.dim}• Line ${finding.line}${c.reset}`;
149
+ }
150
+
151
+ output += "\n";
152
+
153
+ if (verbose) {
154
+ output += ` ${c.dim}${finding.description}${c.reset}\n`;
155
+ output += ` ${colors.accent}Fix:${c.reset} ${finding.recommendation}\n`;
156
+ }
157
+
158
+ return output;
159
+ }
160
+
161
+ async function runContainer(args) {
162
+ const opts = parseArgs(args);
163
+
164
+ if (opts.help) {
165
+ printHelp();
166
+ return EXIT.SUCCESS;
167
+ }
168
+
169
+ if (shouldShowBanner(opts)) {
170
+ printBanner();
171
+ }
172
+
173
+ const spinner = new Spinner();
174
+ spinner.start("Scanning for Dockerfiles...");
175
+
176
+ try {
177
+ // Dynamic import of the security package
178
+ let containerScanner;
179
+ try {
180
+ containerScanner = require("@vibecheck/security").scanDockerfile ||
181
+ require("@vibecheck/security").container?.scanDockerfile;
182
+
183
+ if (!containerScanner) {
184
+ // Try direct import
185
+ const security = require("../../packages/security/src/container/scanner");
186
+ containerScanner = security.scanDockerfile;
187
+ }
188
+ } catch (e) {
189
+ // Fallback: try relative path
190
+ try {
191
+ const security = require("../../packages/security/src/container/scanner");
192
+ containerScanner = security.scanDockerfile;
193
+ } catch {
194
+ spinner.fail("Container scanning module not available");
195
+ console.error(`\n ${colors.critical}${icons.cross}${c.reset} Container scanning requires @vibecheck/security package\n`);
196
+ return EXIT.INTERNAL_ERROR;
197
+ }
198
+ }
199
+
200
+ const targetPath = path.resolve(opts.path);
201
+ const stat = fs.statSync(targetPath);
202
+
203
+ let results = [];
204
+
205
+ if (stat.isDirectory()) {
206
+ // Find all Dockerfiles in directory
207
+ const dockerfiles = [];
208
+
209
+ function findDockerfiles(dir) {
210
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
211
+ for (const entry of entries) {
212
+ const fullPath = path.join(dir, entry.name);
213
+ if (entry.isDirectory() && !["node_modules", ".git", ".terraform"].includes(entry.name)) {
214
+ findDockerfiles(fullPath);
215
+ } else if (
216
+ entry.name === "Dockerfile" ||
217
+ entry.name.match(/^Dockerfile\.\w+$/) ||
218
+ entry.name.match(/\.dockerfile$/i)
219
+ ) {
220
+ dockerfiles.push(fullPath);
221
+ }
222
+ }
223
+ }
224
+
225
+ findDockerfiles(targetPath);
226
+
227
+ if (dockerfiles.length === 0) {
228
+ spinner.succeed("No Dockerfiles found");
229
+ console.log(`\n ${c.dim}No Dockerfiles found in ${targetPath}${c.reset}\n`);
230
+ return EXIT.SUCCESS;
231
+ }
232
+
233
+ spinner.succeed(`Found ${dockerfiles.length} Dockerfile(s)`);
234
+
235
+ for (const dockerfile of dockerfiles) {
236
+ const result = await containerScanner({
237
+ dockerfilePath: dockerfile,
238
+ ignoreRules: opts.ignore,
239
+ minSeverity: opts.minSeverity,
240
+ });
241
+ results.push(result);
242
+ }
243
+ } else {
244
+ // Single file
245
+ spinner.succeed("Scanning Dockerfile");
246
+ const result = await containerScanner({
247
+ dockerfilePath: targetPath,
248
+ ignoreRules: opts.ignore,
249
+ minSeverity: opts.minSeverity,
250
+ });
251
+ results.push(result);
252
+ }
253
+
254
+ // Aggregate results
255
+ const totalFindings = results.flatMap(r => r.findings);
256
+ const totalScore = results.length > 0
257
+ ? Math.round(results.reduce((sum, r) => sum + r.score, 0) / results.length)
258
+ : 100;
259
+
260
+ // JSON output
261
+ if (opts.json) {
262
+ console.log(JSON.stringify({
263
+ success: true,
264
+ results,
265
+ summary: {
266
+ filesScanned: results.length,
267
+ totalFindings: totalFindings.length,
268
+ score: totalScore,
269
+ critical: totalFindings.filter(f => f.severity === "critical").length,
270
+ high: totalFindings.filter(f => f.severity === "high").length,
271
+ medium: totalFindings.filter(f => f.severity === "medium").length,
272
+ low: totalFindings.filter(f => f.severity === "low").length,
273
+ },
274
+ }, null, 2));
275
+
276
+ return totalFindings.some(f => f.severity === "critical" || f.severity === "high")
277
+ ? EXIT.BLOCKING
278
+ : totalFindings.length > 0 ? EXIT.WARNINGS : EXIT.SUCCESS;
279
+ }
280
+
281
+ // SARIF output
282
+ if (opts.sarif) {
283
+ const sarif = {
284
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
285
+ version: "2.1.0",
286
+ runs: [{
287
+ tool: {
288
+ driver: {
289
+ name: "vibecheck-container",
290
+ version: "1.0.0",
291
+ informationUri: "https://vibecheckai.dev",
292
+ },
293
+ },
294
+ results: totalFindings.map(f => ({
295
+ ruleId: f.id,
296
+ level: f.severity === "critical" || f.severity === "high" ? "error" : "warning",
297
+ message: { text: f.description },
298
+ locations: [{
299
+ physicalLocation: {
300
+ artifactLocation: { uri: f.file || opts.path },
301
+ region: f.line ? { startLine: f.line } : undefined,
302
+ },
303
+ }],
304
+ })),
305
+ }],
306
+ };
307
+ console.log(JSON.stringify(sarif, null, 2));
308
+ return EXIT.SUCCESS;
309
+ }
310
+
311
+ // Human-readable output
312
+ console.log("");
313
+
314
+ for (const result of results) {
315
+ const relPath = path.relative(process.cwd(), result.dockerfile);
316
+ console.log(`${c.bold}📄 ${relPath}${c.reset}`);
317
+ console.log(` Score: ${result.score >= 80 ? colors.success : result.score >= 60 ? colors.medium : colors.critical}${result.score}/100${c.reset}`);
318
+ console.log("");
319
+
320
+ if (result.findings.length === 0) {
321
+ console.log(` ${colors.success}${icons.check}${c.reset} No issues found\n`);
322
+ } else {
323
+ // Group by severity
324
+ const bySeverity = {
325
+ critical: result.findings.filter(f => f.severity === "critical"),
326
+ high: result.findings.filter(f => f.severity === "high"),
327
+ medium: result.findings.filter(f => f.severity === "medium"),
328
+ low: result.findings.filter(f => f.severity === "low"),
329
+ };
330
+
331
+ for (const [severity, findings] of Object.entries(bySeverity)) {
332
+ if (findings.length > 0) {
333
+ for (const finding of findings) {
334
+ console.log(formatFinding(finding, opts.verbose));
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ // Recommendations
341
+ if (result.recommendations && result.recommendations.length > 0 && opts.verbose) {
342
+ console.log(`${c.bold} 💡 Recommendations:${c.reset}`);
343
+ for (const rec of result.recommendations) {
344
+ console.log(` • ${rec}`);
345
+ }
346
+ console.log("");
347
+ }
348
+ }
349
+
350
+ // Summary
351
+ console.log(`${c.dim}─────────────────────────────────────────────────────────────${c.reset}`);
352
+ console.log(`${c.bold}Summary:${c.reset} ${results.length} file(s), ${totalFindings.length} finding(s), Score: ${totalScore}/100`);
353
+ console.log("");
354
+
355
+ return totalFindings.some(f => f.severity === "critical" || f.severity === "high")
356
+ ? EXIT.BLOCKING
357
+ : totalFindings.length > 0 ? EXIT.WARNINGS : EXIT.SUCCESS;
358
+
359
+ } catch (error) {
360
+ spinner.fail("Scan failed");
361
+ console.error(`\n ${colors.critical}${icons.cross}${c.reset} ${error.message}\n`);
362
+ return EXIT.INTERNAL_ERROR;
363
+ }
364
+ }
365
+
366
+ module.exports = { runContainer };
@@ -24,6 +24,9 @@ const {
24
24
  getTierFromKey,
25
25
  } = require("./lib/unified-cli-output");
26
26
 
27
+ // Unified Output System
28
+ const { output } = require("./lib/output/index.js");
29
+
27
30
  async function runContext(args) {
28
31
  const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
29
32
  const quiet = shouldSuppressOutput(globalFlags);
@@ -24,44 +24,15 @@ const { formatWorkflowUpsell } = require("./lib/upsell");
24
24
  const { getApiKey } = require("./lib/auth");
25
25
 
26
26
  // ═══════════════════════════════════════════════════════════════════════════════
27
- // ADVANCED TERMINAL - ANSI CODES & UTILITIES
27
+ // TERMINAL UI - Import from shared module
28
28
  // ═══════════════════════════════════════════════════════════════════════════════
29
29
 
30
- const c = {
31
- reset: '\x1b[0m',
32
- bold: '\x1b[1m',
33
- dim: '\x1b[2m',
34
- italic: '\x1b[3m',
35
- underline: '\x1b[4m',
36
- // Colors
37
- black: '\x1b[30m',
38
- red: '\x1b[31m',
39
- green: '\x1b[32m',
40
- yellow: '\x1b[33m',
41
- blue: '\x1b[34m',
42
- magenta: '\x1b[35m',
43
- cyan: '\x1b[36m',
44
- white: '\x1b[37m',
45
- // Bright colors
46
- gray: '\x1b[90m',
47
- brightRed: '\x1b[91m',
48
- brightGreen: '\x1b[92m',
49
- brightYellow: '\x1b[93m',
50
- brightBlue: '\x1b[94m',
51
- brightMagenta: '\x1b[95m',
52
- brightCyan: '\x1b[96m',
53
- brightWhite: '\x1b[97m',
54
- // Cursor control
55
- clearLine: '\x1b[2K',
56
- hideCursor: '\x1b[?25l',
57
- showCursor: '\x1b[?25h',
58
- };
30
+ const { c, rgb, bgRgb, icons, Spinner } = require("./lib/terminal-ui");
59
31
 
60
- // 256-color / True color support
61
- const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
62
- const bgRgb = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
32
+ // Unified Output System
33
+ const { output } = require("./lib/output/index.js");
63
34
 
64
- // Premium color palette
35
+ // Extended color palette for doctor command
65
36
  const colors = {
66
37
  // Gradients for banner
67
38
  gradient1: rgb(100, 200, 255), // Light blue
@@ -305,6 +276,13 @@ async function runDoctor(args, context = {}) {
305
276
  const json = isJsonMode(opts);
306
277
  const executionStart = Date.now();
307
278
 
279
+ // Configure unified output mode
280
+ output.setMode({
281
+ json: json,
282
+ quiet: quiet,
283
+ ci: opts.ci
284
+ });
285
+
308
286
  if (opts.help) {
309
287
  printHelp(shouldShowBanner(opts));
310
288
  return EXIT.SUCCESS;
@@ -603,14 +581,20 @@ function runDoctorLegacy() {
603
581
 
604
582
  // Get tier for upsell
605
583
  const { key } = getApiKey();
606
- const currentTier = key ? "starter" : "free";
584
+ const currentTier = key ? "pro" : "free";
607
585
 
608
586
  if (hasIssues) {
609
587
  console.log(" ❌ Issues found. Fix them and run doctor again.");
610
588
  console.log();
611
- console.log(` ${c.dim}Need help?${c.reset} ${colors.accent}vibecheck fix${c.reset} ${c.dim}can auto-repair many issues${c.reset}`);
589
+
612
590
  if (currentTier === "free") {
613
- console.log(` ${c.dim}Requires STARTER plan • vibecheckai.dev${c.reset}`);
591
+ console.log(` ${c.gray}╭${'─'.repeat(58)}╮${c.reset}`);
592
+ console.log(` ${c.gray}│${c.reset} ${c.magenta}★ PRO${c.reset} Auto-fix environment issues with AI ${c.gray}│${c.reset}`);
593
+ console.log(` ${c.gray}│${c.reset} ${c.cyan}vibecheck fix${c.reset} can auto-repair many issues ${c.gray}│${c.reset}`);
594
+ console.log(` ${c.gray}│${c.reset} ${c.dim}https://vibecheckai.dev/pricing${c.reset} ${c.gray}│${c.reset}`);
595
+ console.log(` ${c.gray}╰${'─'.repeat(58)}╯${c.reset}`);
596
+ } else {
597
+ console.log(` ${c.dim}Need help?${c.reset} ${colors.accent}vibecheck fix${c.reset} ${c.dim}can auto-repair many issues${c.reset}`);
614
598
  }
615
599
  console.log();
616
600
  return EXIT.BLOCKING;
@@ -618,10 +602,13 @@ function runDoctorLegacy() {
618
602
  console.log(" ✅ Environment healthy!");
619
603
  console.log();
620
604
 
621
- // Workflow upsell
622
- const workflow = formatWorkflowUpsell("doctor", currentTier);
623
- if (workflow) {
624
- console.log(` ${workflow}`);
605
+ if (currentTier === "free") {
606
+ console.log(` ${c.gray}╭${'─'.repeat(58)}╮${c.reset}`);
607
+ console.log(` ${c.gray}│${c.reset} ${c.green}✓${c.reset} Ready to scan! Try: ${c.cyan}vibecheck scan${c.reset} ${c.gray}│${c.reset}`);
608
+ console.log(` ${c.gray}│${c.reset} ${c.gray}│${c.reset}`);
609
+ console.log(` ${c.gray}│${c.reset} ${c.magenta}★ PRO${c.reset} AI-powered fixes, CI gates, ship badges ${c.gray}│${c.reset}`);
610
+ console.log(` ${c.gray}│${c.reset} ${c.dim}https://vibecheckai.dev/pricing${c.reset} ${c.gray}│${c.reset}`);
611
+ console.log(` ${c.gray}╰${'─'.repeat(58)}╯${c.reset}`);
625
612
  } else {
626
613
  console.log(` ${c.dim}Ready to scan?${c.reset} ${colors.accent}vibecheck scan${c.reset} ${c.dim}finds AI mistakes before they ship${c.reset}`);
627
614
  }