@vibecheckai/cli 3.2.6 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/bin/registry.js +192 -5
  2. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  3. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  4. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  5. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  6. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  7. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  8. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  11. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  12. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  14. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  15. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  16. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  17. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  18. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  19. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  20. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  21. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  22. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  23. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  24. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  25. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  26. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  27. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  28. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  29. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  30. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  31. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  32. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  35. package/bin/runners/lib/analyzers.js +81 -18
  36. package/bin/runners/lib/authority-badge.js +425 -0
  37. package/bin/runners/lib/cli-output.js +7 -1
  38. package/bin/runners/lib/error-handler.js +16 -9
  39. package/bin/runners/lib/exit-codes.js +275 -0
  40. package/bin/runners/lib/global-flags.js +37 -0
  41. package/bin/runners/lib/help-formatter.js +413 -0
  42. package/bin/runners/lib/logger.js +38 -0
  43. package/bin/runners/lib/unified-cli-output.js +604 -0
  44. package/bin/runners/lib/upsell.js +148 -0
  45. package/bin/runners/runApprove.js +1200 -0
  46. package/bin/runners/runAuth.js +324 -95
  47. package/bin/runners/runCheckpoint.js +39 -21
  48. package/bin/runners/runClassify.js +859 -0
  49. package/bin/runners/runContext.js +136 -24
  50. package/bin/runners/runDoctor.js +108 -68
  51. package/bin/runners/runFix.js +6 -5
  52. package/bin/runners/runGuard.js +212 -118
  53. package/bin/runners/runInit.js +3 -2
  54. package/bin/runners/runMcp.js +130 -52
  55. package/bin/runners/runPolish.js +43 -20
  56. package/bin/runners/runProve.js +1 -2
  57. package/bin/runners/runReport.js +3 -2
  58. package/bin/runners/runScan.js +63 -44
  59. package/bin/runners/runShip.js +3 -4
  60. package/bin/runners/runValidate.js +19 -2
  61. package/bin/runners/runWatch.js +104 -53
  62. package/bin/vibecheck.js +106 -19
  63. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  64. package/mcp-server/agent-firewall-interceptor.js +367 -31
  65. package/mcp-server/authority-tools.js +569 -0
  66. package/mcp-server/conductor/conflict-resolver.js +588 -0
  67. package/mcp-server/conductor/execution-planner.js +544 -0
  68. package/mcp-server/conductor/index.js +377 -0
  69. package/mcp-server/conductor/lock-manager.js +615 -0
  70. package/mcp-server/conductor/request-queue.js +550 -0
  71. package/mcp-server/conductor/session-manager.js +500 -0
  72. package/mcp-server/conductor/tools.js +510 -0
  73. package/mcp-server/index.js +1149 -243
  74. package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
  75. package/mcp-server/lib/logger.cjs +30 -0
  76. package/mcp-server/logger.js +173 -0
  77. package/mcp-server/package.json +2 -2
  78. package/mcp-server/premium-tools.js +2 -2
  79. package/mcp-server/tier-auth.js +245 -35
  80. package/mcp-server/truth-firewall-tools.js +145 -15
  81. package/mcp-server/vibecheck-tools.js +2 -2
  82. package/package.json +2 -3
  83. package/mcp-server/index.old.js +0 -4137
  84. package/mcp-server/package-lock.json +0 -165
@@ -1,73 +1,127 @@
1
1
  /**
2
2
  * vibecheck guard - Unified trust boundary enforcement
3
3
  *
4
- * Combines: validate + claim-verifier + prompt-firewall
5
- *
6
- * Usage:
7
- * vibecheck guard # Run all checks
8
- * vibecheck guard --claims # Verify AI claims against truthpack
9
- * vibecheck guard --prompts # Check for prompt injection
10
- * vibecheck guard --hallucinations # Detect AI hallucination patterns
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * World-Class AI Guardrails
6
+ * ═══════════════════════════════════════════════════════════════════════════════
11
7
  */
12
8
 
13
9
  const path = require("path");
