@vibecheckai/cli 3.0.5 → 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 (54) hide show
  1. package/bin/runners/context/index.js +1 -1
  2. package/bin/runners/lib/entitlements-v2.js +3 -1
  3. package/bin/runners/lib/entitlements.js +3 -0
  4. package/bin/runners/lib/report-html.js +5 -0
  5. package/bin/runners/lib/report-templates.js +5 -0
  6. package/bin/runners/lib/report.js +135 -0
  7. package/bin/runners/lib/ui.js +562 -0
  8. package/bin/runners/runCtx.js +7 -2
  9. package/bin/runners/runGuard.js +168 -0
  10. package/bin/runners/runLabs.js +341 -0
  11. package/bin/runners/runMdc.js +203 -1
  12. package/bin/runners/runProof.zip +0 -0
  13. package/bin/vibecheck.js +415 -774
  14. package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
  15. package/mcp-server/.specs/architecture.mdc +90 -0
  16. package/mcp-server/.specs/security.mdc +30 -0
  17. package/mcp-server/README.md +252 -0
  18. package/mcp-server/agent-checkpoint.js +364 -0
  19. package/mcp-server/architect-tools.js +707 -0
  20. package/mcp-server/audit-mcp.js +206 -0
  21. package/mcp-server/codebase-architect-tools.js +838 -0
  22. package/mcp-server/consolidated-tools.js +804 -0
  23. package/mcp-server/hygiene-tools.js +428 -0
  24. package/mcp-server/index-v1.js +698 -0
  25. package/mcp-server/index.js +2092 -0
  26. package/mcp-server/index.old.js +4137 -0
  27. package/mcp-server/intelligence-tools.js +664 -0
  28. package/mcp-server/intent-drift-tools.js +873 -0
  29. package/mcp-server/mdc-generator.js +298 -0
  30. package/mcp-server/package-lock.json +165 -0
  31. package/mcp-server/package.json +47 -0
  32. package/mcp-server/premium-tools.js +1275 -0
  33. package/mcp-server/test-mcp.js +108 -0
  34. package/mcp-server/test-tools.js +36 -0
  35. package/mcp-server/tier-auth.js +147 -0
  36. package/mcp-server/tools/index.js +72 -0
  37. package/mcp-server/tools-reorganized.ts +244 -0
  38. package/mcp-server/truth-context.js +581 -0
  39. package/mcp-server/truth-firewall-tools.js +1500 -0
  40. package/mcp-server/vibecheck-2.0-tools.js +748 -0
  41. package/mcp-server/vibecheck-tools.js +1075 -0
  42. package/package.json +2 -1
  43. package/bin/guardrail.js +0 -843
  44. package/bin/runners/runAudit.js +0 -2
  45. package/bin/runners/runAutopilot.js +0 -2
  46. package/bin/runners/runCertify.js +0 -2
  47. package/bin/runners/runDashboard.js +0 -10
  48. package/bin/runners/runEnhancedShip.js +0 -2
  49. package/bin/runners/runFixPacks.js +0 -2
  50. package/bin/runners/runNaturalLanguage.js +0 -3
  51. package/bin/runners/runProof.js +0 -2
  52. package/bin/runners/runRealitySniff.js +0 -2
  53. package/bin/runners/runUpgrade.js +0 -2
  54. 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,818 +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
- // Development mode - bypass all tier checks
302
- if (process.env.VIBECHECK_SKIP_AUTH) {
303
- return { allowed: true, tier: "dev" };
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
- // Free commands always work (no API key needed)
307
- if (FREE_COMMANDS.includes(cmd)) {
308
- return { allowed: true, tier: "free" };
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
- // Utility commands always work
312
- if (UTILITY_COMMANDS.includes(cmd)) {
313
- return { allowed: true, tier: "utility" };
314
- }
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 || [])];
315
266
 
