@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.
- package/bin/registry.js +255 -226
- package/bin/runners/lib/analyzers.js +55 -123
- package/bin/runners/lib/entitlements-v2.js +96 -505
- package/bin/runners/lib/scan-output.js +18 -19
- package/bin/runners/lib/ship-output.js +18 -25
- package/bin/runners/lib/upsell.js +90 -338
- package/bin/runners/runScan.js +14 -1
- package/bin/vibecheck.js +6 -11
- package/mcp-server/index.js +13 -623
- package/mcp-server/lib/api-client.cjs +7 -299
- package/mcp-server/package.json +1 -1
- package/mcp-server/tier-auth.js +175 -574
- package/mcp-server/tools-v3.js +495 -533
- package/package.json +1 -1
package/mcp-server/tier-auth.js
CHANGED
|
@@ -1,607 +1,216 @@
|
|
|
1
|
-
/* vibecheck-disable */
|
|
2
1
|
/**
|
|
3
|
-
* MCP Server Tier Authentication
|
|
2
|
+
* MCP Server Tier Authentication
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* Simple 2-tier model:
|
|
5
|
+
* - FREE ($0): Inspect & Observe
|
|
6
|
+
* - PRO ($69/mo): Fix, Prove & Enforce
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
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
|
-
//
|
|
22
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
203
|
-
//
|
|
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
|
-
*
|
|
31
|
+
* FREE TOOLS (7) - Inspect & Observe
|
|
276
32
|
*/
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
//
|
|
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
|
-
*
|
|
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
|
|
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,
|
|
89
|
+
return crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
|
|
306
90
|
}
|
|
307
91
|
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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 =
|
|
101
|
+
const keyHash = hashKey(apiKey);
|
|
346
102
|
const now = Date.now();
|
|
347
103
|
|
|
348
|
-
// Check cache
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
383
|
-
|
|
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
|
|
413
|
-
// Network error - check
|
|
414
|
-
|
|
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
|
-
*
|
|
433
|
-
*
|
|
154
|
+
* Get firewall mode based on tier
|
|
155
|
+
* - FREE: observe (log only)
|
|
156
|
+
* - PRO: enforce (block violations)
|
|
434
157
|
*/
|
|
435
|
-
export
|
|
436
|
-
|
|
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
|
-
*
|
|
163
|
+
* Check if user can use full conductor features
|
|
497
164
|
*/
|
|
498
|
-
export function
|
|
499
|
-
return
|
|
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
|
|
520
|
-
* MCP tools have specific tier requirements separate from CLI features
|
|
170
|
+
* Check if user can approve authorities
|
|
521
171
|
*/
|
|
522
|
-
export
|
|
523
|
-
|
|
524
|
-
|
|
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:
|
|
529
|
-
tier:
|
|
530
|
-
reason:
|
|
531
|
-
|
|
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
|
|
187
|
+
const tier = await getTierFromApiKey(apiKey);
|
|
536
188
|
|
|
537
|
-
if (!
|
|
189
|
+
if (!tier) {
|
|
538
190
|
return {
|
|
539
191
|
hasAccess: false,
|
|
540
192
|
tier: null,
|
|
541
|
-
reason: 'Invalid API key.
|
|
542
|
-
upgradeUrl: 'https://vibecheckai.dev'
|
|
193
|
+
reason: 'Invalid API key.',
|
|
543
194
|
};
|
|
544
195
|
}
|
|
545
196
|
|
|
546
|
-
const
|
|
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
|
|
596
|
-
tier
|
|
597
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
249
|
+
|
|
250
|
+
if (!config?.apiKey) {
|
|
630
251
|
return {
|
|
631
252
|
authenticated: false,
|
|
632
253
|
tier: 'free',
|
|
633
|
-
|
|
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
|
-
|
|
650
|
-
|
|
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
|
-
|
|
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:
|
|
682
|
-
tools:
|
|
683
|
-
|
|
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
|
+
}
|