14
10
  const fs = require("fs");
11
+ const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
12
+ const { EXIT } = require("./lib/exit-codes");
13
+ const {
14
+ ansi,
15
+ sym,
16
+ renderMinimalHeader,
17
+ renderSectionHeader,
18
+ renderVerdict,
19
+ renderSuccess,
20
+ renderError,
21
+ renderWarning,
22
+ renderFooter,
23
+ Spinner,
24
+ getTierFromKey,
25
+ } = require("./lib/unified-cli-output");
15
26
 
16
27
  // Import underlying implementations
17
- const { runValidate } = require("./runValidate");
18
- const { runPromptFirewall } = require("./runPromptFirewall");
19
-
20
- // ANSI colors
21
- const c = {
22
- reset: "\x1b[0m",
23
- dim: "\x1b[2m",
24
- bold: "\x1b[1m",
25
- cyan: "\x1b[36m",
26
- green: "\x1b[32m",
27
- yellow: "\x1b[33m",
28
- red: "\x1b[31m",
29
- magenta: "\x1b[35m",
30
- };
28
+ let runValidate, runPromptFirewall;
29
+ try {
30
+ runValidate = require("./runValidate").runValidate;
31
+ } catch {
32
+ runValidate = null;
33
+ }
34
+ try {
35
+ runPromptFirewall = require("./runPromptFirewall").runPromptFirewall;
36
+ } catch {
37
+ runPromptFirewall = null;
38
+ }
31
39
 
32
40
  function printHelp() {
33
41
  console.log(`
34
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
35
- ${c.bold}vibecheck guard${c.reset} - Trust boundary enforcement for AI outputs
36
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
37
-
38
- ${c.green}USAGE${c.reset}
39
- vibecheck guard [options]
40
-
41
- ${c.yellow}OPTIONS${c.reset}
42
- --claims Verify AI claims against truthpack (route_exists, auth_enforced, etc.)
43
- --prompts Check code for prompt injection vulnerabilities
44
- --hallucinations Detect AI hallucination patterns in generated code
45
- --file <path> Check specific file(s)
46
- --json Output JSON for CI integration
47
- --strict Fail on warnings (default: fail on errors only)
48
-
49
- ${c.magenta}EXAMPLES${c.reset}
50
- vibecheck guard # Run all checks
51
- vibecheck guard --claims --file api.ts # Verify claims in specific file
52
- vibecheck guard --prompts # Prompt injection scan
53
- vibecheck guard --json # CI-friendly output
54
-
55
- ${c.dim}This command unifies trust boundary checks for AI-generated code.${c.reset}
42
+ ${ansi.bold}USAGE${ansi.reset}
43
+ ${ansi.cyan}vibecheck guard${ansi.reset} [options]
44
+
45
+ ${ansi.dim}Aliases: ai-guard, firewall, validate${ansi.reset}
46
+
47
+ Validate AI-generated code and prompts. Detects prompt injection attempts,
48
+ verifies claims against your codebase (hallucination checking), and ensures
49
+ AI outputs meet your standards.
50
+
51
+ ${ansi.bold}CHECK MODES${ansi.reset}
52
+ ${ansi.cyan}--claims${ansi.reset} Verify AI claims against truthpack
53
+ ${ansi.cyan}--prompts${ansi.reset} Check code for prompt injection
54
+ ${ansi.cyan}--hallucinations${ansi.reset} Detect AI hallucination patterns
55
+ ${ansi.dim}(default: run all checks)${ansi.reset}
56
+
57
+ ${ansi.bold}OPTIONS${ansi.reset}
58
+ ${ansi.cyan}--file <path>${ansi.reset} Check specific file(s)
59
+ ${ansi.cyan}--strict${ansi.reset} Fail on warnings (default: fail on errors only)
60
+ ${ansi.cyan}--json${ansi.reset} Output as JSON (CI integration)
61
+ ${ansi.cyan}--quiet, -q${ansi.reset} Suppress non-essential output
62
+ ${ansi.cyan}--help, -h${ansi.reset} Show this help
63
+
64
+ ${ansi.bold}EXAMPLES${ansi.reset}
65
+ ${ansi.dim}# Run all guardrail checks${ansi.reset}
66
+ vibecheck guard
67
+
68
+ ${ansi.dim}# Verify AI claims in specific file${ansi.reset}
69
+ vibecheck guard --claims --file api.ts
70
+
71
+ ${ansi.dim}# Prompt injection scan only${ansi.reset}
72
+ vibecheck guard --prompts
73
+
74
+ ${ansi.dim}# CI pipeline (strict, JSON output)${ansi.reset}
75
+ vibecheck guard --strict --json
76
+
77
+ ${ansi.bold}EXIT CODES${ansi.reset}
78
+ 0 All checks passed
79
+ 1 Warnings found (non-blocking)
80
+ 2 Errors found (blocking issues)
81
+
82
+ ${ansi.dim}────────────────────────────────────────────────────────────────────${ansi.reset}
83
+ ${ansi.dim}Documentation: https://docs.vibecheckai.dev/cli/guard${ansi.reset}
56
84
  `);
57
85
  }
