@vibecheckai/cli 3.2.6 → 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.
Files changed (89) hide show
  1. package/bin/registry.js +306 -90
  2. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  3. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  4. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  5. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  6. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  7. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  8. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  11. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  12. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  14. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  15. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  16. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  17. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  18. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  19. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  20. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  21. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  22. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  23. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  24. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  25. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  26. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  27. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  28. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  29. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  30. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  31. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  32. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  35. package/bin/runners/lib/analyzers.js +136 -141
  36. package/bin/runners/lib/authority-badge.js +425 -0
  37. package/bin/runners/lib/cli-output.js +7 -1
  38. package/bin/runners/lib/entitlements-v2.js +96 -505
  39. package/bin/runners/lib/error-handler.js +16 -9
  40. package/bin/runners/lib/exit-codes.js +275 -0
  41. package/bin/runners/lib/global-flags.js +37 -0
  42. package/bin/runners/lib/help-formatter.js +413 -0
  43. package/bin/runners/lib/logger.js +38 -0
  44. package/bin/runners/lib/scan-output.js +18 -19
  45. package/bin/runners/lib/ship-output.js +18 -25
  46. package/bin/runners/lib/unified-cli-output.js +604 -0
  47. package/bin/runners/lib/upsell.js +105 -205
  48. package/bin/runners/runApprove.js +1200 -0
  49. package/bin/runners/runAuth.js +324 -95
  50. package/bin/runners/runCheckpoint.js +39 -21
  51. package/bin/runners/runClassify.js +859 -0
  52. package/bin/runners/runContext.js +136 -24
  53. package/bin/runners/runDoctor.js +108 -68
  54. package/bin/runners/runFix.js +6 -5
  55. package/bin/runners/runGuard.js +212 -118
  56. package/bin/runners/runInit.js +3 -2
  57. package/bin/runners/runMcp.js +130 -52
  58. package/bin/runners/runPolish.js +43 -20
  59. package/bin/runners/runProve.js +1 -2
  60. package/bin/runners/runReport.js +3 -2
  61. package/bin/runners/runScan.js +77 -45
  62. package/bin/runners/runShip.js +3 -4
  63. package/bin/runners/runValidate.js +19 -2
  64. package/bin/runners/runWatch.js +104 -53
  65. package/bin/vibecheck.js +103 -21
  66. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  67. package/mcp-server/agent-firewall-interceptor.js +367 -31
  68. package/mcp-server/authority-tools.js +569 -0
  69. package/mcp-server/conductor/conflict-resolver.js +588 -0
  70. package/mcp-server/conductor/execution-planner.js +544 -0
  71. package/mcp-server/conductor/index.js +377 -0
  72. package/mcp-server/conductor/lock-manager.js +615 -0
  73. package/mcp-server/conductor/request-queue.js +550 -0
  74. package/mcp-server/conductor/session-manager.js +500 -0
  75. package/mcp-server/conductor/tools.js +510 -0
  76. package/mcp-server/index.js +1152 -856
  77. package/mcp-server/lib/api-client.cjs +13 -0
  78. package/mcp-server/lib/logger.cjs +30 -0
  79. package/mcp-server/logger.js +173 -0
  80. package/mcp-server/package.json +2 -2
  81. package/mcp-server/premium-tools.js +2 -2
  82. package/mcp-server/tier-auth.js +194 -383
  83. package/mcp-server/tools-v3.js +495 -533
  84. package/mcp-server/truth-firewall-tools.js +145 -15
  85. package/mcp-server/vibecheck-tools.js +2 -2
  86. package/package.json +2 -3
  87. package/mcp-server/index.old.js +0 -4137
  88. package/mcp-server/lib/api-client.js +0 -269
  89. package/mcp-server/package-lock.json +0 -165
