@vibecheckai/cli 3.9.1 → 4.0.1

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 (86) hide show
  1. package/README.md +1 -1
  2. package/bin/runners/context/generators/cursor-enhanced.js +99 -13
  3. package/mcp-server/.eslintrc.json +24 -0
  4. package/mcp-server/README.md +425 -135
  5. package/mcp-server/SPEC.md +583 -0
  6. package/mcp-server/configs/README.md +172 -0
  7. package/mcp-server/configs/claude-desktop-pro.json +31 -0
  8. package/mcp-server/configs/claude-desktop-with-workspace.json +25 -0
  9. package/mcp-server/configs/claude-desktop.json +19 -0
  10. package/mcp-server/configs/cursor-mcp.json +21 -0
  11. package/mcp-server/configs/windsurf-mcp.json +17 -0
  12. package/mcp-server/mcp-config.example.json +9 -0
  13. package/mcp-server/package-lock.json +1631 -0
  14. package/mcp-server/package.json +49 -34
  15. package/mcp-server/src/cli.ts +185 -0
  16. package/mcp-server/src/index.ts +85 -0
  17. package/mcp-server/src/server.ts +1933 -0
  18. package/mcp-server/src/services/cache-service.ts +466 -0
  19. package/mcp-server/src/services/cli-service.ts +345 -0
  20. package/mcp-server/src/services/context-manager.ts +717 -0
  21. package/mcp-server/src/services/firewall-service.ts +662 -0
  22. package/mcp-server/src/services/git-service.ts +671 -0
  23. package/mcp-server/src/services/index.ts +52 -0
  24. package/mcp-server/src/services/prompt-builder-service.ts +1031 -0
  25. package/mcp-server/src/services/session-service.ts +550 -0
  26. package/mcp-server/src/services/tier-service.ts +470 -0
  27. package/mcp-server/src/types.ts +351 -0
  28. package/mcp-server/tsconfig.json +16 -27
  29. package/package.json +6 -6
  30. package/mcp-server/.guardrail/audit/audit.log.jsonl +0 -2
  31. package/mcp-server/.specs/architecture.mdc +0 -90
  32. package/mcp-server/.specs/security.mdc +0 -30
  33. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  34. package/mcp-server/agent-checkpoint.js +0 -364
  35. package/mcp-server/agent-firewall-interceptor.js +0 -500
  36. package/mcp-server/architect-tools.js +0 -707
  37. package/mcp-server/audit-mcp.js +0 -206
  38. package/mcp-server/authority-tools.js +0 -569
  39. package/mcp-server/codebase-architect-tools.js +0 -838
  40. package/mcp-server/conductor/conflict-resolver.js +0 -588
  41. package/mcp-server/conductor/execution-planner.js +0 -544
  42. package/mcp-server/conductor/index.js +0 -377
  43. package/mcp-server/conductor/lock-manager.js +0 -615
  44. package/mcp-server/conductor/request-queue.js +0 -550
  45. package/mcp-server/conductor/session-manager.js +0 -500
  46. package/mcp-server/conductor/tools.js +0 -510
  47. package/mcp-server/consolidated-tools.js +0 -1170
  48. package/mcp-server/deprecation-middleware.js +0 -282
  49. package/mcp-server/handlers/index.ts +0 -15
  50. package/mcp-server/handlers/tool-handler.ts +0 -593
  51. package/mcp-server/hygiene-tools.js +0 -428
  52. package/mcp-server/index-v1.js +0 -698
  53. package/mcp-server/index.js +0 -2940
  54. package/mcp-server/intelligence-tools.js +0 -664
  55. package/mcp-server/intent-drift-tools.js +0 -873
  56. package/mcp-server/intent-firewall-interceptor.js +0 -529
  57. package/mcp-server/lib/api-client.cjs +0 -13
  58. package/mcp-server/lib/cache-wrapper.cjs +0 -383
  59. package/mcp-server/lib/error-envelope.js +0 -138
  60. package/mcp-server/lib/executor.ts +0 -499
  61. package/mcp-server/lib/index.ts +0 -29
  62. package/mcp-server/lib/logger.cjs +0 -30
  63. package/mcp-server/lib/rate-limiter.js +0 -166
  64. package/mcp-server/lib/sandbox.test.ts +0 -519
  65. package/mcp-server/lib/sandbox.ts +0 -395
  66. package/mcp-server/lib/types.ts +0 -267
  67. package/mcp-server/logger.js +0 -173
  68. package/mcp-server/manifest.json +0 -473
  69. package/mcp-server/mdc-generator.js +0 -298
  70. package/mcp-server/premium-tools.js +0 -1275
  71. package/mcp-server/proof-tools.js +0 -571
  72. package/mcp-server/registry/tool-registry.js +0 -586
  73. package/mcp-server/registry/tools.json +0 -619
  74. package/mcp-server/registry.test.ts +0 -340
  75. package/mcp-server/test-mcp.js +0 -108
  76. package/mcp-server/test-tools.js +0 -36
  77. package/mcp-server/tests/tier-gating.test.js +0 -297
  78. package/mcp-server/tier-auth.js +0 -767
  79. package/mcp-server/tools/index.js +0 -72
  80. package/mcp-server/tools-reorganized.ts +0 -244
  81. package/mcp-server/tools-v3.js +0 -1004
  82. package/mcp-server/truth-context.js +0 -622
  83. package/mcp-server/truth-firewall-tools.js +0 -2183
  84. package/mcp-server/vibecheck-2.0-tools.js +0 -761
  85. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
  86. package/mcp-server/vibecheck-tools.js +0 -1075
