@vibecheckai/cli 3.3.0 → 3.4.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,607 +1,216 @@
1
- /* vibecheck-disable */
2
1
  /**
3
- * MCP Server Tier Authentication & Authorization
2
+ * MCP Server Tier Authentication
4
3
  *
5
- * UNIFIED AUTHORITY SYSTEM for MCP tools.
4
+ * Simple 2-tier model:
5
+ * - FREE ($0): Inspect & Observe
6
+ * - PRO ($69/mo): Fix, Prove & Enforce
6
7
  *
7
- * Authority Rules:
8
- * - FREE: May see problems, never resolve certainty. Read-only context tools.
9
- * - STARTER: May fix, but not prove. Read tools + advisory actions.
10
- * - PRO: May enforce reality. Full MCP access with proof generation.
11
- * - ENTERPRISE: All features + compliance tools.
12
- *
13
- * Verdict authority is paid. Evidence beats explanations.
8
+ * PRO includes:
9
+ * - Authority System (verdicts, approvals)
10
+ * - Agent Conductor (multi-agent coordination)
11
+ * - Agent Firewall (enforce mode)
14
12
  */
15
13
 
16
14
  import fs from "fs/promises";
17
15
  import path from "path";
18
16
  import os from "os";
19
17
 
20
- // ═══════════════════════════════════════════════════════════════════════════════
21
- // TIER DEFINITIONS - UNIFIED AUTHORITY SYSTEM
22
- // Synchronized with packages/core/src/tier-config.ts
23
- //
24
- // Authority Rules:
25
- // - FREE: May see problems, never resolve certainty
26
- // - STARTER: May fix, but not prove (Advisory verdicts only)
27
- // - PRO: May enforce reality (Full verdict authority)
28
- // ═══════════════════════════════════════════════════════════════════════════════
18
+ // ============================================================================
19
+ // TIERS
20
+ // ============================================================================
29
21
  export const TIERS = {
30
- free: {
31
- name: 'FREE',
32
- price: 0,
33
- order: 0,
34
- // Verdict authority
35
- verdictAuthority: 'NONE',
36
- canIssueVerdicts: false,
37
- canEnforceCI: false,
38
- canGenerateProof: false,
39
- // Agent Firewall configuration
40
- firewall: {
41
- mode: 'observe', // Log only, never block
42
- canOverride: false, // Cannot override blocks
43
- auditEnabled: false, // No audit trail
44
- },
45
- // Limits
46
- limits: {
47
- scans: -1, // Unlimited scans
48
- realityMaxPages: 5,
49
- realityMaxClicks: 20,
50
- mcpRateLimit: 10,
51
- },
52
- // MCP tools allowed on FREE (read-only context only)
53
- mcpTools: [
54
- 'vibecheck.get_truthpack',
55
- 'vibecheck.compile_context',
56
- 'vibecheck.search_evidence',
57
- 'vibecheck_agent_firewall_intercept', // Observe mode
58
- 'vibecheck_conductor_status', // Read-only coordination status
59
- // Authority System - FREE tier
60
- 'vibecheck.classify', // Inventory authority (read-only)
61
- 'vibecheck.authority_list', // List available authorities
62
- ],
63
- // Authority System configuration
64
- authority: {
65
- canClassify: true, // Can run inventory analysis
66
- canApprove: false, // Cannot get verdicts
67
- canEnforce: false, // Cannot enforce in CI
68
- canGenerateBadge: false, // Cannot generate badges
69
- availableAuthorities: ['inventory'],
70
- },
71
- },
72
- starter: {
73
- name: 'STARTER',
74
- price: 39,
75
- order: 1,
76
- // Verdict authority - ADVISORY (verdicts not enforced)
77
- verdictAuthority: 'ADVISORY',
78
- canIssueVerdicts: true, // SHIP, WARN only
79
- canEnforceCI: false, // Cannot block builds
80
- canGenerateProof: false, // Cannot generate proof
81
- // Agent Firewall configuration
82
- firewall: {
83
- mode: 'advisory', // Warn but allow
84
- canOverride: true, // Can override with reason
85
- auditEnabled: true, // Basic audit trail
86
- },
87
- // Limits
88
- limits: {
89
- scans: -1, // Unlimited scans
90
- realityMaxPages: -1, // Unlimited browser testing
91
- realityMaxClicks: -1,
92
- mcpRateLimit: 60,
93
- },
94
- // MCP tools allowed on STARTER (read + advisory actions)
95
- mcpTools: [
96
- 'vibecheck.ctx',
97
- 'vibecheck.scan',
98
- 'vibecheck.ship', // Advisory verdicts only
99
- 'vibecheck.get_truthpack',
100
- 'vibecheck.validate_claim',
101
- 'vibecheck.compile_context',
102
- 'vibecheck.search_evidence',
103
- 'vibecheck.find_counterexamples',
104
- 'vibecheck.check_invariants',
105
- 'vibecheck.fix', // Plan mode only
106
- 'vibecheck_agent_firewall_intercept', // Advisory mode
107
- // Conductor - multi-agent coordination
108
- 'vibecheck_conductor_register',
109
- 'vibecheck_conductor_acquire_lock',
110
- 'vibecheck_conductor_release_lock',
111
- 'vibecheck_conductor_propose',
112
- 'vibecheck_conductor_status',
113
- 'vibecheck_conductor_terminate',
114
- // Authority System - STARTER tier
115
- 'vibecheck.classify', // Inventory authority
116
- 'vibecheck.approve', // Advisory verdicts
117
- 'vibecheck.authority_list', // List authorities
118
- 'vibecheck.authority_approve', // Authority approval (advisory)
119
- ],
120
- // Authority System configuration
121
- authority: {
122
- canClassify: true, // Can run inventory analysis
123
- canApprove: true, // Can get advisory verdicts
124
- canEnforce: false, // Cannot enforce in CI
125
- canGenerateBadge: false, // Cannot generate badges
126
- availableAuthorities: ['inventory', 'safe-consolidation'],
127
- },
128
- },
129
- pro: {
130
- name: 'PRO',
131
- price: 99,
132
- order: 2,
133
- // Verdict authority - ENFORCED (verdicts block CI/PRs)
134
- verdictAuthority: 'ENFORCED',
135
- canIssueVerdicts: true, // SHIP, WARN, BLOCK
136
- canEnforceCI: true, // Can block builds
137
- canGenerateProof: true, // Can generate cryptographic proof
138
- // Agent Firewall configuration
139
- firewall: {
140
- mode: 'enforce', // Block violations
141
- canOverride: true, // Can override with approval
142
- auditEnabled: true, // Full audit trail
143
- criticEnabled: true, // Enable Critic LLM
144
- },
145
- // Limits
146
- limits: {
147
- scans: -1,
148
- realityMaxPages: -1,
149
- realityMaxClicks: -1,
150
- mcpRateLimit: -1, // Unlimited
151
- },
152
- // MCP tools - FULL ACCESS
153
- mcpTools: ['*'], // All tools unlocked
154
- // Authority System configuration
155
- authority: {
156
- canClassify: true, // Can run inventory analysis
157
- canApprove: true, // Can get verdicts
158
- canEnforce: true, // Can enforce in CI (STOP verdicts block)
159
- canGenerateBadge: true, // Can generate authority badges
160
- availableAuthorities: ['inventory', 'safe-consolidation', 'security-remediation'],
161
- },
162
- },
163
- enterprise: {
164
- name: 'ENTERPRISE',
165
- price: 0, // Custom pricing
166
- order: 3,
167
- // Verdict authority - ENFORCED + Compliance
168
- verdictAuthority: 'ENFORCED',
169
- canIssueVerdicts: true,
170
- canEnforceCI: true,
171
- canGenerateProof: true,
172
- // Agent Firewall configuration
173
- firewall: {
174
- mode: 'enforce', // Block violations
175
- canOverride: true, // Can override with multi-approval
176
- auditEnabled: true, // Full compliance audit trail
177
- criticEnabled: true, // Enable Critic LLM
178
- complianceMode: true, // Generate compliance artifacts
179
- },
180
- // Limits
181
- limits: {
182
- scans: -1,
183
- realityMaxPages: -1,
184
- realityMaxClicks: -1,
185
- mcpRateLimit: -1,
186
- },
187
- // MCP tools - FULL ACCESS + Compliance
188
- mcpTools: ['*'],
189
- // Authority System configuration
190
- authority: {
191
- canClassify: true, // Can run inventory analysis
192
- canApprove: true, // Can get verdicts
193
- canEnforce: true, // Can enforce in CI (STOP verdicts block)
194
- canGenerateBadge: true, // Can generate authority badges
195
- canCustomize: true, // Can create custom authorities
196
- availableAuthorities: ['*'], // All authorities including custom
197
- },
198
- }
22
+ free: { name: 'FREE', price: 0 },
23
+ pro: { name: 'PRO', price: 69 },
199
24
  };
