@vibecheckai/cli 3.0.4 → 3.0.7

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 (108) hide show
  1. package/bin/dev/run-v2-torture.js +30 -0
  2. package/bin/runners/context/index.js +1 -1
  3. package/bin/runners/lib/analyzers.js +38 -0
  4. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  5. package/bin/runners/lib/contracts/auth-contract.js +8 -0
  6. package/bin/runners/lib/contracts/env-contract.js +3 -0
  7. package/bin/runners/lib/contracts/external-contract.js +10 -2
  8. package/bin/runners/lib/contracts/route-contract.js +7 -0
  9. package/bin/runners/lib/contracts.js +804 -0
  10. package/bin/runners/lib/detectors-v2.js +703 -0
  11. package/bin/runners/lib/drift.js +425 -0
  12. package/bin/runners/lib/entitlements-v2.js +3 -1
  13. package/bin/runners/lib/entitlements.js +11 -3
  14. package/bin/runners/lib/env-resolver.js +417 -0
  15. package/bin/runners/lib/extractors/client-calls.js +990 -0
  16. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  17. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  18. package/bin/runners/lib/extractors/index.js +363 -0
  19. package/bin/runners/lib/extractors/next-routes.js +524 -0
  20. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  21. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  22. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  23. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  24. package/bin/runners/lib/findings-schema.js +281 -0
  25. package/bin/runners/lib/html-report.js +650 -0
  26. package/bin/runners/lib/missions/templates.js +45 -0
  27. package/bin/runners/lib/policy.js +295 -0
  28. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  29. package/bin/runners/lib/reality/index.js +318 -0
  30. package/bin/runners/lib/reality/request-hashing.js +416 -0
  31. package/bin/runners/lib/reality/request-mapper.js +453 -0
  32. package/bin/runners/lib/reality/safety-rails.js +463 -0
  33. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  34. package/bin/runners/lib/reality/toast-detector.js +393 -0
  35. package/bin/runners/lib/report-html.js +5 -0
  36. package/bin/runners/lib/report-templates.js +5 -0
  37. package/bin/runners/lib/report.js +135 -0
  38. package/bin/runners/lib/route-truth.js +10 -10
  39. package/bin/runners/lib/schema-validator.js +350 -0
  40. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  41. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  42. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  43. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  44. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  45. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  46. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  47. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  48. package/bin/runners/lib/schemas/validator.js +438 -0
  49. package/bin/runners/lib/ui.js +562 -0
  50. package/bin/runners/lib/verdict-engine.js +628 -0
  51. package/bin/runners/runAIAgent.js +228 -1
  52. package/bin/runners/runBadge.js +181 -1
  53. package/bin/runners/runCtx.js +7 -2
  54. package/bin/runners/runCtxDiff.js +301 -0
  55. package/bin/runners/runGuard.js +168 -0
  56. package/bin/runners/runInitGha.js +78 -15
  57. package/bin/runners/runLabs.js +341 -0
  58. package/bin/runners/runLaunch.js +180 -1
  59. package/bin/runners/runMdc.js +203 -1
  60. package/bin/runners/runProof.zip +0 -0
  61. package/bin/runners/runProve.js +23 -0
  62. package/bin/runners/runReplay.js +114 -84
  63. package/bin/runners/runScan.js +111 -32
  64. package/bin/runners/runShip.js +23 -2
  65. package/bin/runners/runTruthpack.js +9 -7
  66. package/bin/runners/runValidate.js +161 -1
  67. package/bin/vibecheck.js +416 -770
  68. package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
  69. package/mcp-server/.specs/architecture.mdc +90 -0
  70. package/mcp-server/.specs/security.mdc +30 -0
  71. package/mcp-server/README.md +252 -0
  72. package/mcp-server/agent-checkpoint.js +364 -0
  73. package/mcp-server/architect-tools.js +707 -0
  74. package/mcp-server/audit-mcp.js +206 -0
  75. package/mcp-server/codebase-architect-tools.js +838 -0
  76. package/mcp-server/consolidated-tools.js +804 -0
  77. package/mcp-server/hygiene-tools.js +428 -0
  78. package/mcp-server/index-v1.js +698 -0
  79. package/mcp-server/index.js +2092 -0
  80. package/mcp-server/index.old.js +4137 -0
  81. package/mcp-server/intelligence-tools.js +664 -0
  82. package/mcp-server/intent-drift-tools.js +873 -0
  83. package/mcp-server/mdc-generator.js +298 -0
  84. package/mcp-server/package-lock.json +165 -0
  85. package/mcp-server/package.json +47 -0
  86. package/mcp-server/premium-tools.js +1275 -0
  87. package/mcp-server/test-mcp.js +108 -0
  88. package/mcp-server/test-tools.js +36 -0
  89. package/mcp-server/tier-auth.js +147 -0
  90. package/mcp-server/tools/index.js +72 -0
  91. package/mcp-server/tools-reorganized.ts +244 -0
  92. package/mcp-server/truth-context.js +581 -0
  93. package/mcp-server/truth-firewall-tools.js +1500 -0
  94. package/mcp-server/vibecheck-2.0-tools.js +748 -0
  95. package/mcp-server/vibecheck-tools.js +1075 -0
  96. package/package.json +10 -8
  97. package/bin/guardrail.js +0 -834
  98. package/bin/runners/runAudit.js +0 -2
  99. package/bin/runners/runAutopilot.js +0 -2
  100. package/bin/runners/runCertify.js +0 -2
  101. package/bin/runners/runDashboard.js +0 -10
  102. package/bin/runners/runEnhancedShip.js +0 -2
  103. package/bin/runners/runFixPacks.js +0 -2
  104. package/bin/runners/runNaturalLanguage.js +0 -3
  105. package/bin/runners/runProof.js +0 -2
  106. package/bin/runners/runRealitySniff.js +0 -2
  107. package/bin/runners/runUpgrade.js +0 -2
  108. package/bin/runners/runVerifyAgentOutput.js +0 -2
package/bin/vibecheck.js CHANGED
@@ -1,17 +1,26 @@
1
1
  #!/usr/bin/env node
2
- // bin/vibecheck.js
2
+ // bin/vibecheck.js - World-Class CLI
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+ // VibeCheck - Proves your app is real
5
+ // Ship with confidence. Catch fake features before your users do.
6
+ // ═══════════════════════════════════════════════════════════════════════════════
7
+
8
+ "use strict";
9
+
3
10
  const readline = require("readline");
4
11
  const path = require("path");
5
12
  const fs = require("fs");
