@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,3 +1,11 @@
1
+ /**
2
+ * vibecheck auth commands - Login, Logout, Whoami
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * World-Class Authentication Experience
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ */
8
+
1
9
  const readline = require("readline");
2
10
  const {
3
11
  saveApiKey,
@@ -5,17 +13,22 @@ const {
5
13
  getApiKey,
6
14
  getEntitlements,
7
15
  } = require("./lib/auth");
8
-
9
- const c = {
10
- reset: "\x1b[0m",
11
- bold: "\x1b[1m",
12
- dim: "\x1b[2m",
13
- red: "\x1b[31m",
14
- green: "\x1b[32m",
15
- yellow: "\x1b[33m",
16
- blue: "\x1b[34m",
17
- cyan: "\x1b[36m",
18
- };
16
+ const { EXIT } = require("./lib/exit-codes");
17
+ const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
18
+ const {
19
+ ansi,
20
+ sym,
21
+ renderMinimalHeader,
22
+ renderSectionHeader,
23
+ renderSuccess,
24
+ renderError,
25
+ renderWarning,
26
+ renderKeyValue,
27
+ renderFooter,
28
+ Spinner,
29
+ getTierBadge,
30
+ TIER,
31
+ } = require("./lib/unified-cli-output");
19
32
 
20
33
  async function prompt(question) {
21
34
  const rl = readline.createInterface({
@@ -31,127 +44,343 @@ async function prompt(question) {
31
44
  });
32
45
  }
33
46
 
47
+ function isValidKeyFormat(key) {
48
+ if (!key || typeof key !== "string") return false;
49
+ return key.length >= 20 && /^[a-zA-Z0-9_-]+$/.test(key);
50
+ }
51
+
34
52
  async function runLogin(args) {
35
- if (args.includes("--help") || args.includes("-h")) {
53
+ const { flags } = parseGlobalFlags(args);
54
+ const quiet = shouldSuppressOutput(flags);
55
+ const json = isJsonMode(flags);
56
+
57
+ if (flags.help) {
36
58
  console.log(`
37
- ${c.bold}vibecheck login${c.reset} - Authenticate with API key
59
+ ${ansi.bold}USAGE${ansi.reset}
60
+ ${ansi.cyan}vibecheck login${ansi.reset} [options]
38
61
 
39
- ${c.bold}Usage:${c.reset}
40
- vibecheck login
62
+ ${ansi.dim}Aliases: auth, signin${ansi.reset}
41
63
 
42
- ${c.bold}Description:${c.reset}
43
- Authenticate with your Vibecheck API key.
44
- Get your API key from https://vibecheckai.dev/settings/keys
64
+ Authenticate with your Vibecheck API key to unlock paid features
65
+ and sync with the dashboard.
45
66
 
46
- ${c.bold}Options:${c.reset}
47
- ${c.cyan}--help, -h${c.reset} Show this help
67
+ ${ansi.bold}OPTIONS${ansi.reset}
68
+ ${ansi.cyan}--key <key>${ansi.reset} Provide API key directly (non-interactive)
69
+ ${ansi.cyan}--json${ansi.reset} Output result as JSON
70
+ ${ansi.cyan}--quiet, -q${ansi.reset} Suppress non-essential output
71
+ ${ansi.cyan}--help, -h${ansi.reset} Show this help
48
72
 
49
- ${c.bold}Examples:${c.reset}
73
+ ${ansi.bold}EXAMPLES${ansi.reset}
74
+ ${ansi.dim}# Interactive login${ansi.reset}
50
75
  vibecheck login
76
+
77
+ ${ansi.dim}# Non-interactive (CI/scripts)${ansi.reset}
78
+ vibecheck login --key YOUR_API_KEY
79
+
80
+ ${ansi.dim}# Using environment variable${ansi.reset}
81
+ VIBECHECK_API_KEY=xxx vibecheck whoami
82
+
83
+ ${ansi.bold}GET YOUR API KEY${ansi.reset}
84
+ https://vibecheckai.dev/settings/keys
85
+
86
+ ${ansi.dim}────────────────────────────────────────────────────────────────────${ansi.reset}
87
+ ${ansi.dim}Documentation: https://docs.vibecheckai.dev/authentication${ansi.reset}
51
88
  `);
52
- return 0;
89
+ return EXIT.SUCCESS;
53
90
  }
54
-
55
- console.log("\n 🔐 vibecheck LOGIN\n");
56
91
 
57
- const existing = getApiKey();
58
- if (existing.key) {
59
- console.log(` Already logged in (source: ${existing.source}).`);
60
- const answer = await prompt(" Do you want to overwrite? (y/N) ");
61
- if (answer.toLowerCase() !== "y") {
62
- console.log(" Cancelled.");
63
- return 0;
92
+ try {
93
+ let key = null;
94
+ const keyIndex = args.indexOf("--key");
95
+ if (keyIndex !== -1 && args[keyIndex + 1]) {
96
+ key = args[keyIndex + 1];
97
+ }
98
+
99
+ if (!quiet && !json) {
100
+ renderMinimalHeader("login", "free");
64
101
  }
65
- }
66
102
 
67
- console.log(
68
- " Paste your API key from https://vibecheckai.dev/settings/keys",
69
- );
70
- const key = await prompt(" API Key: ");
103
+ const existing = getApiKey();
104
+ if (existing.key && !key) {
105
+ if (!quiet && !json) {
106
+ renderWarning(`Already logged in (source: ${existing.source})`);
107
+ }
108
+
109
+ if (process.env.CI || !process.stdin.isTTY) {
110
+ if (json) {
111
+ console.log(JSON.stringify({ success: true, message: "Already logged in", source: existing.source }));
112
+ }
113
+ return EXIT.SUCCESS;
114
+ }
115
+
116
+ const answer = await prompt(` Overwrite existing credentials? (y/N) `);
117
+ if (answer.toLowerCase() !== "y") {
118
+ if (!quiet && !json) console.log(` ${ansi.dim}Cancelled.${ansi.reset}\n`);
119
+ if (json) console.log(JSON.stringify({ success: false, message: "Cancelled by user" }));
120
+ return EXIT.SUCCESS;
121
+ }
122
+ }
71
123
 
72
- if (!key) {
73
- console.error(" ❌ No key provided.");
74
- return 1;
75
- }
124
+ if (!key) {
125
+ if (process.env.CI || !process.stdin.isTTY) {
126
+ const errorMsg = "No API key provided. Use --key flag or set VIBECHECK_API_KEY environment variable.";
127
+ if (json) {
128
+ console.log(JSON.stringify({ success: false, error: errorMsg }));
129
+ } else {
130
+ renderError(errorMsg);
131
+ }
132
+ return EXIT.USER_ERROR;
133
+ }
134
+
135
+ if (!quiet && !json) {
136
+ console.log(` ${ansi.dim}Get your API key at: https://vibecheckai.dev/settings/keys${ansi.reset}\n`);
137
+ }
138
+ key = await prompt(` ${sym.key} API Key: `);
139
+ }
76
140
 
77
- // Validate key by fetching entitlements
78
- console.log(" Verifying...");
79
- const entitlements = await getEntitlements(key);
141
+ if (!key) {
142
+ if (json) {
143
+ console.log(JSON.stringify({ success: false, error: "No API key provided" }));
144
+ } else {
145
+ renderError("No API key provided");
146
+ }
147
+ return EXIT.USER_ERROR;
148
+ }
80
149
 
81
- if (
82
- !entitlements ||
83
- (entitlements.plan === "free" && !key.startsWith("gr_"))
84
- ) {
85
- // If mocking, we might accept anything, but let's pretend valid keys start with gr_
86
- // For now, since it's a mock, we just check if we got entitlements back.
87
- }
150
+ if (!isValidKeyFormat(key)) {
151
+ if (json) {
152
+ console.log(JSON.stringify({ success: false, error: "Invalid API key format" }));
153
+ } else {
154
+ renderError("Invalid API key format");
155
+ console.log(` ${ansi.dim}API keys should be at least 20 characters.${ansi.reset}`);
156
+ }
157
+ return EXIT.USER_ERROR;
158
+ }
159
+
160
+ const spinner = !quiet && !json ? new Spinner("Verifying API key").start() : null;
161
+
162
+ let entitlements;
163
+ try {
164
+ entitlements = await getEntitlements(key);
165
+ } catch (apiError) {
166
+ spinner?.fail(`Failed to verify: ${apiError.message}`);
167
+ if (json) {
168
+ console.log(JSON.stringify({ success: false, error: apiError.message }));
169
+ }
170
+ return EXIT.NETWORK_ERROR;
171
+ }
172
+
173
+ if (!entitlements) {
174
+ spinner?.fail("Invalid API key or server unreachable");
175
+ if (json) {
176
+ console.log(JSON.stringify({ success: false, error: "Invalid API key" }));
177
+ }
178
+ return EXIT.AUTH_FAILED;
179
+ }
88
180
 
89
- saveApiKey(key);
90
- console.log(
91
- ` ✅ Successfully logged in as ${entitlements?.user?.name || "User"}`,
92
- );
93
- console.log(` Plan: ${entitlements?.plan || "Free"}`);
181
+ saveApiKey(key);
182
+
183
+ const result = {
184
+ success: true,
185
+ user: entitlements?.user?.name || "User",
186
+ plan: entitlements?.plan || "free",
187
+ };
188
+
189
+ if (json) {
190
+ console.log(JSON.stringify(result));
191
+ } else if (!quiet) {
192
+ spinner?.succeed(`Logged in as ${ansi.bold}${result.user}${ansi.reset}`);
193
+ console.log(` ${ansi.dim}Plan:${ansi.reset} ${getTierBadge(result.plan)}\n`);
194
+ }
94
195
 
95
- return 0;
196
+ return EXIT.SUCCESS;
197
+ } catch (error) {
198
+ if (json) {
199
+ console.log(JSON.stringify({ success: false, error: error.message }));
200
+ } else {
201
+ renderError(`Login failed: ${error.message}`);
202
+ }
203
+ return EXIT.INTERNAL_ERROR;
204
+ }
96
205
  }
97
206
 
98
207
  async function runLogout(args) {
99
- console.log("\n 🔓 vibecheck LOGOUT\n");
100
- deleteApiKey();
101
- console.log(" ✅ API key removed from local config.");
102
- return 0;
208
+ const { flags } = parseGlobalFlags(args);
209
+ const quiet = shouldSuppressOutput(flags);
210
+ const json = isJsonMode(flags);
211
+
212
+ if (flags.help) {
213
+ console.log(`
214
+ ${ansi.bold}USAGE${ansi.reset}
215
+ ${ansi.cyan}vibecheck logout${ansi.reset}
216
+
217
+ ${ansi.dim}Aliases: signout${ansi.reset}
218
+
219
+ Remove stored API credentials from local config.
220
+
221
+ ${ansi.bold}OPTIONS${ansi.reset}
222
+ ${ansi.cyan}--json${ansi.reset} Output result as JSON
223
+ ${ansi.cyan}--quiet, -q${ansi.reset} Suppress non-essential output
224
+ ${ansi.cyan}--help, -h${ansi.reset} Show this help
225
+ `);
226
+ return EXIT.SUCCESS;
227
+ }
228
+
229
+ try {
230
+ if (!quiet && !json) {
231
+ renderMinimalHeader("logout", "free");
232
+ }
233
+
234
+ deleteApiKey();
235
+
236
+ if (json) {
237
+ console.log(JSON.stringify({ success: true, message: "Logged out" }));
238
+ } else if (!quiet) {
239
+ renderSuccess("API key removed from local config");
240
+ console.log();
241
+ }
242
+
243
+ return EXIT.SUCCESS;
244
+ } catch (error) {
245
+ if (json) {
246
+ console.log(JSON.stringify({ success: false, error: error.message }));
247
+ } else {
248
+ renderError(`Logout failed: ${error.message}`);
249
+ }
250
+ return EXIT.INTERNAL_ERROR;
251
+ }
103
252
  }
104
253
 
105
254
  async function runWhoami(args) {
106
- if (args.includes("--help") || args.includes("-h")) {
255
+ const { flags } = parseGlobalFlags(args);
256
+ const quiet = shouldSuppressOutput(flags);
257
+ const json = isJsonMode(flags);
258
+
259
+ if (flags.help) {
107
260
  console.log(`
108
- ${c.bold}vibecheck whoami${c.reset} - Show current user and plan
261
+ ${ansi.bold}USAGE${ansi.reset}
262
+ ${ansi.cyan}vibecheck whoami${ansi.reset}
109
263
 
110
- ${c.bold}Usage:${c.reset}
111
- vibecheck whoami
264
+ ${ansi.dim}Aliases: me, user${ansi.reset}
112
265
 
113
- ${c.bold}Description:${c.reset}
114
- Display information about the currently authenticated user,
115
- including plan, limits, and scopes.
266
+ Display information about the currently authenticated user,
267
+ including plan, limits, and available scopes.
116
268
 
117
- ${c.bold}Options:${c.reset}
118
- ${c.cyan}--help, -h${c.reset} Show this help
269
+ ${ansi.bold}OPTIONS${ansi.reset}
270
+ ${ansi.cyan}--json${ansi.reset} Output result as JSON
271
+ ${ansi.cyan}--quiet, -q${ansi.reset} Suppress non-essential output
272
+ ${ansi.cyan}--help, -h${ansi.reset} Show this help
119
273
 
120
- ${c.bold}Examples:${c.reset}
274
+ ${ansi.bold}EXAMPLES${ansi.reset}
275
+ ${ansi.dim}# Check current user${ansi.reset}
121
276
  vibecheck whoami
277
+
278
+ ${ansi.dim}# Get JSON for scripts${ansi.reset}
279
+ vibecheck whoami --json
122
280
  `);
123
- return 0;
281
+ return EXIT.SUCCESS;
124
282
  }
125
-
126
- console.log("\n 👤 vibecheck WHOAMI\n");
127
283
 
128
- const { key, source } = getApiKey();
284
+ try {
285
+ if (!quiet && !json) {
286
+ renderMinimalHeader("whoami", "free");
287
+ }
129
288
 
130
- if (!key) {
131
- console.log(" Not logged in.");
132
- console.log(' Run "vibecheck login" or set VIBECHECK_API_KEY.');
133
- return 1;
134
- }
289
+ const { key, source } = getApiKey();
135
290
 
136
- console.log(
137
- ` Source: ${source === "env" ? "Environment Variable" : "Local Config"}`,
138
- );
291
+ if (!key) {
292
+ const result = { authenticated: false, message: "Not logged in" };
293
+
294
+ if (json) {
295
+ console.log(JSON.stringify(result));
296
+ } else {
297
+ renderWarning("Not logged in");
298
+ console.log(` ${ansi.dim}Run "vibecheck login" or set VIBECHECK_API_KEY.${ansi.reset}\n`);
299
+ }
300
+ return EXIT.AUTH_REQUIRED;
301
+ }
139
302
 
140
- const entitlements = await getEntitlements(key);
141
- if (!entitlements) {
142
- console.log(" ⚠️ Invalid API Key or server unreachable.");
143
- return 1;
144
- }
303
+ const spinner = !quiet && !json ? new Spinner("Fetching user info").start() : null;
145
304
 
146
- console.log(` User: ${entitlements.user.name} (${entitlements.user.id})`);
147
- console.log(` Plan: ${entitlements.plan.toUpperCase()}`);
148
- console.log(` Limits: ${entitlements.limits.runsPerMonth} runs/month`);
149
- console.log("");
150
- console.log(" Scopes:");
151
- entitlements.scopes.forEach((s) => console.log(` - ${s}`));
152
- console.log("");
305
+ let entitlements;
306
+ try {
307
+ entitlements = await getEntitlements(key);
308
+ } catch (apiError) {
309
+ spinner?.fail(`Failed to fetch: ${apiError.message}`);
310
+ if (json) {
311
+ console.log(JSON.stringify({ authenticated: true, error: apiError.message }));
312
+ }
313
+ return EXIT.NETWORK_ERROR;
314
+ }
315
+
316
+ if (!entitlements) {
317
+ spinner?.fail("Invalid API key or server unreachable");
318
+ if (json) {
319
+ console.log(JSON.stringify({ authenticated: false, error: "Invalid API key" }));
320
+ } else {
321
+ console.log(` ${ansi.dim}Run "vibecheck login" to re-authenticate.${ansi.reset}\n`);
322
+ }
323
+ return EXIT.AUTH_FAILED;
324
+ }
153
325
 
154
- return 0;
326
+ spinner?.stop(null, null, null); // Clear spinner without message
327
+
328
+ const result = {
329
+ authenticated: true,
330
+ source: source === "env" ? "environment" : "config",
331
+ user: {
332
+ name: entitlements.user?.name,
333
+ id: entitlements.user?.id,
334
+ },
335
+ plan: entitlements.plan,
336
+ limits: entitlements.limits,
337
+ scopes: entitlements.scopes,
338
+ };
339
+
340
+ if (json) {
341
+ console.log(JSON.stringify(result, null, 2));
342
+ } else if (!quiet) {
343
+ renderSectionHeader("Account", sym.key);
344
+
345
+ renderKeyValue([
346
+ { label: "User", value: `${ansi.bold}${result.user.name}${ansi.reset} ${ansi.dim}(${result.user.id})${ansi.reset}` },
347
+ { label: "Plan", value: getTierBadge(result.plan) },
348
+ { label: "Source", value: source === "env" ? "Environment Variable" : "Local Config" },
349
+ { label: "Limits", value: `${result.limits?.runsPerMonth || "unlimited"} runs/month` },
350
+ ]);
351
+
352
+ if (result.scopes && result.scopes.length > 0) {
353
+ console.log();
354
+ console.log(` ${ansi.dim}Scopes:${ansi.reset}`);
355
+ result.scopes.forEach(s => console.log(` ${ansi.gray}${sym.bullet}${ansi.reset} ${s}`));
356
+ }
357
+
358
+ // Tier-specific upsell
359
+ if (result.plan === "free") {
360
+ renderFooter({
361
+ nextSteps: [
362
+ { cmd: "vibecheck scan", desc: "analyze your codebase" },
363
+ ],
364
+ showUpsell: true,
365
+ });
366
+ } else if (result.plan === "starter") {
367
+ console.log();
368
+ console.log(` ${ansi.dim}${sym.star}${ansi.reset} ${ansi.magenta}PRO${ansi.reset}${ansi.dim}: proof loops + badge generation + priority support${ansi.reset}`);
369
+ console.log(` ${ansi.dim} Upgrade ${sym.arrow} https://vibecheckai.dev${ansi.reset}\n`);
370
+ } else {
371
+ console.log();
372
+ }
373
+ }
374
+
375
+ return EXIT.SUCCESS;
376
+ } catch (error) {
377
+ if (json) {
378
+ console.log(JSON.stringify({ authenticated: false, error: error.message }));
379
+ } else {
380
+ renderError(`Whoami failed: ${error.message}`);
381
+ }
382
+ return EXIT.INTERNAL_ERROR;
383
+ }
155
384
  }
156
385
 
157
386
  module.exports = { runLogin, runLogout, runWhoami };
@@ -13,7 +13,8 @@
13
13
  const fs = require("fs");
14
14
  const path = require("path");
15
15
  const entitlements = require("./lib/entitlements-v2");
16
- const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
16
+ const { parseGlobalFlags, shouldShowBanner, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
17
+ const { EXIT } = require("./lib/exit-codes");
17
18
 
18
19
  // ═══════════════════════════════════════════════════════════════════════════════
19
20
  // TERMINAL STYLING
@@ -284,13 +285,25 @@ ${c.bold}TIER${c.reset}
284
285
 
285
286
  async function runCheckpoint(args) {
286
287
  const opts = parseArgs(args);
288
+ const quiet = shouldSuppressOutput(opts);
289
+ const json = isJsonMode(opts) || opts.json;
287
290
 
288
291
  if (opts.help) {
289
292
  printHelp(shouldShowBanner(opts));
290
- return 0;
293
+ return EXIT.SUCCESS;
291
294
  }
292
295
 
293
296
  const projectPath = path.resolve(opts.path);
297
+
298
+ // Validate project path exists
299
+ if (!fs.existsSync(projectPath)) {
300
+ if (json) {
301
+ console.log(JSON.stringify({ success: false, error: `Project path does not exist: ${projectPath}` }));
302
+ } else {
303
+ console.error(`${c.red}${icons.cross}${c.reset} Project path does not exist: ${projectPath}`);
304
+ }
305
+ return EXIT.NOT_FOUND;
306
+ }
294
307
  const vibecheckDir = path.join(projectPath, '.vibecheck');
295
308
  const resultsDir = path.join(vibecheckDir, 'results');
296
309
  const checkpointsDir = path.join(vibecheckDir, 'checkpoints');
@@ -316,17 +329,17 @@ async function runCheckpoint(args) {
316
329
  }
317
330
 
318
331
  if (!baseline) {
319
- console.log(`\n${c.yellow}${icons.warning}${c.reset} No baseline found.`);
320
- console.log(`${c.dim}Run 'vibecheck scan' first to create a baseline.${c.reset}`);
321
- console.log(`${c.dim}Or specify --baseline <file> to use a specific file.${c.reset}\n`);
322
-
323
- if (!opts.json) {
332
+ if (json) {
333
+ console.log(JSON.stringify({ success: false, error: "No baseline found" }));
334
+ } else if (!quiet) {
335
+ console.log(`\n${c.yellow}${icons.warning}${c.reset} No baseline found.`);
336
+ console.log(`${c.dim}Run 'vibecheck scan' first to create a baseline.${c.reset}`);
337
+ console.log(`${c.dim}Or specify --baseline <file> to use a specific file.${c.reset}\n`);
324
338
  console.log(`${c.bold}TIP:${c.reset} Create a baseline with:`);
325
339
  console.log(` ${c.cyan}vibecheck scan${c.reset}`);
326
340
  console.log(` ${c.cyan}cp .vibecheck/results/latest.json .vibecheck/results/baseline.json${c.reset}\n`);
327
341
  }
328
-
329
- return 1;
342
+ return EXIT.NOT_FOUND;
330
343
  }
331
344
 
332
345
  // Load current
@@ -340,9 +353,13 @@ async function runCheckpoint(args) {
340
353
  }
341
354
 
342
355
  if (!current) {
343
- console.log(`\n${c.yellow}${icons.warning}${c.reset} No current results found.`);
344
- console.log(`${c.dim}Run 'vibecheck scan' to generate current results.${c.reset}\n`);
345
- return 1;
356
+ if (json) {
357
+ console.log(JSON.stringify({ success: false, error: "No current results found" }));
358
+ } else if (!quiet) {
359
+ console.log(`\n${c.yellow}${icons.warning}${c.reset} No current results found.`);
360
+ console.log(`${c.dim}Run 'vibecheck scan' to generate current results.${c.reset}\n`);
361
+ }
362
+ return EXIT.NOT_FOUND;
346
363
  }
347
364
 
348
365
  // Extract findings arrays
@@ -408,18 +425,17 @@ async function runCheckpoint(args) {
408
425
  if (comparison.regressions.length > 3) {
409
426
  console.log(` ${c.dim}... and ${comparison.regressions.length - 3} more${c.reset}`);
410
427
  }
411
- return 1;
428
+ return EXIT.BLOCKING;
412
429
  } else {
413
430
  console.log(`${c.green}${icons.check}${c.reset} Checkpoint passed (${checkpoint.summary.fixed} fixed, ${checkpoint.summary.remaining} remaining)`);
414
- return 0;
431
+ return EXIT.SUCCESS;
415
432
  }
416
433
  }
417
434
 
418
435
  // JSON output
419
436
  if (opts.json) {
420
437
  console.log(JSON.stringify(checkpoint, null, 2));
421
- const exitCode = opts.gate && checkpoint.summary.regressions > 0 ? 1 : 0;
422
- return exitCode;
438
+ return opts.gate && checkpoint.summary.regressions > 0 ? EXIT.BLOCKING : EXIT.SUCCESS;
423
439
  }
424
440
 
425
441
  // Human-readable output
@@ -531,14 +547,16 @@ ${c.bold}╔══════════════════════
531
547
  console.log();
532
548
 
533
549
  // Return code:
534
- // - With --gate: 1 if regressions, 0 otherwise
535
- // - Without --gate: always 0 (informational only)
550
+ // - With --gate: BLOCKING (2) if regressions, SUCCESS otherwise
551
+ // - Without --gate: always SUCCESS (informational only)
536
552
  if (opts.gate && comparison.regressions.length > 0) {
537
- console.log(`${c.yellow}${icons.warning} Gate mode: Exiting with code 1 due to regressions${c.reset}\n`);
538
- return 1;
553
+ if (!quiet) {
554
+ console.log(`${c.yellow}${icons.warning} Gate mode: Exiting with code 2 due to regressions${c.reset}\n`);
555
+ }
556
+ return EXIT.BLOCKING;
539
557
  }
540
558
 
541
- return 0;
559
+ return EXIT.SUCCESS;
542
560
  }
543
561
 
544
562
  module.exports = { runCheckpoint };