316
- // Tiered commands with FREE read-only modes
317
- if (TIERED_COMMANDS[cmd]) {
318
- const config = TIERED_COMMANDS[cmd];
319
- // Check if using a FREE tier argument
320
- const hasFreeArg = args.some(arg => config.freeArgs.includes(arg));
321
- // Also allow if no fix pack specified (shows help)
322
- const hasNoFixPack = cmd === "fix" && !args.some(arg => !arg.startsWith("-"));
323
-
324
- if (hasFreeArg || hasNoFixPack) {
325
- return { allowed: true, tier: "free" };
326
- }
327
-
328
- // Requires paid tier
329
- if (!entitlements) {
330
- return {
331
- allowed: false,
332
- 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}`,
333
- };
334
- }
335
-
336
- if (
337
- entitlements.scopes?.includes(config.requiredScope) ||
338
- entitlements.scopes?.includes("*")
339
- ) {
340
- return { allowed: true, tier: config.tier };
341
- }
342
-
343
- return {
344
- allowed: false,
345
- 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}`,
346
- };
347
- }
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
+ }
348
272
 
349
- // Special handling for proof command (has sub-modes with different tiers)
350
- if (cmd === "proof") {
351
- const subMode = args[0]; // mocks or reality
352
- if (subMode === "mocks") {
353
- // Starter+ required
354
- if (!entitlements) {
355
- return {
356
- allowed: false,
357
- 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}`,
358
- };
359
- }
360
- if (
361
- entitlements.scopes?.includes("proof:mocks") ||
362
- entitlements.scopes?.includes("*")
363
- ) {
364
- return { allowed: true, tier: "starter" };
365
- }
366
- return {
367
- allowed: false,
368
- 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}`,
369
- };
370
- } else if (subMode === "reality") {
371
- // Pro+ required
372
- if (!entitlements) {
373
- return {
374
- allowed: false,
375
- 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}`,
376
- };
377
- }
378
- if (
379
- entitlements.scopes?.includes("proof:reality") ||
380
- entitlements.scopes?.includes("*")
381
- ) {
382
- return { allowed: true, tier: "pro" };
383
- }
384
- return {
385
- allowed: false,
386
- 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}`,
387
- };
388
- }
389
- // No submode - show help
390
- return { allowed: true };
391
- }
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
+ }
392
288
 
393
- // STARTER tier commands
394
- if (STARTER_COMMANDS[cmd]) {
395
- const requiredScope = STARTER_COMMANDS[cmd];
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
+ }
396
297
 
397
- if (!entitlements) {
398
- return {
399
- allowed: false,
400
- 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}`,
401
- };
402
- }
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
+ }
403
309
 
404
- if (
405
- entitlements.scopes?.includes(requiredScope) ||
406
- entitlements.scopes?.includes("*")
407
- ) {
408
- return { allowed: true, tier: "starter" };
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 });
326
+ }
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}`);
409
334
  }
410
-
411
- return {
412
- allowed: false,
413
- 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}`,
414
- };
415
335
  }
336
+ console.log(`
337
+ ${c.dim}${sym.boxHorizontal.repeat(64)}${c.reset}
416
338
 
417
- // PRO tier commands
418
- if (PRO_COMMANDS[cmd]) {
419
- const requiredScope = PRO_COMMANDS[cmd];
339
+ ${c.green}QUICK START${c.reset}
420
340
 
421
- if (!entitlements) {
422
- return {
423
- allowed: false,
424
- 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}`,
425
- };
426
- }
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}
427
344
 
428
- if (
429
- entitlements.scopes?.includes(requiredScope) ||
430
- entitlements.scopes?.includes("*")
431
- ) {
432
- return { allowed: true, tier: "pro" };
433
- }
345
+ ${c.dim}Run 'vibecheck <command> --help' for command-specific help.${c.reset}
346
+ `);
347
+ }
434
348
 
