@vibecheckai/cli 3.6.1 → 3.7.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.
@@ -1,7 +1,14 @@
1
1
  /**
2
2
  * Fake Success UI Rule
3
3
  *
4
- * Warns/blocks if UI shows success without mutation.
4
+ * Only flags egregious cases of UI showing success without any backing logic.
5
+ * This rule is now much more lenient to reduce false positives - most success
6
+ * messages are legitimate (form submissions, state updates, etc.)
7
+ *
8
+ * Only flags when:
9
+ * 1. Success message is in a "critical" domain (payments, auth)
10
+ * 2. AND there's zero HTTP/API activity in the entire file
11
+ * 3. AND no state management activity detected
5
12
  */
6
13
 
7
14
  "use strict";
@@ -30,51 +37,37 @@ function evaluate({ claims, evidence, policy }) {
30
37
  return null;
31
38
  }
32
39
 
33
- // Filter out false positives: if there's an HTTP call in the same file, it's likely a response check
34
- const realSuccessClaims = successClaims.filter(claim => {
35
- const claimFile = claim.pointer ? claim.pointer.split(":")[0] : "";
36
- if (!claimFile) return true;
37
-
38
- // Check if there's an HTTP call in the same file (within reasonable distance)
39
- const claimLine = claim.pointer ? parseInt(claim.pointer.split(":")[1]?.split("-")[0] || "0", 10) : 0;
40
- const hasNearbyHttpCall = claims.some(c => {
41
- if (c.type !== CLAIM_TYPES.HTTP_CALL && c.type !== CLAIM_TYPES.ROUTE) return false;
42
- const cFile = c.pointer ? c.pointer.split(":")[0] : "";
43
- if (cFile !== claimFile) return false;
44
- const cLine = c.pointer ? parseInt(c.pointer.split(":")[1]?.split("-")[0] || "0", 10) : 0;
45
- // Within 50 lines is considered "nearby"
46
- return Math.abs(cLine - claimLine) <= 50;
47
- });
48
-
49
- // If there's a nearby HTTP call, this is likely checking the response, not showing UI
50
- // Only flag if there's NO HTTP call nearby
51
- return !hasNearbyHttpCall;
52
- });
53
-
54
- if (realSuccessClaims.length === 0) {
55
- return null; // All were false positives (likely API response checks)
56
- }
57
-
58
- // Check if there's a corresponding HTTP call or side effect globally
40
+ // Check if there's ANY HTTP call, route reference, or side effect in the claims
41
+ // If yes, the file likely has real logic and success messages are legitimate
59
42
  const hasHttpCall = claims.some(c =>
60
43
  c.type === CLAIM_TYPES.HTTP_CALL || c.type === CLAIM_TYPES.ROUTE
61
44
  );
62
45
 
63
46
  const hasSideEffect = claims.some(c => c.type === CLAIM_TYPES.SIDE_EFFECT);
64
47
 