200
25
 
201
- // ═══════════════════════════════════════════════════════════════════════════════
202
- // CLI FEATURE TIER MAPPING
203
- // Maps CLI features (not MCP tools) to minimum required tier
204
- // ═══════════════════════════════════════════════════════════════════════════════
205
- export const CLI_FEATURE_TIERS = {
206
- // FREE - Basic scanning (always available)
207
- 'scan': 'free',
208
- 'ctx': 'free',
209
- 'ship': 'free',
210
- 'classify': 'free', // Authority: inventory (read-only)
211
-
212
- // STARTER - Enhanced features
213
- 'gate': 'starter',
214
- 'badge': 'starter',
215
- 'fix': 'starter', // Plan mode
216
- 'approve': 'starter', // Authority: advisory verdicts
217
- 'authority.starter': 'starter', // Authority System access
218
-
219
- // PRO - Full enforcement
220
- 'prove': 'pro',
221
- 'fix.apply_patches': 'pro', // Apply mode
222
- 'reality': 'pro',
223
- 'autopilot': 'pro',
224
- 'verify': 'pro',
225
- 'authority.pro': 'pro', // Authority: enforced verdicts + badges
226
- 'authority.enforce': 'pro', // Authority: CI blocking
227
- };
228
-
229
- // ═══════════════════════════════════════════════════════════════════════════════
230
- // MCP TOOL → TIER MAPPING (Unified Authority System)
231
- // Aligned with packages/core/src/features.ts
232
- // ═══════════════════════════════════════════════════════════════════════════════
233
- export const MCP_TOOL_TIERS = {
234
- // FREE - Read-only context tools ONLY
235
- // Free users may see problems, never resolve certainty
236
- 'vibecheck.get_truthpack': 'free',
237
- 'vibecheck.compile_context': 'free',
238
- 'vibecheck.search_evidence': 'free',
239
- 'vibecheck_agent_firewall_intercept': 'free', // Observe mode only
240
- 'vibecheck.classify': 'free', // Authority: inventory (read-only)
241
- 'vibecheck.authority_list': 'free', // List available authorities
242
-
243
- // STARTER - Advisory verdict authority
244
- // Starter users may fix, but not prove
245
- 'vibecheck.ctx': 'starter',
246
- 'vibecheck.scan': 'starter',
247
- 'vibecheck.ship': 'starter', // SHIP/WARN (advisory, not enforced)
248
- 'vibecheck.fix': 'starter', // Plan mode only (no apply)
249
- 'vibecheck.validate_claim': 'starter',
250
- 'vibecheck.find_counterexamples': 'starter',
251
- 'vibecheck.check_invariants': 'starter',
252
- 'vibecheck.gate': 'starter', // Advisory gates
253
- 'vibecheck.badge': 'starter', // Unverified badges
254
- 'vibecheck.approve': 'starter', // Authority: advisory verdicts
255
- 'vibecheck.authority_approve': 'starter', // Authority approval (advisory)
256
-
257
- // PRO - Full verdict authority & enforcement
258
- // Pro users may enforce reality
259
- 'vibecheck.prove': 'pro', // Cryptographic proof generation
260
- 'vibecheck.fix.apply': 'pro', // Apply fixes
261
- 'vibecheck.fix.verify': 'pro', // Verify fixes with proof
262
- 'vibecheck.reality': 'pro', // Full browser testing
263
- 'vibecheck.reality.prove': 'pro', // Prove runtime behavior
264
- 'vibecheck.enforce': 'pro', // Enforce verdicts in CI
265
- 'vibecheck.ai_test': 'pro', // AI agent testing
266
- 'vibecheck.autopilot': 'pro', // Autonomous protection
267
- 'vibecheck.report': 'pro', // Advanced reporting
268
- 'vibecheck.allowlist': 'pro', // Manage allowlists
269
- 'vibecheck.status': 'pro', // Advanced status
270
- 'vibecheck.authority_enforce': 'pro', // Authority: enforced verdicts
271
- 'vibecheck.authority_badge': 'pro', // Authority: generate badges
272
- };
26
+ // ============================================================================
27
+ // MCP TOOLS - 15 Core + PRO Features
28
+ // ============================================================================
273
29
 
