@vibecheckai/cli 3.1.6 → 3.2.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/README.md +27 -32
- package/bin/registry.js +208 -343
- package/bin/runners/context/generators/mcp.js +18 -0
- package/bin/runners/context/index.js +72 -4
- package/bin/runners/context/proof-context.js +293 -1
- package/bin/runners/context/security-scanner.js +311 -73
- package/bin/runners/lib/analyzers.js +607 -20
- package/bin/runners/lib/detectors-v2.js +172 -15
- package/bin/runners/lib/entitlements-v2.js +48 -1
- package/bin/runners/lib/evidence-pack.js +678 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/missions/plan.js +231 -41
- package/bin/runners/lib/missions/templates.js +125 -0
- package/bin/runners/lib/scan-output.js +492 -253
- package/bin/runners/lib/ship-output.js +901 -641
- package/bin/runners/runCheckpoint.js +44 -3
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runContext.js +2 -3
- package/bin/runners/runDoctor.js +11 -4
- package/bin/runners/runFix.js +51 -341
- package/bin/runners/runInit.js +37 -20
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +608 -29
- package/bin/runners/runProve.js +210 -25
- package/bin/runners/runReality.js +861 -107
- package/bin/runners/runScan.js +238 -4
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runWatch.js +25 -5
- package/bin/vibecheck.js +35 -47
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +152 -15
- package/mcp-server/package.json +1 -1
- package/mcp-server/proof-tools.js +571 -0
- package/mcp-server/tier-auth.js +22 -19
- package/mcp-server/tools-v3.js +744 -0
- package/mcp-server/truth-firewall-tools.js +190 -4
- package/package.json +3 -1
- package/bin/runners/runBadge.js +0 -916
- package/bin/runners/runContracts.js +0 -105
- package/bin/runners/runCtx.js +0 -680
- package/bin/runners/runCtxDiff.js +0 -301
- package/bin/runners/runCtxGuard.js +0 -176
- package/bin/runners/runCtxSync.js +0 -116
- package/bin/runners/runExport.js +0 -93
- package/bin/runners/runGraph.js +0 -454
- package/bin/runners/runInstall.js +0 -273
- package/bin/runners/runLabs.js +0 -341
- package/bin/runners/runLaunch.js +0 -181
- package/bin/runners/runPR.js +0 -255
- package/bin/runners/runPermissions.js +0 -310
- package/bin/runners/runPreflight.js +0 -580
- package/bin/runners/runReplay.js +0 -499
- package/bin/runners/runSecurity.js +0 -92
- package/bin/runners/runShare.js +0 -212
- package/bin/runners/runStatus.js +0 -102
- package/bin/runners/runVerify.js +0 -272
package/bin/runners/runScan.js
CHANGED
|
@@ -103,6 +103,14 @@ function parseArgs(args) {
|
|
|
103
103
|
noBanner: globalFlags.noBanner || false,
|
|
104
104
|
ci: globalFlags.ci || false,
|
|
105
105
|
quiet: globalFlags.quiet || false,
|
|
106
|
+
// Allowlist subcommand
|
|
107
|
+
allowlist: null, // null = not using allowlist, or 'list' | 'add' | 'remove' | 'check'
|
|
108
|
+
allowlistId: null,
|
|
109
|
+
allowlistPattern: null,
|
|
110
|
+
allowlistReason: null,
|
|
111
|
+
allowlistScope: 'global',
|
|
112
|
+
allowlistFile: null,
|
|
113
|
+
allowlistExpires: null,
|
|
106
114
|
};
|
|
107
115
|
|
|
108
116
|
// Parse command-specific args from cleanArgs
|
|
@@ -118,6 +126,22 @@ function parseArgs(args) {
|
|
|
118
126
|
else if (arg === '--no-save') opts.save = false;
|
|
119
127
|
else if (arg === '--path' || arg === '-p') opts.path = cleanArgs[++i] || process.cwd();
|
|
120
128
|
else if (arg.startsWith('--path=')) opts.path = arg.split('=')[1];
|
|
129
|
+
// Allowlist subcommand support
|
|
130
|
+
else if (arg === '--allowlist') {
|
|
131
|
+
const nextArg = cleanArgs[i + 1];
|
|
132
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
133
|
+
opts.allowlist = nextArg;
|
|
134
|
+
i++;
|
|
135
|
+
} else {
|
|
136
|
+
opts.allowlist = 'list'; // Default to list
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if (arg === '--id' && opts.allowlist) opts.allowlistId = cleanArgs[++i];
|
|
140
|
+
else if (arg === '--pattern' && opts.allowlist) opts.allowlistPattern = cleanArgs[++i];
|
|
141
|
+
else if (arg === '--reason' && opts.allowlist) opts.allowlistReason = cleanArgs[++i];
|
|
142
|
+
else if (arg === '--scope' && opts.allowlist) opts.allowlistScope = cleanArgs[++i];
|
|
143
|
+
else if (arg === '--file' && opts.allowlist) opts.allowlistFile = cleanArgs[++i];
|
|
144
|
+
else if (arg === '--expires' && opts.allowlist) opts.allowlistExpires = cleanArgs[++i];
|
|
121
145
|
else if (!arg.startsWith('-')) opts.path = path.resolve(arg);
|
|
122
146
|
}
|
|
123
147
|
|
|
@@ -142,6 +166,17 @@ function printHelp(showBanner = true) {
|
|
|
142
166
|
${ansi.bold}Fix Mode:${ansi.reset}
|
|
143
167
|
${colors.accent}--autofix, -f${ansi.reset} Apply safe fixes + generate AI missions ${ansi.rgb(0, 200, 255)}[STARTER]${ansi.reset}
|
|
144
168
|
|
|
169
|
+
${ansi.bold}Allowlist (suppress false positives):${ansi.reset}
|
|
170
|
+
${colors.accent}--allowlist list${ansi.reset} Show all allowlist entries
|
|
171
|
+
${colors.accent}--allowlist add${ansi.reset} Add entry to allowlist
|
|
172
|
+
${ansi.dim}--id <finding-id>${ansi.reset} Finding ID to allowlist
|
|
173
|
+
${ansi.dim}--pattern <regex>${ansi.reset} Pattern to match
|
|
174
|
+
${ansi.dim}--reason <text>${ansi.reset} Reason (required)
|
|
175
|
+
${ansi.dim}--scope <global|file|line>${ansi.reset} Scope (default: global)
|
|
176
|
+
${ansi.dim}--expires <days>${ansi.reset} Auto-expire after N days
|
|
177
|
+
${colors.accent}--allowlist remove --id <id>${ansi.reset} Remove entry from allowlist
|
|
178
|
+
${colors.accent}--allowlist check --id <id>${ansi.reset} Check if finding is allowlisted
|
|
179
|
+
|
|
145
180
|
${ansi.bold}Options:${ansi.reset}
|
|
146
181
|
${colors.accent}--url, -u${ansi.reset} Base URL for reality testing (e.g., http://localhost:3000)
|
|
147
182
|
${colors.accent}--verbose, -v${ansi.reset} Show detailed progress
|
|
@@ -157,8 +192,11 @@ function printHelp(showBanner = true) {
|
|
|
157
192
|
${ansi.dim}# Scan + autofix with missions${ansi.reset}
|
|
158
193
|
vibecheck scan --autofix
|
|
159
194
|
|
|
160
|
-
${ansi.dim}#
|
|
161
|
-
vibecheck scan --
|
|
195
|
+
${ansi.dim}# Add false positive to allowlist${ansi.reset}
|
|
196
|
+
vibecheck scan --allowlist add --id R_DEAD_abc123 --reason "Known toggle"
|
|
197
|
+
|
|
198
|
+
${ansi.dim}# List allowlist entries${ansi.reset}
|
|
199
|
+
vibecheck scan --allowlist list
|
|
162
200
|
|
|
163
201
|
${ansi.dim}# Full proof with Playwright${ansi.reset}
|
|
164
202
|
vibecheck scan --reality --url http://localhost:3000
|
|
@@ -169,6 +207,172 @@ function printHelp(showBanner = true) {
|
|
|
169
207
|
`);
|
|
170
208
|
}
|
|
171
209
|
|
|
210
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
211
|
+
// ALLOWLIST MANAGEMENT (integrated from runAllowlist)
|
|
212
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
213
|
+
|
|
214
|
+
async function handleAllowlistCommand(opts) {
|
|
215
|
+
const root = opts.path || process.cwd();
|
|
216
|
+
|
|
217
|
+
// Load evidence-pack module for allowlist functions
|
|
218
|
+
let evidencePack;
|
|
219
|
+
try {
|
|
220
|
+
evidencePack = require("./lib/evidence-pack");
|
|
221
|
+
} catch (e) {
|
|
222
|
+
console.error(`${colors.error}✗${ansi.reset} Failed to load allowlist module: ${e.message}`);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const action = opts.allowlist;
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
switch (action) {
|
|
230
|
+
case "list": {
|
|
231
|
+
const allowlist = evidencePack.loadAllowlist(root);
|
|
232
|
+
|
|
233
|
+
if (opts.json) {
|
|
234
|
+
console.log(JSON.stringify(allowlist, null, 2));
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!opts.quiet) {
|
|
239
|
+
console.log(`\n 📋 ${ansi.bold}Allowlist Entries${ansi.reset}\n`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!allowlist.entries || allowlist.entries.length === 0) {
|
|
243
|
+
console.log(` ${ansi.dim}No entries in allowlist.${ansi.reset}\n`);
|
|
244
|
+
console.log(` ${ansi.dim}Add entries with: vibecheck scan --allowlist add --id <id> --reason "..."${ansi.reset}\n`);
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const entry of allowlist.entries) {
|
|
249
|
+
const expireStr = entry.expiresAt
|
|
250
|
+
? `${ansi.dim}expires ${new Date(entry.expiresAt).toLocaleDateString()}${ansi.reset}`
|
|
251
|
+
: '';
|
|
252
|
+
const scopeStr = entry.scope !== 'global'
|
|
253
|
+
? `${colors.accent}[${entry.scope}]${ansi.reset} `
|
|
254
|
+
: '';
|
|
255
|
+
|
|
256
|
+
console.log(` ${colors.success}✓${ansi.reset} ${ansi.bold}${entry.id}${ansi.reset} ${scopeStr}${expireStr}`);
|
|
257
|
+
|
|
258
|
+
if (entry.findingId) {
|
|
259
|
+
console.log(` ${ansi.dim}Finding:${ansi.reset} ${entry.findingId}`);
|
|
260
|
+
}
|
|
261
|
+
if (entry.pattern) {
|
|
262
|
+
console.log(` ${ansi.dim}Pattern:${ansi.reset} ${entry.pattern}`);
|
|
263
|
+
}
|
|
264
|
+
console.log(` ${ansi.dim}Reason:${ansi.reset} ${entry.reason || 'No reason provided'}`);
|
|
265
|
+
console.log(` ${ansi.dim}Added:${ansi.reset} ${entry.addedAt} by ${entry.addedBy || 'user'}`);
|
|
266
|
+
console.log();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(` ${ansi.dim}Total: ${allowlist.entries.length} entries${ansi.reset}\n`);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
case "add": {
|
|
274
|
+
if (!opts.allowlistId && !opts.allowlistPattern) {
|
|
275
|
+
console.error(`\n ${colors.error}✗${ansi.reset} Either --id or --pattern is required\n`);
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!opts.allowlistReason) {
|
|
280
|
+
console.error(`\n ${colors.error}✗${ansi.reset} --reason is required\n`);
|
|
281
|
+
return 1;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const entry = {
|
|
285
|
+
findingId: opts.allowlistId,
|
|
286
|
+
pattern: opts.allowlistPattern,
|
|
287
|
+
reason: opts.allowlistReason,
|
|
288
|
+
scope: opts.allowlistScope,
|
|
289
|
+
addedBy: 'cli'
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (opts.allowlistFile) entry.file = opts.allowlistFile;
|
|
293
|
+
if (opts.allowlistExpires) {
|
|
294
|
+
const days = parseInt(opts.allowlistExpires, 10);
|
|
295
|
+
entry.expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const added = evidencePack.addToAllowlist(root, entry);
|
|
299
|
+
|
|
300
|
+
if (opts.json) {
|
|
301
|
+
console.log(JSON.stringify({ added }, null, 2));
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
console.log(`\n ${colors.success}+${ansi.reset} Added allowlist entry: ${ansi.bold}${added.id}${ansi.reset}\n`);
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case "remove": {
|
|
310
|
+
if (!opts.allowlistId) {
|
|
311
|
+
console.error(`\n ${colors.error}✗${ansi.reset} --id is required for remove\n`);
|
|
312
|
+
return 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const allowlist = evidencePack.loadAllowlist(root);
|
|
316
|
+
const before = allowlist.entries.length;
|
|
317
|
+
allowlist.entries = allowlist.entries.filter(e => e.id !== opts.allowlistId && e.findingId !== opts.allowlistId);
|
|
318
|
+
const removed = before - allowlist.entries.length;
|
|
319
|
+
|
|
320
|
+
if (removed > 0) {
|
|
321
|
+
evidencePack.saveAllowlist(root, allowlist);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (opts.json) {
|
|
325
|
+
console.log(JSON.stringify({ removed, remaining: allowlist.entries.length }, null, 2));
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (removed > 0) {
|
|
330
|
+
console.log(`\n ${colors.success}-${ansi.reset} Removed ${removed} entry from allowlist\n`);
|
|
331
|
+
} else {
|
|
332
|
+
console.log(`\n ${colors.warning}⚠${ansi.reset} Entry not found: ${opts.allowlistId}\n`);
|
|
333
|
+
}
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
case "check": {
|
|
338
|
+
if (!opts.allowlistId) {
|
|
339
|
+
console.error(`\n ${colors.error}✗${ansi.reset} --id is required for check\n`);
|
|
340
|
+
return 1;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const allowlist = evidencePack.loadAllowlist(root);
|
|
344
|
+
const result = evidencePack.isAllowlisted({ id: opts.allowlistId }, allowlist);
|
|
345
|
+
|
|
346
|
+
if (opts.json) {
|
|
347
|
+
console.log(JSON.stringify(result, null, 2));
|
|
348
|
+
return result.allowed ? 0 : 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (result.allowed) {
|
|
352
|
+
console.log(`\n ${colors.success}✓${ansi.reset} ${ansi.bold}Allowlisted${ansi.reset}`);
|
|
353
|
+
console.log(` ${ansi.dim}Reason:${ansi.reset} ${result.reason}`);
|
|
354
|
+
console.log(` ${ansi.dim}Entry:${ansi.reset} ${result.entry?.id}\n`);
|
|
355
|
+
} else {
|
|
356
|
+
console.log(`\n ${colors.warning}⚠${ansi.reset} ${ansi.bold}Not allowlisted${ansi.reset}\n`);
|
|
357
|
+
}
|
|
358
|
+
return result.allowed ? 0 : 1;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
default:
|
|
362
|
+
console.error(`\n ${colors.error}✗${ansi.reset} Unknown allowlist action: ${action}\n`);
|
|
363
|
+
console.log(` ${ansi.dim}Available: list, add, remove, check${ansi.reset}\n`);
|
|
364
|
+
return 1;
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (opts.json) {
|
|
368
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
369
|
+
} else {
|
|
370
|
+
console.error(`\n ${colors.error}✗${ansi.reset} ${error.message}\n`);
|
|
371
|
+
}
|
|
372
|
+
return 1;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
172
376
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
173
377
|
// AUTOFIX & MISSION GENERATION
|
|
174
378
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -453,6 +657,11 @@ async function runScan(args) {
|
|
|
453
657
|
return 0;
|
|
454
658
|
}
|
|
455
659
|
|
|
660
|
+
// Handle --allowlist subcommand
|
|
661
|
+
if (opts.allowlist) {
|
|
662
|
+
return await handleAllowlistCommand(opts);
|
|
663
|
+
}
|
|
664
|
+
|
|
456
665
|
// Entitlement check (graceful offline handling)
|
|
457
666
|
try {
|
|
458
667
|
await enforceLimit('scans');
|
|
@@ -531,19 +740,44 @@ async function runScan(args) {
|
|
|
531
740
|
// Fallback to JS-based scanner using truth.js and analyzers.js
|
|
532
741
|
useFallbackScanner = true;
|
|
533
742
|
const { buildTruthpack } = require('./lib/truth');
|
|
534
|
-
const {
|
|
743
|
+
const {
|
|
744
|
+
findMissingRoutes,
|
|
745
|
+
findEnvGaps,
|
|
746
|
+
findFakeSuccess,
|
|
747
|
+
findGhostAuth,
|
|
748
|
+
findMockData,
|
|
749
|
+
findTodoFixme,
|
|
750
|
+
findConsoleLogs,
|
|
751
|
+
findHardcodedSecrets,
|
|
752
|
+
findDeadCode,
|
|
753
|
+
findDeprecatedApis,
|
|
754
|
+
findEmptyCatch,
|
|
755
|
+
findUnsafeRegex,
|
|
756
|
+
} = require('./lib/analyzers');
|
|
535
757
|
|
|
536
758
|
scanRouteIntegrity = async function({ projectPath, layers, baseUrl, verbose }) {
|
|
537
759
|
// Build truthpack for route analysis
|
|
538
760
|
const truthpack = await buildTruthpack({ repoRoot: projectPath });
|
|
539
761
|
|
|
540
|
-
// Run analyzers
|
|
762
|
+
// Run ALL analyzers for comprehensive scanning
|
|
541
763
|
const findings = [];
|
|
764
|
+
|
|
765
|
+
// Core analyzers (route integrity, env, auth)
|
|
542
766
|
findings.push(...findMissingRoutes(truthpack));
|
|
543
767
|
findings.push(...findEnvGaps(truthpack));
|
|
544
768
|
findings.push(...findFakeSuccess(projectPath));
|
|
545
769
|
findings.push(...findGhostAuth(truthpack, projectPath));
|
|
546
770
|
|
|
771
|
+
// Code quality analyzers (MOCK, TODO, console.log, etc.)
|
|
772
|
+
findings.push(...findMockData(projectPath));
|
|
773
|
+
findings.push(...findTodoFixme(projectPath));
|
|
774
|
+
findings.push(...findConsoleLogs(projectPath));
|
|
775
|
+
findings.push(...findHardcodedSecrets(projectPath));
|
|
776
|
+
findings.push(...findDeadCode(projectPath));
|
|
777
|
+
findings.push(...findDeprecatedApis(projectPath));
|
|
778
|
+
findings.push(...findEmptyCatch(projectPath));
|
|
779
|
+
findings.push(...findUnsafeRegex(projectPath));
|
|
780
|
+
|
|
547
781
|
// Convert to scan format matching TypeScript scanner output
|
|
548
782
|
const shipBlockers = findings.map((f, i) => ({
|
|
549
783
|
id: f.id || `finding-${i}`,
|
package/bin/runners/runShip.js
CHANGED
|
@@ -1044,23 +1044,39 @@ async function runShip(args, context = {}) {
|
|
|
1044
1044
|
duration: Date.now() - executionStart,
|
|
1045
1045
|
};
|
|
1046
1046
|
|
|
1047
|
+
// Get current tier for output formatting
|
|
1048
|
+
const currentTier = context?.authInfo?.access?.tier || getCurrentTier() || "free";
|
|
1049
|
+
|
|
1047
1050
|
console.log(formatShipOutput(result, {
|
|
1048
1051
|
verbose: opts.verbose,
|
|
1049
1052
|
showFix: opts.fix,
|
|
1050
1053
|
showBadge: opts.badge,
|
|
1051
1054
|
outputDir,
|
|
1052
1055
|
projectPath,
|
|
1056
|
+
tier: currentTier,
|
|
1057
|
+
isVerified: opts.withRuntime || false, // Reality testing = verified
|
|
1053
1058
|
}));
|
|
1054
1059
|
|
|
1055
|
-
// Badge file generation
|
|
1060
|
+
// Badge file generation (STARTER+ only)
|
|
1056
1061
|
if (opts.badge) {
|
|
1057
|
-
const
|
|
1062
|
+
const isVerified = opts.withRuntime && (currentTier === 'pro' || currentTier === 'compliance');
|
|
1063
|
+
const { data: badgeData } = renderBadgeOutput(projectPath, verdict, results.score, {
|
|
1064
|
+
tier: currentTier,
|
|
1065
|
+
isVerified
|
|
1066
|
+
});
|
|
1058
1067
|
|
|
1059
1068
|
// Save badge info
|
|
1060
1069
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
1061
1070
|
fs.writeFileSync(
|
|
1062
1071
|
path.join(outputDir, 'badge.json'),
|
|
1063
|
-
JSON.stringify({
|
|
1072
|
+
JSON.stringify({
|
|
1073
|
+
...badgeData,
|
|
1074
|
+
verdict,
|
|
1075
|
+
score: results.score,
|
|
1076
|
+
tier: currentTier,
|
|
1077
|
+
isVerified,
|
|
1078
|
+
generatedAt: new Date().toISOString()
|
|
1079
|
+
}, null, 2)
|
|
1064
1080
|
);
|
|
1065
1081
|
}
|
|
1066
1082
|
|
package/bin/runners/runWatch.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
const fs = require("fs");
|
|
16
16
|
const path = require("path");
|
|
17
|
+
const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
|
|
17
18
|
const { shipCore } = require("./runShip");
|
|
18
19
|
|
|
19
20
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -202,7 +203,13 @@ function printTopFindings(findings, max = 5) {
|
|
|
202
203
|
.filter(f => f.severity === "BLOCK" || f.severity === "WARN")
|
|
203
204
|
.slice(0, max);
|
|
204
205
|
|
|
205
|
-
if (!top.length)
|
|
206
|
+
if (!top.length) {
|
|
207
|
+
// Show success upsell for PRO
|
|
208
|
+
console.log(` ${colors.shipGreen}✓${c.reset} ${c.bold}No issues detected!${c.reset}`);
|
|
209
|
+
console.log(` ${c.dim}Want a verified badge? Try${c.reset} ${colors.cyan}vibecheck prove${c.reset} ${c.dim}(PRO)${c.reset}`);
|
|
210
|
+
console.log();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
206
213
|
|
|
207
214
|
console.log(` ${c.bold}Top findings:${c.reset}`);
|
|
208
215
|
for (const f of top) {
|
|
@@ -210,6 +217,13 @@ function printTopFindings(findings, max = 5) {
|
|
|
210
217
|
const title = f.title?.length > 50 ? f.title.slice(0, 47) + '...' : f.title;
|
|
211
218
|
console.log(` ${icon}${c.reset} ${title}`);
|
|
212
219
|
}
|
|
220
|
+
|
|
221
|
+
// Show upsell based on findings
|
|
222
|
+
const blockers = findings.filter(f => f.severity === "BLOCK").length;
|
|
223
|
+
if (blockers > 0) {
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(` ${colors.cyan}→${c.reset} ${c.dim}Fix these with${c.reset} ${colors.cyan}vibecheck fix${c.reset} ${c.dim}(STARTER)${c.reset}`);
|
|
226
|
+
}
|
|
213
227
|
console.log();
|
|
214
228
|
}
|
|
215
229
|
|
|
@@ -273,8 +287,10 @@ function watchDirectory(dir, callback) {
|
|
|
273
287
|
};
|
|
274
288
|
}
|
|
275
289
|
|
|
276
|
-
function printHelp() {
|
|
277
|
-
|
|
290
|
+
function printHelp(opts = {}) {
|
|
291
|
+
if (shouldShowBanner(opts)) {
|
|
292
|
+
console.log(BANNER_FULL);
|
|
293
|
+
}
|
|
278
294
|
console.log(`
|
|
279
295
|
${c.bold}Usage:${c.reset} vibecheck watch [options]
|
|
280
296
|
|
|
@@ -314,9 +330,12 @@ function printHelp() {
|
|
|
314
330
|
|
|
315
331
|
async function runWatch(argsOrOpts = {}) {
|
|
316
332
|
// Handle array args from CLI
|
|
333
|
+
let globalOpts = { noBanner: false, json: false, quiet: false, ci: false };
|
|
317
334
|
if (Array.isArray(argsOrOpts)) {
|
|
318
|
-
|
|
319
|
-
|
|
335
|
+
const { flags } = parseGlobalFlags(argsOrOpts);
|
|
336
|
+
globalOpts = { ...globalOpts, ...flags };
|
|
337
|
+
if (globalOpts.help) {
|
|
338
|
+
printHelp(globalOpts);
|
|
320
339
|
return 0;
|
|
321
340
|
}
|
|
322
341
|
const getArg = (flags) => {
|
|
@@ -331,6 +350,7 @@ async function runWatch(argsOrOpts = {}) {
|
|
|
331
350
|
fastifyEntry: getArg(["--fastify-entry"]),
|
|
332
351
|
debounceMs: parseInt(getArg(["--debounce"]) || "500", 10),
|
|
333
352
|
clearScreen: !argsOrOpts.includes("--no-clear"),
|
|
353
|
+
...globalOpts,
|
|
334
354
|
};
|
|
335
355
|
}
|
|
336
356
|
|
package/bin/vibecheck.js
CHANGED
|
@@ -888,6 +888,14 @@ ${c.green}QUICK START - The 5-Step Journey${c.reset}
|
|
|
888
888
|
4. ${c.bold}Prove${c.reset} ${c.cyan}vibecheck prove${c.reset} ${c.magenta}[PRO]${c.reset}
|
|
889
889
|
5. ${c.bold}Ship${c.reset} ${c.cyan}vibecheck ship${c.reset}
|
|
890
890
|
|
|
891
|
+
${c.bold}GLOBAL OPTIONS${c.reset}
|
|
892
|
+
|
|
893
|
+
${c.cyan}--offline, --local${c.reset} Run in offline mode (no API, unlimited local scans)
|
|
894
|
+
${c.cyan}--json${c.reset} Output as JSON
|
|
895
|
+
${c.cyan}--quiet, -q${c.reset} Suppress output
|
|
896
|
+
${c.cyan}--verbose${c.reset} Show detailed output
|
|
897
|
+
${c.cyan}--path, -p <dir>${c.reset} Run in specified directory
|
|
898
|
+
|
|
891
899
|
${c.bold}SHELL COMPLETIONS${c.reset}
|
|
892
900
|
|
|
893
901
|
${c.cyan}vibecheck completion bash${c.reset} ${c.dim}# Add to ~/.bashrc${c.reset}
|
|
@@ -939,6 +947,7 @@ function parseGlobalFlags(rawArgs) {
|
|
|
939
947
|
debug: false,
|
|
940
948
|
strict: false,
|
|
941
949
|
noBanner: false,
|
|
950
|
+
offline: false,
|
|
942
951
|
path: process.cwd(),
|
|
943
952
|
output: null,
|
|
944
953
|
};
|
|
@@ -980,6 +989,10 @@ function parseGlobalFlags(rawArgs) {
|
|
|
980
989
|
case "--no-banner":
|
|
981
990
|
flags.noBanner = true;
|
|
982
991
|
break;
|
|
992
|
+
case "--offline":
|
|
993
|
+
case "--local":
|
|
994
|
+
flags.offline = true;
|
|
995
|
+
break;
|
|
983
996
|
case "--path":
|
|
984
997
|
case "-p":
|
|
985
998
|
flags.path = rawArgs[++i] || process.cwd();
|
|
@@ -1077,16 +1090,14 @@ async function main() {
|
|
|
1077
1090
|
}
|
|
1078
1091
|
if (globalFlags.verbose) cmdArgs.push("--verbose");
|
|
1079
1092
|
if (globalFlags.strict) cmdArgs.push("--strict");
|
|
1093
|
+
if (globalFlags.offline) cmdArgs.push("--offline");
|
|
1080
1094
|
|
|
1081
1095
|
// Unknown command
|
|
1082
1096
|
if (!COMMANDS[cmd]) {
|
|
1083
1097
|
const suggestions = findSimilarCommands(cmd, ALL_COMMANDS);
|
|
1084
1098
|
console.log(`\n${c.red}${sym.error}${c.reset} Unknown command: ${c.yellow}${cmd}${c.reset}`);
|
|
1085
1099
|
|
|
1086
|
-
if (
|
|
1087
|
-
console.log(`\n${c.dim}replay is a PRO feature for session recording.${c.reset}`);
|
|
1088
|
-
console.log(`${c.dim}Free alternative:${c.reset} ${c.cyan}vibecheck reality${c.reset} ${c.dim}(one-time runtime proof)${c.reset}`);
|
|
1089
|
-
} else if (suggestions.length > 0) {
|
|
1100
|
+
if (suggestions.length > 0) {
|
|
1090
1101
|
console.log(`\n${c.dim}Did you mean:${c.reset}`);
|
|
1091
1102
|
suggestions.forEach((s) => {
|
|
1092
1103
|
const actual = ALIAS_MAP[s] || s;
|
|
@@ -1106,8 +1117,13 @@ async function main() {
|
|
|
1106
1117
|
const cmdDef = COMMANDS[cmd];
|
|
1107
1118
|
let authInfo = { key: null };
|
|
1108
1119
|
|
|
1109
|
-
//
|
|
1110
|
-
|
|
1120
|
+
// Check for offline mode (via flag or env var)
|
|
1121
|
+
const isOffline = globalFlags.offline ||
|
|
1122
|
+
process.env.VIBECHECK_OFFLINE === '1' ||
|
|
1123
|
+
process.env.VIBECHECK_LOCAL === '1';
|
|
1124
|
+
|
|
1125
|
+
// Auth check (unless skipAuth or offline mode)
|
|
1126
|
+
if (!cmdDef.skipAuth && !isOffline) {
|
|
1111
1127
|
const auth = getAuthModule();
|
|
1112
1128
|
const { key } = auth.getApiKey();
|
|
1113
1129
|
authInfo.key = key;
|
|
@@ -1139,6 +1155,17 @@ async function main() {
|
|
|
1139
1155
|
}
|
|
1140
1156
|
|
|
1141
1157
|
authInfo.access = access;
|
|
1158
|
+
} else if (isOffline) {
|
|
1159
|
+
// Offline mode - provide basic access info
|
|
1160
|
+
if (!config.quiet && !config.noBanner) {
|
|
1161
|
+
console.log(`${c.cyan}${sym.arrowRight} OFFLINE${c.reset} ${c.dim}mode - local scanning without API${c.reset}`);
|
|
1162
|
+
}
|
|
1163
|
+
authInfo.access = {
|
|
1164
|
+
allowed: true,
|
|
1165
|
+
tier: 'free',
|
|
1166
|
+
caps: { maxFiles: 100, maxDepth: 3 },
|
|
1167
|
+
offline: true
|
|
1168
|
+
};
|
|
1142
1169
|
}
|
|
1143
1170
|
|
|
1144
1171
|
// Update state
|
|
@@ -1173,47 +1200,8 @@ async function main() {
|
|
|
1173
1200
|
runStart,
|
|
1174
1201
|
};
|
|
1175
1202
|
|
|
1176
|
-
// Execute command
|
|
1177
|
-
|
|
1178
|
-
case "prove":
|
|
1179
|
-
case "reality":
|
|
1180
|
-
case "watch":
|
|
1181
|
-
case "ship":
|
|
1182
|
-
case "runtime":
|
|
1183
|
-
case "export":
|
|
1184
|
-
case "security":
|
|
1185
|
-
case "install":
|
|
1186
|
-
case "pr":
|
|
1187
|
-
case "share":
|
|
1188
|
-
exitCode = await runner(cmdArgs, context);
|
|
1189
|
-
break;
|
|
1190
|
-
|
|
1191
|
-
case "ctx":
|
|
1192
|
-
case "truthpack":
|
|
1193
|
-
if (cmdArgs[0] === "sync") {
|
|
1194
|
-
const { runCtxSync } = require("./runners/runCtxSync");
|
|
1195
|
-
exitCode = await runCtxSync({ ...context, fastifyEntry: getArgValue(cmdArgs, ["--fastify-entry"]) });
|
|
1196
|
-
} else if (cmdArgs[0] === "guard") {
|
|
1197
|
-
const { runCtxGuard } = require("./runners/runCtxGuard");
|
|
1198
|
-
exitCode = await runCtxGuard.main(cmdArgs.slice(1));
|
|
1199
|
-
} else if (cmdArgs[0] === "diff") {
|
|
1200
|
-
const { main: ctxDiffMain } = require("./runners/runCtxDiff");
|
|
1201
|
-
exitCode = await ctxDiffMain(cmdArgs.slice(1));
|
|
1202
|
-
} else if (cmdArgs[0] === "search") {
|
|
1203
|
-
const { runContext } = require("./runners/runContext");
|
|
1204
|
-
exitCode = await runContext(["--search", ...cmdArgs.slice(1)]);
|
|
1205
|
-
} else {
|
|
1206
|
-
exitCode = await runner(cmdArgs, context);
|
|
1207
|
-
}
|
|
1208
|
-
break;
|
|
1209
|
-
|
|
1210
|
-
case "status":
|
|
1211
|
-
exitCode = await runner({ ...context, json: cmdArgs.includes("--json") });
|
|
1212
|
-
break;
|
|
1213
|
-
|
|
1214
|
-
default:
|
|
1215
|
-
exitCode = await runner(cmdArgs);
|
|
1216
|
-
}
|
|
1203
|
+
// Execute command - all commands use consistent runner signature
|
|
1204
|
+
exitCode = await runner(cmdArgs, context);
|
|
1217
1205
|
} catch (error) {
|
|
1218
1206
|
console.error(formatError(error, config));
|
|
1219
1207
|
exitCode = 1;
|