@@ -1,25 +1,9 @@
1
1
  /**
2
- * Entitlements v2 - CANONICAL Tier Enforcement
2
+ * VibeCheck Entitlements
3
3
  *
4
- * SINGLE SOURCE OF TRUTH for all tier gating in vibecheck CLI.
5
- * Every command runner MUST use this module for access control.
6
- *
7
- * NO BYPASS ALLOWED:
8
- * - No owner-mode env vars
9
- * - No offline fallback that grants paid features
10
- * - No silent feature access
11
- *
12
- * Exit Codes:
13
- * - 0: Success
14
- * - 2: BLOCK verdict (CI failure)
15
- * - 3: Feature not allowed (upgrade required)
16
- * - 4: Misconfiguration/env error
17
- *
18
- * Tiers:
19
- * - FREE ($0): Basic scanning and validation
20
- * - STARTER ($39/repo/mo): CI/CD gates, PR checks, badges, MCP
21
- * - PRO ($99/repo/mo): Full fix, prove, ai-test, share, advanced reality, permissions, graph, patch apply
22
- * - COMPLIANCE (Enterprise): Advanced compliance packs, audit trails
4
+ * Simple 2-tier model:
5
+ * - FREE ($0): Inspect & Observe
6
+ * - PRO ($69/mo): Fix, Prove & Enforce
23
7
  */
24
8
 
25
9
  "use strict";
@@ -27,301 +11,88 @@
27
11
  const fs = require("fs");
28
12
  const path = require("path");
29
13
  const os = require("os");
30
- const crypto = require("crypto");
31
14
 
32
- // ═══════════════════════════════════════════════════════════════════════════════
15
+ // ============================================================================
33
16
  // EXIT CODES
34
- // ═══════════════════════════════════════════════════════════════════════════════
17
+ // ============================================================================
35
18
  const EXIT_SUCCESS = 0;
36
- const EXIT_BLOCK_VERDICT = 2;
37
19
  const EXIT_FEATURE_NOT_ALLOWED = 3;
38
- const EXIT_MISCONFIG = 4;
39
20
 
40
- // ═══════════════════════════════════════════════════════════════════════════════
41
- // TIER DEFINITIONS - SOURCE OF TRUTH
42
- // ═══════════════════════════════════════════════════════════════════════════════
21
+ // ============================================================================
22
+ // TIERS
23
+ // ============================================================================
43
24
  const TIERS = {
44
- free: { name: "FREE", price: 0, order: 0 },
45
- starter: { name: "STARTER", price: 39, order: 1 }, // Updated pricing
46
- pro: { name: "PRO", price: 99, order: 2 },
47
- compliance: { name: "COMPLIANCE", price: 0, order: 3 }, // Enterprise/on-prem
25
+ free: { name: "FREE", price: 0 },
26
+ pro: { name: "PRO", price: 69 },
48
27
  };
49
28
 