435
- return {
436
- allowed: false,
437
- 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}`,
438
- };
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];
439
356
  }
440
-
441
- return { allowed: true };
357
+ return undefined;
442
358
  }
443
359
 
444
- function printHelp() {
445
- console.log(
446
- `
447
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
448
- ${c.cyan} VIBECHECK${c.reset} - Ship with confidence
449
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
450
-
451
- ${c.green}🚀 QUICK START${c.reset}
452
-
453
- ${c.cyan}ship${c.reset} "Is my app ready?" - Plain English, traffic light score
454
- ${c.cyan}ship --fix${c.reset} Same + auto-fix problems
455
-
456
- ${c.yellow}🧪 TESTING${c.reset} (each does something different!)
457
-
458
- ${c.cyan}scan${c.reset} ${c.dim}Route Integrity${c.reset} - dead links, orphans, coverage, security 🗺️
459
- ${c.cyan}scan --truth${c.reset} ${c.dim}+ Build manifest${c.reset} verification (CI/ship ready)
460
- ${c.cyan}scan --reality${c.reset} ${c.dim}+ Playwright${c.reset} runtime proof (best-in-class)
461
- ${c.cyan}reality${c.reset} ${c.dim}Browser testing${c.reset} - clicks buttons, fills forms, finds broken UI
462
- ${c.cyan}ai-test${c.reset} ${c.dim}AI Agent${c.reset} - autonomous testing + generates fix prompts 🤖
463
-
464
- ${c.magenta}🚦 CI/CD & GATES${c.reset}
465
-
466
- ${c.cyan}gate${c.reset} Block bad deploys - pass/fail for CI pipelines
467
- ${c.cyan}proof mocks${c.reset} Block mock/demo code from reaching production
468
- ${c.cyan}proof reality${c.reset} Runtime GO/NO-GO verification with Playwright
469
-
470
- ${c.blue}🔧 FIX & AUTOMATE${c.reset}
471
-
472
- ${c.cyan}fix${c.reset} Auto-fix detected issues (--plan first, then --apply)
473
- ${c.cyan}autopilot${c.reset} Continuous protection - weekly reports, auto-PRs
474
- ${c.cyan}badge${c.reset} Generate Ship Badge for your README/PR
475
- ${c.cyan}certify${c.reset} Generate vibecheck Certified badge with verification link
476
-
477
- ${c.dim}📦 TRUTH SYSTEM${c.reset}
478
-
479
- ${c.cyan}ctx${c.reset} Generate Truth Pack - ground truth for AI agents
480
- ${c.cyan}ctx --snapshot${c.reset} Save snapshot to .vibecheck/truth/snapshots/
481
- ${c.cyan}graph${c.reset} Build Reality Proof Graph - end-to-end causal chains
482
-
483
- ${c.dim}📦 EXTRAS${c.reset}
484
-
485
- ${c.cyan}audit${c.reset} View/export audit trail (Compliance+ tier)
486
- ${c.cyan}context${c.reset} Generate AI rules files (.cursorrules, .windsurf/rules, etc.)
487
- ${c.cyan}dashboard${c.reset} Real-time monitoring dashboard with live metrics
488
- ${c.cyan}demo${c.reset} Interactive terminal features showcase
489
- ${c.cyan}launch${c.reset} Pre-launch checklist wizard
490
- ${c.cyan}validate${c.reset} Check AI-generated code for hallucinations
491
- ${c.cyan}init${c.reset} Set up vibecheck in your project
492
- ${c.cyan}doctor${c.reset} Debug environment issues
493
- ${c.cyan}mcp${c.reset} Start MCP server for AI editors
494
-
495
- ${c.dim}🔑 ACCOUNT${c.reset}
496
-
497
- ${c.cyan}login${c.reset} Sign in with API key
498
- ${c.cyan}logout${c.reset} Sign out
499
- ${c.cyan}whoami${c.reset} Show current user & plan
500
- ${c.cyan}upgrade${c.reset} Manage subscription & view usage
501
-
502
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
503
-
504
- ${c.green}Examples:${c.reset}
505
-
506
- vibecheck ship ${c.dim}# Quick health check${c.reset}
507
- vibecheck scan ${c.dim}# Route integrity + security analysis${c.reset}
508
- vibecheck scan --truth ${c.dim}# + Build manifest verification${c.reset}
509
- vibecheck scan --reality --url http://localhost:3000 ${c.dim}# Full proof${c.reset}
510
- vibecheck reality --url https://... ${c.dim}# Test live app${c.reset}
511
- vibecheck ai-test --url https://... ${c.dim}# AI explores your app${c.reset}
512
- vibecheck gate ${c.dim}# Block bad deploy in CI${c.reset}
513
- vibecheck badge ${c.dim}# Generate ship badge${c.reset}
514
-
515
- ${c.dim}Run 'vibecheck <command> --help' for details.${c.reset}
516
- `.trim(),
517
- );
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");
518
364
  }
519
365
 
520
- (async function main() {
366
+ // ═══════════════════════════════════════════════════════════════════════════════
367
+ // MAIN ENTRY POINT
368
+ // ═══════════════════════════════════════════════════════════════════════════════
369
+ async function main() {
370
+ const startTime = performance.now();
521
371
  const rawArgs = process.argv.slice(2);
522
-
523
- // Check if the first argument looks like a natural language command
524
- // Natural language commands are typically quoted strings or multi-word phrases
525
- const firstArg = rawArgs[0];
526
- if (firstArg && isNaturalLanguageCommand(firstArg)) {
527
- // Join all args as the natural language input
528
- const nlInput = rawArgs.join(" ");
529
- const exitCode = await runNaturalLanguage(nlInput);
530
- process.exit(exitCode);
531
- }
532
-
533
- const { legacyFrom, routed } = routeArgv(process.argv);
534
-
535
- const cmd = routed[0];
536
- const args = routed.slice(1);
537
-
538
- // Commands that skip auth check entirely
539
- const skipAuthCommands = [
540
- "help",
541
- "-h",
542
- "--help",
543
- "version",
544
- "login",
545
- "logout",
546
- "whoami",
547
- "doctor",
548
- ];
549
-
550
- if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
551
- 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}`);
552
391
  process.exit(0);