58
86
 
59
87
  async function runGuard(args = []) {
88
+ const { flags: globalFlags } = parseGlobalFlags(args);
89
+ const quiet = shouldSuppressOutput(globalFlags);
90
+ const json = isJsonMode(globalFlags) || args.includes("--json");
91
+ const startTime = Date.now();
92
+
60
93
  // Parse arguments
61
- if (args.includes("--help") || args.includes("-h")) {
94
+ if (globalFlags.help || args.includes("--help") || args.includes("-h")) {
62
95
  printHelp();
63
- return 0;
96
+ return EXIT.SUCCESS;
64
97
  }
65
98
 
66
99
  const runClaims = args.includes("--claims") || (!args.includes("--prompts") && !args.includes("--hallucinations"));
67
100
  const runPrompts = args.includes("--prompts") || (!args.includes("--claims") && !args.includes("--hallucinations"));
68
101
  const runHallucinations = args.includes("--hallucinations") || (!args.includes("--claims") && !args.includes("--prompts"));
69
- const jsonOutput = args.includes("--json");
70
102
  const strict = args.includes("--strict");
103
+
104
+ // Validate --file if provided
105
+ const fileIndex = args.indexOf("--file");
106
+ if (fileIndex !== -1) {
107
+ const filePath = args[fileIndex + 1];
108
+ if (!filePath || filePath.startsWith("--")) {
109
+ if (json) {
110
+ console.log(JSON.stringify({ success: false, error: "--file requires a path argument" }));
111
+ } else {
112
+ renderError("--file requires a path argument");
113
+ }
114
+ return EXIT.USER_ERROR;
115
+ }
116
+ if (!fs.existsSync(filePath)) {
117
+ if (json) {
118
+ console.log(JSON.stringify({ success: false, error: `File not found: ${filePath}` }));
119
+ } else {
120
+ renderError(`File not found: ${filePath}`);
121
+ }
122
+ return EXIT.NOT_FOUND;
123
+ }
124
+ }
71
125
 
72
126
  const results = {
73
127
  claims: null,
@@ -78,91 +132,131 @@ async function runGuard(args = []) {
78
132
  warnings: 0,
79
133
  };
80
134
 
81
- console.log(`
82
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
83
- ${c.bold}🛡️ VIBECHECK GUARD${c.reset} - Trust Boundary Enforcement
84
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
85
- `);
135
+ try {
136
+ if (!quiet && !json) {
137
+ renderMinimalHeader("guard", "starter");
138
+ renderSectionHeader("Trust Boundary Checks", sym.shield);
139
+ }
86
140
 
87
- // Run claims verification (validates AI claims against truthpack)
88
- if (runClaims) {
89
- console.log(`${c.dim}▸ Verifying AI claims against truthpack...${c.reset}`);
90
- try {
91
- const validateArgs = args.filter(a => !["--claims", "--prompts", "--hallucinations"].includes(a));
92
- const exitCode = await runValidate(validateArgs);
93
- results.claims = { exitCode, status: exitCode === 0 ? "pass" : "fail" };
94
- if (exitCode !== 0) {
95
- results.errors++;
96
- results.verdict = "FAIL";
141
+ // Run claims verification
142
+ if (runClaims) {
143
+ const spinner = !quiet && !json ? new Spinner("Verifying AI claims against truthpack").start() : null;
144
+
145
+ if (!runValidate) {
146
+ results.claims = { skipped: true, reason: "Validator module not available" };
147
+ spinner?.warn("Claims check skipped: module not available");
148
+ } else {
149
+ try {
150
+ const validateArgs = args.filter(a => !["--claims", "--prompts", "--hallucinations"].includes(a));
151
+ const exitCode = await runValidate(validateArgs);
152
+ results.claims = { exitCode, status: exitCode === 0 ? "pass" : "fail" };
153
+ if (exitCode !== 0) {
154
+ results.errors++;
155
+ results.verdict = "FAIL";
156
+ spinner?.fail("Claim verification failed");
157
+ } else {
158
+ spinner?.succeed("Claims verified");
159
+ }
160
+ } catch (e) {
161
+ results.claims = { error: e.message };
162
+ spinner?.warn(`Claims check failed: ${e.message}`);
163
+ }
97
164
  }
98
- console.log(exitCode === 0
99
- ? ` ${c.green}✓${c.reset} Claims verified`
100
- : ` ${c.red}✗${c.reset} Claim verification failed`);
101
- } catch (e) {
102
- results.claims = { error: e.message };
103
- console.log(` ${c.yellow}⚠${c.reset} Claims check skipped: ${e.message}`);
104
165
  }
105
- }
106
166
 
107
- // Run prompt injection detection
108
- if (runPrompts) {
109
- console.log(`${c.dim}▸ Scanning for prompt injection vulnerabilities...${c.reset}`);
110
- try {
111
- const firewallArgs = args.filter(a => !["--claims", "--prompts", "--hallucinations"].includes(a));
112
- const exitCode = await runPromptFirewall(firewallArgs);
113
- results.prompts = { exitCode, status: exitCode === 0 ? "pass" : "fail" };
114
- if (exitCode !== 0) {
115
- results.warnings++;
116
- if (strict) results.verdict = "FAIL";
167
+ // Run prompt injection detection
168
+ if (runPrompts) {
169
+ const spinner = !quiet && !json ? new Spinner("Scanning for prompt injection vulnerabilities").start() : null;
170
+
171
+ if (!runPromptFirewall) {
172
+ results.prompts = { skipped: true, reason: "Firewall module not available" };
173
+ spinner?.warn("Prompt check skipped: module not available");
174
+ } else {
175
+ try {
176
+ const firewallArgs = args.filter(a => !["--claims", "--prompts", "--hallucinations"].includes(a));
177
+ const exitCode = await runPromptFirewall(firewallArgs);
178
+ results.prompts = { exitCode, status: exitCode === 0 ? "pass" : "fail" };
179
+ if (exitCode !== 0) {
180
+ results.warnings++;
181
+ if (strict) results.verdict = "FAIL";
182
+ spinner?.warn("Prompt injection risks detected");
183
+ } else {
184
+ spinner?.succeed("No prompt injection risks");
185
+ }
186
+ } catch (e) {
187
+ results.prompts = { error: e.message };
188
+ spinner?.warn(`Prompt check failed: ${e.message}`);
189
+ }
117
190
  }
118
- console.log(exitCode === 0
119
- ? ` ${c.green}✓${c.reset} No prompt injection risks`
120
- : ` ${c.yellow}⚠${c.reset} Prompt injection risks detected`);
121
- } catch (e) {
122
- results.prompts = { error: e.message };
123
- console.log(` ${c.yellow}⚠${c.reset} Prompt check skipped: ${e.message}`);
124
191
  }
125
- }
126
192
 
127
- // Run hallucination detection
128
- if (runHallucinations) {
129
- console.log(`${c.dim}▸ Detecting hallucination patterns...${c.reset}`);
130
- // Use validate with hallucination focus
131
- try {
132
- const validateArgs = ["--hallucinations", ...args.filter(a => !["--claims", "--prompts", "--hallucinations"].includes(a))];
133
- const exitCode = await runValidate(validateArgs);
134
- results.hallucinations = { exitCode, status: exitCode === 0 ? "pass" : "fail" };
135
- if (exitCode !== 0) {
136
- results.warnings++;
137
- if (strict) results.verdict = "FAIL";
193
+ // Run hallucination detection
194
+ if (runHallucinations) {
195
+ const spinner = !quiet && !json ? new Spinner("Detecting hallucination patterns").start() : null;
196
+
197
+ if (!runValidate) {
198
+ results.hallucinations = { skipped: true, reason: "Validator module not available" };
199
+ spinner?.warn("Hallucination check skipped: module not available");
200
+ } else {
201
+ try {
202
+ const validateArgs = ["--hallucinations", ...args.filter(a => !["--claims", "--prompts", "--hallucinations"].includes(a))];
203
+ const exitCode = await runValidate(validateArgs);
204
+ results.hallucinations = { exitCode, status: exitCode === 0 ? "pass" : "fail" };
205
+ if (exitCode !== 0) {
206
+ results.warnings++;
207
+ if (strict) results.verdict = "FAIL";
208
+ spinner?.warn("Potential hallucinations detected");
209
+ } else {
210
+ spinner?.succeed("No hallucination patterns");
211
+ }
212
+ } catch (e) {
213
+ results.hallucinations = { error: e.message };
214
+ spinner?.warn(`Hallucination check failed: ${e.message}`);
215
+ }
138
216
  }
139
- console.log(exitCode === 0
140
- ? ` ${c.green}✓${c.reset} No hallucination patterns`
141
- : ` ${c.yellow}⚠${c.reset} Potential hallucinations detected`);
142
- } catch (e) {
143
- results.hallucinations = { error: e.message };
144
- console.log(` ${c.yellow}⚠${c.reset} Hallucination check skipped: ${e.message}`);
145
217
  }
146
- }
147
218
 
148
- // Summary
149
- console.log(`
150
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`);
151
-
152
- if (results.verdict === "PASS") {
153
- console.log(` ${c.green}${c.bold}✓ GUARD PASS${c.reset} - All trust boundaries intact`);
154
- } else {
155
- console.log(` ${c.red}${c.bold}✗ GUARD FAIL${c.reset} - Trust boundary violations detected`);
156
- }
219
+ // Summary
220
+ const duration = Date.now() - startTime;
221
+
222
+ if (!quiet && !json) {
223
+ renderVerdict(results.verdict === "PASS" ? "PASS" : "FAIL", {
224
+ warnings: results.warnings,
225
+ critical: results.errors,
226
+ duration,
227
+ });
228
+
229
+ renderFooter({
230
+ nextSteps: results.verdict === "PASS" ? [
231
+ { cmd: "vibecheck scan", desc: "run full code analysis" },
232
+ { cmd: "vibecheck ship", desc: "get ship verdict" },
233
+ ] : [
234
+ { cmd: "vibecheck fix --plan-only", desc: "view fix recommendations" },
235
+ ],
236
+ docsUrl: "https://docs.vibecheckai.dev/cli/guard",
237
+ });
238
+ }
157
239
 
158
- console.log(`${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
159
- `);
240
+ if (json) {
241
+ console.log(JSON.stringify({ ...results, duration }, null, 2));
242
+ }
160
243
 
161
- if (jsonOutput) {
162
- console.log(JSON.stringify(results, null, 2));
244
+ // Return appropriate exit code
245
+ if (results.verdict === "PASS") {
246
+ return EXIT.SUCCESS;
247
+ } else if (results.errors > 0) {
248
+ return EXIT.BLOCKING;
249
+ } else {
250
+ return EXIT.WARNINGS;
251
+ }
252
+ } catch (error) {
253
+ if (json) {
254
+ console.log(JSON.stringify({ success: false, error: error.message }));
255
+ } else {
256
+ renderError(`Guard check failed: ${error.message}`);
257
+ }
258
+ return EXIT.INTERNAL_ERROR;
163
259
  }
164
-
165
- return results.verdict === "PASS" ? 0 : (results.errors > 0 ? 2 : 1);
166
260
  }
167
261
 
168
262
  module.exports = { runGuard };
@@ -12,6 +12,7 @@
12
12
  const fs = require("fs");
13
13
  const path = require("path");
14
14
  const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
15
+ const { EXIT } = require("./lib/exit-codes");
15
16
 
16
17
  // Use enhanced wizard if available
17
18
  let InitWizard;
@@ -1747,7 +1748,7 @@ async function runInit(args) {
1747
1748
  console.log(` ${colors.info}${ICONS.info}${c.reset} Run ${c.cyan}vibecheck init --repair${c.reset} to fix partial state`);
1748
1749
  }
1749
1750
  if (!opts.dryRun) {
1750
- return 1; // Exit on error unless dry-run
1751
+ return EXIT.INTERNAL_ERROR; // Exit on error unless dry-run
1751
1752
  }
1752
1753
  }
1753
1754
  }
@@ -1765,7 +1766,7 @@ async function runInit(args) {
1765
1766
  }
1766
1767
  }
1767
1768
  if (!opts.dryRun) {
1768
- return 1;
1769
+ return EXIT.INTERNAL_ERROR;
1769
1770
  }
1770
1771
  }
1771
1772
  }
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const path = require("path");
9
+ const fs = require("fs");
9
10
  const http = require("http");
10
11
  const { URL } = require("url");
11
12
 
@@ -14,6 +15,8 @@ const { buildTruthpack, writeTruthpack } = require("./lib/truth");
14
15
  const { shipCore } = require("./runShip");
15
16
  const { generateRunId } = require("./lib/cli-output");
16
17
  const { enforceLimit, enforceFeature, trackUsage } = require("./lib/entitlements");
18
+ const { EXIT } = require("./lib/exit-codes");
19
+ const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
17
20
 
18
21
  // MCP Server class
19
22
  class VibecheckMCPServer {
@@ -642,8 +645,14 @@ class VibecheckMCPServer {
642
645
  });
643
646
 
644
647
  this.server.on("error", err => {
645
- console.error("MCP Server failed to start:", err);
646
- process.exit(1);
648
+ console.error("MCP Server failed to start:", err.message);
649
+ if (err.code === "EADDRINUSE") {
650
+ console.error(` Port ${this.port} is already in use. Try a different port with --port`);
651
+ } else if (err.code === "EACCES") {
652
+ console.error(` Permission denied for port ${this.port}. Use a port > 1024`);
653
+ }
654
+ // Emit error event for caller to handle
655
+ this.emit?.("error", err);
647
656
  });
648
657
  }
649
658
 
@@ -658,54 +667,77 @@ class VibecheckMCPServer {
658
667
  // CLI handler
659
668
  async function runMcp(args) {
660
669
  const opts = parseArgs(args);
670
+ const { flags: globalFlags } = parseGlobalFlags(args);
671
+ const quiet = shouldSuppressOutput(globalFlags);
672
+ const json = isJsonMode(globalFlags);
661
673
 
662
674
  // Check if we're in free tier and only showing help/config
663
675
  const isFreeTier = process.env.VIBECHECK_TIER === "free" || !process.env.VIBECHECK_API_KEY;
664
676
  const isHelpOrConfig = opts.help || opts.printConfig || opts.status || opts.test;
665
677
 
666
678
  if (isFreeTier && !isHelpOrConfig) {
667
- // This will be handled by entitlements system
668
- // But we can show a helpful message
669
- console.log("\n🔌 MCP Server requires STARTER plan or higher");
670
- console.log("Use --help to see available options or");
671
- console.log("Upgrade: https://vibecheckai.dev/pricing\n");
672
- return 3; // EXIT_FEATURE_NOT_ALLOWED
679
+ if (json) {
680
+ console.log(JSON.stringify({ success: false, error: "MCP Server requires STARTER plan or higher" }));
681
+ } else if (!quiet) {
682
+ console.log("\n🔌 MCP Server requires STARTER plan or higher");
683
+ console.log("Use --help to see available options or");
684
+ console.log("Upgrade: https://vibecheckai.dev/pricing\n");
685
+ }
686
+ return EXIT.TIER_REQUIRED;
673
687
  }
674
688
 
675
689
  if (opts.help) {
676
690
  printHelp();
677
- return 0;
691
+ return EXIT.SUCCESS;
678
692
  }
679
693
 
680
694
  if (opts.printConfig) {
681
695
  printClientConfig(opts);
682
- return 0;
696
+ return EXIT.SUCCESS;
683
697
  }
684
698
 
685
699
  if (opts.status) {
686
700
  // Check server status
687
701
  try {
688
- const response = await fetch(`http://${opts.host}:${opts.port}/status`);
702
+ const host = opts.host || "127.0.0.1";
703
+ const port = opts.port || 3000;
704
+ const response = await fetch(`http://${host}:${port}/status`);
689
705
  const status = await response.json();
690
706
  console.log(JSON.stringify(status, null, 2));
691
- return 0;
707
+ return EXIT.SUCCESS;
692
708
  } catch (err) {
693
- console.error("Server not running or not reachable");
694
- return 1;
709
+ if (json) {
710
+ console.log(JSON.stringify({ success: false, error: "Server not running or not reachable" }));
711
+ } else {
712
+ console.error("Server not running or not reachable");
713
+ console.error(" Start the server with: vibecheck mcp");
714
+ }
715
+ return EXIT.NETWORK_ERROR;
695
716
  }
696
717
  }
697
718
 
698
719
  if (opts.test) {
699
720
  // Test connection
700
721
  try {
701
- const response = await fetch(`http://${opts.host}:${opts.port}/tools`);
722
+ const host = opts.host || "127.0.0.1";
723
+ const port = opts.port || 3000;
724
+ const response = await fetch(`http://${host}:${port}/tools`);
702
725
  const tools = await response.json();
703
- console.log("✅ Connection successful");
704
- console.log(`📋 Available tools: ${tools.tools.length}`);
705
- return 0;
726
+ if (json) {
727
+ console.log(JSON.stringify({ success: true, toolCount: tools.tools?.length || 0 }));
728
+ } else {
729
+ console.log("✅ Connection successful");
730
+ console.log(`📋 Available tools: ${tools.tools?.length || 0}`);
731
+ }
732
+ return EXIT.SUCCESS;
706
733
  } catch (err) {
707
- console.error("❌ Connection failed:", err.message);
708
- return 1;
734
+ if (json) {
735
+ console.log(JSON.stringify({ success: false, error: err.message }));
736
+ } else {
737
+ console.error("❌ Connection failed:", err.message);
738
+ console.error(" Ensure the MCP server is running");
739
+ }
740
+ return EXIT.NETWORK_ERROR;
709
741
  }
710
742
  }
711
743
 
@@ -713,42 +745,88 @@ async function runMcp(args) {
713
745
  let config = {};
714
746
  if (opts.config) {
715
747
  const configPath = path.resolve(opts.config);
716
- if (require("fs").existsSync(configPath)) {
717
- config = JSON.parse(require("fs").readFileSync(configPath, "utf8"));
718
- } else {
719
- console.error(`Config file not found: ${configPath}`);
720
- return 1;
748
+ if (!fs.existsSync(configPath)) {
749
+ if (json) {
750
+ console.log(JSON.stringify({ success: false, error: `Config file not found: ${configPath}` }));
751
+ } else {
752
+ console.error(`Config file not found: ${configPath}`);
753
+ }
754
+ return EXIT.NOT_FOUND;
755
+ }
756
+ try {
757
+ config = JSON.parse(fs.readFileSync(configPath, "utf8"));
758
+ } catch (parseErr) {
759
+ if (json) {
760
+ console.log(JSON.stringify({ success: false, error: `Invalid JSON in config: ${parseErr.message}` }));
761
+ } else {
762
+ console.error(`Invalid JSON in config file: ${parseErr.message}`);
763
+ }
764
+ return EXIT.USER_ERROR;
721
765
  }
722
766
  }
723
767
 
724
- // Create and start server
725
- const server = new VibecheckMCPServer({
726
- host: opts.host || config.server?.host || "127.0.0.1",
727
- port: opts.port || config.server?.port || 3000,
728
- allowRemote: opts.allowRemote || config.server?.allowRemote || false,
729
- requireApiKey: opts.requireApiKey !== false,
730
- apiKey: opts.apiKey || config.auth?.apiKey,
731
- logLevel: opts.logLevel || config.logging?.level || "info",
732
- auditLog: opts.auditLog || config.logging?.auditFile
733
- });
734
-
735
- server.start();
736
-
737
- // Handle shutdown
738
- process.on("SIGINT", () => {
739
- console.log("\nShutting down MCP server...");
740
- server.stop();
741
- process.exit(0);
742
- });
743
-
744
- process.on("SIGTERM", () => {
745
- console.log("\nShutting down MCP server...");
746
- server.stop();
747
- process.exit(0);
748
- });
768
+ // Validate port
769
+ const port = opts.port || config.server?.port || 3000;
770
+ if (isNaN(port) || port < 1 || port > 65535) {
771
+ if (json) {
772
+ console.log(JSON.stringify({ success: false, error: `Invalid port: ${port}` }));
773
+ } else {
774
+ console.error(`Invalid port: ${port}`);
775
+ console.error(" Port must be a number between 1 and 65535");
776
+ }
777
+ return EXIT.USER_ERROR;
778
+ }
749
779
 
750
- // Keep process alive
751
- return new Promise(() => {});
780
+ try {
781
+ // Create and start server
782
+ const server = new VibecheckMCPServer({
783
+ host: opts.host || config.server?.host || "127.0.0.1",
784
+ port: port,
785
+ allowRemote: opts.allowRemote || config.server?.allowRemote || false,
786
+ requireApiKey: opts.requireApiKey !== false,
787
+ apiKey: opts.apiKey || config.auth?.apiKey,
788
+ logLevel: opts.logLevel || config.logging?.level || "info",
789
+ auditLog: opts.auditLog || config.logging?.auditFile
790
+ });
791
+
792
+ // Track if server started successfully
793
+ let serverStarted = false;
794
+ let serverError = null;
795
+
796
+ // Listen for server errors before starting
797
+ server.server?.on?.("error", (err) => {
798
+ serverError = err;
799
+ });
800
+
801
+ server.start();
802
+ serverStarted = true;
803
+
804
+ // Handle shutdown gracefully
805
+ const cleanup = () => {
806
+ if (!quiet) console.log("\nShutting down MCP server...");
807
+ server.stop();
808
+ };
809
+
810
+ process.on("SIGINT", () => {
811
+ cleanup();
812
+ process.exit(EXIT.SUCCESS);
813
+ });
814
+
815
+ process.on("SIGTERM", () => {
816
+ cleanup();
817
+ process.exit(EXIT.SUCCESS);
818
+ });
819
+
820
+ // Keep process alive
821
+ return new Promise(() => {});
822
+ } catch (error) {
823
+ if (json) {
824
+ console.log(JSON.stringify({ success: false, error: error.message }));
825
+ } else {
826
+ console.error(`Failed to start MCP server: ${error.message}`);
827
+ }
828
+ return EXIT.INTERNAL_ERROR;
829
+ }
752
830
  }
753
831
 
754
832
  // Argument parsing