274
30
  /**
275
- * Load user configuration from ~/.vibecheck/credentials.json
31
+ * FREE TOOLS (7) - Inspect & Observe
276
32
  */
277
- async function loadUserConfig() {
278
- try {
279
- const configPath = path.join(os.homedir(), '.vibecheck', 'credentials.json');
280
- const configData = await fs.readFile(configPath, 'utf-8');
281
- return JSON.parse(configData);
282
- } catch (error) {
283
- return null;
284
- }
285
- }
286
-
287
- // ═══════════════════════════════════════════════════════════════════════════════
288
- // SECURE TIER VALIDATION - API-First with Caching
289
- // SECURITY: Never trust API key prefix alone - always validate with API
290
- // ═══════════════════════════════════════════════════════════════════════════════
33
+ export const FREE_TOOLS = [
34
+ // Core FREE tools
35
+ 'vibecheck.scan',
36
+ 'vibecheck.ctx',
37
+ 'vibecheck.verify',
38
+ 'vibecheck.report',
39
+ 'vibecheck.status',
40
+ 'vibecheck.doctor',
41
+ 'vibecheck.firewall', // Observe mode only
42
+ // Authority (read-only)
43
+ 'authority.list',
44
+ 'authority.classify',
45
+ // Conductor (status only)
46
+ 'vibecheck_conductor_status',
47
+ ];
291
48
 