6
- const { routeArgv } = require("./_router");
7
- const { warnDeprecationOnce } = require("./_deprecations");
8
- const {
9
- getApiKey,
10
- checkEntitlement,
11
- getEntitlements,
12
- } = require("./runners/lib/auth");
13
-
14
- // Read version from package.json
13
+ const os = require("os");
14
+ const { performance } = require("perf_hooks");
15
+
16
+ // ═══════════════════════════════════════════════════════════════════════════════
17
+ // PERFORMANCE: Track startup time
18
+ // ═══════════════════════════════════════════════════════════════════════════════
19
+ const STARTUP_TIME = performance.now();
20
+
21
+ // ═══════════════════════════════════════════════════════════════════════════════
22
+ // VERSION & METADATA
23
+ // ═══════════════════════════════════════════════════════════════════════════════
15
24
  function getVersion() {
16
25
  try {
17
26
  const pkgPath = path.join(__dirname, "..", "package.json");
@@ -22,813 +31,450 @@ function getVersion() {
22
31
  }
23
32
  }
24
33
 
25
- // Runners
26
- const { runScan } = require("./runners/runScan");
27
- const { runGate } = require("./runners/runGate");
28
- const { runContext } = require("./runners/runContext");
29
- const { runDashboard, runDemo } = require("./runners/runDashboard");
30
- const { runFix } = require("./runners/runFix");
31
- const { runShip } = require("./runners/runShip");
32
- const { runLaunch } = require("./runners/runLaunch");
33
- const { runAutopilot } = require("./runners/runAutopilot");
34
- const { runProof } = require("./runners/runProof");
35
-
36
- // Graceful loading for modules that may have syntax issues
37
- let runReality, runRealitySniff;
38
- try {
39
- runReality = require("./runners/runReality").runReality;
40
- } catch (e) {
41
- runReality = async () => { console.error("Reality runner unavailable:", e.message); return 1; };
42
- }
43
- try {
44
- runRealitySniff = require("./runners/runRealitySniff").runRealitySniff;
45
- } catch (e) {
46
- runRealitySniff = async () => { console.error("RealitySniff runner unavailable:", e.message); return 1; };
47
- }
48
- const { runValidate } = require("./runners/runValidate");
49
- const { runDoctor } = require("./runners/runDoctor");
50
- const { runInit } = require("./runners/runInit");
51
- const { runMcp } = require("./runners/runMcp");
52
- const { runLogin, runLogout, runWhoami } = require("./runners/runAuth");
53
- const {
54
- runNaturalLanguage,
55
- isNaturalLanguageCommand,
56
- } = require("./runners/runNaturalLanguage");
57
- const { runAIAgent } = require("./runners/runAIAgent");
58
- const { runBadge } = require("./runners/runBadge");
59
- const { runUpgrade } = require("./runners/runUpgrade");
60
- const { runCertify } = require("./runners/runCertify");
61
- const { runVerifyAgentOutput } = require("./runners/runVerifyAgentOutput");
62
- const { runFixPacks } = require("./runners/runFixPacks");
63
- const { runAudit } = require("./runners/runAudit");
64
- const { runMdc } = require("./runners/runMdc");
65
- const { runEnhancedShip } = require("./runners/runEnhancedShip");
66
- const { runPromptFirewall } = require("./runners/runPromptFirewall");
67
-
68
- // Route Truth v1 - ctx command
69
- const { runCtx } = require("./runners/runCtx");
70
- // Share command
71
- const { runShare } = require("./runners/runShare");
72
- // PR command
73
- const { runPR } = require("./runners/runPR");
74
- // Init GHA command
75
- const { runInitGha } = require("./runners/runInitGha");
76
- // Install command
77
- const { runInstall } = require("./runners/runInstall");
78
- // Prove command
79
- const { runProve } = require("./runners/runProve");
80
- // Watch command
81
- const { runWatch } = require("./runners/runWatch");
82
- // Status command
83
- const { runStatus } = require("./runners/runStatus");
84
- // Graph command - Reality Proof Graph
85
- const { runGraph } = require("./runners/runGraph");
86
- // Permissions command - AuthZ Matrix & IDOR
87
- const { runPermissions } = require("./runners/runPermissions");
88
- // Replay command - Record and replay user sessions
89
- const { runReplay } = require("./runners/runReplay");
90
- // Context Contracts commands
91
- const { runCtxSync } = require("./runners/runCtxSync");
92
- const { runCtxGuard } = require("./runners/runCtxGuard");
93
-
94
- // Truth Pack v1 - Core truth system
95
- let runTruthpack;
96
- try {
97
- runTruthpack = require("./runners/runTruthpack").runTruthpack;
98
- } catch (e) {
99
- runTruthpack = async () => { console.error("Truthpack runner unavailable:", e.message); return 1; };
100
- }
101
-
102
34
  const VERSION = getVersion();
103
-
104
- // ANSI colors
105
- const c = {
35
+ const CLI_NAME = "vibecheck";
36
+ const CONFIG_FILE = ".vibecheckrc";
37
+ const CACHE_DIR = path.join(os.homedir(), ".vibecheck");
38
+ const STATE_FILE = path.join(CACHE_DIR, "state.json");
39
+ const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
40
+
41
+ // ═══════════════════════════════════════════════════════════════════════════════
42
+ // ANSI STYLES - Premium terminal styling with gradient support
43
+ // ═══════════════════════════════════════════════════════════════════════════════
44
+ const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
45
+ const SUPPORTS_TRUECOLOR = SUPPORTS_COLOR && (
46
+ process.env.COLORTERM === "truecolor" ||
47
+ process.env.TERM_PROGRAM === "iTerm.app" ||
48
+ process.env.TERM_PROGRAM === "Apple_Terminal" ||
49
+ process.env.WT_SESSION // Windows Terminal
50
+ );
51
+
52
+ const c = SUPPORTS_COLOR ? {
106
53
  reset: "\x1b[0m",
54
+ bold: "\x1b[1m",
107
55
  dim: "\x1b[2m",
108
- cyan: "\x1b[36m",
56
+ italic: "\x1b[3m",
57
+ underline: "\x1b[4m",
58
+ blink: "\x1b[5m",
59
+ inverse: "\x1b[7m",
60
+ hidden: "\x1b[8m",
61
+ strikethrough: "\x1b[9m",
62
+ black: "\x1b[30m",
63
+ red: "\x1b[31m",
109
64
  green: "\x1b[32m",
110
65
  yellow: "\x1b[33m",
111
- red: "\x1b[31m",
112
66
  blue: "\x1b[34m",
113
67
  magenta: "\x1b[35m",
114
- };
115
-
116
- // Detect CI/CD environment (non-interactive)
117
- function isCI() {
118
- return !!(
119
- process.env.CI ||
120
- process.env.CONTINUOUS_INTEGRATION ||
121
- process.env.RAILWAY_ENVIRONMENT ||
122
- process.env.VERCEL ||
123
- process.env.NETLIFY ||
124
- process.env.GITHUB_ACTIONS ||
125
- process.env.GITLAB_CI ||
126
- process.env.CIRCLECI ||
127
- process.env.TRAVIS ||
128
- process.env.BUILDKITE ||
129
- process.env.RENDER ||
130
- process.env.HEROKU ||
131
- !process.stdin.isTTY
132
- );
68
+ cyan: "\x1b[36m",
69
+ white: "\x1b[37m",
70
+ gray: "\x1b[90m",
71
+ brightRed: "\x1b[91m",
72
+ brightGreen: "\x1b[92m",
73
+ brightYellow: "\x1b[93m",
74
+ brightBlue: "\x1b[94m",
75
+ brightMagenta: "\x1b[95m",
76
+ brightCyan: "\x1b[96m",
77
+ brightWhite: "\x1b[97m",
78
+ bgRed: "\x1b[41m",
79
+ bgGreen: "\x1b[42m",
80
+ bgYellow: "\x1b[43m",
81
+ bgBlue: "\x1b[44m",
82
+ bgMagenta: "\x1b[45m",
83
+ bgCyan: "\x1b[46m",
84
+ bgWhite: "\x1b[47m",
85
+ bgGray: "\x1b[100m",
86
+ rgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `\x1b[38;2;${r};${g};${b}m` : "",
87
+ bgRgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `\x1b[48;2;${r};${g};${b}m` : "",
88
+ } : Object.fromEntries([
89
+ "reset", "bold", "dim", "italic", "underline", "blink", "inverse", "hidden", "strikethrough",
90
+ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "gray",
91
+ "brightRed", "brightGreen", "brightYellow", "brightBlue", "brightMagenta", "brightCyan", "brightWhite",
92
+ "bgRed", "bgGreen", "bgYellow", "bgBlue", "bgMagenta", "bgCyan", "bgWhite", "bgGray"
93
+ ].map(k => [k, ""]));
94
+
95
+ // Gradient text generator (cyan → magenta → yellow)
96
+ function gradient(text, colors = [[0, 255, 255], [255, 0, 255], [255, 255, 0]]) {
97
+ if (!SUPPORTS_TRUECOLOR) return `${c.cyan}${text}${c.reset}`;
98
+ const chars = [...text];
99
+ const len = chars.length;
100
+ if (len === 0) return text;
101
+ return chars.map((char, i) => {
102
+ const t = i / Math.max(len - 1, 1);
103
+ const segmentLen = colors.length - 1;
104
+ const segment = Math.min(Math.floor(t * segmentLen), segmentLen - 1);
105
+ const localT = (t * segmentLen) - segment;
106
+ const c1 = colors[segment];
107
+ const c2 = colors[segment + 1] || c1;
108
+ const r = Math.round(c1[0] + (c2[0] - c1[0]) * localT);
109
+ const g = Math.round(c1[1] + (c2[1] - c1[1]) * localT);
110
+ const b = Math.round(c1[2] + (c2[2] - c1[2]) * localT);
111
+ return `${c.rgb(r, g, b)}${char}`;
112
+ }).join("") + c.reset;
133
113
  }
134
114
 
135
- // ============================================================================
136
- // TIER-BASED COMMAND ACCESS
137
- // ============================================================================
138
- // FREE ($0) - No API key needed
139
- const FREE_COMMANDS = [
140
- "help",
141
- "version",
142
- "doctor",
143
- "init",
144
- "login",
145
- "logout",
146
- "whoami",
147
- "scan", // Route integrity + security analysis
148
- "validate", // AI code validation
149
- "badge", // Generate badges
150
- "certify", // Certification badges (SEO fuel)
151
- "context", // AI rules generator
152
- "dashboard", // Real-time monitoring
153
- "demo", // Interactive demo
154
- "upgrade", // Subscription management
155
- "verify-agent-output", // Verify AI agent output
156
- "mdc", // MDC documentation generator
157
- "prompt-firewall", // Prompt firewall (free tier with limited features)
158
- "firewall", // Alias for prompt-firewall
159
- "ctx", // Truth Pack generator
160
- "truthpack", // Alias for ctx
161
- "replay", // Record and replay user sessions
162
- "graph", // Reality Proof Graph
163
- "status", // Quick project status
164
- "watch", // Continuous dev mode
165
- "prove", // One command reality proof
166
- "install", // Zero-friction onboarding
167
- "pr", // PR comment generator
168
- "share", // Share bundle generator
169
- "reality", // Runtime UI verification
170
- ];
171
-
172
- // STARTER ($19/mo) - Requires API key with starter+ plan
173
- const STARTER_COMMANDS = {
174
- ship: "ship:audit", // Plain English audit
175
- "enhanced-ship": "enhanced-ship:full", // Enhanced ship decision with all features
176
- gate: "gate:ci", // CI/CD gate
177
- reality: "reality:basic", // Browser testing
178
- launch: "launch:checklist", // Pre-launch wizard
179
- };
180
-
181
- // PRO ($49/mo) - Requires API key with pro+ plan
182
- const PRO_COMMANDS = {
183
- "ai-test": "ai:agent", // AI Agent testing
184
- ai: "ai:agent",
185
- agent: "ai:agent",
186
- // fix: removed - handled specially to allow --plan-only on FREE tier
187
- autopilot: "autopilot:enable", // Continuous protection
188
- };
189
-
190
- // Commands with FREE tier read-only modes
191
- const TIERED_COMMANDS = {
192
- fix: {
193
- freeArgs: ["--plan-only", "--help", "-h"], // Allow these args on FREE
194
- requiredScope: "fix:apply",
195
- tier: "pro",
196
- },
197
- };
198
-
199
- // Special: proof command has sub-modes with different tiers
200
- const PROOF_COMMANDS = {
201
- mocks: "proof:mocks", // Starter+
202
- reality: "proof:reality", // Pro+
203
- };
204
-
205
- // Commands that always work (utilities)
206
- const UTILITY_COMMANDS = ["mcp", "rules", "api", "deps", "sbom", "fixpacks"];
207
-
208
- // Compliance tier commands
209
- const COMPLIANCE_COMMANDS = {
210
- audit: "audit:full", // Full audit trail
115
+ // ═══════════════════════════════════════════════════════════════════════════════
116
+ // UNICODE SYMBOLS - Rich visual indicators
117
+ // ═══════════════════════════════════════════════════════════════════════════════
118
+ const SUPPORTS_UNICODE = process.platform !== "win32" || process.env.WT_SESSION || process.env.TERM_PROGRAM;
119
+
120
+ const sym = SUPPORTS_UNICODE ? {
121
+ success: "", error: "✗", warning: "⚠", info: "ℹ", pending: "○", active: "●",
122
+ spinner: ["", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
123
+ progress: ["", "▒", "▓", "█"],
124
+ arrow: "", arrowRight: "▸", arrowDown: "▾", bullet: "•",
125
+ star: "", starEmpty: "☆", heart: "♥", shield: "🛡️", rocket: "🚀",
126
+ fire: "🔥", sparkles: "✨", check: "☑", box: "☐", lock: "🔒",
127
+ key: "🔑", lightning: "⚡", clock: "⏱", folder: "📁", file: "📄",
128
+ gear: "", chart: "📊",
129
+ boxTopLeft: "", boxTopRight: "╮", boxBottomLeft: "╰", boxBottomRight: "╯",
130
+ boxHorizontal: "", boxVertical: "│", boxCross: "┼",
131
+ boxHorizontalDown: "", boxHorizontalUp: "┴",
132
+ boxVerticalRight: "", boxVerticalLeft: "┤",
133
+ dblBoxTopLeft: "", dblBoxTopRight: "╗", dblBoxBottomLeft: "╚", dblBoxBottomRight: "╝",
134
+ dblBoxHorizontal: "", dblBoxVertical: "║",
135
+ } : {
136
+ success: "", error: "×", warning: "!", info: "i", pending: "o", active: "*",
137
+ spinner: ["-", "\\", "|", "/"], progress: [".", ":", "#", "#"],
138
+ arrow: "->", arrowRight: ">", arrowDown: "v", bullet: "*",
139
+ star: "*", starEmpty: "o", heart: "<3", shield: "[#]", rocket: ">>",
140
+ fire: "(!)", sparkles: "*", check: "[x]", box: "[ ]", lock: "[L]",
141
+ key: "[K]", lightning: "!", clock: "[T]", folder: "[D]", file: "[F]",
142
+ gear: "[G]", chart: "[C]",
143
+ boxTopLeft: "+", boxTopRight: "+", boxBottomLeft: "+", boxBottomRight: "+",
144
+ boxHorizontal: "-", boxVertical: "|", boxCross: "+",
145
+ boxHorizontalDown: "+", boxHorizontalUp: "+",
146
+ boxVerticalRight: "+", boxVerticalLeft: "+",
147
+ dblBoxTopLeft: "+", dblBoxTopRight: "+", dblBoxBottomLeft: "+", dblBoxBottomRight: "+",
148
+ dblBoxHorizontal: "=", dblBoxVertical: "||",
211
149
  };
212
150
 
213
- async function prompt(question) {
214
- const rl = readline.createInterface({
215
- input: process.stdin,
216
- output: process.stdout,
217
- });
218
- return new Promise((resolve) => {
219
- rl.question(question, (answer) => {
220
- rl.close();
221
- resolve(answer.trim());
222
- });
223
- });
151
+ // ═══════════════════════════════════════════════════════════════════════════════
152
+ // SPINNER & PROGRESS
153
+ // ═══════════════════════════════════════════════════════════════════════════════
154
+ function isCI() {
155
+ return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.RAILWAY_ENVIRONMENT ||
156
+ process.env.VERCEL || process.env.NETLIFY || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI ||
157
+ process.env.CIRCLECI || process.env.TRAVIS || process.env.BUILDKITE || process.env.RENDER ||
158
+ process.env.HEROKU || process.env.CODEBUILD_BUILD_ID || process.env.JENKINS_URL ||
159
+ process.env.TEAMCITY_VERSION || process.env.TF_BUILD || !process.stdin.isTTY);
224
160
  }
225
161
 
226
- async function showWelcomeAndPromptLogin() {
227
- console.log(`
228
- ${c.cyan}╔════════════════════════════════════════════════════════════╗
229
- ║ ${c.reset}🛡️ VIBECHECK${c.cyan} ║
230
- ╚════════════════════════════════════════════════════════════╝${c.reset}
231
-
232
- ${c.dim}Ship with confidence. Catch fake features before your users do.${c.reset}
162
+ class Spinner {
163
+ constructor(text = "") {
164
+ this.text = text; this.frame = 0; this.interval = null;
165
+ this.stream = process.stderr; this.isCI = isCI();
166
+ }
167
+ start(text) {
168
+ if (text) this.text = text;
169
+ if (this.isCI) { this.stream.write(`${c.dim}${sym.pending}${c.reset} ${this.text}\n`); return this; }
170
+ this.interval = setInterval(() => {
171
+ const spinner = sym.spinner[this.frame % sym.spinner.length];
172
+ this.stream.write(`\r${c.cyan}${spinner}${c.reset} ${this.text}`);
173
+ this.frame++;
174
+ }, 80);
175
+ return this;
176
+ }
177
+ update(text) { this.text = text; if (this.isCI) this.stream.write(` ${c.dim}${sym.arrowRight}${c.reset} ${text}\n`); return this; }
178
+ succeed(text) { this.stop(); this.stream.write(`\r${c.green}${sym.success}${c.reset} ${text || this.text}\n`); return this; }
179
+ fail(text) { this.stop(); this.stream.write(`\r${c.red}${sym.error}${c.reset} ${text || this.text}\n`); return this; }
180
+ warn(text) { this.stop(); this.stream.write(`\r${c.yellow}${sym.warning}${c.reset} ${text || this.text}\n`); return this; }
181
+ info(text) { this.stop(); this.stream.write(`\r${c.blue}${sym.info}${c.reset} ${text || this.text}\n`); return this; }
182
+ stop() { if (this.interval) { clearInterval(this.interval); this.interval = null; this.stream.write("\r\x1b[K"); } return this; }
183
+ }
233
184
 
234
- `);
185
+ function stripAnsi(str) { return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, ""); }
235
186
 
236
- const { key, source } = getApiKey();
237
-
238
- if (!key) {
239
- // In CI/CD environments, skip interactive prompts
240
- if (isCI()) {
241
- console.log(`${c.yellow}⚠ No API key found${c.reset}`);
242
- console.log(
243
- `${c.dim}Running in CI mode with FREE tier features.${c.reset}`,
244
- );
245
- console.log(
246
- `${c.dim}Set VIBECHECK_API_KEY env var to unlock more features.${c.reset}\n`,
247
- );
248
- return { key: null, entitlements: null };
249
- }
187
+ function ensureCacheDir() { if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true }); }
250
188
 
251
- console.log(`${c.yellow}⚠ No API key found${c.reset}`);
252
- console.log(`
253
- ${c.dim}To unlock all features, you need a vibecheck API key.${c.reset}
189
+ function loadState() {
190
+ try { if (fs.existsSync(STATE_FILE)) return JSON.parse(fs.readFileSync(STATE_FILE, "utf-8")); } catch {}
191
+ return { firstRun: Date.now(), lastRun: null, runCount: 0, lastUpdateCheck: null, latestVersion: null, commandHistory: [], favorites: [] };
192
+ }
254
193
 
255
- ${c.green}FREE${c.reset} scan, validate, badge, doctor, init
256
- ${c.cyan}STARTER${c.reset} ship, gate, reality, launch, proof mocks ${c.dim}($29/mo)${c.reset}
257
- ${c.magenta}PRO${c.reset} ai-test, fix, autopilot, proof reality ${c.dim}($99/mo)${c.reset}
194
+ function saveState(state) { try { ensureCacheDir(); fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); } catch {} }
258
195
 
259
- ${c.dim}Get your API key at: ${c.cyan}https://vibecheckai.dev/settings/keys${c.reset}
260
- `);
196
+ function loadConfig() {
197
+ const config = { debug: false, verbose: false, quiet: false, color: true, analytics: true, updateCheck: true, timeout: 30000, maxRetries: 3 };
198
+ const projectConfigPath = path.join(process.cwd(), CONFIG_FILE);
199
+ if (fs.existsSync(projectConfigPath)) { try { Object.assign(config, JSON.parse(fs.readFileSync(projectConfigPath, "utf-8"))); } catch {} }
200
+ if (process.env.VIBECHECK_DEBUG === "true") config.debug = true;
201
+ if (process.env.VIBECHECK_VERBOSE === "true") config.verbose = true;
202
+ if (process.env.NO_COLOR || process.env.VIBECHECK_NO_COLOR) config.color = false;
203
+ return config;
204
+ }
261
205
 
262
- const answer = await prompt(
263
- `${c.cyan}?${c.reset} Do you have an API key? (y/N) `,
264
- );
265
-
266
- if (answer.toLowerCase() === "y") {
267
- const apiKey = await prompt(`${c.cyan}?${c.reset} Paste your API key: `);
268
- if (apiKey) {
269
- const { saveApiKey } = require("./runners/lib/auth");
270
- console.log(`\n${c.dim}Verifying...${c.reset}`);
271
-
272
- const entitlements = await getEntitlements(apiKey);
273
- if (entitlements) {
274
- saveApiKey(apiKey);
275
- console.log(
276
- `\n${c.green}✓${c.reset} Logged in as ${entitlements.user?.name || "User"}`,
277
- );
278
- console.log(
279
- `${c.dim}Plan: ${entitlements.plan?.toUpperCase() || "FREE"}${c.reset}\n`,
280
- );
281
- return { key: apiKey, entitlements };
282
- } else {
283
- console.log(`\n${c.red}✗${c.reset} Invalid API key\n`);
284
- }
285
- }
206
+ // ═══════════════════════════════════════════════════════════════════════════════
207
+ // FUZZY MATCHING
208
+ // ═══════════════════════════════════════════════════════════════════════════════
209
+ function levenshtein(a, b) {
210
+ const matrix = [];
211
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
212
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
213
+ for (let i = 1; i <= b.length; i++) {
214
+ for (let j = 1; j <= a.length; j++) {
215
+ if (b.charAt(i - 1) === a.charAt(j - 1)) matrix[i][j] = matrix[i - 1][j - 1];
216
+ else matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
286
217
  }
287
-
288
- console.log(`
289
- ${c.dim}Continuing in FREE mode. Some features will be limited.${c.reset}
290
- ${c.dim}Run ${c.cyan}vibecheck login${c.dim} anytime to upgrade.${c.reset}
291
- `);
292
- return { key: null, entitlements: null };
293
218
  }
294
-
295
- // User has API key, get entitlements
296
- const entitlements = await getEntitlements(key);
297
- return { key, entitlements };
219
+ return matrix[b.length][a.length];
298
220
  }
299
221
 
300
- async function checkCommandAccess(cmd, entitlements, args = []) {
301
- // Free commands always work (no API key needed)
302
- if (FREE_COMMANDS.includes(cmd)) {
303
- return { allowed: true, tier: "free" };
222
+ function findSimilarCommands(input, commands, maxDistance = 3) {
223
+ const matches = [];
224
+ for (const cmd of commands) {
225
+ const distance = levenshtein(input.toLowerCase(), cmd.toLowerCase());
226
+ if (distance <= maxDistance) matches.push({ cmd, distance });
227
+ if (cmd.toLowerCase().startsWith(input.toLowerCase()) && input.length >= 2) matches.push({ cmd, distance: 0.5 });
304
228
  }
229
+ return matches.sort((a, b) => a.distance - b.distance).map(m => m.cmd).slice(0, 3);
230
+ }
305
231
 
306
- // Utility commands always work
307
- if (UTILITY_COMMANDS.includes(cmd)) {
308
- return { allowed: true, tier: "utility" };
309
- }
232
+ // ═══════════════════════════════════════════════════════════════════════════════
233
+ // COMMAND REGISTRY
234
+ // ═══════════════════════════════════════════════════════════════════════════════
235
+ const COMMANDS = {
236
+ scan: { description: "Static truth - routes, contracts, secrets, coverage", tier: "free", category: "proof", aliases: ["s", "check"], runner: () => require("./runners/runScan").runScan },
237
+ ship: { description: "Verdict engine - SHIP / WARN / BLOCK decision", tier: "free", category: "proof", aliases: ["verdict"], runner: () => require("./runners/runShip").runShip },
238
+ reality: { description: "Runtime proof - Playwright clicks every button", tier: "free", category: "proof", aliases: ["r", "test", "e2e"], runner: () => { try { return require("./runners/runReality").runReality; } catch (e) { return async () => { console.error("Reality runner unavailable:", e.message); return 1; }; } } },
239
+ fix: { description: "Mission-based repair - targeted fixes with proof", tier: "pro", category: "proof", freeArgs: ["--plan-only", "--help", "-h"], aliases: ["f", "repair"], runner: () => require("./runners/runFix").runFix },
240
+ prove: { description: "One command - runs the full loop, fixes issues, proves SHIP", tier: "free", category: "proof", aliases: ["p", "full", "all"], runner: () => require("./runners/runProve").runProve },
241
+ report: { description: "HTML artifact - shareable proof of what shipped", tier: "free", category: "proof", aliases: ["html", "artifact"], runner: () => require("./runners/runReport").runReport },
242
+ init: { description: "Set up vibecheck (--gha for GitHub Actions)", tier: "free", category: "setup", aliases: ["setup", "configure"], runner: () => require("./runners/runInit").runInit },
243
+ install: { description: "Zero-friction onboarding - auto-detects everything", tier: "free", category: "setup", aliases: ["i", "bootstrap"], runner: () => require("./runners/runInstall").runInstall },
244
+ doctor: { description: "Environment + project diagnostics", tier: "free", category: "setup", aliases: ["health", "diag"], runner: () => require("./runners/runDoctor").runDoctor },
245
+ watch: { description: "Continuous mode - re-runs on file changes", tier: "free", category: "setup", aliases: ["w", "dev"], runner: () => require("./runners/runWatch").runWatch },
246
+ ctx: { description: "Generate truthpack - ground truth for AI agents", tier: "free", category: "truth", aliases: ["truthpack", "tp"], subcommands: ["build", "diff", "guard", "sync", "search"], runner: () => require("./runners/runCtx").runCtx },
247
+ guard: { description: "Trust boundaries - validates AI claims + prompt injection", tier: "free", category: "truth", aliases: ["validate", "trust"], runner: () => require("./runners/runGuard").runGuard },
248
+ context: { description: "Generate AI rules (.cursorrules, .windsurf/rules, etc.)", tier: "free", category: "truth", aliases: ["rules", "ai-rules"], runner: () => require("./runners/runContext").runContext },
249
+ mcp: { description: "Start MCP server for AI coding agents", tier: "free", category: "extras", aliases: [], runner: () => require("./runners/runMcp").runMcp },
250
+ badge: { description: "Generate ship badge for README/PR", tier: "free", category: "extras", aliases: ["b"], runner: () => require("./runners/runBadge").runBadge },
251
+ pr: { description: "Generate PR comment with findings", tier: "free", category: "extras", aliases: ["pull-request"], runner: () => require("./runners/runPR").runPR },
252
+ labs: { description: "Experimental features", tier: "free", category: "extras", aliases: ["experimental", "beta"], runner: () => require("./runners/runLabs").runLabs },
253
+ gate: { description: "CI/CD gate - blocks deploys on failures", tier: "starter", category: "ci", aliases: ["ci", "block"], scope: "gate:ci", runner: () => require("./runners/runGate").runGate },
254
+ "ai-test": { description: "AI Agent testing - autonomous test generation", tier: "pro", category: "automation", aliases: ["ai", "agent"], scope: "ai:agent", runner: () => require("./runners/runAIAgent").runAIAgent },
255
+ login: { description: "Authenticate with API key", tier: "free", category: "account", aliases: ["auth", "signin"], runner: () => require("./runners/runAuth").runLogin, skipAuth: true },
256
+ logout: { description: "Remove stored credentials", tier: "free", category: "account", aliases: ["signout"], runner: () => require("./runners/runAuth").runLogout, skipAuth: true },
257
+ whoami: { description: "Show current user and plan", tier: "free", category: "account", aliases: ["me", "user"], runner: () => require("./runners/runAuth").runWhoami, skipAuth: true },
258
+ mdc: { description: "Generate MDC specifications", tier: "free", category: "truth", aliases: [], runner: () => require("./runners/runMdc").runMdc },
259
+ status: { description: "Project status dashboard", tier: "free", category: "setup", aliases: ["st"], runner: () => require("./runners/runStatus").runStatus },
260
+ share: { description: "Generate share pack for PR/docs", tier: "free", category: "extras", aliases: [], runner: () => require("./runners/runShare").runShare },
261
+ };
310
262
 
311
- // Tiered commands with FREE read-only modes
312
- if (TIERED_COMMANDS[cmd]) {
313
- const config = TIERED_COMMANDS[cmd];
314
- // Check if using a FREE tier argument
315
- const hasFreeArg = args.some(arg => config.freeArgs.includes(arg));
316
- // Also allow if no fix pack specified (shows help)
317
- const hasNoFixPack = cmd === "fix" && !args.some(arg => !arg.startsWith("-"));
318
-
319
- if (hasFreeArg || hasNoFixPack) {
320
- return { allowed: true, tier: "free" };
321
- }
322
-
323
- // Requires paid tier
324
- if (!entitlements) {
325
- return {
326
- allowed: false,
327
- reason: `${c.yellow}${cmd}${c.reset} requires a ${c.magenta}PRO${c.reset} plan.\n\n Run ${c.cyan}vibecheck login${c.reset} to authenticate.\n Get your API key at: ${c.cyan}https://vibecheckai.dev/settings/keys${c.reset}`,
328
- };
329
- }
330
-
331
- if (
332
- entitlements.scopes?.includes(config.requiredScope) ||
333
- entitlements.scopes?.includes("*")
334
- ) {
335
- return { allowed: true, tier: config.tier };
336
- }
337
-
338
- return {
339
- allowed: false,
340
- reason: `Your ${c.yellow}${entitlements.plan?.toUpperCase()}${c.reset} plan doesn't include this feature.\n\n Required: ${config.requiredScope}\n Upgrade to ${c.magenta}PRO${c.reset} at: ${c.cyan}https://vibecheckai.dev/pricing${c.reset}`,
341
- };
342
- }
263
+ const ALIAS_MAP = {};
264
+ for (const [cmd, def] of Object.entries(COMMANDS)) { for (const alias of def.aliases || []) ALIAS_MAP[alias] = cmd; }
265
+ const ALL_COMMANDS = [...Object.keys(COMMANDS), ...Object.values(COMMANDS).flatMap(c => c.aliases || [])];
343
266
 
344
- // Special handling for proof command (has sub-modes with different tiers)
345
- if (cmd === "proof") {
346
- const subMode = args[0]; // mocks or reality
347
- if (subMode === "mocks") {
348
- // Starter+ required
349
- if (!entitlements) {
350
- return {
351
- allowed: false,
352
- reason: `${c.yellow}proof mocks${c.reset} requires a ${c.cyan}STARTER${c.reset} plan or higher.\n\n Run ${c.cyan}vibecheck login${c.reset} to authenticate.\n Get your API key at: ${c.cyan}https://vibecheckai.dev/settings/keys${c.reset}`,
353
- };
354
- }
355
- if (
356
- entitlements.scopes?.includes("proof:mocks") ||
357
- entitlements.scopes?.includes("*")
358
- ) {
359
- return { allowed: true, tier: "starter" };
360
- }
361
- return {
362
- allowed: false,
363
- reason: `Your ${c.yellow}${entitlements.plan?.toUpperCase()}${c.reset} plan doesn't include mock detection.\n\n Upgrade to ${c.cyan}STARTER${c.reset} at: ${c.cyan}https://vibecheckai.dev/pricing${c.reset}`,
364
- };
365
- } else if (subMode === "reality") {
366
- // Pro+ required
367
- if (!entitlements) {
368
- return {
369
- allowed: false,
370
- reason: `${c.yellow}proof reality${c.reset} requires a ${c.magenta}PRO${c.reset} plan.\n\n Run ${c.cyan}vibecheck login${c.reset} to authenticate.\n Get your API key at: ${c.cyan}https://vibecheckai.dev/settings/keys${c.reset}`,
371
- };
372
- }
373
- if (
374
- entitlements.scopes?.includes("proof:reality") ||
375
- entitlements.scopes?.includes("*")
376
- ) {
377
- return { allowed: true, tier: "pro" };
378
- }
379
- return {
380
- allowed: false,
381
- reason: `Your ${c.yellow}${entitlements.plan?.toUpperCase()}${c.reset} plan doesn't include runtime verification.\n\n Upgrade to ${c.magenta}PRO${c.reset} at: ${c.cyan}https://vibecheckai.dev/pricing${c.reset}`,
382
- };
383
- }
384
- // No submode - show help
385
- return { allowed: true };
386
- }
267
+ function getRunner(cmd) {
268
+ const def = COMMANDS[cmd];
269
+ if (!def) return null;
270
+ try { return def.runner(); } catch (e) { return async () => { console.error(`${c.red}${sym.error}${c.reset} Failed to load ${cmd}: ${e.message}`); return 1; }; }
271
+ }
387
272
 
388
- // STARTER tier commands
389
- if (STARTER_COMMANDS[cmd]) {
390
- const requiredScope = STARTER_COMMANDS[cmd];
273
+ // ═══════════════════════════════════════════════════════════════════════════════
274
+ // AUTH & ACCESS CONTROL
275
+ // ═══════════════════════════════════════════════════════════════════════════════
276
+ let authModule = null;
277
+ function getAuthModule() { if (!authModule) authModule = require("./runners/lib/auth"); return authModule; }
278
+
279
+ async function checkCommandAccess(cmd, args, entitlements) {
280
+ const def = COMMANDS[cmd];
281
+ if (!def) return { allowed: true };
282
+ if (def.tier === "free") return { allowed: true, tier: "free" };
283
+ if (!entitlements) return { allowed: false, tier: def.tier, reason: formatAccessDenied(cmd, def.tier, null) };
284
+ const hasAccess = entitlements.scopes?.includes(def.scope) || entitlements.scopes?.includes("*");
285
+ if (!hasAccess) return { allowed: false, tier: def.tier, reason: formatAccessDenied(cmd, def.tier, entitlements.plan) };
286
+ return { allowed: true, tier: def.tier };
287
+ }
391
288
 
392
- if (!entitlements) {
393
- return {
394
- allowed: false,
395
- reason: `${c.yellow}${cmd}${c.reset} requires a ${c.cyan}STARTER${c.reset} plan or higher.\n\n Run ${c.cyan}vibecheck login${c.reset} to authenticate.\n Get your API key at: ${c.cyan}https://vibecheckai.dev/settings/keys${c.reset}`,
396
- };
397
- }
289
+ function formatAccessDenied(cmd, requiredTier, currentPlan) {
290
+ const tierColors = { starter: c.cyan, pro: c.magenta, enterprise: c.yellow };
291
+ const tierColor = tierColors[requiredTier] || c.white;
292
+ let msg = `${c.yellow}${cmd}${c.reset} requires a ${tierColor}${requiredTier.toUpperCase()}${c.reset} plan.\n\n`;
293
+ if (!currentPlan) { msg += ` Run ${c.cyan}vibecheck login${c.reset} to authenticate.\n Get your API key at: ${c.cyan}https://vibecheckai.dev/settings/keys${c.reset}`; }
294
+ else { msg += ` Your current plan: ${c.yellow}${currentPlan.toUpperCase()}${c.reset}\n Upgrade at: ${c.cyan}https://vibecheckai.dev/pricing${c.reset}`; }
295
+ return msg;
296
+ }
398
297
 
399
- if (
400
- entitlements.scopes?.includes(requiredScope) ||
401
- entitlements.scopes?.includes("*")
402
- ) {
403
- return { allowed: true, tier: "starter" };
404
- }
298
+ // ═══════════════════════════════════════════════════════════════════════════════
299
+ // HELP SYSTEM
300
+ // ═══════════════════════════════════════════════════════════════════════════════
301
+ function printBanner() {
302
+ console.log(`
303
+ ${c.dim}${sym.boxTopLeft}${sym.boxHorizontal.repeat(60)}${sym.boxTopRight}${c.reset}
304
+ ${c.dim}${sym.boxVertical}${c.reset} ${gradient("VIBECHECK", [[0, 255, 255], [138, 43, 226], [255, 20, 147]])} ${c.dim}v${VERSION}${c.reset}${" ".repeat(60 - 13 - VERSION.length - 4)}${c.dim}${sym.boxVertical}${c.reset}
305
+ ${c.dim}${sym.boxVertical}${c.reset} ${c.dim}Ship with confidence. Catch fake features before users do.${c.reset} ${c.dim}${sym.boxVertical}${c.reset}
306
+ ${c.dim}${sym.boxBottomLeft}${sym.boxHorizontal.repeat(60)}${sym.boxBottomRight}${c.reset}
307
+ `);
308
+ }
405
309
 
406
- return {
407
- allowed: false,
408
- reason: `Your ${c.yellow}${entitlements.plan?.toUpperCase()}${c.reset} plan doesn't include this feature.\n\n Required: ${requiredScope}\n Upgrade to ${c.cyan}STARTER${c.reset} at: ${c.cyan}https://vibecheckai.dev/pricing${c.reset}`,
409
- };
310
+ function printHelp() {
311
+ printBanner();
312
+ const categories = {
313
+ proof: { name: "THE PROOF LOOP", color: c.green, icon: sym.shield },
314
+ setup: { name: "SETUP & DIAGNOSTICS", color: c.yellow, icon: sym.gear },
315
+ truth: { name: "TRUTH SYSTEM", color: c.magenta, icon: sym.lightning },
316
+ extras: { name: "EXTRAS", color: c.dim, icon: sym.star },
317
+ ci: { name: "CI/CD", color: c.cyan, icon: sym.rocket },
318
+ automation: { name: "AUTOMATION", color: c.blue, icon: sym.fire },
319
+ account: { name: "ACCOUNT", color: c.dim, icon: sym.key },
320
+ };
321
+ const grouped = {};
322
+ for (const [cmd, def] of Object.entries(COMMANDS)) {
323
+ const cat = def.category || "extras";
324
+ if (!grouped[cat]) grouped[cat] = [];
325
+ grouped[cat].push({ cmd, ...def });
410
326
  }
411
-
412
- // PRO tier commands
413
- if (PRO_COMMANDS[cmd]) {
414
- const requiredScope = PRO_COMMANDS[cmd];
415
-
416
- if (!entitlements) {
417
- return {
418
- allowed: false,
419
- reason: `${c.yellow}${cmd}${c.reset} requires a ${c.magenta}PRO${c.reset} plan.\n\n Run ${c.cyan}vibecheck login${c.reset} to authenticate.\n Get your API key at: ${c.cyan}https://vibecheckai.dev/settings/keys${c.reset}`,
420
- };
421
- }
422
-
423
- if (
424
- entitlements.scopes?.includes(requiredScope) ||
425
- entitlements.scopes?.includes("*")
426
- ) {
427
- return { allowed: true, tier: "pro" };
327
+ for (const [catKey, commands] of Object.entries(grouped)) {
328
+ const cat = categories[catKey] || { name: catKey.toUpperCase(), color: c.white, icon: sym.bullet };
329
+ console.log(`\n${cat.color}${cat.icon} ${cat.name}${c.reset}\n`);
330
+ for (const { cmd, description, tier, aliases } of commands) {
331
+ const tierBadge = tier === "starter" ? `${c.cyan}[STARTER]${c.reset} ` : tier === "pro" ? `${c.magenta}[PRO]${c.reset} ` : "";
332
+ const aliasStr = aliases?.length ? `${c.dim}(${aliases.join(", ")})${c.reset}` : "";
333
+ console.log(` ${c.cyan}${cmd.padEnd(12)}${c.reset} ${tierBadge}${description} ${aliasStr}`);
428
334
  }
429
-
430
- return {
431
- allowed: false,
432
- reason: `Your ${c.yellow}${entitlements.plan?.toUpperCase()}${c.reset} plan doesn't include this feature.\n\n Required: ${requiredScope}\n Upgrade to ${c.magenta}PRO${c.reset} at: ${c.cyan}https://vibecheckai.dev/pricing${c.reset}`,
433
- };
434
335
  }
336
+ console.log(`
337
+ ${c.dim}${sym.boxHorizontal.repeat(64)}${c.reset}
435
338
 
436
- return { allowed: true };
437
- }
438
-
439
- function printHelp() {
440
- console.log(
441
- `
442
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
443
- ${c.cyan} VIBECHECK${c.reset} - Ship with confidence
444
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
445
-
446
- ${c.green}🚀 QUICK START${c.reset}
447
-
448
- ${c.cyan}ship${c.reset} "Is my app ready?" - Plain English, traffic light score
449
- ${c.cyan}ship --fix${c.reset} Same + auto-fix problems
450
-
451
- ${c.yellow}🧪 TESTING${c.reset} (each does something different!)
452
-
453
- ${c.cyan}scan${c.reset} ${c.dim}Route Integrity${c.reset} - dead links, orphans, coverage, security 🗺️
454
- ${c.cyan}scan --truth${c.reset} ${c.dim}+ Build manifest${c.reset} verification (CI/ship ready)
455
- ${c.cyan}scan --reality${c.reset} ${c.dim}+ Playwright${c.reset} runtime proof (best-in-class)
456
- ${c.cyan}reality${c.reset} ${c.dim}Browser testing${c.reset} - clicks buttons, fills forms, finds broken UI
457
- ${c.cyan}ai-test${c.reset} ${c.dim}AI Agent${c.reset} - autonomous testing + generates fix prompts 🤖
458
-
459
- ${c.magenta}🚦 CI/CD & GATES${c.reset}
460
-
461
- ${c.cyan}gate${c.reset} Block bad deploys - pass/fail for CI pipelines
462
- ${c.cyan}proof mocks${c.reset} Block mock/demo code from reaching production
463
- ${c.cyan}proof reality${c.reset} Runtime GO/NO-GO verification with Playwright
464
-
465
- ${c.blue}🔧 FIX & AUTOMATE${c.reset}
466
-
467
- ${c.cyan}fix${c.reset} Auto-fix detected issues (--plan first, then --apply)
468
- ${c.cyan}autopilot${c.reset} Continuous protection - weekly reports, auto-PRs
469
- ${c.cyan}badge${c.reset} Generate Ship Badge for your README/PR
470
- ${c.cyan}certify${c.reset} Generate vibecheck Certified badge with verification link
471
-
472
- ${c.dim}📦 TRUTH SYSTEM${c.reset}
473
-
474
- ${c.cyan}ctx${c.reset} Generate Truth Pack - ground truth for AI agents
475
- ${c.cyan}ctx --snapshot${c.reset} Save snapshot to .vibecheck/truth/snapshots/
476
- ${c.cyan}graph${c.reset} Build Reality Proof Graph - end-to-end causal chains
477
-
478
- ${c.dim}📦 EXTRAS${c.reset}
479
-
480
- ${c.cyan}audit${c.reset} View/export audit trail (Compliance+ tier)
481
- ${c.cyan}context${c.reset} Generate AI rules files (.cursorrules, .windsurf/rules, etc.)
482
- ${c.cyan}dashboard${c.reset} Real-time monitoring dashboard with live metrics
483
- ${c.cyan}demo${c.reset} Interactive terminal features showcase
484
- ${c.cyan}launch${c.reset} Pre-launch checklist wizard
485
- ${c.cyan}validate${c.reset} Check AI-generated code for hallucinations
486
- ${c.cyan}init${c.reset} Set up vibecheck in your project
487
- ${c.cyan}doctor${c.reset} Debug environment issues
488
- ${c.cyan}mcp${c.reset} Start MCP server for AI editors
489
-
490
- ${c.dim}🔑 ACCOUNT${c.reset}
491
-
492
- ${c.cyan}login${c.reset} Sign in with API key
493
- ${c.cyan}logout${c.reset} Sign out
494
- ${c.cyan}whoami${c.reset} Show current user & plan
495
- ${c.cyan}upgrade${c.reset} Manage subscription & view usage
339
+ ${c.green}QUICK START${c.reset}
496
340
 
497
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
341
+ ${c.bold}"Check my repo"${c.reset} ${c.cyan}vibecheck scan${c.reset}
342
+ ${c.bold}"Can I ship?"${c.reset} ${c.cyan}vibecheck prove --url http://localhost:3000${c.reset}
343
+ ${c.bold}"Why did it fail?"${c.reset} ${c.cyan}vibecheck report${c.reset} ${c.dim}(then: vibecheck fix)${c.reset}
498
344
 
499
- ${c.green}Examples:${c.reset}
345
+ ${c.dim}Run 'vibecheck <command> --help' for command-specific help.${c.reset}
346
+ `);
347
+ }
500
348
 
501
- vibecheck ship ${c.dim}# Quick health check${c.reset}
502
- vibecheck scan ${c.dim}# Route integrity + security analysis${c.reset}
503
- vibecheck scan --truth ${c.dim}# + Build manifest verification${c.reset}
504
- vibecheck scan --reality --url http://localhost:3000 ${c.dim}# Full proof${c.reset}
505
- vibecheck reality --url https://... ${c.dim}# Test live app${c.reset}
506
- vibecheck ai-test --url https://... ${c.dim}# AI explores your app${c.reset}
507
- vibecheck gate ${c.dim}# Block bad deploy in CI${c.reset}
508
- vibecheck badge ${c.dim}# Generate ship badge${c.reset}
349
+ // ═══════════════════════════════════════════════════════════════════════════════
350
+ // HELPER FUNCTIONS
351
+ // ═══════════════════════════════════════════════════════════════════════════════
352
+ function getArgValue(args, flags) {
353
+ for (const flag of flags) {
354
+ const idx = args.indexOf(flag);
355
+ if (idx !== -1 && idx < args.length - 1) return args[idx + 1];
356
+ }
357
+ return undefined;
358
+ }
509
359
 
510
- ${c.dim}Run 'vibecheck <command> --help' for details.${c.reset}
511
- `.trim(),
512
- );
360
+ function formatError(error, config) {
361
+ const lines = [`${c.red}${sym.error} Error:${c.reset} ${error.message}`];
362
+ if (config.debug && error.stack) { lines.push("", `${c.dim}Stack trace:${c.reset}`, c.dim + error.stack.split("\n").slice(1).join("\n") + c.reset); }
363
+ return lines.join("\n");
513
364
  }
514
365
 
515
- (async function main() {
366
+ // ═══════════════════════════════════════════════════════════════════════════════
367
+ // MAIN ENTRY POINT
368
+ // ═══════════════════════════════════════════════════════════════════════════════
369
+ async function main() {
370
+ const startTime = performance.now();
516
371
  const rawArgs = process.argv.slice(2);
517
-
518
- // Check if the first argument looks like a natural language command
519
- // Natural language commands are typically quoted strings or multi-word phrases
520
- const firstArg = rawArgs[0];
521
- if (firstArg && isNaturalLanguageCommand(firstArg)) {
522
- // Join all args as the natural language input
523
- const nlInput = rawArgs.join(" ");
524
- const exitCode = await runNaturalLanguage(nlInput);
525
- process.exit(exitCode);
526
- }
527
-
528
- const { legacyFrom, routed } = routeArgv(process.argv);
529
-
530
- const cmd = routed[0];
531
- const args = routed.slice(1);
532
-
533
- // Commands that skip auth check entirely
534
- const skipAuthCommands = [
535
- "help",
536
- "-h",
537
- "--help",
538
- "version",
539
- "login",
540
- "logout",
541
- "whoami",
542
- "doctor",
543
- ];
544
-
545
- if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
546
- printHelp();
372
+ const config = loadConfig();
373
+ const state = loadState();
374
+
375
+ const globalFlags = {
376
+ debug: rawArgs.includes("--debug") || rawArgs.includes("-d"),
377
+ verbose: rawArgs.includes("--verbose") || rawArgs.includes("-v"),
378
+ quiet: rawArgs.includes("--quiet") || rawArgs.includes("-q"),
379
+ help: rawArgs.includes("--help") || rawArgs.includes("-h"),
380
+ version: rawArgs.includes("--version") || rawArgs.includes("-V"),
381
+ };
382
+
383
+ if (globalFlags.debug) config.debug = true;
384
+ if (globalFlags.verbose) config.verbose = true;
385
+ if (globalFlags.quiet) config.quiet = true;
386
+
387
+ const args = rawArgs.filter(a => !["--debug", "-d", "--verbose", "-v", "--quiet", "-q", "--help", "-h", "--version", "-V"].includes(a));
388
+
389
+ if (globalFlags.version || args[0] === "version") {
390
+ console.log(`vibecheck v${VERSION}`);
547
391
  process.exit(0);
548
392
  }
549
-
550
- // Check for first run (no API key) - only for non-skip commands
551
- let authInfo = { key: null, entitlements: null };
552
-
553
- if (!skipAuthCommands.includes(cmd)) {
554
- // Check if this is a free command or utility - if so, skip entitlement checks
555
- const isFreeCommand = FREE_COMMANDS.includes(cmd) || UTILITY_COMMANDS.includes(cmd);
556
- const isKnownCommand = isFreeCommand ||
557
- STARTER_COMMANDS[cmd] ||
558
- PRO_COMMANDS[cmd] ||
559
- TIERED_COMMANDS[cmd] ||
560
- PROOF_COMMANDS[cmd] ||
561
- COMPLIANCE_COMMANDS[cmd];
562
-
563
- // For unknown commands, skip auth and let them fail with "Unknown command" message
564
- if (!isKnownCommand) {
565
- // Skip authentication for unknown commands - they'll show help/error message
566
- authInfo = { key: null, entitlements: null };
567
- } else {
568
- const { key } = getApiKey();
569
-
570
- // First run without API key - show welcome and prompt
571
- if (!key && !process.env.VIBECHECK_SKIP_AUTH) {
572
- authInfo = await showWelcomeAndPromptLogin();
573
- } else if (key && !isFreeCommand) {
574
- // Has API key and command requires auth - get entitlements silently
575
- // For free commands, we don't need to check entitlements
576
- try {
577
- const entitlements = await getEntitlements(key);
578
- authInfo = { key, entitlements };
579
- } catch (error) {
580
- // If API is unavailable, allow free commands to proceed
581
- // Paid commands will be blocked in checkCommandAccess
582
- if (isFreeCommand) {
583
- authInfo = { key, entitlements: null };
584
- } else {
585
- throw error;
586
- }
587
- }
588
- } else if (key && isFreeCommand) {
589
- // Free command with key - no need to fetch entitlements
590
- authInfo = { key, entitlements: null };
591
- }
592
- }
593
-
594
- // Check if user has access to this command (only for known commands)
595
- if (isKnownCommand) {
596
- const access = await checkCommandAccess(cmd, authInfo.entitlements, args);
597
-
598
- if (!access.allowed) {
599
- console.log(`\n${c.red}✗ Access Denied${c.reset}\n`);
600
- console.log(access.reason);
601
- console.log("");
602
- process.exit(1);
603
- }
604
-
605
- // Show tier info for paid features
606
- if (access.tier === "starter") {
607
- console.log(`${c.cyan}▸ STARTER${c.reset} ${c.dim}feature${c.reset}\n`);
608
- } else if (access.tier === "pro") {
609
- console.log(`${c.magenta}▸ PRO${c.reset} ${c.dim}feature${c.reset}\n`);
610
- }
393
+
394
+ if (!args[0]) { printHelp(); process.exit(0); }
395
+
396
+ // Handle command-specific help (vibecheck <cmd> --help)
397
+ if (globalFlags.help && args[0] && COMMANDS[args[0]]) {
398
+ // Pass --help to the command runner
399
+ } else if (globalFlags.help && !args[0]) {
400
+ printHelp(); process.exit(0);
401
+ }
402
+
403
+ let cmd = args[0];
404
+ if (ALIAS_MAP[cmd]) { cmd = ALIAS_MAP[cmd]; }
405
+ let cmdArgs = args.slice(1);
406
+
407
+ // Add --help back to cmdArgs if it was passed with a command
408
+ if (globalFlags.help) cmdArgs = ["--help", ...cmdArgs];
409
+
410
+ if (!COMMANDS[cmd]) {
411
+ const suggestions = findSimilarCommands(cmd, ALL_COMMANDS);
412
+ console.log(`\n${c.red}${sym.error}${c.reset} Unknown command: ${c.yellow}${cmd}${c.reset}`);
413
+ if (suggestions.length > 0) {
414
+ console.log(`\n${c.dim}Did you mean:${c.reset}`);
415
+ suggestions.forEach(s => { const actual = ALIAS_MAP[s] || s; const def = COMMANDS[actual]; console.log(` ${c.cyan}vibecheck ${s}${c.reset} ${c.dim}${def?.description || ""}${c.reset}`); });
611
416
  }
417
+ console.log(`\n${c.dim}Run 'vibecheck --help' for available commands.${c.reset}\n`);
418
+ process.exit(1);
612
419
  }
613
-
614
- // Deprecation suggestions
615
- if (legacyFrom) {
616
- const suggestion = routed.slice(0, 2).join(" ");
617
- warnDeprecationOnce(legacyFrom, suggestion, VERSION);
420
+
421
+ const cmdDef = COMMANDS[cmd];
422
+ let authInfo = { key: null, entitlements: null };
423
+
424
+ if (!cmdDef.skipAuth) {
425
+ const auth = getAuthModule();
426
+ const { key } = auth.getApiKey();
427
+
428
+ if (key && cmdDef.tier !== "free") {
429
+ try { authInfo.key = key; authInfo.entitlements = await auth.getEntitlements(key); } catch (e) { if (config.verbose) console.log(`${c.yellow}${sym.warning}${c.reset} ${c.dim}Could not verify credentials${c.reset}`); }
430
+ } else { authInfo.key = key; }
431
+
432
+ const access = await checkCommandAccess(cmd, cmdArgs, authInfo.entitlements);
433
+ if (!access.allowed) { console.log(`\n${c.red}${sym.error} Access Denied${c.reset}\n`); console.log(access.reason); console.log(""); process.exit(1); }
434
+ if (access.tier === "starter" && !config.quiet) console.log(`${c.cyan}${sym.arrowRight} STARTER${c.reset} ${c.dim}feature${c.reset}`);
435
+ else if (access.tier === "pro" && !config.quiet) console.log(`${c.magenta}${sym.arrowRight} PRO${c.reset} ${c.dim}feature${c.reset}`);
618
436
  }
619
-
437
+
438
+ state.runCount++; state.lastRun = Date.now();
439
+ state.commandHistory = [...(state.commandHistory || []).slice(-99), { cmd, timestamp: Date.now() }];
440
+ saveState(state);
441
+
442
+ let exitCode = 0;
620
443
  try {
621
- let exitCode = 0;
444
+ const runner = getRunner(cmd);
445
+ if (!runner) { console.error(`${c.red}${sym.error}${c.reset} Failed to load runner for: ${cmd}`); process.exit(1); }
446
+
447
+ const context = { repoRoot: process.cwd(), config, state, authInfo, version: VERSION, isCI: isCI() };
448
+
622
449
  switch (cmd) {
623
- case "scan":
624
- exitCode = await runScan(args);
625
- break;
626
- case "gate":
627
- exitCode = await runGate(args);
628
- break;
629
- case "ship":
630
- exitCode = await runShip(args);
631
- break;
632
- case "enhanced-ship":
633
- exitCode = await runEnhancedShip(args);
634
- break;
635
- case "prompt-firewall":
636
- case "firewall":
637
- exitCode = await runPromptFirewall(args);
638
- break;
639
- case "launch":
640
- exitCode = await runLaunch(args);
641
- break;
642
- case "autopilot":
643
- exitCode = await runAutopilot(args);
644
- break;
645
- case "fix":
646
- exitCode = await runFix(args);
647
- break;
648
- case "share":
649
- exitCode = await runShare({
650
- repoRoot: process.cwd(),
651
- missionDir: args.find((a, i) => args[i-1] === '--mission-dir'),
652
- outputDir: args.find((a, i) => args[i-1] === '--output-dir'),
653
- prComment: args.includes('--pr-comment')
654
- });
655
- break;
656
- case "pr":
657
- exitCode = await runPR({
658
- repoRoot: process.cwd(),
659
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry'),
660
- out: args.find((a, i) => args[i-1] === '--out'),
661
- failOnWarn: args.includes('--fail-on-warn'),
662
- maxFindings: Number(args.find((a, i) => args[i-1] === '--max-findings')) || 12
663
- });
664
- break;
665
- case "install":
666
- exitCode = await runInstall({ repoRoot: process.cwd() });
667
- break;
668
- case "prove":
669
- exitCode = await runProve({
670
- repoRoot: process.cwd(),
671
- url: args.find((a, i) => args[i-1] === '--url' || args[i-1] === '-u'),
672
- auth: args.find((a, i) => args[i-1] === '--auth'),
673
- storageState: args.find((a, i) => args[i-1] === '--storage-state'),
674
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry'),
675
- maxFixRounds: Number(args.find((a, i) => args[i-1] === '--max-fix-rounds')) || 3,
676
- maxMissions: Number(args.find((a, i) => args[i-1] === '--max-missions')) || 8,
677
- maxSteps: Number(args.find((a, i) => args[i-1] === '--max-steps')) || 10,
678
- skipReality: args.includes('--skip-reality'),
679
- skipFix: args.includes('--skip-fix'),
680
- headed: args.includes('--headed'),
681
- danger: args.includes('--danger'),
682
- maxPages: Number(args.find((a, i) => args[i-1] === '--max-pages')) || 18,
683
- maxDepth: Number(args.find((a, i) => args[i-1] === '--max-depth')) || 2,
684
- timeoutMs: Number(args.find((a, i) => args[i-1] === '--timeout-ms')) || 15000
685
- });
686
- break;
687
- case "watch":
688
- exitCode = await runWatch({
689
- repoRoot: process.cwd(),
690
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry'),
691
- debounceMs: Number(args.find((a, i) => args[i-1] === '--debounce')) || 500,
692
- clearScreen: !args.includes('--no-clear')
693
- });
694
- break;
695
- case "status":
696
- exitCode = await runStatus({
697
- repoRoot: process.cwd(),
698
- json: args.includes('--json')
699
- });
700
- break;
701
- case "proof":
702
- exitCode = await runProof(args);
703
- break;
704
- case "reality":
705
- exitCode = await runReality({
706
- repoRoot: process.cwd(),
707
- url: args.find((a, i) => args[i-1] === '--url' || args[i-1] === '-u'),
708
- auth: args.find((a, i) => args[i-1] === '--auth'),
709
- storageState: args.find((a, i) => args[i-1] === '--storage-state'),
710
- saveStorageState: args.find((a, i) => args[i-1] === '--save-storage-state'),
711
- truthpack: args.find((a, i) => args[i-1] === '--truthpack'),
712
- verifyAuth: args.includes('--verify-auth'),
713
- headed: args.includes('--headed'),
714
- maxPages: Number(args.find((a, i) => args[i-1] === '--max-pages')) || 18,
715
- maxDepth: Number(args.find((a, i) => args[i-1] === '--max-depth')) || 2,
716
- danger: args.includes('--danger'),
717
- timeoutMs: Number(args.find((a, i) => args[i-1] === '--timeout-ms')) || 15000
718
- });
719
- break;
720
- case "reality-sniff":
721
- case "sniff":
722
- exitCode = await runRealitySniff(args);
723
- break;
724
- case "ai-test":
725
- case "ai":
726
- case "agent":
727
- exitCode = await runAIAgent(args);
728
- break;
729
- case "validate":
730
- exitCode = await runValidate(args);
731
- break;
732
- case "doctor":
733
- exitCode = runDoctor(args);
734
- break;
735
- case "init":
736
- // Enterprise init handles all flags including --gha, --gitlab, --compliance, etc.
737
- exitCode = await runInit(args);
738
- break;
739
- case "mcp":
740
- exitCode = runMcp(args);
741
- break;
742
- case "login":
743
- exitCode = await runLogin(args);
744
- break;
745
- case "logout":
746
- exitCode = await runLogout(args);
747
- break;
748
- case "whoami":
749
- exitCode = await runWhoami(args);
750
- break;
751
- case "badge":
752
- exitCode = await runBadge(args);
753
- break;
754
- case "context":
755
- case "rules":
756
- exitCode = await runContext(args);
757
- break;
758
- case "dashboard":
759
- exitCode = await runDashboard(args);
760
- break;
761
- case "demo":
762
- exitCode = await runDemo(args);
763
- break;
764
- case "upgrade":
765
- exitCode = await runUpgrade(args);
766
- break;
767
- case "certify":
768
- exitCode = await runCertify(args, process.cwd());
769
- break;
770
- case "verify-agent-output":
771
- exitCode = await runVerifyAgentOutput(args);
772
- break;
773
- case "fixpacks":
774
- exitCode = await runFixPacks(args);
775
- break;
776
- case "audit":
777
- exitCode = await runAudit(args);
778
- break;
779
- case "mdc":
780
- exitCode = await runMdc(args);
781
- break;
782
- case "graph":
783
- exitCode = await runGraph(args);
784
- break;
785
- case "permissions":
786
- case "authz":
787
- exitCode = await runPermissions(args);
788
- break;
789
- case "ctx":
790
- case "truthpack":
791
- // Check for subcommands
792
- if (args[0] === "sync") {
793
- exitCode = await runCtxSync({
794
- repoRoot: process.cwd(),
795
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry')
796
- });
797
- break;
798
- }
799
- if (args[0] === "guard") {
800
- exitCode = await runCtxGuard.main(args.slice(1));
801
- break;
802
- }
803
- // Parse args for ctx command - use Route Truth v1
804
- const fastifyEntryIdx = args.indexOf('--fastify-entry');
805
- const fastifyEntry = fastifyEntryIdx !== -1 ? args[fastifyEntryIdx + 1] : undefined;
806
- await runCtx({
807
- repoRoot: process.cwd(),
808
- fastifyEntry,
809
- print: args.includes('--print'),
810
- });
811
- exitCode = 0;
812
- break;
813
- case "version":
814
- console.log(`vibecheck v${VERSION}`);
815
- break;
816
- default:
817
- // Try natural language parsing as fallback for unknown commands
818
- const nlInput = [cmd, ...args].join(" ");
819
- if (isNaturalLanguageCommand(nlInput)) {
820
- exitCode = await runNaturalLanguage(nlInput);
821
- } else {
822
- process.stderr.write(`Unknown command: ${cmd}\n\n`);
823
- printHelp();
824
- exitCode = 1;
825
- }
450
+ case "prove": exitCode = await runner({ ...context, url: getArgValue(cmdArgs, ["--url", "-u"]), auth: getArgValue(cmdArgs, ["--auth"]), storageState: getArgValue(cmdArgs, ["--storage-state"]), fastifyEntry: getArgValue(cmdArgs, ["--fastify-entry"]), maxFixRounds: parseInt(getArgValue(cmdArgs, ["--max-fix-rounds"]) || "3", 10), skipReality: cmdArgs.includes("--skip-reality"), skipFix: cmdArgs.includes("--skip-fix"), headed: cmdArgs.includes("--headed"), danger: cmdArgs.includes("--danger") }); break;
451
+ case "reality": exitCode = await runner({ ...context, url: getArgValue(cmdArgs, ["--url", "-u"]), auth: getArgValue(cmdArgs, ["--auth"]), storageState: getArgValue(cmdArgs, ["--storage-state"]), saveStorageState: getArgValue(cmdArgs, ["--save-storage-state"]), truthpack: getArgValue(cmdArgs, ["--truthpack"]), verifyAuth: cmdArgs.includes("--verify-auth"), headed: cmdArgs.includes("--headed"), danger: cmdArgs.includes("--danger") }); break;
452
+ case "watch": exitCode = await runner({ ...context, fastifyEntry: getArgValue(cmdArgs, ["--fastify-entry"]), debounceMs: parseInt(getArgValue(cmdArgs, ["--debounce"]) || "500", 10), clearScreen: !cmdArgs.includes("--no-clear") }); break;
453
+ case "ctx": case "truthpack":
454
+ if (cmdArgs[0] === "sync") { const { runCtxSync } = require("./runners/runCtxSync"); exitCode = await runCtxSync({ ...context, fastifyEntry: getArgValue(cmdArgs, ["--fastify-entry"]) }); }
455
+ else if (cmdArgs[0] === "guard") { const { runCtxGuard } = require("./runners/runCtxGuard"); exitCode = await runCtxGuard.main(cmdArgs.slice(1)); }
456
+ else if (cmdArgs[0] === "diff") { const { main: ctxDiffMain } = require("./runners/runCtxDiff"); exitCode = await ctxDiffMain(cmdArgs.slice(1)); }
457
+ else if (cmdArgs[0] === "search") { const { runContext } = require("./runners/runContext"); exitCode = await runContext(["--search", ...cmdArgs.slice(1)]); }
458
+ else { exitCode = await runner({ ...context, fastifyEntry: getArgValue(cmdArgs, ["--fastify-entry"]), print: cmdArgs.includes("--print") }); }
459
+ break;
460
+ case "install": exitCode = await runner(context); break;
461
+ case "status": exitCode = await runner({ ...context, json: cmdArgs.includes("--json") }); break;
462
+ case "pr": exitCode = await runner({ ...context, fastifyEntry: getArgValue(cmdArgs, ["--fastify-entry"]), out: getArgValue(cmdArgs, ["--out"]), failOnWarn: cmdArgs.includes("--fail-on-warn"), maxFindings: parseInt(getArgValue(cmdArgs, ["--max-findings"]) || "12", 10) }); break;
463
+ case "share": exitCode = await runner({ ...context, missionDir: getArgValue(cmdArgs, ["--mission-dir"]), outputDir: getArgValue(cmdArgs, ["--output-dir"]), prComment: cmdArgs.includes("--pr-comment") }); break;
464
+ default: exitCode = await runner(cmdArgs);
826
465
  }
827
- process.exit(exitCode);
828
- } catch (err) {
829
- process.stderr.write(
830
- err && err.stack ? err.stack + "\n" : String(err) + "\n",
831
- );
832
- process.exit(1);
833
- }
834
- })();
466
+ } catch (error) { console.error(formatError(error, config)); exitCode = 1; }
467
+
468
+ if (config.debug) console.log(`\n${c.dim}${sym.clock} Total: ${(performance.now() - startTime).toFixed(0)}ms${c.reset}`);
469
+ process.exit(exitCode);
470
+ }
471
+
472
+ // ═══════════════════════════════════════════════════════════════════════════════
473
+ // GRACEFUL SHUTDOWN
474
+ // ═══════════════════════════════════════════════════════════════════════════════
475
+ process.on("SIGINT", () => { console.log(`\n${c.yellow}${sym.warning}${c.reset} Interrupted`); process.exit(130); });
476
+ process.on("SIGTERM", () => { console.log(`\n${c.yellow}${sym.warning}${c.reset} Terminated`); process.exit(143); });
477
+ process.on("uncaughtException", (error) => { console.error(`\n${c.red}${sym.error} Uncaught Exception:${c.reset} ${error.message}`); if (process.env.VIBECHECK_DEBUG === "true") console.error(c.dim + error.stack + c.reset); process.exit(1); });
478
+ process.on("unhandledRejection", (reason) => { console.error(`\n${c.red}${sym.error} Unhandled Rejection:${c.reset} ${reason}`); process.exit(1); });
479
+
480
+ main().catch((error) => { console.error(`\n${c.red}${sym.error} Fatal:${c.reset} ${error.message}`); process.exit(1); });