553
392
  }
554
-
555
- // Check for first run (no API key) - only for non-skip commands
556
- let authInfo = { key: null, entitlements: null };
557
-
558
- if (!skipAuthCommands.includes(cmd)) {
559
- // Check if this is a free command or utility - if so, skip entitlement checks
560
- const isFreeCommand = FREE_COMMANDS.includes(cmd) || UTILITY_COMMANDS.includes(cmd);
561
- const isKnownCommand = isFreeCommand ||
562
- STARTER_COMMANDS[cmd] ||
563
- PRO_COMMANDS[cmd] ||
564
- TIERED_COMMANDS[cmd] ||
565
- PROOF_COMMANDS[cmd] ||
566
- COMPLIANCE_COMMANDS[cmd];
567
-
568
- // For unknown commands, skip auth and let them fail with "Unknown command" message
569
- if (!isKnownCommand) {
570
- // Skip authentication for unknown commands - they'll show help/error message
571
- authInfo = { key: null, entitlements: null };
572
- } else {
573
- const { key } = getApiKey();
574
-
575
- // First run without API key - show welcome and prompt
576
- if (!key && !process.env.VIBECHECK_SKIP_AUTH) {
577
- authInfo = await showWelcomeAndPromptLogin();
578
- } else if (key && !isFreeCommand) {
579
- // Has API key and command requires auth - get entitlements silently
580
- // For free commands, we don't need to check entitlements
581
- try {
582
- const entitlements = await getEntitlements(key);
583
- authInfo = { key, entitlements };
584
- } catch (error) {
585
- // If API is unavailable, allow free commands to proceed
586
- // Paid commands will be blocked in checkCommandAccess
587
- if (isFreeCommand) {
588
- authInfo = { key, entitlements: null };
589
- } else {
590
- throw error;
591
- }
592
- }
593
- } else if (key && isFreeCommand) {
594
- // Free command with key - no need to fetch entitlements
595
- authInfo = { key, entitlements: null };
596
- }
597
- }
598
-
599
- // Check if user has access to this command (only for known commands)
600
- if (isKnownCommand) {
601
- const access = await checkCommandAccess(cmd, authInfo.entitlements, args);
602
-
603
- if (!access.allowed) {
604
- console.log(`\n${c.red}✗ Access Denied${c.reset}\n`);
605
- console.log(access.reason);
606
- console.log("");
607
- process.exit(1);
608
- }
609
-
610
- // Show tier info for paid features
611
- if (access.tier === "starter") {
612
- console.log(`${c.cyan}▸ STARTER${c.reset} ${c.dim}feature${c.reset}\n`);
613
- } else if (access.tier === "pro") {
614
- console.log(`${c.magenta}▸ PRO${c.reset} ${c.dim}feature${c.reset}\n`);
615
- }
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}`); });
616
416
  }
417
+ console.log(`\n${c.dim}Run 'vibecheck --help' for available commands.${c.reset}\n`);
418
+ process.exit(1);
617
419
  }
618
-
619
- // Deprecation suggestions
620
- if (legacyFrom) {
621
- const suggestion = routed.slice(0, 2).join(" ");
622
- 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}`);
623
436
  }