292
49
  /**
293
- * Tier cache to avoid hammering the API on every request
294
- * Structure: Map<apiKeyHash, { tier, validatedAt, expiresAt }>
50
+ * PRO TOOLS (8 Core + Authority + Conductor + Firewall) - Fix, Prove & Enforce
295
51
  */
52
+ export const PRO_TOOLS = [
53
+ // Core PRO tools
54
+ 'vibecheck.ship',
55
+ 'vibecheck.fix',
56
+ 'vibecheck.prove',
57
+ 'vibecheck.gate',
58
+ 'vibecheck.badge',
59
+ 'vibecheck.reality',
60
+ 'vibecheck.ai_test',
61
+ 'vibecheck.share',
62
+
63
+ // Authority System (full)
64
+ 'authority.approve',
65
+ 'authority.enforce',
66
+
67
+ // Agent Conductor (full multi-agent coordination)
68
+ 'vibecheck_conductor_register',
69
+ 'vibecheck_conductor_acquire_lock',
70
+ 'vibecheck_conductor_release_lock',
71
+ 'vibecheck_conductor_propose',
72
+ 'vibecheck_conductor_terminate',
73
+
74
+ // Agent Firewall (enforce mode)
75
+ 'vibecheck_agent_firewall_intercept',
76
+ 'vibecheck.firewall.enforce',
77
+ ];
78
+
79
+ export const ALL_TOOLS = [...FREE_TOOLS, ...PRO_TOOLS];
80
+
81
+ // ============================================================================
82
+ // TIER CACHE
83
+ // ============================================================================
296
84
  const tierCache = new Map();
297
- const TIER_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
298
- const TIER_CACHE_MAX_SIZE = 1000; // Prevent unbounded growth
85
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
299
86
 
300
- /**
301
- * Hash API key for cache key (don't store raw keys in memory)
302
- */
303
- function hashApiKey(apiKey) {
87
+ function hashKey(apiKey) {
304
88
  const crypto = require('crypto');
305
- return crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 32);
89
+ return crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
306
90
  }
307
91
 
308
- /**
309
- * Evict expired entries and enforce max size
310
- */
311
- function cleanupTierCache() {
312
- const now = Date.now();
313
- for (const [key, value] of tierCache) {
314
- if (value.expiresAt < now) {
315
- tierCache.delete(key);
316
- }
317
- }
318
- // If still too large, evict oldest entries
319
- if (tierCache.size > TIER_CACHE_MAX_SIZE) {
320
- const entries = Array.from(tierCache.entries())
321
- .sort((a, b) => a[1].validatedAt - b[1].validatedAt);
322
- const toDelete = entries.slice(0, tierCache.size - TIER_CACHE_MAX_SIZE);
323
- for (const [key] of toDelete) {
324
- tierCache.delete(key);
325
- }
326
- }
327
- }
92
+ // ============================================================================
93
+ // TIER VALIDATION
94
+ // ============================================================================
328
95
 
