@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.
- package/README.md +1 -1
- package/bin/runners/context/generators/cursor-enhanced.js +99 -13
- package/mcp-server/.eslintrc.json +24 -0
- package/mcp-server/README.md +425 -135
- package/mcp-server/SPEC.md +583 -0
- package/mcp-server/configs/README.md +172 -0
- package/mcp-server/configs/claude-desktop-pro.json +31 -0
- package/mcp-server/configs/claude-desktop-with-workspace.json +25 -0
- package/mcp-server/configs/claude-desktop.json +19 -0
- package/mcp-server/configs/cursor-mcp.json +21 -0
- package/mcp-server/configs/windsurf-mcp.json +17 -0
- package/mcp-server/mcp-config.example.json +9 -0
- package/mcp-server/package-lock.json +1631 -0
- package/mcp-server/package.json +49 -34
- package/mcp-server/src/cli.ts +185 -0
- package/mcp-server/src/index.ts +85 -0
- package/mcp-server/src/server.ts +1933 -0
- package/mcp-server/src/services/cache-service.ts +466 -0
- package/mcp-server/src/services/cli-service.ts +345 -0
- package/mcp-server/src/services/context-manager.ts +717 -0
- package/mcp-server/src/services/firewall-service.ts +662 -0
- package/mcp-server/src/services/git-service.ts +671 -0
- package/mcp-server/src/services/index.ts +52 -0
- package/mcp-server/src/services/prompt-builder-service.ts +1031 -0
- package/mcp-server/src/services/session-service.ts +550 -0
- package/mcp-server/src/services/tier-service.ts +470 -0
- package/mcp-server/src/types.ts +351 -0
- package/mcp-server/tsconfig.json +16 -27
- package/package.json +6 -6
- package/mcp-server/.guardrail/audit/audit.log.jsonl +0 -2
- package/mcp-server/.specs/architecture.mdc +0 -90
- package/mcp-server/.specs/security.mdc +0 -30
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-checkpoint.js +0 -364
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/architect-tools.js +0 -707
- package/mcp-server/audit-mcp.js +0 -206
- package/mcp-server/authority-tools.js +0 -569
- package/mcp-server/codebase-architect-tools.js +0 -838
- package/mcp-server/conductor/conflict-resolver.js +0 -588
- package/mcp-server/conductor/execution-planner.js +0 -544
- package/mcp-server/conductor/index.js +0 -377
- package/mcp-server/conductor/lock-manager.js +0 -615
- package/mcp-server/conductor/request-queue.js +0 -550
- package/mcp-server/conductor/session-manager.js +0 -500
- package/mcp-server/conductor/tools.js +0 -510
- package/mcp-server/consolidated-tools.js +0 -1170
- package/mcp-server/deprecation-middleware.js +0 -282
- package/mcp-server/handlers/index.ts +0 -15
- package/mcp-server/handlers/tool-handler.ts +0 -593
- package/mcp-server/hygiene-tools.js +0 -428
- package/mcp-server/index-v1.js +0 -698
- package/mcp-server/index.js +0 -2940
- package/mcp-server/intelligence-tools.js +0 -664
- package/mcp-server/intent-drift-tools.js +0 -873
- package/mcp-server/intent-firewall-interceptor.js +0 -529
- package/mcp-server/lib/api-client.cjs +0 -13
- package/mcp-server/lib/cache-wrapper.cjs +0 -383
- package/mcp-server/lib/error-envelope.js +0 -138
- package/mcp-server/lib/executor.ts +0 -499
- package/mcp-server/lib/index.ts +0 -29
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/lib/rate-limiter.js +0 -166
- package/mcp-server/lib/sandbox.test.ts +0 -519
- package/mcp-server/lib/sandbox.ts +0 -395
- package/mcp-server/lib/types.ts +0 -267
- package/mcp-server/logger.js +0 -173
- package/mcp-server/manifest.json +0 -473
- package/mcp-server/mdc-generator.js +0 -298
- package/mcp-server/premium-tools.js +0 -1275
- package/mcp-server/proof-tools.js +0 -571
- package/mcp-server/registry/tool-registry.js +0 -586
- package/mcp-server/registry/tools.json +0 -619
- package/mcp-server/registry.test.ts +0 -340
- package/mcp-server/test-mcp.js +0 -108
- package/mcp-server/test-tools.js +0 -36
- package/mcp-server/tests/tier-gating.test.js +0 -297
- package/mcp-server/tier-auth.js +0 -767
- package/mcp-server/tools/index.js +0 -72
- package/mcp-server/tools-reorganized.ts +0 -244
- package/mcp-server/tools-v3.js +0 -1004
- package/mcp-server/truth-context.js +0 -622
- package/mcp-server/truth-firewall-tools.js +0 -2183
- package/mcp-server/vibecheck-2.0-tools.js +0 -761
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/mcp-server/vibecheck-tools.js +0 -1075
package/mcp-server/tier-auth.js
DELETED
|
@@ -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
|
-
}
|