624
-
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;
625
443
  try {
626
- 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
+
627
449
  switch (cmd) {
628
- case "scan":
629
- exitCode = await runScan(args);
630
- break;
631
- case "gate":
632
- exitCode = await runGate(args);
633
- break;
634
- case "ship":
635
- exitCode = await runShip(args);
636
- break;
637
- case "enhanced-ship":
638
- exitCode = await runEnhancedShip(args);
639
- break;
640
- case "prompt-firewall":
641
- case "firewall":
642
- exitCode = await runPromptFirewall(args);
643
- break;
644
- case "launch":
645
- exitCode = await runLaunch(args);
646
- break;
647
- case "autopilot":
648
- exitCode = await runAutopilot(args);
649
- break;
650
- case "fix":
651
- exitCode = await runFix(args);
652
- break;
653
- case "share":
654
- exitCode = await runShare({
655
- repoRoot: process.cwd(),
656
- missionDir: args.find((a, i) => args[i-1] === '--mission-dir'),
657
- outputDir: args.find((a, i) => args[i-1] === '--output-dir'),
658
- prComment: args.includes('--pr-comment')
659
- });
660
- break;
661
- case "pr":
662
- exitCode = await runPR({
663
- repoRoot: process.cwd(),
664
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry'),
665
- out: args.find((a, i) => args[i-1] === '--out'),
666
- failOnWarn: args.includes('--fail-on-warn'),
667
- maxFindings: Number(args.find((a, i) => args[i-1] === '--max-findings')) || 12
668
- });
669
- break;
670
- case "install":
671
- exitCode = await runInstall({ repoRoot: process.cwd() });
672
- break;
673
- case "prove":
674
- exitCode = await runProve({
675
- repoRoot: process.cwd(),
676
- url: args.find((a, i) => args[i-1] === '--url' || args[i-1] === '-u'),
677
- auth: args.find((a, i) => args[i-1] === '--auth'),
678
- storageState: args.find((a, i) => args[i-1] === '--storage-state'),
679
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry'),
680
- maxFixRounds: Number(args.find((a, i) => args[i-1] === '--max-fix-rounds')) || 3,
681
- maxMissions: Number(args.find((a, i) => args[i-1] === '--max-missions')) || 8,
682
- maxSteps: Number(args.find((a, i) => args[i-1] === '--max-steps')) || 10,
683
- skipReality: args.includes('--skip-reality'),
684
- skipFix: args.includes('--skip-fix'),
685
- headed: args.includes('--headed'),
686
- danger: args.includes('--danger'),
687
- maxPages: Number(args.find((a, i) => args[i-1] === '--max-pages')) || 18,
688
- maxDepth: Number(args.find((a, i) => args[i-1] === '--max-depth')) || 2,
689
- timeoutMs: Number(args.find((a, i) => args[i-1] === '--timeout-ms')) || 15000
690
- });
691
- break;
692
- case "watch":
693
- exitCode = await runWatch({
694
- repoRoot: process.cwd(),
695
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry'),
696
- debounceMs: Number(args.find((a, i) => args[i-1] === '--debounce')) || 500,
697
- clearScreen: !args.includes('--no-clear')
698
- });
699
- break;
700
- case "status":
701
- exitCode = await runStatus({
702
- repoRoot: process.cwd(),
703
- json: args.includes('--json')
704
- });
705
- break;
706
- case "proof":
707
- exitCode = await runProof(args);
708
- break;
709
- case "reality":
710
- exitCode = await runReality({
711
- repoRoot: process.cwd(),
712
- url: args.find((a, i) => args[i-1] === '--url' || args[i-1] === '-u'),
713
- auth: args.find((a, i) => args[i-1] === '--auth'),
714
- storageState: args.find((a, i) => args[i-1] === '--storage-state'),
715
- saveStorageState: args.find((a, i) => args[i-1] === '--save-storage-state'),
716
- truthpack: args.find((a, i) => args[i-1] === '--truthpack'),
717
- verifyAuth: args.includes('--verify-auth'),
718
- headed: args.includes('--headed'),
719
- maxPages: Number(args.find((a, i) => args[i-1] === '--max-pages')) || 18,
720
- maxDepth: Number(args.find((a, i) => args[i-1] === '--max-depth')) || 2,
721
- danger: args.includes('--danger'),
722
- timeoutMs: Number(args.find((a, i) => args[i-1] === '--timeout-ms')) || 15000
723
- });
724
- break;
725
- case "reality-sniff":
726
- case "sniff":
727
- exitCode = await runRealitySniff(args);
728
- break;
729
- case "ai-test":
730
- case "ai":
731
- case "agent":
732
- exitCode = await runAIAgent(args);
733
- break;
734
- case "validate":
735
- exitCode = await runValidate(args);
736
- break;
737
- case "doctor":
738
- exitCode = await runDoctor(args);
739
- break;
740
- case "init":
741
- // Enterprise init handles all flags including --gha, --gitlab, --compliance, etc.
742
- exitCode = await runInit(args);
743
- break;
744
- case "mcp":
745
- exitCode = runMcp(args);
746
- break;
747
- case "login":
748
- exitCode = await runLogin(args);
749
- break;
750
- case "logout":
751
- exitCode = await runLogout(args);
752
- break;
753
- case "whoami":
754
- exitCode = await runWhoami(args);
755
- break;
756
- case "badge":
757
- exitCode = await runBadge(args);
758
- break;
759
- case "context":
760
- case "rules":
761
- exitCode = await runContext(args);
762
- break;
763
- case "dashboard":
764
- exitCode = await runDashboard(args);
765
- break;
766
- case "demo":
767
- exitCode = await runDemo(args);
768
- break;
769
- case "upgrade":
770
- exitCode = await runUpgrade(args);
771
- break;
772
- case "certify":
773
- exitCode = await runCertify(args, process.cwd());
774
- break;
775
- case "verify-agent-output":
776
- exitCode = await runVerifyAgentOutput(args);
777
- break;
778
- case "fixpacks":
779
- exitCode = await runFixPacks(args);
780
- break;
781
- case "audit":
782
- exitCode = await runAudit(args);
783
- break;
784
- case "mdc":
785
- exitCode = await runMdc(args);
786
- break;
787
- case "graph":
788
- exitCode = await runGraph(args);
789
- break;
790
- case "permissions":
791
- case "authz":
792
- exitCode = await runPermissions(args);
793
- break;
794
- case "ctx":
795
- case "truthpack":
796
- // Check for subcommands
797
- if (args[0] === "sync") {
798
- exitCode = await runCtxSync({
799
- repoRoot: process.cwd(),
800
- fastifyEntry: args.find((a, i) => args[i-1] === '--fastify-entry')
801
- });
802
- break;
803
- }
804
- if (args[0] === "guard") {
805
- exitCode = await runCtxGuard.main(args.slice(1));
806
- break;
807
- }
808
- // Parse args for ctx command - use Route Truth v1
809
- const fastifyEntryIdx = args.indexOf('--fastify-entry');
810
- const fastifyEntry = fastifyEntryIdx !== -1 ? args[fastifyEntryIdx + 1] : undefined;
811
- await runCtx({
812
- repoRoot: process.cwd(),
813
- fastifyEntry,
814
- print: args.includes('--print'),
815
- });
816
- exitCode = 0;
817
- break;
818
- case "version":
819
- console.log(`vibecheck v${VERSION}`);
820
- break;
821
- default:
822
- // Try natural language parsing as fallback for unknown commands
823
- const nlInput = [cmd, ...args].join(" ");
824
- if (isNaturalLanguageCommand(nlInput)) {
825
- exitCode = await runNaturalLanguage(nlInput);
826
- } else {
827
- process.stderr.write(`Unknown command: ${cmd}\n\n`);
828
- printHelp();
829
- exitCode = 1;
830
- }
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);
831
465
  }
832
- process.exit(exitCode);
833
- } catch (err) {
834
- process.stderr.write(
835
- err && err.stack ? err.stack + "\n" : String(err) + "\n",
836
- );
837
- process.exit(1);
838
- }
839
- })();
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); });