329
- /**
330
- * Determine tier from API key - ALWAYS validates with API first
331
- *
332
- * SECURITY FIX: Previous version checked prefix patterns BEFORE API validation,
333
- * allowing attackers to spoof tier with fake keys like "gr_pro_anything".
334
- * Now we ALWAYS validate with API first. Prefix is never authoritative.
335
- *
336
- * @param {string} apiKey - The API key to validate
337
- * @returns {Promise<string|null>} - Tier name or null if invalid
338
- */
339
- async function getTierFromApiKey(apiKey) {
340
- if (!apiKey || typeof apiKey !== 'string') return null;
341
-
342
- // Validate key format (basic sanity check)
343
- if (apiKey.length < 10 || apiKey.length > 256) return null;
96
+ export async function getTierFromApiKey(apiKey) {
97
+ if (!apiKey || typeof apiKey !== 'string' || apiKey.length < 10) {
98
+ return null;
99
+ }
344
100
 
345
- const keyHash = hashApiKey(apiKey);
101
+ const keyHash = hashKey(apiKey);
346
102
  const now = Date.now();
347
103
 
348
- // Check cache first (only if not expired)
104
+ // Check cache
349
105
  const cached = tierCache.get(keyHash);
350
106
  if (cached && cached.expiresAt > now) {
351
107
  return cached.tier;
352
108
  }
353
109
 
354
- // ALWAYS validate with API - this is the authoritative source
110
+ // Validate with API
355
111
  try {
356
- const controller = new AbortController();
357
- const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
358
-
359
112
  const response = await fetch('https://api.vibecheckai.dev/whoami', {
360
- headers: {
361
- 'Authorization': `Bearer ${apiKey}`,
362
- 'Content-Type': 'application/json',
363
- },
364
- signal: controller.signal,
113
+ headers: { 'Authorization': `Bearer ${apiKey}` },
114
+ signal: AbortSignal.timeout(10000),
365
115
  });
366
116
 
367
- clearTimeout(timeoutId);
368
-
369
117
  if (!response.ok) {
370
- // API explicitly rejected this key - it's invalid
371
- // Cache the rejection briefly to avoid hammering on invalid keys
372
- tierCache.set(keyHash, {
373
- tier: null,
374
- validatedAt: now,
375
- expiresAt: now + 60000, // Cache rejections for 1 minute
376
- });
377
118
  return null;
378
119
  }
379
120
 
380
121
  const data = await response.json();
122
+ const plan = data.plan?.toLowerCase() || 'free';
381
123
 
382
- // Map API response to tier
383
- let tier;
384
- switch (data.plan?.toLowerCase()) {
385
- case 'starter':
386
- tier = 'starter';
387
- break;
388
- case 'pro':
389
- tier = 'pro';
390
- break;
391
- case 'enterprise':
392
- tier = 'enterprise';
393
- break;
394
- default:
395
- tier = 'free';
396
- }
397
-
398
- // Cache the validated tier
399
- tierCache.set(keyHash, {
400
- tier,
401
- validatedAt: now,
402
- expiresAt: now + TIER_CACHE_TTL_MS,
403
- });
404
-
405
- // Periodic cleanup
406
- if (tierCache.size > TIER_CACHE_MAX_SIZE * 0.9) {
407
- cleanupTierCache();
408
- }
124
+ // Any paid plan = pro
125
+ const tier = (plan === 'free') ? 'free' : 'pro';
409
126
 
127
+ tierCache.set(keyHash, { tier, expiresAt: now + CACHE_TTL });
410
128
  return tier;
411
129
 
412
- } catch (error) {
413
- // Network error - check if we have a recent valid cache entry
414
- // (This allows graceful degradation during brief network issues)
415
- if (cached && cached.tier !== null) {
416
- // Only use stale cache if it was validated within last 15 minutes
417
- // and the original validation was successful (tier !== null)
418
- const staleTolerance = 15 * 60 * 1000; // 15 minutes
419
- if (now - cached.validatedAt < staleTolerance) {
420
- console.error(`[TIER-AUTH] API unreachable, using stale cache for ${keyHash.slice(0, 8)}...`);
421
- return cached.tier;
422
- }
423
- }
424
-
425
- // No valid cache - fail closed for security
426
- console.error('[TIER-AUTH] API validation failed with no valid cache, denying access:', error.message);
130
+ } catch {
131
+ // Network error - check stale cache
132
+ if (cached) return cached.tier;
427
133
  return null;
428
134
  }
429
135
  }
430
136
 
137
+ // ============================================================================
138
+ // ACCESS CONTROL
139
+ // ============================================================================
140
+
141
+ export function isPro(tier) {
142
+ return tier === 'pro';
143
+ }
144
+
145
+ export function canAccessTool(tier, toolName) {
146
+ // PRO gets everything
147
+ if (tier === 'pro') return true;
148
+
149
+ // FREE can access FREE tools
150
+ return FREE_TOOLS.includes(toolName);
151
+ }
152
+
431
153
  /**
432
- * Check if user has access to a specific feature
433
- * Matches CLI entitlements-v2.js logic
154
+ * Get firewall mode based on tier
155
+ * - FREE: observe (log only)
156
+ * - PRO: enforce (block violations)
434
157
  */
435
- export async function getFeatureAccessStatus(featureName, providedApiKey = null) {
436
- // Try to load user config
437
- const userConfig = await loadUserConfig();
438
- const apiKey = providedApiKey || userConfig?.apiKey;
439
-
440
- if (!apiKey) {
441
- return {
442
- hasAccess: false,
443
- tier: null,
444
- reason: 'No API key provided. Please set your API key with `vibecheck login`.',
445
- upgradeUrl: 'https://vibecheckai.dev'
446
- };
447
- }
448
-
449
- const currentTier = await getTierFromApiKey(apiKey);
450
-
451
- if (!currentTier) {
452
- return {
453
- hasAccess: false,
454
- tier: null,
455
- reason: 'Invalid API key. Please check your API key or get a new one at https://vibecheckai.dev',
456
- upgradeUrl: 'https://vibecheckai.dev'
457
- };
458
- }
459
- const currentTierConfig = TIERS[currentTier];
460
-
461
- // Find which tier requires this feature using CLI_FEATURE_TIERS mapping
462
- const requiredTier = CLI_FEATURE_TIERS[featureName];
463
-
464
- // If feature not found in mapping, allow it (default to free)
465
- if (!requiredTier) {
466
- return {
467
- hasAccess: true,
468
- tier: currentTier,
469
- reason: `Feature '${featureName}' has no tier restriction`
470
- };
471
- }
472
-
473
- const requiredTierConfig = TIERS[requiredTier];
474
-
475
- // Check if current tier meets minimum requirement (using order)
476
- const hasAccess = currentTierConfig.order >= requiredTierConfig.order;
477
-
478
- if (!hasAccess) {
479
- return {
480
- hasAccess: false,
481
- tier: currentTier,
482
- requiredTier,
483
- reason: `${featureName} requires ${requiredTierConfig.name} tier ($${requiredTierConfig.price}/mo) or higher. Current tier: ${currentTierConfig.name}`,
484
- upgradeUrl: 'https://vibecheckai.dev'
485
- };
486
- }
487
-
488
- return {
489
- hasAccess: true,
490
- tier: currentTier,
491
- reason: 'Access granted'
492
- };
158
+ export function getFirewallMode(tier) {
159
+ return tier === 'pro' ? 'enforce' : 'observe';
493
160
  }
494
161
 
495
162
  /**
496
- * Middleware for MCP tool handlers
163
+ * Check if user can use full conductor features
497
164
  */
498
- export function withTierCheck(featureName, handler) {
499
- return async (args) => {
500
- const access = await getFeatureAccessStatus(featureName, args?.apiKey);
501
-
502
- if (!access.hasAccess) {
503
- return {
504
- content: [{
505
- type: "text",
506
- text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nUpgrade at: ${access.upgradeUrl}`
507
- }],
508
- isError: true
509
- };
510
- }
511
-
512
- // Add tier info to args for the handler
513
- args._tier = access.tier;
514
- return handler(args);
515
- };
165
+ export function canUseCondcutor(tier) {
166
+ return tier === 'pro';
516
167
  }
517
168
 
518
169
  /**
519
- * Check if user has access to a specific MCP tool
520
- * MCP tools have specific tier requirements separate from CLI features
170
+ * Check if user can approve authorities
521
171
  */
522
- export async function getMcpToolAccess(toolName, providedApiKey = null) {
523
- const userConfig = await loadUserConfig();
524
- const apiKey = providedApiKey || userConfig?.apiKey;
525
-
172
+ export function canApproveAuthority(tier) {
173
+ return tier === 'pro';
174
+ }
175
+
176
+ export async function getMcpToolAccess(toolName, apiKey) {
526
177
  if (!apiKey) {
527
178
  return {
528
- hasAccess: false,
529
- tier: null,
530
- reason: 'No API key provided. Please set your API key with `vibecheck login`.',
531
- upgradeUrl: 'https://vibecheckai.dev'
179
+ hasAccess: FREE_TOOLS.includes(toolName),
180
+ tier: 'free',
181
+ reason: FREE_TOOLS.includes(toolName)
182
+ ? 'Access granted (free tool)'
183
+ : 'This tool requires Pro. Set API key with `vibecheck login`.',
532
184
  };
533
185
  }
534
186
 
535
- const currentTier = await getTierFromApiKey(apiKey);
187
+ const tier = await getTierFromApiKey(apiKey);
536
188
 
537
- if (!currentTier) {
189
+ if (!tier) {
538
190
  return {
539
191
  hasAccess: false,
540
192
  tier: null,
541
- reason: 'Invalid API key. Please check your API key or get a new one at https://vibecheckai.dev',
542
- upgradeUrl: 'https://vibecheckai.dev'
193
+ reason: 'Invalid API key.',
543
194
  };
544
195
  }
545
196
 
546
- const currentTierConfig = TIERS[currentTier];
547
-
548
- // Check if tool is allowed for current tier
549
- const allowedTools = [];
550
-
551
- // Accumulate tools from current tier and all lower tiers
552
- for (const [tierName, tierConfig] of Object.entries(TIERS)) {
553
- if (tierConfig.order <= currentTierConfig.order) {
554
- if (tierConfig.mcpTools) {
555
- if (tierConfig.mcpTools.includes('*')) {
556
- // Compliance tier - all tools allowed
557
- return {
558
- hasAccess: true,
559
- tier: currentTier,
560
- reason: 'Full MCP access'
561
- };
562
- }
563
- allowedTools.push(...tierConfig.mcpTools);
564
- }
565
- }
566
- }
567
-
568
- // Check if tool is in allowed list
569
- const hasAccess = allowedTools.includes(toolName);
570
-
571
- if (!hasAccess) {
572
- // Find which tier has this tool
573
- let requiredTier = null;
574
- for (const [tierName, tierConfig] of Object.entries(TIERS)) {
575
- if (tierConfig.mcpTools?.includes(toolName) || tierConfig.mcpTools?.includes('*')) {
576
- requiredTier = tierName;
577
- break;
578
- }
579
- }
580
-
581
- const requiredTierConfig = requiredTier ? TIERS[requiredTier] : null;
582
-
583
- return {
584
- hasAccess: false,
585
- tier: currentTier,
586
- requiredTier,
587
- reason: requiredTierConfig
588
- ? `${toolName} requires ${requiredTierConfig.name} tier ($${requiredTierConfig.price}/mo). Current: ${currentTierConfig.name}`
589
- : `${toolName} is not available`,
590
- upgradeUrl: 'https://vibecheckai.dev'
591
- };
592
- }
197
+ const hasAccess = canAccessTool(tier, toolName);
593
198
 
594
199
  return {
595
- hasAccess: true,
596
- tier: currentTier,
597
- reason: 'Access granted'
200
+ hasAccess,
201
+ tier,
202
+ firewallMode: getFirewallMode(tier),
203
+ reason: hasAccess
204
+ ? 'Access granted'
205
+ : `${toolName} requires Pro ($69/mo). Upgrade at https://vibecheckai.dev/pricing`,
598
206
  };
599
207
  }
600
208
 
601
- /**
602
- * Middleware for MCP tool handlers with tool-specific checking
603
- */
604
- export function withMcpToolCheck(toolName, handler) {
209
+ // ============================================================================
210
+ // MIDDLEWARE
211
+ // ============================================================================
212
+
213
+ export function withTierCheck(toolName, handler) {
605
214
  return async (args) => {
606
215
  const access = await getMcpToolAccess(toolName, args?.apiKey);
607
216
 
@@ -609,77 +218,69 @@ export function withMcpToolCheck(toolName, handler) {
609
218
  return {
610
219
  content: [{
611
220
  type: "text",
612
- text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nUpgrade at: ${access.upgradeUrl}`
221
+ text: `This tool requires Pro.\n\n${toolName} is a Pro feature.\n\nUpgrade to Pro ($69/mo) to unlock:\n- Authority System (verdicts & approvals)\n- Agent Conductor (multi-agent coordination)\n- Agent Firewall (enforce mode)\n\nhttps://vibecheckai.dev/pricing`
613
222
  }],
614
223
  isError: true
615
224
  };
616
225
  }
617
226
 
618
- // Add tier info to args for the handler
619
227
  args._tier = access.tier;
228
+ args._firewallMode = access.firewallMode;
620
229
  return handler(args);
621
230
  };
622
231
  }
623
232
 
624
- /**
625
- * Get current user info
626
- */
233
+ // ============================================================================
234
+ // USER INFO
235
+ // ============================================================================
236
+
237
+ async function loadUserConfig() {
238
+ try {
239
+ const configPath = path.join(os.homedir(), '.vibecheck', 'credentials.json');
240
+ const data = await fs.readFile(configPath, 'utf-8');
241
+ return JSON.parse(data);
242
+ } catch {
243
+ return null;
244
+ }
245
+ }
246
+
627
247
  export async function getUserInfo() {
628
248
  const config = await loadUserConfig();
629
- if (!config) {
249
+
250
+ if (!config?.apiKey) {
630
251
  return {
631
252
  authenticated: false,
632
253
  tier: 'free',
633
- message: 'Not authenticated. Run: vibecheck auth --key YOUR_API_KEY'
254
+ tools: FREE_TOOLS,
255
+ firewallMode: 'observe',
634
256
  };
635
257
  }
636
258
 
637
- const tier = getTierFromApiKey(config.apiKey);
638
- const tierConfig = TIERS[tier];
639
-
640
- // Get features available for this tier from CLI_FEATURE_TIERS
641
- const availableFeatures = Object.entries(CLI_FEATURE_TIERS)
642
- .filter(([, reqTier]) => TIERS[reqTier].order <= tierConfig.order)
643
- .map(([feature]) => feature);
259
+ const tier = await getTierFromApiKey(config.apiKey);
644
260
 
645
261
  return {
646
262
  authenticated: true,
647
- tier,
263
+ tier: tier || 'free',
648
264
  email: config.email,
649
- authenticatedAt: config.authenticatedAt,
650
- features: availableFeatures,
651
- limits: tierConfig.limits,
652
- mcpTools: tierConfig.mcpTools,
265
+ tools: tier === 'pro' ? ALL_TOOLS : FREE_TOOLS,
266
+ firewallMode: getFirewallMode(tier || 'free'),
653
267
  };
654
268
  }
655
269
 
656
- /**
657
- * Get list of MCP tools available for current tier
658
- */
659
- export async function getAvailableMcpTools(providedApiKey = null) {
660
- const userConfig = await loadUserConfig();
661
- const apiKey = providedApiKey || userConfig?.apiKey;
662
-
663
- const currentTier = getTierFromApiKey(apiKey);
664
- const currentTierConfig = TIERS[currentTier];
665
-
666
- const allowedTools = new Set();
667
-
668
- // Accumulate tools from current tier and all lower tiers
669
- for (const [tierName, tierConfig] of Object.entries(TIERS)) {
670
- if (tierConfig.order <= currentTierConfig.order) {
671
- if (tierConfig.mcpTools) {
672
- if (tierConfig.mcpTools.includes('*')) {
673
- return { tier: currentTier, tools: ['*'], unlimited: true };
674
- }
675
- tierConfig.mcpTools.forEach(t => allowedTools.add(t));
676
- }
677
- }
678
- }
679
-
270
+ export async function getAvailableMcpTools(apiKey) {
271
+ const tier = apiKey ? await getTierFromApiKey(apiKey) : 'free';
680
272
  return {
681
- tier: currentTier,
682
- tools: Array.from(allowedTools),
683
- unlimited: false
273
+ tier: tier || 'free',
274
+ tools: (tier === 'pro') ? ALL_TOOLS : FREE_TOOLS,
275
+ firewallMode: getFirewallMode(tier || 'free'),
684
276
  };
685
277
  }
278
+
279
+ // Legacy exports for backward compatibility
280
+ export async function getFeatureAccessStatus(featureName, apiKey) {
281
+ return getMcpToolAccess(featureName, apiKey);
282
+ }
283
+
284
+ export function withMcpToolCheck(toolName, handler) {
285
+ return withTierCheck(toolName, handler);
286
+ }