50
- // ═══════════════════════════════════════════════════════════════════════════════
51
- // ENTITLEMENTS MATRIX - SOURCE OF TRUTH
52
- // Format: feature -> { minTier, caps?, downgrade? }
53
- // ═══════════════════════════════════════════════════════════════════════════════
54
- const ENTITLEMENTS = {
55
- // ─────────────────────────────────────────────────────────────────────────────
56
- // CORE COMMANDS
57
- // ─────────────────────────────────────────────────────────────────────────────
58
- "scan": { minTier: "free" },
59
- "scan.autofix": { minTier: "starter" }, // Apply safe fixes + missions
60
- "ship": { minTier: "free", caps: { free: "static-only" } },
61
- "ship.static": { minTier: "free" },
62
- "ship.full": { minTier: "pro" },
63
-
64
- // ─────────────────────────────────────────────────────────────────────────────
65
- // INIT MODES
66
- // ─────────────────────────────────────────────────────────────────────────────
67
- "init": { minTier: "free" },
68
- "init.local": { minTier: "free" }, // Full local setup
69
- "init.connect": { minTier: "starter" }, // GitHub Actions + PR comments
70
-
71
- // ─────────────────────────────────────────────────────────────────────────────
72
- // CHECKPOINT
73
- // ─────────────────────────────────────────────────────────────────────────────
74
- "checkpoint": { minTier: "free", downgrade: "checkpoint.basic" },
75
- "checkpoint.basic": { minTier: "free" }, // Basic diff comparison
76
- "checkpoint.hallucination": { minTier: "pro" }, // Hallucination scoring
77
-
78
- // ─────────────────────────────────────────────────────────────────────────────
79
- // REALITY TESTING
80
- // ─────────────────────────────────────────────────────────────────────────────
81
- "reality": { minTier: "free", downgrade: "reality.preview" },
82
- "reality.preview": { minTier: "free", caps: { free: { maxPages: 5, maxClicks: 20, noAuthBoundary: true } } },
83
- "reality.basic": { minTier: "starter", caps: { starter: { maxPages: 50, maxClicks: 200, basicAuthVerify: true } } },
84
- "reality.full": { minTier: "pro" },
85
- "reality.advanced_auth_boundary": { minTier: "pro" },
86
-
87
- // ─────────────────────────────────────────────────────────────────────────────
88
- // PROVE COMMAND
89
- // ─────────────────────────────────────────────────────────────────────────────
90
- "prove": { minTier: "pro" },
91
-
92
- // ─────────────────────────────────────────────────────────────────────────────
93
- // FIX COMMAND
94
- // ─────────────────────────────────────────────────────────────────────────────
95
- "fix": { minTier: "free", downgrade: "fix.plan_only" },
96
- "fix.plan_only": { minTier: "free" }, // Generate missions, don't apply
97
- "fix.apply_patches": { minTier: "pro" }, // Apply patches automatically
98
- "fix.loop": { minTier: "pro" }, // Continuous fix loop
99
-
100
- // ─────────────────────────────────────────────────────────────────────────────
101
- // REPORT FORMATS
102
- // ─────────────────────────────────────────────────────────────────────────────
103
- "report": { minTier: "free", downgrade: "report.html_md" },
104
- "report.html_md": { minTier: "free" },
105
- "report.sarif_csv": { minTier: "starter" }, // SARIF/CSV at STARTER
106
- "report.compliance_packs": { minTier: "compliance" },
107
-
108
- // ─────────────────────────────────────────────────────────────────────────────
109
- // SETUP & DX
110
- // ─────────────────────────────────────────────────────────────────────────────
111
- "install": { minTier: "free" },
112
- "doctor": { minTier: "free" },
113
- "status": { minTier: "free" },
114
- "watch": { minTier: "free", downgrade: "watch.local" },
115
- "watch.local": { minTier: "free" }, // Local-only file watching
116
- "watch.pr": { minTier: "starter" }, // PR updates on changes
117
- "preflight": { minTier: "free" },
118
- "polish": { minTier: "free" },
119
-
120
- // ─────────────────────────────────────────────────────────────────────────────
121
- // AI TRUTH
122
- // ─────────────────────────────────────────────────────────────────────────────
123
- "ctx": { minTier: "free" },
124
- "guard": { minTier: "free" },
125
- "context": { minTier: "free" },
126
- "mdc": { minTier: "free" },
127
- "contracts": { minTier: "free" },
128
-
129
- // ─────────────────────────────────────────────────────────────────────────────
130
- // EXPORT COMMAND (subcommands gate individually)
131
- // ─────────────────────────────────────────────────────────────────────────────
132
- "export": { minTier: "free" }, // Base export command is free, subcommands gate themselves
133
-
134
- // ─────────────────────────────────────────────────────────────────────────────
135
- // RUNTIME COMMAND (browser-based verification)
136
- // ─────────────────────────────────────────────────────────────────────────────
137
- "runtime": { minTier: "free", downgrade: "reality.preview" }, // Same as reality
138
-
139
- // ─────────────────────────────────────────────────────────────────────────────
140
- // SECURITY COMMAND (AuthZ & IDOR)
141
- // ─────────────────────────────────────────────────────────────────────────────
142
- "security": { minTier: "pro" },
143
-
144
- // ─────────────────────────────────────────────────────────────────────────────
145
- // AI FEATURES - Token/Cost Controls
146
- // ─────────────────────────────────────────────────────────────────────────────
147
- "ai": { minTier: "starter", downgrade: "ai.none" },
148
- "ai.none": { minTier: "free", caps: { free: "No AI features - upgrade for AI" } },
149
- "ai.starter": {
150
- minTier: "starter",
151
- caps: {
152
- starter: {
153
- model: "gpt-3.5-turbo", // Cheaper model for Starter
154
- maxCallsPerMonth: 10, // 10 AI calls/month
155
- maxInputTokensPerRequest: 2000, // ~$0.002 max input cost
156
- maxOutputTokensPerRequest: 1000, // ~$0.002 max output cost
157
- maxCostPerMonth: 0.50, // $0.50 max AI spend/month
158
- }
159
- }
160
- },
161
- "ai.pro": {
162
- minTier: "pro",
163
- caps: {
164
- pro: {
165
- model: "gpt-4-turbo-preview", // Premium model for Pro
166
- alternateModel: "claude-3-5-sonnet-20241022", // Fallback
167
- maxCallsPerMonth: 50, // 50 AI calls/month
168
- maxInputTokensPerRequest: 4000, // ~$0.04 max input cost
169
- maxOutputTokensPerRequest: 2000, // ~$0.06 max output cost
170
- maxCostPerMonth: 5.00, // $5.00 max AI spend/month
171
- }
172
- }
173
- },
174
- "ai.compliance": {
175
- minTier: "compliance",
176
- caps: {
177
- compliance: {
178
- model: "gpt-4-turbo-preview",
179
- alternateModel: "claude-3-5-sonnet-20241022",
180
- maxCallsPerMonth: 500, // 500 AI calls/month
181
- maxInputTokensPerRequest: 8000,
182
- maxOutputTokensPerRequest: 4000,
183
- maxCostPerMonth: 50.00, // $50 max AI spend/month
184
- }
185
- }
186
- },
187
-
188
- // ─────────────────────────────────────────────────────────────────────────────
189
- // PRO ONLY
190
- // ─────────────────────────────────────────────────────────────────────────────
191
- "replay": { minTier: "pro" },
192
- "share": { minTier: "pro" },
193
- "ai-test": { minTier: "pro" },
194
- "permissions": { minTier: "pro" },
195
- "graph": { minTier: "pro" },
196
-
197
- // ─────────────────────────────────────────────────────────────────────────────
198
- // STARTER AND ABOVE
199
- // ─────────────────────────────────────────────────────────────────────────────
200
- "gate": { minTier: "starter" },
201
- "pr": { minTier: "starter" },
202
- "badge": { minTier: "starter", downgrade: "badge.basic" },
203
- "badge.basic": { minTier: "starter" }, // Basic badge (STARTER)
204
- "badge.verified": { minTier: "pro" }, // Verified badge with seal (PRO)
205
- "fix_hints": { minTier: "starter" }, // Fix hints/missions in output
206
- "launch": { minTier: "starter" },
207
- "dashboard_sync": { minTier: "starter" },
208
-
209
- // MCP Server
210
- "mcp": { minTier: "starter", downgrade: "mcp.help_only" },
211
- "mcp.help_only": { minTier: "free", caps: { free: "help and print-config only" } },
212
- "mcp.read_only": { minTier: "starter", caps: { starter: "read-only safe tools, rate limited" } },
213
- "mcp.full": { minTier: "pro", caps: { pro: "full tools, audit logs, higher limits" } },
214
-
215
- // ─────────────────────────────────────────────────────────────────────────────
216
- // ACCOUNT (ALWAYS FREE)
217
- // ─────────────────────────────────────────────────────────────────────────────
218
- "login": { minTier: "free" },
219
- "logout": { minTier: "free" },
220
- "whoami": { minTier: "free" },
221
-
222
- // Labs/experimental
223
- "labs": { minTier: "free" },
224
- };
225
-
226
- // ═══════════════════════════════════════════════════════════════════════════════
227
- // LIMITS BY TIER
228
- // ═══════════════════════════════════════════════════════════════════════════════
229
- const LIMITS = {
230
- free: {
231
- realityMaxPages: 5,
232
- realityMaxClicks: 20,
233
- realityAuthBoundary: false,
234
- reportFormats: ["html", "md"],
235
- fixApplyPatches: false,
236
- scansPerMonth: 50,
237
- shipChecksPerMonth: 20,
238
- },
239
- starter: {
240
- realityMaxPages: 50,
241
- realityMaxClicks: 200,
242
- realityAuthBoundary: false,
243
- realityAdvancedAuth: false,
244
- reportFormats: ["html", "md", "sarif", "csv"],
245
- fixApplyPatches: false,
246
- scansPerMonth: 500,
247
- shipChecksPerMonth: 200,
248
- },
249
- pro: {
250
- realityMaxPages: -1, // unlimited
251
- realityMaxClicks: -1,
252
- realityAuthBoundary: true,
253
- realityAdvancedAuth: true,
254
- reportFormats: ["html", "md", "sarif", "csv"],
255
- fixApplyPatches: true,
256
- scansPerMonth: -1, // unlimited
257
- shipChecksPerMonth: -1,
258
- },
259
- compliance: {
260
- realityMaxPages: -1,
261
- realityMaxClicks: -1,
262
- realityAuthBoundary: true,
263
- realityAdvancedAuth: true,
264
- reportFormats: ["html", "md", "sarif", "csv", "compliance"],
265
- fixApplyPatches: true,
266
- scansPerMonth: -1,
267
- shipChecksPerMonth: -1,
268
- },
269
- };
270
-
271
- const API_BASE_URL = process.env.VIBECHECK_API_URL || "https://api.vibecheckai.dev";
272
-
273
- // ═══════════════════════════════════════════════════════════════════════════════
274
- // CACHE PATHS
275
- // ═══════════════════════════════════════════════════════════════════════════════
276
- function getEntitlementsCachePath(projectPath) {
277
- return path.join(projectPath || process.cwd(), ".vibecheck", ".entitlements.json");
29
+ // ============================================================================
30
+ // GATED FEATURES
31
+ // ============================================================================
32
+ const FREE_FEATURES = [
33
+ // Setup & environment
34
+ "init", "doctor", "install", "status", "watch", "preflight",
35
+ // Scan & analysis
36
+ "scan", "runtime",
37
+ // AI verification
38
+ "ctx", "contracts", "verify",
39
+ // Reports
40
+ "report", "export",
41
+ // Account
42
+ "login", "logout", "whoami",
43
+ // Preview modes
44
+ "reality.preview", "firewall.observe",
45
+ // Misc
46
+ "labs", "mdc",
47
+ ];
48
+
49
+ const PRO_FEATURES = [
50
+ // CI/CD & PR
51
+ "gate", "pr", "badge", "ship",
52
+ // Fixes
53
+ "fix", "fix.apply", "scan.autofix",
54
+ // Prove & verify
55
+ "prove", "replay", "permissions", "graph", "ai-test", "share",
56
+ // Advanced
57
+ "checkpoint", "polish", "guard", "context",
58
+ // Full modes
59
+ "firewall.enforce", "reality.full", "mcp.full",
60
+ // All FREE features
61
+ ...FREE_FEATURES,
62
+ ];
63
+
64
+ function isPro(tier) {
65
+ return tier === "pro";
278
66
  }
279
67
 
280
- function getGlobalConfigPath() {
281
- const home = os.homedir();
282
- if (process.platform === "win32") {
283
- return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "vibecheck", "config.json");
284
- }
285
- return path.join(home, ".config", "vibecheck", "config.json");
286
- }
287
-
288
- // ═══════════════════════════════════════════════════════════════════════════════
289
- // TIER COMPARISON
290
- // ═══════════════════════════════════════════════════════════════════════════════
291
- function tierMeetsMinimum(currentTier, requiredTier) {
292
- const current = TIERS[currentTier]?.order ?? -1;
293
- const required = TIERS[requiredTier]?.order ?? 999;
294
- return current >= required;
68
+ function tierHasFeature(tier, feature) {
69
+ if (tier === "pro") return true; // PRO has everything
70
+ return FREE_FEATURES.includes(feature);
295
71
  }
296
72
 
297
- function getTierLabel(tier) {
298
- return TIERS[tier]?.name || tier.toUpperCase();
299
- }
73
+ // ============================================================================
74
+ // API
75
+ // ============================================================================
76
+ const API_BASE_URL = process.env.VIBECHECK_API_URL || "https://api.vibecheckai.dev";
300
77
 
301
- // ═══════════════════════════════════════════════════════════════════════════════
302
- // CORE API: getTier()
303
- // ═══════════════════════════════════════════════════════════════════════════════
304
78
  let _cachedTier = null;
305
79
  let _cachedTierExpiry = 0;
306
80
 
307
81
  async function getTier(options = {}) {
308
- const { apiKey, projectPath, forceRefresh = false } = options;
82
+ const { apiKey, forceRefresh = false } = options;
309
83
 
310
- // Check cache (5 minute TTL)
311
84
  if (!forceRefresh && _cachedTier && Date.now() < _cachedTierExpiry) {
312
85
  return _cachedTier;
313
86
  }
314
87
 
315
- // No API key = free tier
316
88
  if (!apiKey) {
317
89
  _cachedTier = "free";
318
90
  _cachedTierExpiry = Date.now() + 300000;
319
91
  return "free";
320
92
  }
321
93
 
322
- // Fetch from API
323
94
  try {
324
- const res = await fetch(`${API_BASE_URL}/v1/entitlements`, {
95
+ const res = await fetch(`${API_BASE_URL}/v1/auth/whoami`, {
325
96
  method: "GET",
326
97
  headers: { "Authorization": `Bearer ${apiKey}` },
327
98
  signal: AbortSignal.timeout(5000),
@@ -329,113 +100,33 @@ async function getTier(options = {}) {
329
100
 
330
101
  if (res.ok) {
331
102
  const data = await res.json();
332
- _cachedTier = data.tier || "free";
103
+ // Map any paid tier to 'pro'
104
+ const plan = data.plan || data.tier || "free";
105
+ _cachedTier = (plan === "free") ? "free" : "pro";
333
106
  _cachedTierExpiry = Date.now() + 300000;
334
-
335
- // Cache locally
336
- try {
337
- const cachePath = getEntitlementsCachePath(projectPath);
338
- const dir = path.dirname(cachePath);
339
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
340
- fs.writeFileSync(cachePath, JSON.stringify({ tier: _cachedTier, fetchedAt: new Date().toISOString() }, null, 2));
341
- } catch {}
342
-
343
107
  return _cachedTier;
344
108
  }
345
-
346
- if (res.status === 401) {
347
- return "free"; // Invalid key = free tier
348
- }
349
109
  } catch {
350
- // Network error - check local cache
351
- try {
352
- const cachePath = getEntitlementsCachePath(projectPath);
353
- if (fs.existsSync(cachePath)) {
354
- const cached = JSON.parse(fs.readFileSync(cachePath, "utf8"));
355
- // Only use cache if less than 24 hours old
356
- const fetchedAt = new Date(cached.fetchedAt).getTime();
357
- if (Date.now() - fetchedAt < 24 * 3600 * 1000) {
358
- return cached.tier || "free";
359
- }
360
- }
361
- } catch {}
110
+ // Network error - default to free
362
111
  }
363
112
 
364
- // Default to free (no offline bypass to paid features)
365
113
  return "free";
366
114
  }
367
115
 
368
- // ═══════════════════════════════════════════════════════════════════════════════
369
- // CORE API: getLimits()
370
- // ═══════════════════════════════════════════════════════════════════════════════
371
- function getLimits(tier) {
372
- return LIMITS[tier] || LIMITS.free;
373
- }
374
-
375
- // ═══════════════════════════════════════════════════════════════════════════════
376
- // CORE API: enforce() - THE GATEKEEPER
377
- // ═══════════════════════════════════════════════════════════════════════════════
378
- /**
379
- * Enforce feature access. Returns enforcement result.
380
- *
381
- * @param {string} feature - Feature key (e.g., "prove", "fix.apply_patches")
382
- * @param {object} options - { apiKey?, projectPath?, silent? }
383
- * @returns {object} - { allowed, tier, downgrade?, exitCode, message }
384
- */
116
+ // ============================================================================
117
+ // ENFORCE
118
+ // ============================================================================
385
119
  async function enforce(feature, options = {}) {
386
- const { apiKey, projectPath, silent = false } = options;
387
-
388
- const tier = await getTier({ apiKey, projectPath });
389
- const entitlement = ENTITLEMENTS[feature];
120
+ const { apiKey, silent = false } = options;
121
+ const tier = await getTier({ apiKey });
390
122
 
391
- if (!entitlement) {
392
- // Unknown feature - block by default
393
- return {
394
- allowed: false,
395
- tier,
396
- exitCode: EXIT_MISCONFIG,
397
- message: `Unknown feature: ${feature}`,
398
- };
399
- }
400
-
401
- const hasAccess = tierMeetsMinimum(tier, entitlement.minTier);
123
+ const hasAccess = tierHasFeature(tier, feature);
402
124
 
403
125
  if (hasAccess) {
404
- // Full access
405
- return {
406
- allowed: true,
407
- tier,
408
- limits: getLimits(tier),
409
- caps: entitlement.caps?.[tier] || null,
410
- };
126
+ return { allowed: true, tier };
411
127
  }
412
128
 
413
- // Check for downgrade option
414
- if (entitlement.downgrade) {
415
- const downgradeEntitlement = ENTITLEMENTS[entitlement.downgrade];
416
- if (downgradeEntitlement && tierMeetsMinimum(tier, downgradeEntitlement.minTier)) {
417
- // Downgrade allowed
418
- const caps = downgradeEntitlement.caps?.[tier] || null;
419
- const message = formatDowngradeMessage(feature, entitlement.downgrade, tier, entitlement.minTier, caps);
420
-
421
- if (!silent) {
422
- console.log(message);
423
- }
424
-
425
- return {
426
- allowed: true,
427
- tier,
428
- downgrade: entitlement.downgrade,
429
- limits: getLimits(tier),
430
- caps,
431
- message,
432
- };
433
- }
434
- }
435
-
436
- // Not allowed - generate upgrade message
437
- const message = formatUpgradeMessage(feature, tier, entitlement.minTier);
438
-
129
+ const message = formatUpgradeMessage(feature);
439
130
  if (!silent) {
440
131
  console.error(message);
441
132
  }
@@ -443,80 +134,11 @@ async function enforce(feature, options = {}) {
443
134
  return {
444
135
  allowed: false,
445
136
  tier,
446
- requiredTier: entitlement.minTier,
447
137
  exitCode: EXIT_FEATURE_NOT_ALLOWED,
448
138
  message,
449
139
  };
450
140
  }
451
141
 
452
- // ═══════════════════════════════════════════════════════════════════════════════
453
- // MESSAGING
454
- // ═══════════════════════════════════════════════════════════════════════════════
455
- const c = {
456
- reset: "\x1b[0m",
457
- bold: "\x1b[1m",
458
- dim: "\x1b[2m",
459
- red: "\x1b[31m",
460
- green: "\x1b[32m",
461
- yellow: "\x1b[33m",
462
- cyan: "\x1b[36m",
463
- magenta: "\x1b[35m",
464
- };
465
-
466
- function formatUpgradeMessage(feature, currentTier, requiredTier) {
467
- const tierColors = { starter: c.cyan, pro: c.magenta, enterprise: c.yellow };
468
- const reqColor = tierColors[requiredTier] || c.yellow;
469
-
470
- return `
471
- ${c.red}${c.bold}⛔ Feature Not Available${c.reset}
472
-
473
- ${c.yellow}${feature}${c.reset} requires ${reqColor}${getTierLabel(requiredTier)}${c.reset} plan.
474
- Your current plan: ${c.dim}${getTierLabel(currentTier)}${c.reset}
475
-
476
- ${c.cyan}Upgrade at:${c.reset} https://vibecheckai.dev/pricing
477
-
478
- ${c.dim}Exit code: ${EXIT_FEATURE_NOT_ALLOWED}${c.reset}
479
- `;
480
- }
481
-
482
- function formatDowngradeMessage(feature, downgradeTo, currentTier, requiredTier, caps) {
483
- const tierColors = { starter: c.cyan, pro: c.magenta, enterprise: c.yellow };
484
- const reqColor = tierColors[requiredTier] || c.yellow;
485
-
486
- let capsStr = "";
487
- if (caps) {
488
- if (typeof caps === "string") {
489
- capsStr = ` ${c.dim}Mode: ${caps}${c.reset}\n`;
490
- } else if (typeof caps === "object") {
491
- const entries = Object.entries(caps).map(([k, v]) => `${k}: ${v}`).join(", ");
492
- capsStr = ` ${c.dim}Limits: ${entries}${c.reset}\n`;
493
- }
494
- }
495
-
496
- return `
497
- ${c.yellow}${c.bold}⚠ Running in Preview Mode${c.reset}
498
-
499
- Full ${c.yellow}${feature}${c.reset} requires ${reqColor}${getTierLabel(requiredTier)}${c.reset} plan.
500
- Running ${c.green}${downgradeTo}${c.reset} instead.
501
- ${capsStr}
502
- ${c.cyan}Upgrade for full access:${c.reset} https://vibecheckai.dev/pricing
503
- `;
504
- }
505
-
506
- // ═══════════════════════════════════════════════════════════════════════════════
507
- // CONVENIENCE HELPERS
508
- // ═══════════════════════════════════════════════════════════════════════════════
509
-
510
- /**
511
- * Check if a command is allowed (does not print messages)
512
- */
513
- async function checkCommand(command, options = {}) {
514
- return enforce(command, { ...options, silent: true });
515
- }
516
-
517
- /**
518
- * Enforce and exit if not allowed
519
- */
520
142
  async function enforceOrExit(feature, options = {}) {
521
143
  const result = await enforce(feature, options);
522
144
  if (!result.allowed) {
@@ -525,82 +147,51 @@ async function enforceOrExit(feature, options = {}) {
525
147
  return result;
526
148
  }
527
149
 
528
- /**
529
- * Get the minimum tier required for a feature
530
- */
531
- function getMinTierForFeature(feature) {
532
- return ENTITLEMENTS[feature]?.minTier || "enterprise";
150
+ async function checkCommand(command, options = {}) {
151
+ return enforce(command, { ...options, silent: true });
533
152
  }
534
153
 
535
- /**
536
- * Check if tier has access to feature (sync, for help display)
537
- */
538
- function tierHasFeature(tier, feature) {
539
- const entitlement = ENTITLEMENTS[feature];
540
- if (!entitlement) return false;
541
- return tierMeetsMinimum(tier, entitlement.minTier);
542
- }
154
+ // ============================================================================
155
+ // MESSAGING
156
+ // ============================================================================
157
+ const c = {
158
+ reset: "\x1b[0m",
159
+ bold: "\x1b[1m",
160
+ cyan: "\x1b[36m",
161
+ yellow: "\x1b[33m",
162
+ };
543
163
 
544
- /**
545
- * Get all features for a tier
546
- */
547
- function getFeaturesForTier(tier) {
548
- const features = [];
549
- for (const [feature, def] of Object.entries(ENTITLEMENTS)) {
550
- if (tierMeetsMinimum(tier, def.minTier)) {
551
- features.push(feature);
552
- }
553
- }
554
- return features;
555
- }
164
+ function formatUpgradeMessage(feature) {
165
+ return `
166
+ ${c.bold}This feature requires Pro.${c.reset}
556
167
 
557
- // ═══════════════════════════════════════════════════════════════════════════════
558
- // COMMAND GROUPING FOR HELP DISPLAY
559
- // ═══════════════════════════════════════════════════════════════════════════════
560
- const COMMAND_GROUPS = {
561
- "Proof Loop": ["scan", "ship", "reality", "prove", "fix", "report"],
562
- "Setup & DX": ["install", "init", "doctor", "status", "watch", "launch"],
563
- "AI Truth": ["ctx", "guard", "context", "mdc"],
564
- "CI & Collaboration": ["gate", "pr", "badge"],
565
- "Reporting": ["report"],
566
- "Automation": ["ai-test", "mcp", "share"],
567
- };
168
+ ${c.yellow}${feature}${c.reset} is a Pro feature.
568
169
 
569
- function getCommandGroup(command) {
570
- for (const [group, commands] of Object.entries(COMMAND_GROUPS)) {
571
- if (commands.includes(command)) return group;
572
- }
573
- return "Other";
170
+ Upgrade to Pro ($69/mo) to unlock Fix, Prove & Enforce capabilities.
171
+
172
+ vibecheck upgrade
173
+ https://vibecheckai.dev/pricing
174
+ `;
574
175
  }
575
176
 
576
- // ═══════════════════════════════════════════════════════════════════════════════
177
+ // ============================================================================
577
178
  // EXPORTS
578
- // ═══════════════════════════════════════════════════════════════════════════════
179
+ // ============================================================================
579
180
  module.exports = {
580
- // Core API
181
+ // Core
581
182
  getTier,
582
- getLimits,
583
183
  enforce,
584
184
  enforceOrExit,
585
185
  checkCommand,
586
186
 
587
- // Tier helpers
588
- tierMeetsMinimum,
589
- getTierLabel,
590
- getMinTierForFeature,
187
+ // Helpers
188
+ isPro,
591
189
  tierHasFeature,
592
- getFeaturesForTier,
593
-
594
- // Command grouping
595
- COMMAND_GROUPS,
596
- getCommandGroup,
597
190
 
598
191
  // Constants
599
192
  TIERS,
600
- ENTITLEMENTS,
601
- LIMITS,
193
+ FREE_FEATURES,
194
+ PRO_FEATURES,
602
195
  EXIT_SUCCESS,
603
- EXIT_BLOCK_VERDICT,
604
196
  EXIT_FEATURE_NOT_ALLOWED,
605
- EXIT_MISCONFIG,
606
197
  };