@vibecheckai/cli 3.1.8 → 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/bin/registry.js +106 -116
- 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/runDoctor.js +10 -2
- package/bin/runners/runFix.js +51 -341
- package/bin/runners/runInit.js +11 -0
- 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 +846 -101
- package/bin/runners/runScan.js +238 -4
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runWatch.js +14 -1
- package/bin/vibecheck.js +32 -2
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +152 -15
- 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/runInstall.js +0 -281
- package/bin/runners/runLabs.js +0 -341
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
|
@@ -203,7 +203,13 @@ function printTopFindings(findings, max = 5) {
|
|
|
203
203
|
.filter(f => f.severity === "BLOCK" || f.severity === "WARN")
|
|
204
204
|
.slice(0, max);
|
|
205
205
|
|
|
206
|
-
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
|
+
}
|
|
207
213
|
|
|
208
214
|
console.log(` ${c.bold}Top findings:${c.reset}`);
|
|
209
215
|
for (const f of top) {
|
|
@@ -211,6 +217,13 @@ function printTopFindings(findings, max = 5) {
|
|
|
211
217
|
const title = f.title?.length > 50 ? f.title.slice(0, 47) + '...' : f.title;
|
|
212
218
|
console.log(` ${icon}${c.reset} ${title}`);
|
|
213
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
|
+
}
|
|
214
227
|
console.log();
|
|
215
228
|
}
|
|
216
229
|
|
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,6 +1090,7 @@ 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]) {
|
|
@@ -1103,8 +1117,13 @@ async function main() {
|
|
|
1103
1117
|
const cmdDef = COMMANDS[cmd];
|
|
1104
1118
|
let authInfo = { key: null };
|
|
1105
1119
|
|
|
1106
|
-
//
|
|
1107
|
-
|
|
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) {
|
|
1108
1127
|
const auth = getAuthModule();
|
|
1109
1128
|
const { key } = auth.getApiKey();
|
|
1110
1129
|
authInfo.key = key;
|
|
@@ -1136,6 +1155,17 @@ async function main() {
|
|
|
1136
1155
|
}
|
|
1137
1156
|
|
|
1138
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
|
+
};
|
|
1139
1169
|
}
|
|
1140
1170
|
|
|
1141
1171
|
// Update state
|