@vibecheckai/cli 3.0.7 → 3.0.9
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 +77 -484
- package/bin/runners/cli-utils.js +6 -6
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
- package/bin/runners/lib/entitlements-v2.js +409 -299
- package/bin/runners/lib/firewall-prompt.js +1 -1
- package/bin/runners/lib/sandbox/proof-chain.js +3 -3
- package/bin/runners/runFix.js +20 -0
- package/bin/runners/runInstall.js +41 -1
- package/bin/runners/runMcp.js +58 -0
- package/bin/runners/runPR.js +80 -12
- package/bin/runners/runProve.js +85 -27
- package/bin/runners/runReality.js +136 -16
- package/bin/runners/runReport.js +40 -0
- package/bin/runners/runScan.js +6 -6
- package/bin/runners/runShare.js +64 -4
- package/bin/runners/runShip.js +97 -1
- package/bin/runners/runStatus.js +3 -1
- package/bin/runners/runWatch.js +63 -6
- package/bin/vibecheck.js +161 -62
- package/package.json +6 -2
package/bin/runners/runScan.js
CHANGED
|
@@ -88,12 +88,12 @@ const gradientPink = rgb(255, 105, 180);
|
|
|
88
88
|
const gradientOrange = rgb(255, 165, 0);
|
|
89
89
|
|
|
90
90
|
const BANNER = `
|
|
91
|
-
${rgb(0, 200, 255)}
|
|
92
|
-
${rgb(30, 180, 255)}
|
|
93
|
-
${rgb(60, 160, 255)} ██║
|
|
94
|
-
${rgb(90, 140, 255)} ██║
|
|
95
|
-
${rgb(120, 120, 255)}
|
|
96
|
-
${rgb(150, 100, 255)}
|
|
91
|
+
${rgb(0, 200, 255)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
|
|
92
|
+
${rgb(30, 180, 255)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
|
|
93
|
+
${rgb(60, 160, 255)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
|
|
94
|
+
${rgb(90, 140, 255)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
|
|
95
|
+
${rgb(120, 120, 255)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
|
|
96
|
+
${rgb(150, 100, 255)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
|
|
97
97
|
|
|
98
98
|
${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
|
|
99
99
|
${c.dim} │${c.reset} ${rgb(255, 255, 255)}${c.bold}Route Integrity${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Security${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Quality${c.reset} ${c.dim}•${c.reset} ${rgb(100, 100, 100)}Ship with Confidence${c.reset} ${c.dim}│${c.reset}
|
package/bin/runners/runShare.js
CHANGED
|
@@ -3,12 +3,72 @@ const fs = require("fs");
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const { buildSharePack, findLatestMissionDir } = require("./lib/share-pack");
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
function printHelp() {
|
|
7
|
+
console.log(`
|
|
8
|
+
vibecheck share - Generate shareable proof bundles
|
|
9
|
+
|
|
10
|
+
USAGE
|
|
11
|
+
vibecheck share [options]
|
|
12
|
+
|
|
13
|
+
OPTIONS
|
|
14
|
+
--mission-dir <path> Specific mission directory to share
|
|
15
|
+
--output-dir <path> Output directory for share pack
|
|
16
|
+
--pr-comment Print PR comment to stdout
|
|
17
|
+
--help, -h Show this help
|
|
18
|
+
|
|
19
|
+
WHAT IT DOES
|
|
20
|
+
1. Finds latest mission pack from .vibecheck/missions/
|
|
21
|
+
2. Generates share bundle with:
|
|
22
|
+
- share.json (machine readable)
|
|
23
|
+
- share.md (human readable)
|
|
24
|
+
- pr_comment.md (GitHub ready)
|
|
25
|
+
|
|
26
|
+
OUTPUT FILES
|
|
27
|
+
.vibecheck/missions/<timestamp>/share/share.json
|
|
28
|
+
.vibecheck/missions/<timestamp>/share/share.md
|
|
29
|
+
.vibecheck/missions/<timestamp>/share/pr_comment.md
|
|
30
|
+
|
|
31
|
+
EXAMPLES
|
|
32
|
+
vibecheck share # Share latest mission
|
|
33
|
+
vibecheck share --pr-comment # Print PR comment body
|
|
34
|
+
vibecheck fix --autopilot --share # Fix then auto-share
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function runShare(argsOrOpts = {}) {
|
|
39
|
+
// Handle array args from CLI
|
|
40
|
+
if (Array.isArray(argsOrOpts)) {
|
|
41
|
+
if (argsOrOpts.includes("--help") || argsOrOpts.includes("-h")) {
|
|
42
|
+
printHelp();
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
const getArg = (flags) => {
|
|
46
|
+
for (const f of flags) {
|
|
47
|
+
const idx = argsOrOpts.indexOf(f);
|
|
48
|
+
if (idx !== -1 && idx < argsOrOpts.length - 1) return argsOrOpts[idx + 1];
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
argsOrOpts = {
|
|
53
|
+
missionDir: getArg(["--mission-dir"]),
|
|
54
|
+
outputDir: getArg(["--output-dir"]),
|
|
55
|
+
prComment: argsOrOpts.includes("--pr-comment"),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { repoRoot, missionDir, outputDir, prComment } = argsOrOpts;
|
|
7
60
|
const root = repoRoot || process.cwd();
|
|
8
61
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
62
|
+
let resolvedMissionDir;
|
|
63
|
+
try {
|
|
64
|
+
resolvedMissionDir = missionDir
|
|
65
|
+
? (path.isAbsolute(missionDir) ? missionDir : path.join(root, missionDir))
|
|
66
|
+
: findLatestMissionDir(root);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.log(`\n❌ Error: ${e.message}\n`);
|
|
69
|
+
console.log(`Run 'vibecheck fix --autopilot' first to generate mission packs.\n`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
12
72
|
|
|
13
73
|
const res = buildSharePack({
|
|
14
74
|
repoRoot: root,
|
package/bin/runners/runShip.js
CHANGED
|
@@ -571,6 +571,7 @@ ${c.bold}EXAMPLES${c.reset}
|
|
|
571
571
|
translated,
|
|
572
572
|
results,
|
|
573
573
|
outputDir,
|
|
574
|
+
results.routeTruth?.findings || [], // Pass modern analyzer findings
|
|
574
575
|
);
|
|
575
576
|
printFixResults(fixResults);
|
|
576
577
|
}
|
|
@@ -699,7 +700,7 @@ ${c.bold}EXAMPLES${c.reset}
|
|
|
699
700
|
* 2. Updates .gitignore to protect sensitive files
|
|
700
701
|
* 3. Generates fixes.md with detailed manual fix instructions
|
|
701
702
|
*/
|
|
702
|
-
async function runAutoFix(projectPath, translated, results, outputDir) {
|
|
703
|
+
async function runAutoFix(projectPath, translated, results, outputDir, analyzerFindings = []) {
|
|
703
704
|
const fixResults = {
|
|
704
705
|
envExampleCreated: false,
|
|
705
706
|
gitignoreUpdated: false,
|
|
@@ -984,6 +985,101 @@ async function runAutoFix(projectPath, translated, results, outputDir) {
|
|
|
984
985
|
}
|
|
985
986
|
}
|
|
986
987
|
|
|
988
|
+
// 5. Add modern analyzer findings to AI prompt
|
|
989
|
+
if (analyzerFindings && analyzerFindings.length > 0) {
|
|
990
|
+
aiPrompt += "---\n\n";
|
|
991
|
+
aiPrompt += "## Analyzer Findings\n\n";
|
|
992
|
+
aiPrompt += "The following issues were detected by static analysis:\n\n";
|
|
993
|
+
|
|
994
|
+
// Group by category
|
|
995
|
+
const findingsByCategory = {};
|
|
996
|
+
for (const f of analyzerFindings) {
|
|
997
|
+
const cat = f.category || "Other";
|
|
998
|
+
if (!findingsByCategory[cat]) findingsByCategory[cat] = [];
|
|
999
|
+
findingsByCategory[cat].push(f);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
for (const [category, findings] of Object.entries(findingsByCategory)) {
|
|
1003
|
+
const blockers = findings.filter(f => f.severity === "BLOCK");
|
|
1004
|
+
const warnings = findings.filter(f => f.severity === "WARN");
|
|
1005
|
+
|
|
1006
|
+
aiPrompt += `### ${category} (${blockers.length} blockers, ${warnings.length} warnings)\n\n`;
|
|
1007
|
+
|
|
1008
|
+
// Show blockers first (limit to 10 per category to avoid huge prompts)
|
|
1009
|
+
const toShow = [...blockers.slice(0, 10), ...warnings.slice(0, 5)];
|
|
1010
|
+
|
|
1011
|
+
for (const finding of toShow) {
|
|
1012
|
+
aiPrompt += `#### Fix ${fixNum}: ${finding.title}\n\n`;
|
|
1013
|
+
aiPrompt += `**Severity:** ${finding.severity === "BLOCK" ? "🔴 BLOCKER" : "🟡 WARNING"}\n\n`;
|
|
1014
|
+
|
|
1015
|
+
if (finding.evidence && finding.evidence.length > 0) {
|
|
1016
|
+
const ev = finding.evidence[0];
|
|
1017
|
+
aiPrompt += `**File:** \`${ev.file}${ev.lines ? `:${ev.lines}` : ""}\`\n\n`;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
aiPrompt += `**Problem:** ${finding.title}\n\n`;
|
|
1021
|
+
|
|
1022
|
+
if (finding.why) {
|
|
1023
|
+
aiPrompt += `**Why this matters:** ${finding.why}\n\n`;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (finding.fixHints && finding.fixHints.length > 0) {
|
|
1027
|
+
aiPrompt += "**How to fix:**\n";
|
|
1028
|
+
for (const hint of finding.fixHints) {
|
|
1029
|
+
aiPrompt += `- ${hint}\n`;
|
|
1030
|
+
}
|
|
1031
|
+
aiPrompt += "\n";
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Add category-specific fix instructions
|
|
1035
|
+
if (category === "MissingRoute" || finding.title?.includes("route")) {
|
|
1036
|
+
aiPrompt += "**Action:**\n";
|
|
1037
|
+
aiPrompt += "1. Check if this API endpoint should exist\n";
|
|
1038
|
+
aiPrompt += "2. If yes, create the route handler in your backend\n";
|
|
1039
|
+
aiPrompt += "3. If no, update the frontend to use the correct endpoint\n";
|
|
1040
|
+
aiPrompt += "4. Ensure the route is registered with your framework (Express, Fastify, Next.js API)\n\n";
|
|
1041
|
+
} else if (category === "EnvGap" || finding.title?.includes("env")) {
|
|
1042
|
+
aiPrompt += "**Action:**\n";
|
|
1043
|
+
aiPrompt += "1. Add the missing environment variable to `.env.example`\n";
|
|
1044
|
+
aiPrompt += "2. Document what the variable is for\n";
|
|
1045
|
+
aiPrompt += "3. Add to your deployment environment\n\n";
|
|
1046
|
+
} else if (category === "GhostAuth" || finding.title?.includes("auth")) {
|
|
1047
|
+
aiPrompt += "**Action:**\n";
|
|
1048
|
+
aiPrompt += "1. Add authentication middleware to this route\n";
|
|
1049
|
+
aiPrompt += "2. Verify the middleware checks for valid session/token\n";
|
|
1050
|
+
aiPrompt += "3. Return 401/403 for unauthorized requests\n\n";
|
|
1051
|
+
} else if (category === "FakeSuccess" || finding.title?.includes("fake")) {
|
|
1052
|
+
aiPrompt += "**Action:**\n";
|
|
1053
|
+
aiPrompt += "1. Ensure success UI only shows after confirmed API success\n";
|
|
1054
|
+
aiPrompt += "2. Check response.ok or status before showing success\n";
|
|
1055
|
+
aiPrompt += "3. Add proper error handling for failed requests\n\n";
|
|
1056
|
+
} else if (category === "StripeWebhook" || finding.title?.includes("Stripe")) {
|
|
1057
|
+
aiPrompt += "**Action:**\n";
|
|
1058
|
+
aiPrompt += "1. Verify Stripe webhook signature using stripe.webhooks.constructEvent()\n";
|
|
1059
|
+
aiPrompt += "2. Add idempotency key handling to prevent duplicate processing\n";
|
|
1060
|
+
aiPrompt += "3. Return 200 only after successful processing\n\n";
|
|
1061
|
+
} else if (category === "PaidSurface" || finding.title?.includes("paid")) {
|
|
1062
|
+
aiPrompt += "**Action:**\n";
|
|
1063
|
+
aiPrompt += "1. Add server-side entitlement check before allowing access\n";
|
|
1064
|
+
aiPrompt += "2. Verify the user's subscription status from your database\n";
|
|
1065
|
+
aiPrompt += "3. Return 403 if user doesn't have access to this feature\n\n";
|
|
1066
|
+
} else if (category === "OwnerMode" || finding.title?.includes("OWNER")) {
|
|
1067
|
+
aiPrompt += "**Action:**\n";
|
|
1068
|
+
aiPrompt += "1. Remove or disable OWNER_MODE/bypass flags in production\n";
|
|
1069
|
+
aiPrompt += "2. Use proper feature flags that require authentication\n";
|
|
1070
|
+
aiPrompt += "3. Never ship code that bypasses auth checks\n\n";
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
fixNum++;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (blockers.length > 10) {
|
|
1077
|
+
aiPrompt += `\n*...and ${blockers.length - 10} more blockers in this category*\n\n`;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
aiPrompt += "---\n\n";
|
|
987
1083
|
aiPrompt += "## Verification\n\n";
|
|
988
1084
|
aiPrompt += "After applying fixes:\n";
|
|
989
1085
|
aiPrompt += "1. Run `npm run build` or `pnpm build` to check for errors\n";
|
package/bin/runners/runStatus.js
CHANGED
|
@@ -130,7 +130,9 @@ ${c.bold}Quick Actions${c.reset}
|
|
|
130
130
|
${lastShip?.meta?.verdict === "SHIP" ? `${c.green}✓ Ready to deploy!${c.reset}` : ""}
|
|
131
131
|
`);
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
// Exit 0 when showing status successfully (even if no ship yet)
|
|
134
|
+
// Only return non-zero if explicitly checking for BLOCK
|
|
135
|
+
return 0;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
module.exports = { runStatus };
|
package/bin/runners/runWatch.js
CHANGED
|
@@ -158,12 +158,69 @@ function watchDirectory(dir, callback) {
|
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
function printHelp() {
|
|
162
|
+
console.log(`
|
|
163
|
+
${c.cyan}${c.bold}vibecheck watch${c.reset} - Continuous Dev Mode
|
|
164
|
+
|
|
165
|
+
${c.bold}USAGE${c.reset}
|
|
166
|
+
vibecheck watch [options]
|
|
167
|
+
|
|
168
|
+
${c.bold}OPTIONS${c.reset}
|
|
169
|
+
--fastify-entry <path> Fastify entry file for route extraction
|
|
170
|
+
--debounce <ms> Debounce delay in ms (default: 500)
|
|
171
|
+
--no-clear Don't clear screen between runs
|
|
172
|
+
--help, -h Show this help
|
|
173
|
+
|
|
174
|
+
${c.bold}WHAT IT DOES${c.reset}
|
|
175
|
+
1. Runs initial ship analysis
|
|
176
|
+
2. Watches for file changes in your project
|
|
177
|
+
3. Re-runs analysis on each change
|
|
178
|
+
4. Shows live verdict dashboard
|
|
179
|
+
|
|
180
|
+
${c.bold}WATCHED FILES${c.reset}
|
|
181
|
+
.ts, .tsx, .js, .jsx, .json, .env, .md, .yml, .yaml
|
|
182
|
+
|
|
183
|
+
${c.bold}IGNORED${c.reset}
|
|
184
|
+
node_modules, .next, .vibecheck, dist, build, .git
|
|
185
|
+
|
|
186
|
+
${c.bold}EXAMPLES${c.reset}
|
|
187
|
+
vibecheck watch # Start watching
|
|
188
|
+
vibecheck watch --debounce 1000 # 1s debounce
|
|
189
|
+
vibecheck watch --no-clear # Keep history
|
|
190
|
+
|
|
191
|
+
${c.dim}Press Ctrl+C to stop watching${c.reset}
|
|
192
|
+
`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function runWatch(argsOrOpts = {}) {
|
|
196
|
+
// Handle array args from CLI
|
|
197
|
+
if (Array.isArray(argsOrOpts)) {
|
|
198
|
+
if (argsOrOpts.includes("--help") || argsOrOpts.includes("-h")) {
|
|
199
|
+
printHelp();
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
const getArg = (flags) => {
|
|
203
|
+
for (const f of flags) {
|
|
204
|
+
const idx = argsOrOpts.indexOf(f);
|
|
205
|
+
if (idx !== -1 && idx < argsOrOpts.length - 1) return argsOrOpts[idx + 1];
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
};
|
|
209
|
+
argsOrOpts = {
|
|
210
|
+
repoRoot: process.cwd(),
|
|
211
|
+
fastifyEntry: getArg(["--fastify-entry"]),
|
|
212
|
+
debounceMs: parseInt(getArg(["--debounce"]) || "500", 10),
|
|
213
|
+
clearScreen: !argsOrOpts.includes("--no-clear"),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const {
|
|
218
|
+
repoRoot,
|
|
219
|
+
fastifyEntry,
|
|
220
|
+
debounceMs = 500,
|
|
221
|
+
clearScreen = true
|
|
222
|
+
} = argsOrOpts;
|
|
223
|
+
|
|
167
224
|
const root = repoRoot || process.cwd();
|
|
168
225
|
let runCount = 0;
|
|
169
226
|
let lastFile = null;
|
package/bin/vibecheck.js
CHANGED
|
@@ -230,34 +230,53 @@ function findSimilarCommands(input, commands, maxDistance = 3) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
233
|
-
//
|
|
233
|
+
// ENTITLEMENTS (v2 - Single Source of Truth)
|
|
234
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
235
|
+
const entitlements = require("./runners/lib/entitlements-v2");
|
|
236
|
+
|
|
237
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
238
|
+
// COMMAND REGISTRY - Tiers match entitlements-v2.js EXACTLY
|
|
234
239
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
235
240
|
const COMMANDS = {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
prove: { description: "
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
241
|
+
// PROOF LOOP
|
|
242
|
+
scan: { description: "Static analysis - routes, secrets, contracts", tier: "free", category: "proof", aliases: ["s", "check"], runner: () => require("./runners/runScan").runScan },
|
|
243
|
+
ship: { description: "Verdict engine - SHIP / WARN / BLOCK", tier: "free", category: "proof", aliases: ["verdict"], caps: "static-only on FREE", runner: () => require("./runners/runShip").runShip },
|
|
244
|
+
reality: { description: "Runtime proof - Playwright clicks every button", tier: "free", category: "proof", aliases: ["r", "test", "e2e"], caps: "preview mode on FREE (5 pages, no auth)", runner: () => { try { return require("./runners/runReality").runReality; } catch (e) { return async () => { console.error("Reality runner unavailable:", e.message); return 1; }; } } },
|
|
245
|
+
prove: { description: "Full proof loop - ctx → reality → ship → fix", tier: "pro", category: "proof", aliases: ["p", "full", "all"], runner: () => require("./runners/runProve").runProve },
|
|
246
|
+
fix: { description: "AI-powered auto-fix", tier: "free", category: "proof", caps: "--plan-only on FREE/STARTER", aliases: ["f", "repair"], runner: () => require("./runners/runFix").runFix },
|
|
247
|
+
report: { description: "Generate HTML/MD/SARIF reports", tier: "free", category: "proof", caps: "HTML/MD only on FREE", aliases: ["html", "artifact"], runner: () => require("./runners/runReport").runReport },
|
|
248
|
+
|
|
249
|
+
// SETUP & DX
|
|
250
|
+
install: { description: "Zero-friction onboarding", tier: "free", category: "setup", aliases: ["i", "bootstrap"], runner: () => require("./runners/runInstall").runInstall },
|
|
251
|
+
init: { description: "Project setup wizard", tier: "free", category: "setup", aliases: ["setup", "configure"], runner: () => require("./runners/runInit").runInit },
|
|
252
|
+
doctor: { description: "Environment diagnostics", tier: "free", category: "setup", aliases: ["health", "diag"], runner: () => require("./runners/runDoctor").runDoctor },
|
|
253
|
+
status: { description: "Project health dashboard", tier: "free", category: "setup", aliases: ["st"], runner: () => require("./runners/runStatus").runStatus },
|
|
254
|
+
watch: { description: "Continuous mode - re-runs on changes", tier: "free", category: "setup", aliases: ["w", "dev"], runner: () => require("./runners/runWatch").runWatch },
|
|
255
|
+
launch: { description: "Pre-launch checklist wizard", tier: "starter", category: "setup", aliases: ["checklist", "preflight"], runner: () => require("./runners/runLaunch").runLaunch },
|
|
256
|
+
|
|
257
|
+
// AI TRUTH
|
|
258
|
+
ctx: { description: "Generate truthpack for AI agents", tier: "free", category: "truth", aliases: ["truthpack", "tp"], subcommands: ["build", "diff", "guard", "sync", "search"], runner: () => require("./runners/runCtx").runCtx },
|
|
259
|
+
guard: { description: "Validate AI claims against truth", tier: "free", category: "truth", aliases: ["validate", "trust"], runner: () => require("./runners/runGuard").runGuard },
|
|
260
|
+
context: { description: "Generate .cursorrules, .windsurf/rules", tier: "free", category: "truth", aliases: ["rules", "ai-rules"], runner: () => require("./runners/runContext").runContext },
|
|
261
|
+
mdc: { description: "Generate MDC specifications", tier: "free", category: "truth", aliases: [], runner: () => require("./runners/runMdc").runMdc },
|
|
262
|
+
|
|
263
|
+
// CI & COLLABORATION (STARTER+)
|
|
264
|
+
gate: { description: "CI/CD gate - blocks deploys on failures", tier: "starter", category: "ci", aliases: ["ci", "block"], runner: () => require("./runners/runGate").runGate },
|
|
265
|
+
pr: { description: "Generate PR comment with findings", tier: "starter", category: "ci", aliases: ["pull-request"], runner: () => require("./runners/runPR").runPR },
|
|
266
|
+
badge: { description: "Generate ship badge for README", tier: "starter", category: "ci", aliases: ["b"], runner: () => require("./runners/runBadge").runBadge },
|
|
267
|
+
|
|
268
|
+
// AUTOMATION (STARTER+/PRO)
|
|
269
|
+
mcp: { description: "Start MCP server for AI IDEs", tier: "starter", category: "automation", aliases: [], runner: () => require("./runners/runMcp").runMcp },
|
|
270
|
+
share: { description: "Generate share pack for PR/docs", tier: "pro", category: "automation", aliases: [], runner: () => require("./runners/runShare").runShare },
|
|
271
|
+
"ai-test": { description: "AI autonomous test generation", tier: "pro", category: "automation", aliases: ["ai", "agent"], runner: () => require("./runners/runAIAgent").runAIAgent },
|
|
272
|
+
|
|
273
|
+
// ACCOUNT (always free)
|
|
255
274
|
login: { description: "Authenticate with API key", tier: "free", category: "account", aliases: ["auth", "signin"], runner: () => require("./runners/runAuth").runLogin, skipAuth: true },
|
|
256
275
|
logout: { description: "Remove stored credentials", tier: "free", category: "account", aliases: ["signout"], runner: () => require("./runners/runAuth").runLogout, skipAuth: true },
|
|
257
276
|
whoami: { description: "Show current user and plan", tier: "free", category: "account", aliases: ["me", "user"], runner: () => require("./runners/runAuth").runWhoami, skipAuth: true },
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
277
|
+
|
|
278
|
+
// EXTRAS
|
|
279
|
+
labs: { description: "Experimental features", tier: "free", category: "extras", aliases: ["experimental", "beta"], runner: () => require("./runners/runLabs").runLabs },
|
|
261
280
|
};
|
|
262
281
|
|
|
263
282
|
const ALIAS_MAP = {};
|
|
@@ -271,27 +290,62 @@ function getRunner(cmd) {
|
|
|
271
290
|
}
|
|
272
291
|
|
|
273
292
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
274
|
-
// AUTH & ACCESS CONTROL
|
|
293
|
+
// AUTH & ACCESS CONTROL (uses entitlements-v2 - NO BYPASS ALLOWED)
|
|
275
294
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
276
295
|
let authModule = null;
|
|
277
296
|
function getAuthModule() { if (!authModule) authModule = require("./runners/lib/auth"); return authModule; }
|
|
278
297
|
|
|
279
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Check command access using entitlements-v2 module.
|
|
300
|
+
* NO OWNER MODE. NO ENV VAR BYPASS. NO OFFLINE ESCALATION.
|
|
301
|
+
*/
|
|
302
|
+
async function checkCommandAccess(cmd, args, authInfo) {
|
|
280
303
|
const def = COMMANDS[cmd];
|
|
281
304
|
if (!def) return { allowed: true };
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
305
|
+
|
|
306
|
+
// Use centralized entitlements enforcement
|
|
307
|
+
const result = await entitlements.enforce(cmd, {
|
|
308
|
+
apiKey: authInfo?.key,
|
|
309
|
+
projectPath: process.cwd(),
|
|
310
|
+
silent: true, // We'll handle messaging ourselves
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (result.allowed) {
|
|
314
|
+
return {
|
|
315
|
+
allowed: true,
|
|
316
|
+
tier: result.tier,
|
|
317
|
+
downgrade: result.downgrade,
|
|
318
|
+
limits: result.limits,
|
|
319
|
+
caps: result.caps,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Not allowed - return with proper exit code
|
|
324
|
+
return {
|
|
325
|
+
allowed: false,
|
|
326
|
+
tier: result.tier,
|
|
327
|
+
requiredTier: result.requiredTier,
|
|
328
|
+
exitCode: result.exitCode,
|
|
329
|
+
reason: formatAccessDenied(cmd, result.requiredTier, result.tier),
|
|
330
|
+
};
|
|
287
331
|
}
|
|
288
332
|
|
|
289
|
-
function formatAccessDenied(cmd, requiredTier,
|
|
333
|
+
function formatAccessDenied(cmd, requiredTier, currentTier) {
|
|
290
334
|
const tierColors = { starter: c.cyan, pro: c.magenta, enterprise: c.yellow };
|
|
291
335
|
const tierColor = tierColors[requiredTier] || c.white;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
336
|
+
const tierLabel = entitlements.getTierLabel(requiredTier);
|
|
337
|
+
const currentLabel = entitlements.getTierLabel(currentTier);
|
|
338
|
+
|
|
339
|
+
let msg = `\n${c.red}${c.bold}⛔ Feature Not Available${c.reset}\n\n`;
|
|
340
|
+
msg += ` ${c.yellow}${cmd}${c.reset} requires ${tierColor}${tierLabel}${c.reset} plan.\n`;
|
|
341
|
+
msg += ` Your current plan: ${c.dim}${currentLabel}${c.reset}\n\n`;
|
|
342
|
+
|
|
343
|
+
if (currentTier === "free") {
|
|
344
|
+
msg += ` ${c.cyan}Get started:${c.reset} vibecheck login\n`;
|
|
345
|
+
}
|
|
346
|
+
msg += ` ${c.cyan}Upgrade at:${c.reset} https://vibecheckai.dev/pricing\n`;
|
|
347
|
+
msg += `\n ${c.dim}Exit code: ${entitlements.EXIT_FEATURE_NOT_ALLOWED}${c.reset}\n`;
|
|
348
|
+
|
|
295
349
|
return msg;
|
|
296
350
|
}
|
|
297
351
|
|
|
@@ -309,40 +363,70 @@ ${c.dim}${sym.boxBottomLeft}${sym.boxHorizontal.repeat(60)}${sym.boxBottomRight}
|
|
|
309
363
|
|
|
310
364
|
function printHelp() {
|
|
311
365
|
printBanner();
|
|
366
|
+
|
|
367
|
+
// Categories ordered as specified
|
|
368
|
+
const categoryOrder = ["proof", "setup", "truth", "ci", "automation", "account", "extras"];
|
|
312
369
|
const categories = {
|
|
313
|
-
proof: { name: "
|
|
314
|
-
setup: { name: "SETUP &
|
|
315
|
-
truth: { name: "TRUTH
|
|
316
|
-
|
|
317
|
-
ci: { name: "CI/CD", color: c.cyan, icon: sym.rocket },
|
|
370
|
+
proof: { name: "PROOF LOOP", color: c.green, icon: sym.shield },
|
|
371
|
+
setup: { name: "SETUP & DX", color: c.yellow, icon: sym.gear },
|
|
372
|
+
truth: { name: "AI TRUTH", color: c.magenta, icon: sym.lightning },
|
|
373
|
+
ci: { name: "CI & COLLABORATION", color: c.cyan, icon: sym.rocket },
|
|
318
374
|
automation: { name: "AUTOMATION", color: c.blue, icon: sym.fire },
|
|
319
375
|
account: { name: "ACCOUNT", color: c.dim, icon: sym.key },
|
|
376
|
+
extras: { name: "EXTRAS", color: c.dim, icon: sym.star },
|
|
320
377
|
};
|
|
378
|
+
|
|
379
|
+
// Group commands
|
|
321
380
|
const grouped = {};
|
|
322
381
|
for (const [cmd, def] of Object.entries(COMMANDS)) {
|
|
323
382
|
const cat = def.category || "extras";
|
|
324
383
|
if (!grouped[cat]) grouped[cat] = [];
|
|
325
384
|
grouped[cat].push({ cmd, ...def });
|
|
326
385
|
}
|
|
327
|
-
|
|
328
|
-
|
|
386
|
+
|
|
387
|
+
// Print in order
|
|
388
|
+
for (const catKey of categoryOrder) {
|
|
389
|
+
const commands = grouped[catKey];
|
|
390
|
+
if (!commands || commands.length === 0) continue;
|
|
391
|
+
|
|
392
|
+
const cat = categories[catKey];
|
|
329
393
|
console.log(`\n${cat.color}${cat.icon} ${cat.name}${c.reset}\n`);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
394
|
+
|
|
395
|
+
for (const { cmd, description, tier, aliases, caps } of commands) {
|
|
396
|
+
// Tier badge with color
|
|
397
|
+
let tierBadge = "";
|
|
398
|
+
if (tier === "free") {
|
|
399
|
+
tierBadge = `${c.green}[FREE]${c.reset} `;
|
|
400
|
+
} else if (tier === "starter") {
|
|
401
|
+
tierBadge = `${c.cyan}[STARTER]${c.reset} `;
|
|
402
|
+
} else if (tier === "pro") {
|
|
403
|
+
tierBadge = `${c.magenta}[PRO]${c.reset} `;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Caps info (e.g., "preview mode on FREE")
|
|
407
|
+
const capsStr = caps ? `${c.dim}(${caps})${c.reset}` : "";
|
|
408
|
+
|
|
409
|
+
console.log(` ${c.cyan}${cmd.padEnd(12)}${c.reset} ${tierBadge}${description} ${capsStr}`);
|
|
334
410
|
}
|
|
335
411
|
}
|
|
412
|
+
|
|
336
413
|
console.log(`
|
|
337
414
|
${c.dim}${sym.boxHorizontal.repeat(64)}${c.reset}
|
|
338
415
|
|
|
416
|
+
${c.green}TIERS${c.reset}
|
|
417
|
+
|
|
418
|
+
${c.green}FREE${c.reset} $0 scan, ship, ctx, doctor, report (HTML/MD)
|
|
419
|
+
${c.cyan}STARTER${c.reset} $29/mo + gate, launch, pr, badge, mcp, reality full
|
|
420
|
+
${c.magenta}PRO${c.reset} $99/mo + prove, fix apply, share, ai-test, compliance
|
|
421
|
+
|
|
339
422
|
${c.green}QUICK START${c.reset}
|
|
340
423
|
|
|
341
424
|
${c.bold}"Check my repo"${c.reset} ${c.cyan}vibecheck scan${c.reset}
|
|
342
|
-
${c.bold}"Can I ship?"${c.reset} ${c.cyan}vibecheck
|
|
343
|
-
${c.bold}"
|
|
425
|
+
${c.bold}"Can I ship?"${c.reset} ${c.cyan}vibecheck ship${c.reset}
|
|
426
|
+
${c.bold}"Full proof loop"${c.reset} ${c.cyan}vibecheck prove --url http://localhost:3000${c.reset} ${c.magenta}[PRO]${c.reset}
|
|
344
427
|
|
|
345
428
|
${c.dim}Run 'vibecheck <command> --help' for command-specific help.${c.reset}
|
|
429
|
+
${c.dim}Pricing: https://vibecheckai.dev/pricing${c.reset}
|
|
346
430
|
`);
|
|
347
431
|
}
|
|
348
432
|
|
|
@@ -419,20 +503,35 @@ async function main() {
|
|
|
419
503
|
}
|
|
420
504
|
|
|
421
505
|
const cmdDef = COMMANDS[cmd];
|
|
422
|
-
let authInfo = { key: null
|
|
506
|
+
let authInfo = { key: null };
|
|
423
507
|
|
|
424
508
|
if (!cmdDef.skipAuth) {
|
|
425
509
|
const auth = getAuthModule();
|
|
426
510
|
const { key } = auth.getApiKey();
|
|
511
|
+
authInfo.key = key;
|
|
427
512
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
513
|
+
// Use entitlements-v2 for access control (NO BYPASS)
|
|
514
|
+
const access = await checkCommandAccess(cmd, cmdArgs, authInfo);
|
|
515
|
+
|
|
516
|
+
if (!access.allowed) {
|
|
517
|
+
console.log(access.reason);
|
|
518
|
+
// Use proper exit code: 3 = feature not allowed
|
|
519
|
+
process.exit(access.exitCode || entitlements.EXIT_FEATURE_NOT_ALLOWED);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Show downgrade notice if applicable
|
|
523
|
+
if (access.downgrade && !config.quiet) {
|
|
524
|
+
console.log(`${c.yellow}${sym.warning}${c.reset} Running in ${c.yellow}${access.downgrade}${c.reset} mode (upgrade for full access)`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Show tier badge
|
|
528
|
+
if (!config.quiet) {
|
|
529
|
+
if (access.tier === "starter") console.log(`${c.cyan}${sym.arrowRight} STARTER${c.reset} ${c.dim}feature${c.reset}`);
|
|
530
|
+
else if (access.tier === "pro") console.log(`${c.magenta}${sym.arrowRight} PRO${c.reset} ${c.dim}feature${c.reset}`);
|
|
531
|
+
}
|
|
431
532
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (access.tier === "starter" && !config.quiet) console.log(`${c.cyan}${sym.arrowRight} STARTER${c.reset} ${c.dim}feature${c.reset}`);
|
|
435
|
-
else if (access.tier === "pro" && !config.quiet) console.log(`${c.magenta}${sym.arrowRight} PRO${c.reset} ${c.dim}feature${c.reset}`);
|
|
533
|
+
// Attach access info for runners to use
|
|
534
|
+
authInfo.access = access;
|
|
436
535
|
}
|
|
437
536
|
|
|
438
537
|
state.runCount++; state.lastRun = Date.now();
|
|
@@ -447,20 +546,20 @@ async function main() {
|
|
|
447
546
|
const context = { repoRoot: process.cwd(), config, state, authInfo, version: VERSION, isCI: isCI() };
|
|
448
547
|
|
|
449
548
|
switch (cmd) {
|
|
450
|
-
case "prove": exitCode = await runner(
|
|
451
|
-
case "reality": exitCode = await runner(
|
|
452
|
-
case "watch": exitCode = await runner(
|
|
549
|
+
case "prove": exitCode = await runner(cmdArgs); break;
|
|
550
|
+
case "reality": exitCode = await runner(cmdArgs); break;
|
|
551
|
+
case "watch": exitCode = await runner(cmdArgs); break;
|
|
453
552
|
case "ctx": case "truthpack":
|
|
454
553
|
if (cmdArgs[0] === "sync") { const { runCtxSync } = require("./runners/runCtxSync"); exitCode = await runCtxSync({ ...context, fastifyEntry: getArgValue(cmdArgs, ["--fastify-entry"]) }); }
|
|
455
554
|
else if (cmdArgs[0] === "guard") { const { runCtxGuard } = require("./runners/runCtxGuard"); exitCode = await runCtxGuard.main(cmdArgs.slice(1)); }
|
|
456
555
|
else if (cmdArgs[0] === "diff") { const { main: ctxDiffMain } = require("./runners/runCtxDiff"); exitCode = await ctxDiffMain(cmdArgs.slice(1)); }
|
|
457
556
|
else if (cmdArgs[0] === "search") { const { runContext } = require("./runners/runContext"); exitCode = await runContext(["--search", ...cmdArgs.slice(1)]); }
|
|
458
|
-
else { exitCode = await runner(
|
|
557
|
+
else { exitCode = await runner(cmdArgs); }
|
|
459
558
|
break;
|
|
460
|
-
case "install": exitCode = await runner(
|
|
559
|
+
case "install": exitCode = await runner(cmdArgs); break;
|
|
461
560
|
case "status": exitCode = await runner({ ...context, json: cmdArgs.includes("--json") }); break;
|
|
462
|
-
case "pr": exitCode = await runner(
|
|
463
|
-
case "share": exitCode = await runner(
|
|
561
|
+
case "pr": exitCode = await runner(cmdArgs); break;
|
|
562
|
+
case "share": exitCode = await runner(cmdArgs); break;
|
|
464
563
|
default: exitCode = await runner(cmdArgs);
|
|
465
564
|
}
|
|
466
565
|
} catch (error) { console.error(formatError(error, config)); exitCode = 1; }
|