65
- if (!hasHttpCall && !hasSideEffect) {
66
- // Check if domain requires blocking
67
- const blockDomains = ruleConfig.block_if_domain || [];
68
- const fileDomain = realSuccessClaims[0].domain || "general";
69
-
70
- const shouldBlock = blockDomains.includes(fileDomain);
71
-
48
+ // If there's any API activity, allow all success messages
49
+ if (hasHttpCall || hasSideEffect) {
50
+ return null;
51
+ }
52
+
53
+ // Check if any success claim is in a critical domain
54
+ const criticalDomains = ruleConfig.block_if_domain || ["payments", "auth"];
55
+ const criticalSuccessClaims = successClaims.filter(claim => {
56
+ const domain = claim.domain || "general";
57
+ return criticalDomains.includes(domain);
58
+ });
59
+
60
+ // Only flag if:
61
+ // 1. There's a success claim in a critical domain
62
+ // 2. AND there's zero API activity
63
+ // 3. AND we have multiple success claims (single success is likely legitimate)
64
+ if (criticalSuccessClaims.length > 0 && successClaims.length > 2) {
72
65
  return {
73
66
  rule: "fake_success_ui",
74
- severity: shouldBlock ? "block" : (ruleConfig.severity || "warn"),
75
- message: `Fake success UI: Success message shown but no HTTP call or side effect detected`,
76
- claimId: `claim_${claims.indexOf(realSuccessClaims[0])}`,
77
- claim: realSuccessClaims[0]
67
+ severity: ruleConfig.severity || "warn", // Default to warn, not block
68
+ message: `Multiple success messages (${successClaims.length}) in ${criticalSuccessClaims[0].domain} without API calls - verify logic exists`,
69
+ claimId: `claim_${claims.indexOf(criticalSuccessClaims[0])}`,
70
+ claim: criticalSuccessClaims[0]
78
71
  };
79
72
  }
80
73
 
@@ -12,6 +12,10 @@ const { CLAIM_TYPES } = require("../../claims/claim-types");
12
12
  /**
13
13
  * Common environment variables that are safe to use without explicit declaration.
14
14
  * These are well-known Node.js, Next.js, and common deployment platform vars.
15
+ *
16
+ * NOTE: Be generous with this list - most env vars are set by the user's deployment
17
+ * environment and aren't hallucinations. We only want to flag truly phantom vars
18
+ * that the agent invented with no basis.
15
19
  */
16
20
  const SAFE_ENV_VARS = new Set([
17
21
  // Node.js core
@@ -19,6 +23,7 @@ const SAFE_ENV_VARS = new Set([
19
23
  "NODE_OPTIONS",
20
24
  "NODE_PATH",
21
25
  "NODE_DEBUG",
26
+ "NODE_TLS_REJECT_UNAUTHORIZED",
22
27
 
23
28
  // Next.js / React
24
29
  "NEXT_PUBLIC_", // Prefix pattern
@@ -27,6 +32,7 @@ const SAFE_ENV_VARS = new Set([
27
32
  "VERCEL_ENV",
28
33
  "VERCEL_URL",
29
34
  "NEXT_RUNTIME",
35
+ "ANALYZE",
30
36
 
31
37
  // Common deployment
32
38
  "PORT",
@@ -43,25 +49,70 @@ const SAFE_ENV_VARS = new Set([
43
49
  "PWD",
44
50
  "SHELL",
45
51
  "TERM",
52
+ "TMPDIR",
53
+ "TEMP",
54
+ "TMP",
46
55
 
47
56
  // Testing
48
57
  "TEST",
49
58
  "JEST_WORKER_ID",
50
59
  "VITEST",
60
+ "PLAYWRIGHT_TEST_BASE_URL",
51
61
 
52
62
  // Build tools
53
63
  "npm_package_name",
54
64
  "npm_package_version",
55
65
  "npm_lifecycle_event",
66
+
67
+ // Database (common naming patterns)
68
+ "DATABASE_URL",
69
+ "DB_HOST",
70
+ "DB_PORT",
71
+ "DB_USER",
72
+ "DB_PASSWORD",
73
+ "DB_NAME",
74
+ "POSTGRES_URL",
75
+ "MYSQL_URL",
76
+ "REDIS_URL",
77
+ "MONGODB_URI",
78
+
79
+ // Auth (common naming patterns)
80
+ "JWT_SECRET",
81
+ "SESSION_SECRET",
82
+ "AUTH_SECRET",
83
+ "NEXTAUTH_SECRET",
84
+ "NEXTAUTH_URL",
85
+
86
+ // API keys (generic patterns)
87
+ "API_KEY",
88
+ "API_SECRET",
89
+ "SECRET_KEY",
90
+
91
+ // Email
92
+ "SMTP_HOST",
93
+ "SMTP_PORT",
94
+ "SMTP_USER",
95
+ "SMTP_PASSWORD",
96
+ "MAIL_HOST",
97
+ "MAIL_PORT",
98
+ "RESEND_API_KEY",
99
+ "SENDGRID_API_KEY",
100
+
101
+ // Payments
102
+ "STRIPE_SECRET_KEY",
103
+ "STRIPE_PUBLISHABLE_KEY",
104
+ "STRIPE_WEBHOOK_SECRET",
56
105
  ]);
57
106
 
58
107
  /**
59
- * Prefix patterns that are safe
108
+ * Prefix patterns that are safe - these are from known platforms/tools
60
109
  */
61
110
  const SAFE_ENV_PREFIXES = [
62
111
  "NEXT_PUBLIC_",
63
112
  "REACT_APP_",
64
113
  "VITE_",
114
+ "VUE_APP_",
115
+ "NUXT_",
65
116
  "npm_",
66
117
  "GITHUB_",
67
118
  "CI_",
@@ -70,6 +121,18 @@ const SAFE_ENV_PREFIXES = [
70
121
  "RENDER_",
71
122
  "HEROKU_",
72
123
  "AWS_",
124
+ "AZURE_",
125
+ "GOOGLE_",
126
+ "FIREBASE_",
127
+ "SUPABASE_",
128
+ "CLERK_",
129
+ "AUTH0_",
130
+ "SENTRY_",
131
+ "DATADOG_",
132
+ "NEW_RELIC_",
133
+ "OPENAI_",
134
+ "ANTHROPIC_",
135
+ "VIBECHECK_",
73
136
  ];
74
137
 
75
138
  /**
@@ -143,10 +206,12 @@ function evaluate({ claims, evidence, policy }) {
143
206
  };
144
207
  }
145
208
 
209
+ // Default to "warn" - only block if explicitly configured
210
+ // Most env vars are real, just not in .env.example
146
211
  return {
147
212
  rule: "ghost_env",
148
- severity: ruleConfig.severity || "block",
149
- message: `Ghost env var: ${envVar} is used but not declared`,
213
+ severity: ruleConfig.severity || "warn",
214
+ message: `Env var ${envVar} used but not found in .env.example`,
150
215
  claimId: `claim_${i}`,
151
216
  claim
152
217
  };
@@ -170,10 +170,12 @@ function evaluate({ claims, evidence, policy }) {
170
170
  continue;
171
171
  }
172
172
 
173
+ // Default to "warn" - only block if explicitly configured
174
+ // Route references are often correct, just not in truthpack yet
173
175
  return {
174
176
  rule: "ghost_route",
175
- severity: ruleConfig.severity || "block",
176
- message: `Ghost route: ${routePath} is referenced but not registered in truthpack`,
177
+ severity: ruleConfig.severity || "warn",
178
+ message: `Route ${routePath} not found in truthpack - verify it exists`,
177
179
  claimId: `claim_${i}`,
178
180
  claim
179
181
  };
@@ -18,11 +18,12 @@
18
18
  const DEFAULT_THRESHOLDS = {
19
19
  /**
20
20
  * Score thresholds for automatic decisions
21
- * Raised to reduce noise for normal development
21
+ * Raised significantly to focus only on real issues (hallucinations, drift)
22
+ * Normal development changes should almost never trigger
22
23
  */
23
- autoAllow: 30, // Auto-allow if score <= this (raised from 15)
24
- requireConfirm: 70, // Require confirmation if score > this (raised from 50)
25
- autoBlock: 100, // Auto-block if score >= this (raised from 80)
24
+ autoAllow: 50, // Auto-allow if score <= this (raised from 30)
25
+ requireConfirm: 85, // Require confirmation if score > this (raised from 70)
26
+ autoBlock: 120, // Auto-block if score >= this (raised from 100)
26
27
 
27
28
  /**
28
29
  * Vector-specific thresholds
@@ -106,7 +106,7 @@ const ERROR_TEMPLATES = {
106
106
  message: 'Invalid API key format',
107
107
  suggestion: 'Check your API key format',
108
108
  nextSteps: [
109
- 'API keys should start with vc_',
109
+ 'API keys should start with grl_ (from dashboard)',
110
110
  'Get a new key: https://app.vibecheckai.dev/settings/api-keys',
111
111
  'Run: vibecheck login --key YOUR_KEY',
112
112
  ],
@@ -52,12 +52,12 @@ const LOGO_VIBECHECK = `
52
52
 
53
53
  // HERO: REPORTS (Blue/Cyan) - Fixed to say "REPORTS" plural
54
54
  const LOGO_REPORTS = `
55
- ██████╗ ███████╗██████╗ ██████╗ ██████╗ ███████╗██████╗ ████████╗███████╗
56
- ██╔══██╗██╔════╝██╔══██╗██╔═══██╗██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝
57
- ██████╔╝█████╗ ██████╔╝██║ ██║██████╔╝███████╗██████╔╝ ██║ ███████╗
58
- ██╔══██╗██╔══╝ ██╔═══╝ ██║ ██║██╔══██╗╚════██║██╔══██╗ ██║ ╚════██║
59
- ██║ ██║███████╗██║ ╚██████╔╝██║ ██║███████║██║ ██║ ██║ ███████║
60
- ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝
55
+ ██████╗ ███████╗██████╗ ██████╗ ██████╗ ████████╗███████╗
56
+ ██╔══██╗██╔════╝██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝██╔════╝
57
+ ██████╔╝█████╗ ██████╔╝██║ ██║██████╔╝ ██║ ███████╗
58
+ ██╔══██╗██╔══╝ ██╔═══╝ ██║ ██║██╔══██╗ ██║ ╚════██║
59
+ ██║ ██║███████╗██║ ╚██████╔╝██║ ██║ ██║ ███████║
60
+ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
61
61
  `;
62
62
 
63
63
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibecheck-mcp-server",
3
- "version": "3.6.1",
3
+ "version": "3.7.0",
4
4
  "description": "Professional MCP server for vibecheck - Intelligent development environment vibechecks",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -51,6 +51,7 @@ export const ERROR_CODES = {
51
51
  INVALID_API_KEY: 'INVALID_API_KEY',
52
52
  RATE_LIMITED: 'RATE_LIMITED',
53
53
  OPTION_NOT_ENTITLED: 'OPTION_NOT_ENTITLED',
54
+ API_KEY_REQUIRED: 'API_KEY_REQUIRED',
54
55
  };
55
56
 
56
57
  // ============================================================================
@@ -237,10 +238,48 @@ export function createTierErrorEnvelope(code, message, extra = {}) {
237
238
  };
238
239
  }
239
240
 
241
+ /**
242
+ * Create API_KEY_REQUIRED error envelope
243
+ * Used when a PRO tool is requested but no API key is provided
244
+ */
245
+ export function apiKeyRequiredError(toolName) {
246
+ return createTierErrorEnvelope(ERROR_CODES.API_KEY_REQUIRED, `API key required for ${toolName}`, {
247
+ tool: toolName,
248
+ userAction: 'Add API Key',
249
+ retryable: true,
250
+ nextSteps: [
251
+ 'Get your API key at https://vibecheckai.dev/dashboard',
252
+ 'Run: vibecheck login',
253
+ 'Or set VIBECHECK_API_KEY environment variable',
254
+ 'Then pass apiKey in tool arguments or configure in ~/.vibecheck/credentials.json',
255
+ ],
256
+ });
257
+ }
258
+
240
259
  /**
241
260
  * Create NOT_ENTITLED error envelope
242
261
  */
243
- export function notEntitledError(toolName, currentTier = 'free', requiredTier = 'pro') {
262
+ export function notEntitledError(toolName, currentTier = 'free', requiredTier = 'pro', hasApiKey = true) {
263
+ // If no API key was provided, give more specific guidance
264
+ if (!hasApiKey) {
265
+ return createTierErrorEnvelope(ERROR_CODES.NOT_ENTITLED, `${toolName} requires PRO - add your API key`, {
266
+ tier: currentTier,
267
+ required: requiredTier,
268
+ tool: toolName,
269
+ userAction: 'Add API Key',
270
+ retryable: true,
271
+ nextSteps: [
272
+ `${toolName} requires PRO ($69/mo) subscription`,
273
+ 'If you have PRO, add your API key:',
274
+ ' - Run: vibecheck login',
275
+ ' - Or pass apiKey in tool arguments',
276
+ ' - Or set VIBECHECK_API_KEY environment variable',
277
+ 'Get your API key at https://vibecheckai.dev/dashboard',
278
+ 'Upgrade at https://vibecheckai.dev/pricing',
279
+ ],
280
+ });
281
+ }
282
+
244
283
  return createTierErrorEnvelope(ERROR_CODES.NOT_ENTITLED, `Requires ${requiredTier.toUpperCase()}`, {
245
284
  tier: currentTier,
246
285
  required: requiredTier,
@@ -326,6 +365,24 @@ export async function getTierFromApiKey(apiKey) {
326
365
  }
327
366
  }
328
367
 
368
+ // ============================================================================
369
+ // USER CONFIG
370
+ // ============================================================================
371
+
372
+ /**
373
+ * Load user configuration from ~/.vibecheck/credentials.json
374
+ * @returns {Promise<{apiKey?: string, email?: string}|null>}
375
+ */
376
+ async function loadUserConfig() {
377
+ try {
378
+ const configPath = path.join(os.homedir(), '.vibecheck', 'credentials.json');
379
+ const data = await fs.readFile(configPath, 'utf-8');
380
+ return JSON.parse(data);
381
+ } catch {
382
+ return null;
383
+ }
384
+ }
385
+
329
386
  // ============================================================================
330
387
  // ACCESS CONTROL
331
388
  // ============================================================================
@@ -410,6 +467,34 @@ export function canApproveAuthority(tier) {
410
467
  return tier === 'pro';
411
468
  }
412
469
 
470
+ /**
471
+ * Resolve API key from multiple sources
472
+ * Priority: 1. Passed directly 2. Environment variable 3. Credentials file
473
+ *
474
+ * @param {string} apiKey - API key passed directly (optional)
475
+ * @returns {Promise<{apiKey: string|null, source: string}>}
476
+ */
477
+ export async function resolveApiKey(apiKey) {
478
+ // 1. Use directly provided API key
479
+ if (apiKey && typeof apiKey === 'string' && apiKey.length >= 10) {
480
+ return { apiKey, source: 'args' };
481
+ }
482
+
483
+ // 2. Check environment variable
484
+ const envKey = process.env.VIBECHECK_API_KEY;
485
+ if (envKey && typeof envKey === 'string' && envKey.length >= 10) {
486
+ return { apiKey: envKey, source: 'env' };
487
+ }
488
+
489
+ // 3. Check credentials file
490
+ const config = await loadUserConfig();
491
+ if (config?.apiKey && typeof config.apiKey === 'string' && config.apiKey.length >= 10) {
492
+ return { apiKey: config.apiKey, source: 'credentials' };
493
+ }
494
+
495
+ return { apiKey: null, source: 'none' };
496
+ }
497
+
413
498
  /**
414
499
  * Get MCP tool access with full ErrorEnvelope support
415
500
  *
@@ -419,18 +504,30 @@ export function canApproveAuthority(tier) {
419
504
  * @returns {Promise<{hasAccess: boolean, tier: string, error?: object}>}
420
505
  */
421
506
  export async function getMcpToolAccess(toolName, apiKey, args = {}) {
422
- const tier = await getTierFromApiKey(apiKey);
507
+ // Resolve API key from multiple sources
508
+ const resolved = await resolveApiKey(apiKey);
509
+ const resolvedApiKey = resolved.apiKey;
510
+ const hasApiKey = resolvedApiKey !== null;
511
+
512
+ const tier = await getTierFromApiKey(resolvedApiKey);
423
513
 
424
514
  // Check tool-level access
425
515
  const hasToolAccess = canAccessTool(tier, toolName);
426
516
 
427
517
  if (!hasToolAccess) {
518
+ // Check if this is a PRO tool and no API key was provided
519
+ const isPROTool = PRO_TOOLS.includes(toolName);
520
+
428
521
  return {
429
522
  hasAccess: false,
430
523
  tier,
524
+ hasApiKey,
525
+ apiKeySource: resolved.source,
431
526
  firewallMode: getFirewallMode(tier),
432
- error: notEntitledError(toolName, tier, 'pro'),
433
- reason: `${toolName} requires Pro ($69/mo). Upgrade at https://vibecheckai.dev/pricing`,
527
+ error: notEntitledError(toolName, tier, 'pro', hasApiKey),
528
+ reason: hasApiKey
529
+ ? `${toolName} requires Pro ($69/mo). Upgrade at https://vibecheckai.dev/pricing`
530
+ : `${toolName} requires Pro. Add your API key first: run 'vibecheck login' or pass apiKey in tool arguments.`,
434
531
  };
435
532
  }
436
533
 
@@ -440,6 +537,8 @@ export async function getMcpToolAccess(toolName, apiKey, args = {}) {
440
537
  return {
441
538
  hasAccess: false,
442
539
  tier,
540
+ hasApiKey,
541
+ apiKeySource: resolved.source,
443
542
  firewallMode: getFirewallMode(tier),
444
543
  error: optionNotEntitledError(toolName, optionCheck.blockedOption, tier, optionCheck.required),
445
544
  reason: `Option --${optionCheck.blockedOption} requires Pro`,
@@ -449,6 +548,8 @@ export async function getMcpToolAccess(toolName, apiKey, args = {}) {
449
548
  return {
450
549
  hasAccess: true,
451
550
  tier,
551
+ hasApiKey,
552
+ apiKeySource: resolved.source,
452
553
  firewallMode: getFirewallMode(tier),
453
554
  reason: 'Access granted',
454
555
  };
@@ -515,16 +616,6 @@ export async function checkTierGate(toolName, apiKey, args = {}) {
515
616
  // USER INFO
516
617
  // ============================================================================
517
618
 
518
- async function loadUserConfig() {
519
- try {
520
- const configPath = path.join(os.homedir(), '.vibecheck', 'credentials.json');
521
- const data = await fs.readFile(configPath, 'utf-8');
522
- return JSON.parse(data);
523
- } catch {
524
- return null;
525
- }
526
- }
527
-
528
619
  export async function getUserInfo() {
529
620
  const config = await loadUserConfig();
530
621
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecheckai/cli",
3
- "version": "3.6.1",
3
+ "version": "3.7.0",
4
4
  "description": "Vibecheck CLI - Ship with confidence. One verdict: SHIP | WARN | BLOCK.",
5
5
  "main": "bin/vibecheck.js",
6
6
  "bin": {