@@ -1,767 +0,0 @@
1
- /**
2
- * MCP Server Tier Authentication
3
- *
4
- * ═══════════════════════════════════════════════════════════════════════════
5
- * TIER MODEL - Aligned with CLI entitlements-v2.js
6
- * ═══════════════════════════════════════════════════════════════════════════
7
- *
8
- * Simple 2-tier model:
9
- * - FREE ($0): Inspect & Observe
10
- * - PRO ($49/mo): Fix, Prove & Enforce
11
- *
12
- * ┌────────────────────────────────────────────────────────────────────────┐
13
- * │ MCP Tool │ Tier │ CLI Equivalent │
14
- * ├────────────────────────────────────────────────────────────────────────┤
15
- * │ vibecheck.scan │ FREE │ scan │
16
- * │ vibecheck.scan (--autofix) │ PRO │ scan.autofix │
17
- * │ vibecheck.ctx │ FREE │ ctx │
18
- * │ vibecheck.verify │ FREE │ verify │
19
- * │ vibecheck.report │ FREE │ report │
20
- * │ vibecheck.status │ FREE │ status │
21
- * │ vibecheck.doctor │ FREE │ doctor │
22
- * │ vibecheck.firewall (observe) │ FREE │ firewall.observe │
23
- * │ vibecheck.firewall (enforce) │ PRO │ firewall.enforce │
24
- * │ vibecheck.ship │ PRO │ ship │
25
- * │ vibecheck.fix │ PRO │ fix │
26
- * │ vibecheck.fix (--apply) │ PRO │ fix.apply │
27
- * │ vibecheck.prove │ PRO │ prove │
28
- * │ vibecheck.gate │ PRO │ gate │
29
- * │ vibecheck.badge │ PRO │ badge │
30
- * │ vibecheck.reality │ PRO │ reality.full │
31
- * │ vibecheck.ai_test │ PRO │ ai-test │
32
- * │ vibecheck.share │ PRO │ share │
33
- * │ authority.list │ FREE │ (read-only authority) │
34
- * │ authority.classify │ FREE │ (inventory analysis) │
35
- * │ authority.approve │ PRO │ (execute authority) │
36
- * │ vibecheck_conductor_status │ FREE │ (status only) │
37
- * │ vibecheck_conductor_* │ PRO │ (full coordination) │
38
- * │ vibecheck_agent_firewall_* │ PRO │ (enforce mode) │
39
- * └────────────────────────────────────────────────────────────────────────┘
40
- */
41
-
42
- import fs from "fs/promises";
43
- import path from "path";
44
- import os from "os";
45
- import crypto from "crypto";
46
-
47
- // ============================================================================
48
- // REDACTION - Secure API key masking
49
- // ============================================================================
50
-
51
- /**
52
- * Mask API key for secure display/logging.
53
- * @param {string} key
54
- * @returns {string}
55
- */
56
- export function redactApiKey(key) {
57
- if (!key || typeof key !== 'string' || key.length < 8) {
58
- return '****';
59
- }
60
-
61
- if (key.startsWith('grl_')) {
62
- return `grl_${'*'.repeat(8)}${key.slice(-4)}`;
63
- }
64
-
65
- const legacyMatch = key.match(/^(gr_[a-z]+_)/);
66
- if (legacyMatch) {
67
- return `${legacyMatch[1]}${'*'.repeat(8)}${key.slice(-4)}`;
68
- }
69
-
70
- if (key.length >= 12) {
71
- return `${key.slice(0, 3)}****${key.slice(-4)}`;
72
- }
73
-
74
- return '****';
75
- }
76
-
77
- // ============================================================================
78
- // ERROR CODES - Standard error envelope codes
79
- // ============================================================================
80
- export const ERROR_CODES = {
81
- NOT_ENTITLED: 'NOT_ENTITLED',
82
- INVALID_API_KEY: 'INVALID_API_KEY',
83
- RATE_LIMITED: 'RATE_LIMITED',
84
- OPTION_NOT_ENTITLED: 'OPTION_NOT_ENTITLED',
85
- API_KEY_REQUIRED: 'API_KEY_REQUIRED',
86
- };
87
-
88
- // ============================================================================
89
- // TIERS - Simple 2-tier model matching CLI
90
- // ============================================================================
91
- export const TIERS = {
92
- free: { name: 'FREE', price: 0 },
93
- pro: { name: 'PRO', price: 69 },
94
- };
95
-
96
- // ============================================================================
97
- // MCP TOOLS - Aligned with CLI entitlements-v2.js
98
- // ============================================================================
99
-
100
- /**
101
- * FREE TOOLS - Inspect & Observe
102
- * Matches CLI FREE_FEATURES: scan, ctx, verify, report, status, doctor, etc.
103
- */
104
- export const FREE_TOOLS = [
105
- // Core analysis (CLI: scan, ctx, verify)
106
- 'vibecheck.scan',
107
- 'vibecheck.ctx',
108
- 'vibecheck.verify',
109
-
110
- // Reports & setup (CLI: report, status, doctor)
111
- 'vibecheck.report',
112
- 'vibecheck.status',
113
- 'vibecheck.doctor',
114
-
115
- // Firewall observe mode (CLI: firewall.observe)
116
- 'vibecheck.firewall',
117
-
118
- // Authority (read-only)
119
- 'authority.list',
120
- 'authority.classify',
121
-
122
- // Conductor (status only)
123
- 'vibecheck_conductor_status',
124
-
125
- // Labs & experimental (CLI: labs, mdc)
126
- 'vibecheck.labs',
127
- 'vibecheck.mdc',
128
-
129
- // Allowlist management
130
- 'vibecheck.allowlist',
131
-
132
- // Context generation (basic - CLI: ctx)
133
- 'vibecheck.context',
134
-
135
- // Next action recommendation
136
- 'vibecheck.get_next_action',
137
- ];
138
-
139
- /**
140
- * PRO TOOLS - Fix, Prove & Enforce
141
- * Matches CLI PRO_FEATURES: ship, fix, prove, gate, badge, etc.
142
- */
143
- export const PRO_TOOLS = [
144
- // Core PRO (CLI: ship, fix, prove, gate, badge)
145
- 'vibecheck.ship',
146
- 'vibecheck.fix',
147
- 'vibecheck.prove',
148
- 'vibecheck.gate',
149
- 'vibecheck.badge',
150
-
151
- // Runtime verification (CLI: reality.full, ai-test)
152
- 'vibecheck.reality',
153
- 'vibecheck.ai_test',
154
-
155
- // Sharing & PR (CLI: share, pr)
156
- 'vibecheck.share',
157
-
158
- // Authority System (full - execute verdicts)
159
- 'authority.approve',
160
- 'authority.enforce',
161
-
162
- // Agent Conductor (full multi-agent coordination)
163
- 'vibecheck_conductor_register',
164
- 'vibecheck_conductor_acquire_lock',
165
- 'vibecheck_conductor_release_lock',
166
- 'vibecheck_conductor_propose',
167
- 'vibecheck_conductor_terminate',
168
-
169
- // Agent Firewall (enforce mode - CLI: firewall.enforce)
170
- 'vibecheck_agent_firewall_intercept',
171
- 'vibecheck.firewall.enforce',
172
-
173
- // Advanced features (CLI: checkpoint, polish, guard)
174
- 'vibecheck.checkpoint',
175
- 'vibecheck.polish',
176
- 'vibecheck.guard',
177
- ];
178
-
179
- export const ALL_TOOLS = [...FREE_TOOLS, ...PRO_TOOLS];
180
-
181
- /**
182
- * OPTION-LEVEL GATES
183
- * Some tools are FREE at base level but specific options require PRO
184
- */
185
- export const OPTION_GATES = {
186
- 'vibecheck.scan': {
187
- autofix: 'pro', // scan --autofix requires PRO
188
- fix: 'pro', // scan --fix requires PRO
189
- },
190
- 'vibecheck.fix': {
191
- apply: 'pro', // fix --apply requires PRO (plan is PRO too)
192
- loop: 'pro', // fix --loop requires PRO
193
- },
194
- 'vibecheck.firewall': {
195
- enforce: 'pro', // firewall --enforce requires PRO
196
- mode: { enforce: 'pro' },
197
- },
198
- 'vibecheck.reality': {
199
- full: 'pro', // reality full mode requires PRO
200
- auth: 'pro', // auth boundary testing requires PRO
201
- },
202
- };
203
-
204
- // ============================================================================
205
- // TIER CACHE
206
- // ============================================================================
207
- const tierCache = new Map();
208
- const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
209
-
210
- /**
211
- * Check if developer mode bypass is allowed.
212
- *
213
- * SECURITY: VIBECHECK_DEV_PRO is ONLY allowed in non-production environments.
214
- * This prevents environment variable injection from granting PRO access in production.
215
- *
216
- * @returns {{ enabled: boolean, tier?: string }} Dev override status
217
- */
218
- export function getDevModeOverride() {
219
- // SECURITY: Never allow dev override in production
220
- if (process.env.NODE_ENV === 'production') {
221
- return { enabled: false };
222
- }
223
- // Also block in CI environments to prevent pipeline exploitation
224
- if (process.env.CI === 'true' || process.env.CI === '1') {
225
- return { enabled: false };
226
- }
227
- // Only in development with explicit flag
228
- if (process.env.VIBECHECK_DEV_PRO === '1' && process.env.NODE_ENV === 'development') {
229
- console.warn('[DEV] VIBECHECK_DEV_PRO override active - PRO features unlocked');
230
- return { enabled: true, tier: 'pro' };
231
- }
232
- return { enabled: false };
233
- }
234
-
235
- /**
236
- * Check if developer mode bypass is allowed (legacy function for backward compatibility)
237
- * @returns {boolean} True only if in development AND VIBECHECK_DEV_PRO=1
238
- */
239
- function isDevProBypassAllowed() {
240
- return getDevModeOverride().enabled;
241
- }
242
-
243
- function hashKey(apiKey) {
244
- const crypto = require('crypto');
245
- return crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
246
- }
247
-
248
- // ============================================================================
249
- // ERROR ENVELOPE - Standard format for tier errors
250
- // ============================================================================
251
-
252
- /**
253
- * Create a standard ErrorEnvelope for tier-related errors
254
- * @param {string} code - Error code (NOT_ENTITLED, INVALID_API_KEY, etc.)
255
- * @param {string} message - Human-readable error message
256
- * @param {object} extra - Additional properties
257
- * @returns {object} ErrorEnvelope
258
- */
259
- export function createTierErrorEnvelope(code, message, extra = {}) {
260
- return {
261
- code,
262
- message,
263
- userAction: extra.userAction || 'Open billing',
264
- retryable: extra.retryable ?? false,
265
- tier: extra.tier,
266
- required: extra.required,
267
- upgradeUrl: 'https://vibecheckai.dev/pricing',
268
- ...extra,
269
- };
270
- }
271
-
272
- /**
273
- * Create API_KEY_REQUIRED error envelope
274
- * Used when a PRO tool is requested but no API key is provided
275
- */
276
- export function apiKeyRequiredError(toolName) {
277
- return createTierErrorEnvelope(ERROR_CODES.API_KEY_REQUIRED, `API key required for ${toolName}`, {
278
- tool: toolName,
279
- userAction: 'Add API Key',
280
- retryable: true,
281
- nextSteps: [
282
- 'Get your API key at https://vibecheckai.dev/dashboard',
283
- 'Run: vibecheck login',
284
- 'Or set VIBECHECK_API_KEY environment variable',
285
- 'Then pass apiKey in tool arguments or configure in ~/.vibecheck/credentials.json',
286
- ],
287
- });
288
- }
289
-
290
- /**
291
- * Create NOT_ENTITLED error envelope
292
- */
293
- export function notEntitledError(toolName, currentTier = 'free', requiredTier = 'pro', hasApiKey = true) {
294
- // If no API key was provided, give more specific guidance
295
- if (!hasApiKey) {
296
- return createTierErrorEnvelope(ERROR_CODES.NOT_ENTITLED, `${toolName} requires PRO - add your API key`, {
297
- tier: currentTier,
298
- required: requiredTier,
299
- tool: toolName,
300
- userAction: 'Add API Key',
301
- retryable: true,
302
- nextSteps: [
303
- `${toolName} requires PRO ($49/mo) subscription`,
304
- 'If you have PRO, add your API key:',
305
- ' - Run: vibecheck login',
306
- ' - Or pass apiKey in tool arguments',
307
- ' - Or set VIBECHECK_API_KEY environment variable',
308
- 'Get your API key at https://vibecheckai.dev/dashboard',
309
- 'Upgrade at https://vibecheckai.dev/pricing',
310
- ],
311
- });
312
- }
313
-
314
- return createTierErrorEnvelope(ERROR_CODES.NOT_ENTITLED, `Requires ${requiredTier.toUpperCase()}`, {
315
- tier: currentTier,
316
- required: requiredTier,
317
- tool: toolName,
318
- userAction: 'Open billing',
319
- retryable: false,
320
- nextSteps: [
321
- `Upgrade to ${requiredTier.toUpperCase()} ($49/mo) to unlock this feature`,
322
- 'Visit https://vibecheckai.dev/pricing',
323
- 'Run: vibecheck upgrade',
324
- ],
325
- });
326
- }
327
-
328
- /**
329
- * Create OPTION_NOT_ENTITLED error envelope
330
- */
331
- export function optionNotEntitledError(toolName, option, currentTier = 'free', requiredTier = 'pro') {
332
- return createTierErrorEnvelope(ERROR_CODES.OPTION_NOT_ENTITLED, `Option --${option} requires ${requiredTier.toUpperCase()}`, {
333
- tier: currentTier,
334
- required: requiredTier,
335
- tool: toolName,
336
- option,
337
- userAction: 'Open billing',
338
- retryable: false,
339
- nextSteps: [
340
- `The --${option} flag requires ${requiredTier.toUpperCase()} subscription`,
341
- `Base ${toolName} is available on FREE tier`,
342
- 'Visit https://vibecheckai.dev/pricing to upgrade',
343
- ],
344
- });
345
- }
346
-
347
- // ============================================================================
348
- // TIER VALIDATION
349
- // ============================================================================
350
-
351
- export async function getTierFromApiKey(apiKey) {
352
- // Developer mode bypass (blocked in production)
353
- if (isDevProBypassAllowed()) {
354
- return 'pro';
355
- }
356
-
357
- if (!apiKey || typeof apiKey !== 'string' || apiKey.length < 10) {
358
- return 'free'; // No API key = free tier (not null)
359
- }
360
-
361
- const keyHash = hashKey(apiKey);
362
- const now = Date.now();
363
-
364
- // Check cache
365
- const cached = tierCache.get(keyHash);
366
- if (cached && cached.expiresAt > now) {
367
- return cached.tier;
368
- }
369
-
370
- // Validate with API
371
- try {
372
- const response = await fetch('https://api.vibecheckai.dev/v1/auth/whoami', {
373
- headers: { 'Authorization': `Bearer ${apiKey}` },
374
- signal: AbortSignal.timeout(10000),
375
- });
376
-
377
- if (!response.ok) {
378
- // Invalid key - default to free
379
- tierCache.set(keyHash, { tier: 'free', expiresAt: now + 60000 }); // Short cache for invalid
380
- return 'free';
381
- }
382
-
383
- const data = await response.json();
384
- const plan = data.plan?.toLowerCase() || data.tier?.toLowerCase() || 'free';
385
-
386
- // Map any paid plan to 'pro' (STARTER, PRO, ENTERPRISE all = pro)
387
- const tier = (plan === 'free') ? 'free' : 'pro';
388
-
389
- tierCache.set(keyHash, { tier, expiresAt: now + CACHE_TTL });
390
- return tier;
391
-
392
- } catch {
393
- // Network error - check stale cache or default to free
394
- if (cached) return cached.tier;
395
- return 'free';
396
- }
397
- }
398
-
399
- // ============================================================================
400
- // USER CONFIG
401
- // ============================================================================
402
-
403
- /**
404
- * Load user configuration from credential files.
405
- * Checks multiple locations in priority order:
406
- * 1. ~/.vibecheck/auth.json (new unified format)
407
- * 2. ~/.vibecheck/credentials.json (legacy TS CLI)
408
- * 3. ~/.config/vibecheck/config.json (legacy JS CLI)
409
- *
410
- * @returns {Promise<{apiKey?: string, email?: string}|null>}
411
- */
412
- async function loadUserConfig() {
413
- const locations = [
414
- // New unified auth file (highest priority)
415
- path.join(os.homedir(), '.vibecheck', 'auth.json'),
416
- // Legacy credentials file
417
- path.join(os.homedir(), '.vibecheck', 'credentials.json'),
418
- // Legacy config file (Unix)
419
- path.join(os.homedir(), '.config', 'vibecheck', 'config.json'),
420
- ];
421
-
422
- // Windows legacy location
423
- if (process.platform === 'win32' && process.env.APPDATA) {
424
- locations.push(path.join(process.env.APPDATA, 'vibecheck', 'config.json'));
425
- }
426
-
427
- for (const configPath of locations) {
428
- try {
429
- const data = await fs.readFile(configPath, 'utf-8');
430
- const config = JSON.parse(data);
431
- if (config?.apiKey) {
432
- return config;
433
- }
434
- } catch {
435
- // Try next location
436
- continue;
437
- }
438
- }
439
-
440
- return null;
441
- }
442
-
443
- // ============================================================================
444
- // ACCESS CONTROL
445
- // ============================================================================
446
-
447
- export function isPro(tier) {
448
- // Developer mode bypass (blocked in production)
449
- if (isDevProBypassAllowed()) return true;
450
- return tier === 'pro';
451
- }
452
-
453
- export function canAccessTool(tier, toolName) {
454
- // Developer mode bypass (blocked in production)
455
- if (isDevProBypassAllowed()) return true;
456
-
457
- // PRO gets everything
458
- if (tier === 'pro') return true;
459
-
460
- // FREE can access FREE tools
461
- return FREE_TOOLS.includes(toolName);
462
- }
463
-
464
- /**
465
- * Check option-level access
466
- * @param {string} tier - User tier
467
- * @param {string} toolName - Tool name
468
- * @param {object} args - Tool arguments
469
- * @returns {{ allowed: boolean, blockedOption?: string, required?: string }}
470
- */
471
- export function checkOptionAccess(tier, toolName, args) {
472
- // Developer mode (blocked in production) or PRO = full access
473
- if (isDevProBypassAllowed() || tier === 'pro') {
474
- return { allowed: true };
475
- }
476
-
477
- const gates = OPTION_GATES[toolName];
478
- if (!gates || !args) {
479
- return { allowed: true };
480
- }
481
-
482
- // Check each gated option
483
- for (const [option, requiredTier] of Object.entries(gates)) {
484
- // Handle nested object gates (e.g., mode: { enforce: 'pro' })
485
- if (typeof requiredTier === 'object') {
486
- const argValue = args[option];
487
- if (argValue && requiredTier[argValue] === 'pro') {
488
- return { allowed: false, blockedOption: `${option}=${argValue}`, required: 'pro' };
489
- }
490
- } else if (args[option] === true && requiredTier === 'pro') {
491
- return { allowed: false, blockedOption: option, required: 'pro' };
492
- }
493
- }
494
-
495
- return { allowed: true };
496
- }
497
-
498
- /**
499
- * Get firewall mode based on tier
500
- * - FREE: observe (log only)
501
- * - PRO: enforce (block violations)
502
- */
503
- export function getFirewallMode(tier) {
504
- if (isDevProBypassAllowed()) return 'enforce';
505
- return tier === 'pro' ? 'enforce' : 'observe';
506
- }
507
-
508
- /**
509
- * Check if user can use full conductor features
510
- */
511
- export function canUseConductor(tier) {
512
- if (isDevProBypassAllowed()) return true;
513
- return tier === 'pro';
514
- }
515
-
516
- // Legacy alias (typo fix)
517
- export const canUseCondcutor = canUseConductor;
518
-
519
- /**
520
- * Check if user can approve authorities
521
- */
522
- export function canApproveAuthority(tier) {
523
- if (isDevProBypassAllowed()) return true;
524
- return tier === 'pro';
525
- }
526
-
527
- /**
528
- * Resolve API key from multiple sources
529
- * Priority: 1. Passed directly 2. Environment variable 3. Credentials file
530
- *
531
- * @param {string} apiKey - API key passed directly (optional)
532
- * @returns {Promise<{apiKey: string|null, source: string}>}
533
- */
534
- export async function resolveApiKey(apiKey) {
535
- // 1. Use directly provided API key
536
- if (apiKey && typeof apiKey === 'string' && apiKey.length >= 10) {
537
- return { apiKey, source: 'args' };
538
- }
539
-
540
- // 2. Check environment variable
541
- const envKey = process.env.VIBECHECK_API_KEY;
542
- if (envKey && typeof envKey === 'string' && envKey.length >= 10) {
543
- return { apiKey: envKey, source: 'env' };
544
- }
545
-
546
- // 3. Check credentials file
547
- const config = await loadUserConfig();
548
- if (config?.apiKey && typeof config.apiKey === 'string' && config.apiKey.length >= 10) {
549
- return { apiKey: config.apiKey, source: 'credentials' };
550
- }
551
-
552
- return { apiKey: null, source: 'none' };
553
- }
554
-
555
- /**
556
- * Get MCP tool access with full ErrorEnvelope support
557
- *
558
- * @param {string} toolName - Tool name
559
- * @param {string} apiKey - API key (optional)
560
- * @param {object} args - Tool arguments for option-level checks
561
- * @returns {Promise<{hasAccess: boolean, tier: string, error?: object}>}
562
- */
563
- export async function getMcpToolAccess(toolName, apiKey, args = {}) {
564
- // Resolve API key from multiple sources
565
- const resolved = await resolveApiKey(apiKey);
566
- const resolvedApiKey = resolved.apiKey;
567
- const hasApiKey = resolvedApiKey !== null;
568
-
569
- const tier = await getTierFromApiKey(resolvedApiKey);
570
-
571
- // Check tool-level access
572
- const hasToolAccess = canAccessTool(tier, toolName);
573
-
574
- if (!hasToolAccess) {
575
- // Check if this is a PRO tool and no API key was provided
576
- const isPROTool = PRO_TOOLS.includes(toolName);
577
-
578
- return {
579
- hasAccess: false,
580
- tier,
581
- hasApiKey,
582
- apiKeySource: resolved.source,
583
- firewallMode: getFirewallMode(tier),
584
- error: notEntitledError(toolName, tier, 'pro', hasApiKey),
585
- reason: hasApiKey
586
- ? `${toolName} requires Pro ($49/mo). Upgrade at https://vibecheckai.dev/pricing`
587
- : `${toolName} requires Pro. Add your API key first: run 'vibecheck login' or pass apiKey in tool arguments.`,
588
- };
589
- }
590
-
591
- // Check option-level access
592
- const optionCheck = checkOptionAccess(tier, toolName, args);
593
- if (!optionCheck.allowed) {
594
- return {
595
- hasAccess: false,
596
- tier,
597
- hasApiKey,
598
- apiKeySource: resolved.source,
599
- firewallMode: getFirewallMode(tier),
600
- error: optionNotEntitledError(toolName, optionCheck.blockedOption, tier, optionCheck.required),
601
- reason: `Option --${optionCheck.blockedOption} requires Pro`,
602
- };
603
- }
604
-
605
- return {
606
- hasAccess: true,
607
- tier,
608
- hasApiKey,
609
- apiKeySource: resolved.source,
610
- firewallMode: getFirewallMode(tier),
611
- reason: 'Access granted',
612
- };
613
- }
614
-
615
- // ============================================================================
616
- // MIDDLEWARE
617
- // ============================================================================
618
-
619
- /**
620
- * Middleware that wraps a tool handler with tier checking
621
- * Returns proper ErrorEnvelope on failure
622
- */
623
- export function withTierCheck(toolName, handler) {
624
- return async (args) => {
625
- const access = await getMcpToolAccess(toolName, args?.apiKey, args);
626
-
627
- if (!access.hasAccess) {
628
- // Return proper ErrorEnvelope format
629
- return {
630
- content: [{
631
- type: "text",
632
- text: JSON.stringify({
633
- ok: false,
634
- error: access.error,
635
- }, null, 2)
636
- }],
637
- isError: true,
638
- // Also include error envelope at top level for structured access
639
- _error: access.error,
640
- };
641
- }
642
-
643
- args._tier = access.tier;
644
- args._firewallMode = access.firewallMode;
645
- return handler(args);
646
- };
647
- }
648
-
649
- /**
650
- * Create tier gate response for direct use in handlers
651
- * Returns null if access granted, ErrorEnvelope if denied
652
- */
653
- export async function checkTierGate(toolName, apiKey, args = {}) {
654
- const access = await getMcpToolAccess(toolName, apiKey, args);
655
-
656
- if (!access.hasAccess) {
657
- return {
658
- content: [{
659
- type: "text",
660
- text: JSON.stringify({
661
- ok: false,
662
- error: access.error,
663
- }, null, 2)
664
- }],
665
- isError: true,
666
- };
667
- }
668
-
669
- return null; // Access granted
670
- }
671
-
672
- // ============================================================================
673
- // USER INFO
674
- // ============================================================================
675
-
676
- export async function getUserInfo() {
677
- const config = await loadUserConfig();
678
-
679
- if (!config?.apiKey) {
680
- return {
681
- authenticated: false,
682
- tier: 'free',
683
- tools: FREE_TOOLS,
684
- firewallMode: 'observe',
685
- };
686
- }
687
-
688
- const tier = await getTierFromApiKey(config.apiKey);
689
-
690
- return {
691
- authenticated: true,
692
- tier: tier || 'free',
693
- email: config.email,
694
- tools: tier === 'pro' ? ALL_TOOLS : FREE_TOOLS,
695
- firewallMode: getFirewallMode(tier || 'free'),
696
- };
697
- }
698
-
699
- export async function getAvailableMcpTools(apiKey) {
700
- const tier = apiKey ? await getTierFromApiKey(apiKey) : 'free';
701
- return {
702
- tier: tier || 'free',
703
- tools: (tier === 'pro') ? ALL_TOOLS : FREE_TOOLS,
704
- firewallMode: getFirewallMode(tier || 'free'),
705
- };
706
- }
707
-
708
- // ============================================================================
709
- // LEGACY EXPORTS - Backward compatibility
710
- // ============================================================================
711
-
712
- /**
713
- * @deprecated Use getMcpToolAccess instead
714
- */
715
- export async function getFeatureAccessStatus(featureName, apiKey, args = {}) {
716
- const result = await getMcpToolAccess(featureName, apiKey, args);
717
- // Add upgradeUrl for legacy consumers
718
- return {
719
- ...result,
720
- upgradeUrl: 'https://vibecheckai.dev/pricing',
721
- };
722
- }
723
-
724
- /**
725
- * @deprecated Use withTierCheck instead
726
- */
727
- export function withMcpToolCheck(toolName, handler) {
728
- return withTierCheck(toolName, handler);
729
- }
730
-
731
- // ============================================================================
732
- // TOOL TIER MAPPING - For documentation and testing
733
- // ============================================================================
734
-
735
- /**
736
- * Get the tier requirements table for all MCP tools
737
- * Used for documentation and test generation
738
- */
739
- export function getToolTierTable() {
740
- const table = [];
741
-
742
- for (const tool of FREE_TOOLS) {
743
- table.push({ tool, tier: 'FREE', options: OPTION_GATES[tool] || null });
744
- }
745
-
746
- for (const tool of PRO_TOOLS) {
747
- table.push({ tool, tier: 'PRO', options: null });
748
- }
749
-
750
- return table;
751
- }
752
-
753
- /**
754
- * Print formatted tier table (for CLI/debugging)
755
- */
756
- export function printTierTable() {
757
- console.log('┌────────────────────────────────────┬──────┬─────────────────────────┐');
758
- console.log('│ MCP Tool │ Tier │ Gated Options │');
759
- console.log('├────────────────────────────────────┼──────┼─────────────────────────┤');
760
-
761
- for (const { tool, tier, options } of getToolTierTable()) {
762
- const optStr = options ? Object.keys(options).join(', ') : '-';
763
- console.log(`│ ${tool.padEnd(34)} │ ${tier.padEnd(4)} │ ${optStr.padEnd(23)} │`);
764
- }
765
-
766
- console.log('└────────────────────────────────────┴──────┴─────────────────────────┘');
767
- }