@vibecheckai/cli 3.1.5 → 3.1.8
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 +216 -341
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
- package/bin/runners/lib/global-flags.js +213 -0
- package/bin/runners/lib/init-wizard.js +566 -273
- package/bin/runners/lib/interactive-menu.js +1496 -0
- package/bin/runners/runCheckpoint.js +503 -502
- package/bin/runners/runContext.js +16 -9
- package/bin/runners/runDoctor.js +20 -33
- package/bin/runners/runFix.js +19 -11
- package/bin/runners/runInit.js +866 -68
- package/bin/runners/runInstall.js +14 -6
- package/bin/runners/runProve.js +18 -12
- package/bin/runners/runReality.js +15 -6
- package/bin/runners/runReport.js +31 -18
- package/bin/runners/runScan.js +28 -17
- package/bin/runners/runShip.js +27 -21
- package/bin/runners/runWatch.js +11 -4
- package/bin/vibecheck.js +851 -212
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/bin/runners/runBadge.js +0 -916
- package/bin/runners/runContracts.js +0 -105
- package/bin/runners/runCtx.js +0 -675
- 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/runLaunch.js +0 -181
- package/bin/runners/runPR.js +0 -255
- package/bin/runners/runPermissions.js +0 -304
- 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 -86
- package/bin/runners/runVerify.js +0 -272
package/bin/vibecheck.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// bin/vibecheck.js - World-Class CLI
|
|
2
|
+
// bin/vibecheck.js - World-Class CLI (Refactored)
|
|
3
3
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
4
|
// VibeCheck - Proves your app is real
|
|
5
5
|
// Ship with confidence. Catch fake features before your users do.
|
|
@@ -7,38 +7,71 @@
|
|
|
7
7
|
|
|
8
8
|
"use strict";
|
|
9
9
|
|
|
10
|
-
const readline = require("readline");
|
|
11
|
-
const path = require("path");
|
|
12
|
-
const fs = require("fs");
|
|
13
|
-
const os = require("os");
|
|
14
10
|
const { performance } = require("perf_hooks");
|
|
15
|
-
const
|
|
11
|
+
const STARTUP_TIME = performance.now();
|
|
16
12
|
|
|
17
13
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
-
//
|
|
14
|
+
// LAZY LOADING - Defer all requires until needed for fast startup
|
|
19
15
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
-
const
|
|
16
|
+
const lazyModules = {};
|
|
17
|
+
|
|
18
|
+
function lazy(name, loader) {
|
|
19
|
+
return () => {
|
|
20
|
+
if (!lazyModules[name]) {
|
|
21
|
+
lazyModules[name] = loader();
|
|
22
|
+
}
|
|
23
|
+
return lazyModules[name];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getFs = lazy("fs", () => require("fs"));
|
|
28
|
+
const getPath = lazy("path", () => require("path"));
|
|
29
|
+
const getOs = lazy("os", () => require("os"));
|
|
30
|
+
const getCrypto = lazy("crypto", () => require("crypto"));
|
|
31
|
+
const getHttps = lazy("https", () => require("https"));
|
|
32
|
+
const getReadline = lazy("readline", () => require("readline"));
|
|
21
33
|
|
|
22
34
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
23
|
-
// VERSION & METADATA
|
|
35
|
+
// VERSION & METADATA (lazy loaded)
|
|
24
36
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
37
|
+
let _version = null;
|
|
25
38
|
function getVersion() {
|
|
39
|
+
if (_version) return _version;
|
|
26
40
|
try {
|
|
41
|
+
const fs = getFs();
|
|
42
|
+
const path = getPath();
|
|
27
43
|
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
28
44
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
29
|
-
|
|
45
|
+
_version = pkg.version || "0.0.0";
|
|
30
46
|
} catch {
|
|
31
|
-
|
|
47
|
+
_version = "0.0.0";
|
|
32
48
|
}
|
|
49
|
+
return _version;
|
|
33
50
|
}
|
|
34
51
|
|
|
35
|
-
const VERSION = getVersion();
|
|
36
52
|
const CLI_NAME = "vibecheck";
|
|
37
53
|
const CONFIG_FILE = ".vibecheckrc";
|
|
38
|
-
const CACHE_DIR = path.join(os.homedir(), ".vibecheck");
|
|
39
|
-
const STATE_FILE = path.join(CACHE_DIR, "state.json");
|
|
40
54
|
const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
|
|
41
55
|
|
|
56
|
+
// Cache/state paths (computed lazily)
|
|
57
|
+
let _cacheDir = null;
|
|
58
|
+
let _stateFile = null;
|
|
59
|
+
function getCacheDir() {
|
|
60
|
+
if (!_cacheDir) {
|
|
61
|
+
const os = getOs();
|
|
62
|
+
const path = getPath();
|
|
63
|
+
_cacheDir = path.join(os.homedir(), ".vibecheck");
|
|
64
|
+
}
|
|
65
|
+
return _cacheDir;
|
|
66
|
+
}
|
|
67
|
+
function getStateFile() {
|
|
68
|
+
if (!_stateFile) {
|
|
69
|
+
const path = getPath();
|
|
70
|
+
_stateFile = path.join(getCacheDir(), "state.json");
|
|
71
|
+
}
|
|
72
|
+
return _stateFile;
|
|
73
|
+
}
|
|
74
|
+
|
|
42
75
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
43
76
|
// ANSI STYLES - Premium terminal styling with gradient support
|
|
44
77
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -47,7 +80,7 @@ const SUPPORTS_TRUECOLOR = SUPPORTS_COLOR && (
|
|
|
47
80
|
process.env.COLORTERM === "truecolor" ||
|
|
48
81
|
process.env.TERM_PROGRAM === "iTerm.app" ||
|
|
49
82
|
process.env.TERM_PROGRAM === "Apple_Terminal" ||
|
|
50
|
-
process.env.WT_SESSION
|
|
83
|
+
process.env.WT_SESSION
|
|
51
84
|
);
|
|
52
85
|
|
|
53
86
|
const c = SUPPORTS_COLOR ? {
|
|
@@ -93,7 +126,12 @@ const c = SUPPORTS_COLOR ? {
|
|
|
93
126
|
"bgRed", "bgGreen", "bgYellow", "bgBlue", "bgMagenta", "bgCyan", "bgWhite", "bgGray"
|
|
94
127
|
].map(k => [k, ""]));
|
|
95
128
|
|
|
96
|
-
//
|
|
129
|
+
// Add no-op rgb functions for non-color mode
|
|
130
|
+
if (!SUPPORTS_COLOR) {
|
|
131
|
+
c.rgb = () => "";
|
|
132
|
+
c.bgRgb = () => "";
|
|
133
|
+
}
|
|
134
|
+
|
|
97
135
|
function gradient(text, colors = [[0, 255, 255], [255, 0, 255], [255, 255, 0]]) {
|
|
98
136
|
if (!SUPPORTS_TRUECOLOR) return `${c.cyan}${text}${c.reset}`;
|
|
99
137
|
const chars = [...text];
|
|
@@ -114,7 +152,7 @@ function gradient(text, colors = [[0, 255, 255], [255, 0, 255], [255, 255, 0]])
|
|
|
114
152
|
}
|
|
115
153
|
|
|
116
154
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
117
|
-
// UNICODE SYMBOLS
|
|
155
|
+
// UNICODE SYMBOLS
|
|
118
156
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
119
157
|
const SUPPORTS_UNICODE = process.platform !== "win32" || process.env.WT_SESSION || process.env.TERM_PROGRAM;
|
|
120
158
|
|
|
@@ -150,24 +188,48 @@ const sym = SUPPORTS_UNICODE ? {
|
|
|
150
188
|
};
|
|
151
189
|
|
|
152
190
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
153
|
-
//
|
|
191
|
+
// CI DETECTION
|
|
154
192
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
155
193
|
function isCI() {
|
|
156
|
-
return !!(
|
|
157
|
-
process.env.
|
|
158
|
-
process.env.
|
|
159
|
-
process.env.
|
|
160
|
-
process.env.
|
|
194
|
+
return !!(
|
|
195
|
+
process.env.CI ||
|
|
196
|
+
process.env.CONTINUOUS_INTEGRATION ||
|
|
197
|
+
process.env.RAILWAY_ENVIRONMENT ||
|
|
198
|
+
process.env.VERCEL ||
|
|
199
|
+
process.env.NETLIFY ||
|
|
200
|
+
process.env.GITHUB_ACTIONS ||
|
|
201
|
+
process.env.GITLAB_CI ||
|
|
202
|
+
process.env.CIRCLECI ||
|
|
203
|
+
process.env.TRAVIS ||
|
|
204
|
+
process.env.BUILDKITE ||
|
|
205
|
+
process.env.RENDER ||
|
|
206
|
+
process.env.HEROKU ||
|
|
207
|
+
process.env.CODEBUILD_BUILD_ID ||
|
|
208
|
+
process.env.JENKINS_URL ||
|
|
209
|
+
process.env.TEAMCITY_VERSION ||
|
|
210
|
+
process.env.TF_BUILD ||
|
|
211
|
+
!process.stdin.isTTY
|
|
212
|
+
);
|
|
161
213
|
}
|
|
162
214
|
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
216
|
+
// SPINNER CLASS
|
|
217
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
163
218
|
class Spinner {
|
|
164
219
|
constructor(text = "") {
|
|
165
|
-
this.text = text;
|
|
166
|
-
this.
|
|
220
|
+
this.text = text;
|
|
221
|
+
this.frame = 0;
|
|
222
|
+
this.interval = null;
|
|
223
|
+
this.stream = process.stderr;
|
|
224
|
+
this.isCI = isCI();
|
|
167
225
|
}
|
|
226
|
+
|
|
168
227
|
start(text) {
|
|
169
228
|
if (text) this.text = text;
|
|
170
|
-
if (this.isCI) {
|
|
229
|
+
if (this.isCI) {
|
|
230
|
+
this.stream.write(`${c.dim}${sym.pending}${c.reset} ${this.text}\n`);
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
171
233
|
this.interval = setInterval(() => {
|
|
172
234
|
const spinner = sym.spinner[this.frame % sym.spinner.length];
|
|
173
235
|
this.stream.write(`\r${c.cyan}${spinner}${c.reset} ${this.text}`);
|
|
@@ -175,35 +237,183 @@ class Spinner {
|
|
|
175
237
|
}, 80);
|
|
176
238
|
return this;
|
|
177
239
|
}
|
|
178
|
-
update(text) { this.text = text; if (this.isCI) this.stream.write(` ${c.dim}${sym.arrowRight}${c.reset} ${text}\n`); return this; }
|
|
179
|
-
succeed(text) { this.stop(); this.stream.write(`\r${c.green}${sym.success}${c.reset} ${text || this.text}\n`); return this; }
|
|
180
|
-
fail(text) { this.stop(); this.stream.write(`\r${c.red}${sym.error}${c.reset} ${text || this.text}\n`); return this; }
|
|
181
|
-
warn(text) { this.stop(); this.stream.write(`\r${c.yellow}${sym.warning}${c.reset} ${text || this.text}\n`); return this; }
|
|
182
|
-
info(text) { this.stop(); this.stream.write(`\r${c.blue}${sym.info}${c.reset} ${text || this.text}\n`); return this; }
|
|
183
|
-
stop() { if (this.interval) { clearInterval(this.interval); this.interval = null; this.stream.write("\r\x1b[K"); } return this; }
|
|
184
|
-
}
|
|
185
240
|
|
|
186
|
-
|
|
241
|
+
update(text) {
|
|
242
|
+
this.text = text;
|
|
243
|
+
if (this.isCI) this.stream.write(` ${c.dim}${sym.arrowRight}${c.reset} ${text}\n`);
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
187
246
|
|
|
188
|
-
|
|
247
|
+
succeed(text) {
|
|
248
|
+
this.stop();
|
|
249
|
+
this.stream.write(`\r${c.green}${sym.success}${c.reset} ${text || this.text}\n`);
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
fail(text) {
|
|
254
|
+
this.stop();
|
|
255
|
+
this.stream.write(`\r${c.red}${sym.error}${c.reset} ${text || this.text}\n`);
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
warn(text) {
|
|
260
|
+
this.stop();
|
|
261
|
+
this.stream.write(`\r${c.yellow}${sym.warning}${c.reset} ${text || this.text}\n`);
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
info(text) {
|
|
266
|
+
this.stop();
|
|
267
|
+
this.stream.write(`\r${c.blue}${sym.info}${c.reset} ${text || this.text}\n`);
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
stop() {
|
|
272
|
+
if (this.interval) {
|
|
273
|
+
clearInterval(this.interval);
|
|
274
|
+
this.interval = null;
|
|
275
|
+
this.stream.write("\r\x1b[K");
|
|
276
|
+
}
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
282
|
+
// STATE MANAGEMENT (lazy file I/O)
|
|
283
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
284
|
+
function ensureCacheDir() {
|
|
285
|
+
const fs = getFs();
|
|
286
|
+
const cacheDir = getCacheDir();
|
|
287
|
+
if (!fs.existsSync(cacheDir)) {
|
|
288
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
189
291
|
|
|
190
292
|
function loadState() {
|
|
191
|
-
try {
|
|
192
|
-
|
|
293
|
+
try {
|
|
294
|
+
const fs = getFs();
|
|
295
|
+
const stateFile = getStateFile();
|
|
296
|
+
if (fs.existsSync(stateFile)) {
|
|
297
|
+
return JSON.parse(fs.readFileSync(stateFile, "utf-8"));
|
|
298
|
+
}
|
|
299
|
+
} catch {}
|
|
300
|
+
return {
|
|
301
|
+
firstRun: Date.now(),
|
|
302
|
+
lastRun: null,
|
|
303
|
+
runCount: 0,
|
|
304
|
+
lastUpdateCheck: null,
|
|
305
|
+
latestVersion: null,
|
|
306
|
+
commandHistory: [],
|
|
307
|
+
favorites: [],
|
|
308
|
+
};
|
|
193
309
|
}
|
|
194
310
|
|
|
195
|
-
function saveState(state) {
|
|
311
|
+
function saveState(state) {
|
|
312
|
+
try {
|
|
313
|
+
const fs = getFs();
|
|
314
|
+
ensureCacheDir();
|
|
315
|
+
fs.writeFileSync(getStateFile(), JSON.stringify(state, null, 2));
|
|
316
|
+
} catch {}
|
|
317
|
+
}
|
|
196
318
|
|
|
197
319
|
function loadConfig() {
|
|
198
|
-
const
|
|
320
|
+
const fs = getFs();
|
|
321
|
+
const path = getPath();
|
|
322
|
+
|
|
323
|
+
const config = {
|
|
324
|
+
debug: false,
|
|
325
|
+
verbose: false,
|
|
326
|
+
quiet: false,
|
|
327
|
+
color: true,
|
|
328
|
+
analytics: true,
|
|
329
|
+
updateCheck: true,
|
|
330
|
+
timeout: 30000,
|
|
331
|
+
maxRetries: 3,
|
|
332
|
+
noBanner: false,
|
|
333
|
+
};
|
|
334
|
+
|
|
199
335
|
const projectConfigPath = path.join(process.cwd(), CONFIG_FILE);
|
|
200
|
-
if (fs.existsSync(projectConfigPath)) {
|
|
336
|
+
if (fs.existsSync(projectConfigPath)) {
|
|
337
|
+
try {
|
|
338
|
+
Object.assign(config, JSON.parse(fs.readFileSync(projectConfigPath, "utf-8")));
|
|
339
|
+
} catch {}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Environment overrides
|
|
201
343
|
if (process.env.VIBECHECK_DEBUG === "true") config.debug = true;
|
|
202
344
|
if (process.env.VIBECHECK_VERBOSE === "true") config.verbose = true;
|
|
345
|
+
if (process.env.VIBECHECK_QUIET === "true") config.quiet = true;
|
|
346
|
+
if (process.env.VIBECHECK_NO_BANNER === "true") config.noBanner = true;
|
|
203
347
|
if (process.env.NO_COLOR || process.env.VIBECHECK_NO_COLOR) config.color = false;
|
|
348
|
+
|
|
204
349
|
return config;
|
|
205
350
|
}
|
|
206
351
|
|
|
352
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
353
|
+
// UPDATE CHECKER - Actually implemented
|
|
354
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
355
|
+
async function checkForUpdates(state, config) {
|
|
356
|
+
if (!config.updateCheck) return null;
|
|
357
|
+
if (isCI()) return null;
|
|
358
|
+
|
|
359
|
+
const now = Date.now();
|
|
360
|
+
if (state.lastUpdateCheck && (now - state.lastUpdateCheck) < UPDATE_CHECK_INTERVAL) {
|
|
361
|
+
// Use cached version if recent
|
|
362
|
+
if (state.latestVersion && state.latestVersion !== getVersion()) {
|
|
363
|
+
return state.latestVersion;
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Async check - don't block CLI startup
|
|
369
|
+
return new Promise((resolve) => {
|
|
370
|
+
const https = getHttps();
|
|
371
|
+
const timeout = setTimeout(() => resolve(null), 3000); // 3s timeout
|
|
372
|
+
|
|
373
|
+
const req = https.get(
|
|
374
|
+
"https://registry.npmjs.org/@vibecheckai/cli/latest",
|
|
375
|
+
{ headers: { "Accept": "application/json", "User-Agent": `vibecheck-cli/${getVersion()}` } },
|
|
376
|
+
(res) => {
|
|
377
|
+
let data = "";
|
|
378
|
+
res.on("data", (chunk) => { data += chunk; });
|
|
379
|
+
res.on("end", () => {
|
|
380
|
+
clearTimeout(timeout);
|
|
381
|
+
try {
|
|
382
|
+
const pkg = JSON.parse(data);
|
|
383
|
+
const latest = pkg.version;
|
|
384
|
+
state.lastUpdateCheck = now;
|
|
385
|
+
state.latestVersion = latest;
|
|
386
|
+
saveState(state);
|
|
387
|
+
|
|
388
|
+
if (latest && latest !== getVersion()) {
|
|
389
|
+
resolve(latest);
|
|
390
|
+
} else {
|
|
391
|
+
resolve(null);
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
resolve(null);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
req.on("error", () => {
|
|
401
|
+
clearTimeout(timeout);
|
|
402
|
+
resolve(null);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
req.end();
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function printUpdateNotice(latestVersion) {
|
|
410
|
+
const currentVersion = getVersion();
|
|
411
|
+
console.log();
|
|
412
|
+
console.log(`${c.yellow}${sym.sparkles}${c.reset} Update available: ${c.dim}${currentVersion}${c.reset} ${sym.arrow} ${c.green}${latestVersion}${c.reset}`);
|
|
413
|
+
console.log(` Run ${c.cyan}npm update -g @vibecheckai/cli${c.reset} to update`);
|
|
414
|
+
console.log();
|
|
415
|
+
}
|
|
416
|
+
|
|
207
417
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
208
418
|
// FUZZY MATCHING
|
|
209
419
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -213,8 +423,15 @@ function levenshtein(a, b) {
|
|
|
213
423
|
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
|
214
424
|
for (let i = 1; i <= b.length; i++) {
|
|
215
425
|
for (let j = 1; j <= a.length; j++) {
|
|
216
|
-
if (b.charAt(i - 1) === a.charAt(j - 1))
|
|
217
|
-
|
|
426
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
427
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
428
|
+
} else {
|
|
429
|
+
matrix[i][j] = Math.min(
|
|
430
|
+
matrix[i - 1][j - 1] + 1,
|
|
431
|
+
matrix[i][j - 1] + 1,
|
|
432
|
+
matrix[i - 1][j] + 1
|
|
433
|
+
);
|
|
434
|
+
}
|
|
218
435
|
}
|
|
219
436
|
}
|
|
220
437
|
return matrix[b.length][a.length];
|
|
@@ -224,63 +441,89 @@ function findSimilarCommands(input, commands, maxDistance = 3) {
|
|
|
224
441
|
const matches = [];
|
|
225
442
|
for (const cmd of commands) {
|
|
226
443
|
const distance = levenshtein(input.toLowerCase(), cmd.toLowerCase());
|
|
227
|
-
if (distance <= maxDistance)
|
|
228
|
-
|
|
444
|
+
if (distance <= maxDistance) {
|
|
445
|
+
matches.push({ cmd, distance });
|
|
446
|
+
}
|
|
447
|
+
if (cmd.toLowerCase().startsWith(input.toLowerCase()) && input.length >= 2) {
|
|
448
|
+
matches.push({ cmd, distance: 0.5 });
|
|
449
|
+
}
|
|
229
450
|
}
|
|
230
|
-
return matches.sort((a, b) => a.distance - b.distance).map(m => m.cmd).slice(0, 3);
|
|
451
|
+
return [...new Set(matches.sort((a, b) => a.distance - b.distance).map(m => m.cmd))].slice(0, 3);
|
|
231
452
|
}
|
|
232
453
|
|
|
233
454
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
234
|
-
//
|
|
455
|
+
// COMMAND REGISTRY (lazy loaded)
|
|
235
456
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
236
|
-
|
|
457
|
+
let _registry = null;
|
|
458
|
+
function getRegistry() {
|
|
459
|
+
if (!_registry) {
|
|
460
|
+
_registry = require("./registry");
|
|
461
|
+
}
|
|
462
|
+
return _registry;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function getRunner(cmd) {
|
|
466
|
+
const registry = getRegistry();
|
|
467
|
+
return registry.getRunner(cmd, { red: c.red, reset: c.reset, errorSymbol: sym.error });
|
|
468
|
+
}
|
|
237
469
|
|
|
238
470
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
239
|
-
//
|
|
471
|
+
// ENTITLEMENTS & UPSELL (lazy loaded)
|
|
240
472
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
241
|
-
|
|
473
|
+
let _entitlements = null;
|
|
474
|
+
let _upsell = null;
|
|
475
|
+
|
|
476
|
+
function getEntitlements() {
|
|
477
|
+
if (!_entitlements) {
|
|
478
|
+
_entitlements = require("./runners/lib/entitlements-v2");
|
|
479
|
+
}
|
|
480
|
+
return _entitlements;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function getUpsell() {
|
|
484
|
+
if (!_upsell) {
|
|
485
|
+
_upsell = require("./runners/lib/upsell");
|
|
486
|
+
}
|
|
487
|
+
return _upsell;
|
|
488
|
+
}
|
|
242
489
|
|
|
243
490
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
244
|
-
// CLI OUTPUT UTILITIES
|
|
491
|
+
// CLI OUTPUT UTILITIES (lazy loaded)
|
|
245
492
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
493
|
+
let _cliOutput = null;
|
|
494
|
+
function getCliOutput() {
|
|
495
|
+
if (!_cliOutput) {
|
|
496
|
+
_cliOutput = require("./runners/lib/cli-output");
|
|
497
|
+
}
|
|
498
|
+
return _cliOutput;
|
|
499
|
+
}
|
|
252
500
|
|
|
253
501
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
254
|
-
//
|
|
502
|
+
// AUTH MODULE (lazy loaded)
|
|
255
503
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
504
|
+
let _authModule = null;
|
|
505
|
+
function getAuthModule() {
|
|
506
|
+
if (!_authModule) {
|
|
507
|
+
_authModule = require("./runners/lib/auth");
|
|
508
|
+
}
|
|
509
|
+
return _authModule;
|
|
261
510
|
}
|
|
262
511
|
|
|
263
512
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
264
|
-
//
|
|
513
|
+
// ACCESS CONTROL
|
|
265
514
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
266
|
-
let authModule = null;
|
|
267
|
-
function getAuthModule() { if (!authModule) authModule = require("./runners/lib/auth"); return authModule; }
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Check command access using entitlements-v2 module.
|
|
271
|
-
* NO OWNER MODE. NO ENV VAR BYPASS. NO OFFLINE ESCALATION.
|
|
272
|
-
*/
|
|
273
515
|
async function checkCommandAccess(cmd, args, authInfo) {
|
|
274
|
-
const
|
|
516
|
+
const registry = getRegistry();
|
|
517
|
+
const def = registry.COMMANDS[cmd];
|
|
275
518
|
if (!def) return { allowed: true };
|
|
276
|
-
|
|
277
|
-
|
|
519
|
+
|
|
520
|
+
const entitlements = getEntitlements();
|
|
278
521
|
const result = await entitlements.enforce(cmd, {
|
|
279
522
|
apiKey: authInfo?.key,
|
|
280
523
|
projectPath: process.cwd(),
|
|
281
|
-
silent: true,
|
|
524
|
+
silent: true,
|
|
282
525
|
});
|
|
283
|
-
|
|
526
|
+
|
|
284
527
|
if (result.allowed) {
|
|
285
528
|
return {
|
|
286
529
|
allowed: true,
|
|
@@ -290,29 +533,272 @@ async function checkCommandAccess(cmd, args, authInfo) {
|
|
|
290
533
|
caps: result.caps,
|
|
291
534
|
};
|
|
292
535
|
}
|
|
293
|
-
|
|
294
|
-
|
|
536
|
+
|
|
537
|
+
const upsell = getUpsell();
|
|
295
538
|
return {
|
|
296
539
|
allowed: false,
|
|
297
540
|
tier: result.tier,
|
|
298
541
|
requiredTier: result.requiredTier,
|
|
299
542
|
exitCode: result.exitCode,
|
|
300
|
-
reason:
|
|
543
|
+
reason: upsell.formatDenied(cmd, {
|
|
544
|
+
currentTier: result.tier,
|
|
545
|
+
requiredTier: result.requiredTier,
|
|
546
|
+
}),
|
|
301
547
|
};
|
|
302
548
|
}
|
|
303
549
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
550
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
551
|
+
// SHELL COMPLETIONS
|
|
552
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
553
|
+
function generateBashCompletion() {
|
|
554
|
+
const registry = getRegistry();
|
|
555
|
+
const commands = Object.keys(registry.COMMANDS);
|
|
556
|
+
const aliases = Object.keys(registry.ALIAS_MAP);
|
|
557
|
+
const allCmds = [...commands, ...aliases].join(" ");
|
|
558
|
+
|
|
559
|
+
return `# vibecheck bash completion
|
|
560
|
+
# Add to ~/.bashrc or ~/.bash_profile:
|
|
561
|
+
# eval "$(vibecheck completion bash)"
|
|
562
|
+
|
|
563
|
+
_vibecheck_completions() {
|
|
564
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
565
|
+
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
566
|
+
|
|
567
|
+
# Main commands
|
|
568
|
+
local commands="${allCmds}"
|
|
569
|
+
|
|
570
|
+
# Global flags
|
|
571
|
+
local global_flags="--help --version --json --ci --quiet --verbose --debug --path --output --no-banner"
|
|
572
|
+
|
|
573
|
+
case "\${prev}" in
|
|
574
|
+
vibecheck|vc)
|
|
575
|
+
COMPREPLY=($(compgen -W "\${commands} \${global_flags}" -- "\${cur}"))
|
|
576
|
+
return 0
|
|
577
|
+
;;
|
|
578
|
+
--path|--output|-p|-o)
|
|
579
|
+
COMPREPLY=($(compgen -d -- "\${cur}"))
|
|
580
|
+
return 0
|
|
581
|
+
;;
|
|
582
|
+
init)
|
|
583
|
+
COMPREPLY=($(compgen -W "--local --connect --quick --enterprise --ci --soc2 --hipaa --gdpr --team --mcp" -- "\${cur}"))
|
|
584
|
+
return 0
|
|
585
|
+
;;
|
|
586
|
+
scan)
|
|
587
|
+
COMPREPLY=($(compgen -W "--fix --autofix --json --strict --path" -- "\${cur}"))
|
|
588
|
+
return 0
|
|
589
|
+
;;
|
|
590
|
+
report)
|
|
591
|
+
COMPREPLY=($(compgen -W "--format --output html md json sarif csv" -- "\${cur}"))
|
|
592
|
+
return 0
|
|
593
|
+
;;
|
|
594
|
+
completion)
|
|
595
|
+
COMPREPLY=($(compgen -W "bash zsh fish" -- "\${cur}"))
|
|
596
|
+
return 0
|
|
597
|
+
;;
|
|
598
|
+
esac
|
|
599
|
+
|
|
600
|
+
if [[ "\${cur}" == -* ]]; then
|
|
601
|
+
COMPREPLY=($(compgen -W "\${global_flags}" -- "\${cur}"))
|
|
602
|
+
fi
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
complete -F _vibecheck_completions vibecheck
|
|
606
|
+
complete -F _vibecheck_completions vc
|
|
607
|
+
`;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function generateZshCompletion() {
|
|
611
|
+
const registry = getRegistry();
|
|
612
|
+
const commands = Object.entries(registry.COMMANDS);
|
|
613
|
+
|
|
614
|
+
let cmdList = commands.map(([cmd, def]) => {
|
|
615
|
+
const desc = (def.description || "").replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
616
|
+
return ` '${cmd}:${desc}'`;
|
|
617
|
+
}).join(" \\\n");
|
|
618
|
+
|
|
619
|
+
return `#compdef vibecheck vc
|
|
620
|
+
# vibecheck zsh completion
|
|
621
|
+
# Add to ~/.zshrc:
|
|
622
|
+
# eval "$(vibecheck completion zsh)"
|
|
623
|
+
|
|
624
|
+
_vibecheck() {
|
|
625
|
+
local -a commands
|
|
626
|
+
commands=(
|
|
627
|
+
${cmdList}
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
local -a global_opts
|
|
631
|
+
global_opts=(
|
|
632
|
+
'--help[Show help]'
|
|
633
|
+
'--version[Show version]'
|
|
634
|
+
'--json[Output as JSON]'
|
|
635
|
+
'--ci[CI mode]'
|
|
636
|
+
'--quiet[Suppress output]'
|
|
637
|
+
'--verbose[Verbose output]'
|
|
638
|
+
'--debug[Debug mode]'
|
|
639
|
+
'--path[Project path]:directory:_directories'
|
|
640
|
+
'--output[Output directory]:directory:_directories'
|
|
641
|
+
'--no-banner[Hide banner]'
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
_arguments -C \\
|
|
645
|
+
"1: :->command" \\
|
|
646
|
+
"*::arg:->args"
|
|
647
|
+
|
|
648
|
+
case "\$state" in
|
|
649
|
+
command)
|
|
650
|
+
_describe -t commands 'vibecheck commands' commands
|
|
651
|
+
_describe -t options 'options' global_opts
|
|
652
|
+
;;
|
|
653
|
+
args)
|
|
654
|
+
case "\$words[1]" in
|
|
655
|
+
init)
|
|
656
|
+
_arguments \\
|
|
657
|
+
'--local[Local setup only]' \\
|
|
658
|
+
'--connect[GitHub integration]' \\
|
|
659
|
+
'--quick[Quick setup]' \\
|
|
660
|
+
'--enterprise[Enterprise mode]' \\
|
|
661
|
+
'--ci[Setup CI/CD]' \\
|
|
662
|
+
'--soc2[SOC2 compliance]' \\
|
|
663
|
+
'--hipaa[HIPAA compliance]'
|
|
664
|
+
;;
|
|
665
|
+
scan)
|
|
666
|
+
_arguments \\
|
|
667
|
+
'--fix[Apply fixes]' \\
|
|
668
|
+
'--autofix[Auto-fix issues]' \\
|
|
669
|
+
'--json[JSON output]' \\
|
|
670
|
+
'--strict[Strict mode]'
|
|
671
|
+
;;
|
|
672
|
+
report)
|
|
673
|
+
_arguments \\
|
|
674
|
+
'--format[Output format]:format:(html md json sarif csv)' \\
|
|
675
|
+
'--output[Output file]:file:_files'
|
|
676
|
+
;;
|
|
677
|
+
completion)
|
|
678
|
+
_arguments '1:shell:(bash zsh fish)'
|
|
679
|
+
;;
|
|
680
|
+
esac
|
|
681
|
+
;;
|
|
682
|
+
esac
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
compdef _vibecheck vibecheck
|
|
686
|
+
compdef _vibecheck vc
|
|
687
|
+
`;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function generateFishCompletion() {
|
|
691
|
+
const registry = getRegistry();
|
|
692
|
+
const commands = Object.entries(registry.COMMANDS);
|
|
693
|
+
|
|
694
|
+
let completions = `# vibecheck fish completion
|
|
695
|
+
# Add to ~/.config/fish/completions/vibecheck.fish
|
|
696
|
+
# Or run: vibecheck completion fish > ~/.config/fish/completions/vibecheck.fish
|
|
697
|
+
|
|
698
|
+
# Disable file completion by default
|
|
699
|
+
complete -c vibecheck -f
|
|
700
|
+
complete -c vc -f
|
|
701
|
+
|
|
702
|
+
# Global flags
|
|
703
|
+
complete -c vibecheck -l help -d 'Show help'
|
|
704
|
+
complete -c vibecheck -l version -d 'Show version'
|
|
705
|
+
complete -c vibecheck -l json -d 'Output as JSON'
|
|
706
|
+
complete -c vibecheck -l ci -d 'CI mode'
|
|
707
|
+
complete -c vibecheck -l quiet -d 'Suppress output'
|
|
708
|
+
complete -c vibecheck -l verbose -d 'Verbose output'
|
|
709
|
+
complete -c vibecheck -l debug -d 'Debug mode'
|
|
710
|
+
complete -c vibecheck -l path -d 'Project path' -r -a '(__fish_complete_directories)'
|
|
711
|
+
complete -c vibecheck -l output -d 'Output directory' -r -a '(__fish_complete_directories)'
|
|
712
|
+
complete -c vibecheck -l no-banner -d 'Hide banner'
|
|
713
|
+
|
|
714
|
+
# Commands
|
|
715
|
+
`;
|
|
716
|
+
|
|
717
|
+
for (const [cmd, def] of commands) {
|
|
718
|
+
const desc = (def.description || "").replace(/'/g, "\\'");
|
|
719
|
+
completions += `complete -c vibecheck -n '__fish_use_subcommand' -a '${cmd}' -d '${desc}'\n`;
|
|
720
|
+
completions += `complete -c vc -n '__fish_use_subcommand' -a '${cmd}' -d '${desc}'\n`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Add aliases
|
|
724
|
+
for (const [alias, target] of Object.entries(registry.ALIAS_MAP)) {
|
|
725
|
+
const def = registry.COMMANDS[target];
|
|
726
|
+
if (def) {
|
|
727
|
+
const desc = `Alias for ${target}`;
|
|
728
|
+
completions += `complete -c vibecheck -n '__fish_use_subcommand' -a '${alias}' -d '${desc}'\n`;
|
|
729
|
+
completions += `complete -c vc -n '__fish_use_subcommand' -a '${alias}' -d '${desc}'\n`;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
completions += `
|
|
734
|
+
# init subcommand options
|
|
735
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from init' -l local -d 'Local setup only'
|
|
736
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from init' -l connect -d 'GitHub integration'
|
|
737
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from init' -l quick -d 'Quick setup'
|
|
738
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from init' -l enterprise -d 'Enterprise mode'
|
|
739
|
+
|
|
740
|
+
# scan subcommand options
|
|
741
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from scan' -l fix -d 'Apply fixes'
|
|
742
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from scan' -l autofix -d 'Auto-fix issues'
|
|
743
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from scan' -l strict -d 'Strict mode'
|
|
744
|
+
|
|
745
|
+
# report subcommand options
|
|
746
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from report' -l format -d 'Output format' -r -a 'html md json sarif csv'
|
|
747
|
+
|
|
748
|
+
# completion subcommand
|
|
749
|
+
complete -c vibecheck -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell type'
|
|
750
|
+
`;
|
|
751
|
+
|
|
752
|
+
return completions;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function runCompletion(args) {
|
|
756
|
+
const shell = args[0];
|
|
757
|
+
|
|
758
|
+
if (!shell || shell === "--help" || shell === "-h") {
|
|
759
|
+
console.log(`
|
|
760
|
+
${c.bold}Usage:${c.reset} vibecheck completion <shell>
|
|
761
|
+
|
|
762
|
+
${c.bold}Shells:${c.reset}
|
|
763
|
+
bash Generate bash completions
|
|
764
|
+
zsh Generate zsh completions
|
|
765
|
+
fish Generate fish completions
|
|
766
|
+
|
|
767
|
+
${c.bold}Installation:${c.reset}
|
|
768
|
+
${c.dim}# Bash (add to ~/.bashrc)${c.reset}
|
|
769
|
+
eval "$(vibecheck completion bash)"
|
|
770
|
+
|
|
771
|
+
${c.dim}# Zsh (add to ~/.zshrc)${c.reset}
|
|
772
|
+
eval "$(vibecheck completion zsh)"
|
|
773
|
+
|
|
774
|
+
${c.dim}# Fish${c.reset}
|
|
775
|
+
vibecheck completion fish > ~/.config/fish/completions/vibecheck.fish
|
|
776
|
+
`);
|
|
777
|
+
return 0;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
switch (shell.toLowerCase()) {
|
|
781
|
+
case "bash":
|
|
782
|
+
console.log(generateBashCompletion());
|
|
783
|
+
return 0;
|
|
784
|
+
case "zsh":
|
|
785
|
+
console.log(generateZshCompletion());
|
|
786
|
+
return 0;
|
|
787
|
+
case "fish":
|
|
788
|
+
console.log(generateFishCompletion());
|
|
789
|
+
return 0;
|
|
790
|
+
default:
|
|
791
|
+
console.error(`${c.red}${sym.error}${c.reset} Unknown shell: ${shell}`);
|
|
792
|
+
console.error(`Supported shells: bash, zsh, fish`);
|
|
793
|
+
return 1;
|
|
794
|
+
}
|
|
310
795
|
}
|
|
311
796
|
|
|
312
797
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
313
798
|
// HELP SYSTEM
|
|
314
799
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
315
800
|
function printBanner() {
|
|
801
|
+
const VERSION = getVersion();
|
|
316
802
|
console.log(`
|
|
317
803
|
${c.dim}${sym.boxTopLeft}${sym.boxHorizontal.repeat(60)}${sym.boxTopRight}${c.reset}
|
|
318
804
|
${c.dim}${sym.boxVertical}${c.reset} ${gradient("VIBECHECK", [[0, 255, 255], [138, 43, 226], [255, 20, 147]])} ${c.dim}v${VERSION}${c.reset}${" ".repeat(60 - 13 - VERSION.length - 4)}${c.dim}${sym.boxVertical}${c.reset}
|
|
@@ -321,9 +807,19 @@ ${c.dim}${sym.boxBottomLeft}${sym.boxHorizontal.repeat(60)}${sym.boxBottomRight}
|
|
|
321
807
|
`);
|
|
322
808
|
}
|
|
323
809
|
|
|
324
|
-
function printHelp() {
|
|
325
|
-
printBanner();
|
|
326
|
-
|
|
810
|
+
function printHelp(showBanner = true) {
|
|
811
|
+
if (showBanner) printBanner();
|
|
812
|
+
|
|
813
|
+
const registry = getRegistry();
|
|
814
|
+
const { COMMANDS, ALIAS_MAP } = registry;
|
|
815
|
+
|
|
816
|
+
// Build reverse alias map for display
|
|
817
|
+
const reverseAliases = {};
|
|
818
|
+
for (const [alias, target] of Object.entries(ALIAS_MAP)) {
|
|
819
|
+
if (!reverseAliases[target]) reverseAliases[target] = [];
|
|
820
|
+
reverseAliases[target].push(alias);
|
|
821
|
+
}
|
|
822
|
+
|
|
327
823
|
// Categories ordered as specified
|
|
328
824
|
const categoryOrder = ["setup", "analysis", "proof", "quality", "truth", "output", "ci", "automation", "account", "extras"];
|
|
329
825
|
const categories = {
|
|
@@ -338,7 +834,7 @@ function printHelp() {
|
|
|
338
834
|
account: { name: "ACCOUNT", color: c.dim, icon: sym.key },
|
|
339
835
|
extras: { name: "EXTRAS", color: c.dim, icon: sym.starEmpty },
|
|
340
836
|
};
|
|
341
|
-
|
|
837
|
+
|
|
342
838
|
// Group commands
|
|
343
839
|
const grouped = {};
|
|
344
840
|
for (const [cmd, def] of Object.entries(COMMANDS)) {
|
|
@@ -346,33 +842,35 @@ function printHelp() {
|
|
|
346
842
|
if (!grouped[cat]) grouped[cat] = [];
|
|
347
843
|
grouped[cat].push({ cmd, ...def });
|
|
348
844
|
}
|
|
349
|
-
|
|
845
|
+
|
|
350
846
|
// Print in order
|
|
351
847
|
for (const catKey of categoryOrder) {
|
|
352
848
|
const commands = grouped[catKey];
|
|
353
849
|
if (!commands || commands.length === 0) continue;
|
|
354
|
-
|
|
850
|
+
|
|
355
851
|
const cat = categories[catKey];
|
|
356
852
|
console.log(`\n${cat.color}${cat.icon} ${cat.name}${c.reset}\n`);
|
|
357
|
-
|
|
358
|
-
for (const { cmd, description, tier,
|
|
359
|
-
// Tier badge
|
|
853
|
+
|
|
854
|
+
for (const { cmd, description, tier, caps } of commands) {
|
|
855
|
+
// Tier badge
|
|
360
856
|
let tierBadge = "";
|
|
361
|
-
if (tier === "free") {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
857
|
+
if (tier === "free") tierBadge = `${c.green}[FREE]${c.reset} `;
|
|
858
|
+
else if (tier === "starter") tierBadge = `${c.cyan}[STARTER]${c.reset} `;
|
|
859
|
+
else if (tier === "pro") tierBadge = `${c.magenta}[PRO]${c.reset} `;
|
|
860
|
+
|
|
861
|
+
// Aliases (from reverseAliases map built earlier)
|
|
862
|
+
const aliasList = reverseAliases[cmd] || [];
|
|
863
|
+
const aliasStr = aliasList.length > 0
|
|
864
|
+
? `${c.dim}(${aliasList.slice(0, 3).join(", ")})${c.reset} `
|
|
865
|
+
: "";
|
|
866
|
+
|
|
867
|
+
// Caps info
|
|
370
868
|
const capsStr = caps ? `${c.dim}(${caps})${c.reset}` : "";
|
|
371
|
-
|
|
372
|
-
console.log(` ${c.cyan}${cmd.padEnd(12)}${c.reset} ${tierBadge}${description} ${capsStr}`);
|
|
869
|
+
|
|
870
|
+
console.log(` ${c.cyan}${cmd.padEnd(12)}${c.reset} ${aliasStr}${tierBadge}${description} ${capsStr}`);
|
|
373
871
|
}
|
|
374
872
|
}
|
|
375
|
-
|
|
873
|
+
|
|
376
874
|
console.log(`
|
|
377
875
|
${c.dim}${sym.boxHorizontal.repeat(64)}${c.reset}
|
|
378
876
|
|
|
@@ -390,6 +888,12 @@ ${c.green}QUICK START - The 5-Step Journey${c.reset}
|
|
|
390
888
|
4. ${c.bold}Prove${c.reset} ${c.cyan}vibecheck prove${c.reset} ${c.magenta}[PRO]${c.reset}
|
|
391
889
|
5. ${c.bold}Ship${c.reset} ${c.cyan}vibecheck ship${c.reset}
|
|
392
890
|
|
|
891
|
+
${c.bold}SHELL COMPLETIONS${c.reset}
|
|
892
|
+
|
|
893
|
+
${c.cyan}vibecheck completion bash${c.reset} ${c.dim}# Add to ~/.bashrc${c.reset}
|
|
894
|
+
${c.cyan}vibecheck completion zsh${c.reset} ${c.dim}# Add to ~/.zshrc${c.reset}
|
|
895
|
+
${c.cyan}vibecheck completion fish${c.reset} ${c.dim}# Save to completions dir${c.reset}
|
|
896
|
+
|
|
393
897
|
${c.dim}Run 'vibecheck <command> --help' for command-specific help.${c.reset}
|
|
394
898
|
${c.dim}Pricing: https://vibecheckai.dev/pricing${c.reset}
|
|
395
899
|
`);
|
|
@@ -406,58 +910,165 @@ function getArgValue(args, flags) {
|
|
|
406
910
|
return undefined;
|
|
407
911
|
}
|
|
408
912
|
|
|
913
|
+
function hasFlag(args, flags) {
|
|
914
|
+
for (const flag of flags) {
|
|
915
|
+
if (args.includes(flag)) return true;
|
|
916
|
+
}
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
|
|
409
920
|
function formatError(error, config) {
|
|
410
921
|
const lines = [`${c.red}${sym.error} Error:${c.reset} ${error.message}`];
|
|
411
|
-
if (config.debug && error.stack) {
|
|
922
|
+
if (config.debug && error.stack) {
|
|
923
|
+
lines.push("", `${c.dim}Stack trace:${c.reset}`, c.dim + error.stack.split("\n").slice(1).join("\n") + c.reset);
|
|
924
|
+
}
|
|
412
925
|
return lines.join("\n");
|
|
413
926
|
}
|
|
414
927
|
|
|
928
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
929
|
+
// FLAG PARSING
|
|
930
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
931
|
+
function parseGlobalFlags(rawArgs) {
|
|
932
|
+
const flags = {
|
|
933
|
+
help: false,
|
|
934
|
+
version: false,
|
|
935
|
+
json: false,
|
|
936
|
+
ci: false,
|
|
937
|
+
quiet: false,
|
|
938
|
+
verbose: false,
|
|
939
|
+
debug: false,
|
|
940
|
+
strict: false,
|
|
941
|
+
noBanner: false,
|
|
942
|
+
path: process.cwd(),
|
|
943
|
+
output: null,
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
const cleanArgs = [];
|
|
947
|
+
let i = 0;
|
|
948
|
+
|
|
949
|
+
while (i < rawArgs.length) {
|
|
950
|
+
const arg = rawArgs[i];
|
|
951
|
+
|
|
952
|
+
switch (arg) {
|
|
953
|
+
case "--help":
|
|
954
|
+
case "-h":
|
|
955
|
+
flags.help = true;
|
|
956
|
+
break;
|
|
957
|
+
case "--version":
|
|
958
|
+
case "-v":
|
|
959
|
+
flags.version = true;
|
|
960
|
+
break;
|
|
961
|
+
case "--json":
|
|
962
|
+
flags.json = true;
|
|
963
|
+
break;
|
|
964
|
+
case "--ci":
|
|
965
|
+
flags.ci = true;
|
|
966
|
+
break;
|
|
967
|
+
case "--quiet":
|
|
968
|
+
case "-q":
|
|
969
|
+
flags.quiet = true;
|
|
970
|
+
break;
|
|
971
|
+
case "--verbose":
|
|
972
|
+
flags.verbose = true;
|
|
973
|
+
break;
|
|
974
|
+
case "--debug":
|
|
975
|
+
flags.debug = true;
|
|
976
|
+
break;
|
|
977
|
+
case "--strict":
|
|
978
|
+
flags.strict = true;
|
|
979
|
+
break;
|
|
980
|
+
case "--no-banner":
|
|
981
|
+
flags.noBanner = true;
|
|
982
|
+
break;
|
|
983
|
+
case "--path":
|
|
984
|
+
case "-p":
|
|
985
|
+
flags.path = rawArgs[++i] || process.cwd();
|
|
986
|
+
break;
|
|
987
|
+
case "--output":
|
|
988
|
+
case "-o":
|
|
989
|
+
flags.output = rawArgs[++i] || null;
|
|
990
|
+
break;
|
|
991
|
+
default:
|
|
992
|
+
if (arg.startsWith("--path=")) {
|
|
993
|
+
flags.path = arg.split("=")[1];
|
|
994
|
+
} else if (arg.startsWith("--output=")) {
|
|
995
|
+
flags.output = arg.split("=")[1];
|
|
996
|
+
} else {
|
|
997
|
+
cleanArgs.push(arg);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
i++;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return { flags, cleanArgs };
|
|
1004
|
+
}
|
|
1005
|
+
|
|
415
1006
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
416
1007
|
// MAIN ENTRY POINT
|
|
417
1008
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
418
1009
|
async function main() {
|
|
419
1010
|
const startTime = performance.now();
|
|
420
1011
|
const rawArgs = process.argv.slice(2);
|
|
1012
|
+
|
|
1013
|
+
// Parse global flags
|
|
1014
|
+
const { flags: globalFlags, cleanArgs } = parseGlobalFlags(rawArgs);
|
|
1015
|
+
|
|
1016
|
+
// Handle version (fast path - no config loading)
|
|
1017
|
+
if (globalFlags.version) {
|
|
1018
|
+
console.log(`vibecheck v${getVersion()}`);
|
|
1019
|
+
process.exit(0);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Load config and state
|
|
421
1023
|
const config = loadConfig();
|
|
422
1024
|
const state = loadState();
|
|
423
|
-
|
|
424
|
-
//
|
|
425
|
-
const { flags: globalFlags, parsed: cleanArgs } = parseStandardFlags(rawArgs);
|
|
426
|
-
|
|
427
|
-
// Update config based on flags
|
|
1025
|
+
|
|
1026
|
+
// Apply flag overrides to config
|
|
428
1027
|
if (globalFlags.debug) config.debug = true;
|
|
429
1028
|
if (globalFlags.verbose) config.verbose = true;
|
|
430
1029
|
if (globalFlags.quiet) config.quiet = true;
|
|
431
|
-
if (globalFlags.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
console.log(`vibecheck v${VERSION}`);
|
|
436
|
-
process.exit(0);
|
|
1030
|
+
if (globalFlags.noBanner) config.noBanner = true;
|
|
1031
|
+
if (globalFlags.ci) {
|
|
1032
|
+
config.quiet = true;
|
|
1033
|
+
config.noBanner = true;
|
|
437
1034
|
}
|
|
438
|
-
|
|
1035
|
+
|
|
439
1036
|
// Handle no command
|
|
440
|
-
if (!cleanArgs[0]) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (globalFlags.help && cleanArgs[0] && COMMANDS[cleanArgs[0]]) {
|
|
444
|
-
// Pass --help to the command runner
|
|
445
|
-
} else if (globalFlags.help && !cleanArgs[0]) {
|
|
446
|
-
printHelp(); process.exit(0);
|
|
1037
|
+
if (!cleanArgs[0]) {
|
|
1038
|
+
printHelp(!config.noBanner);
|
|
1039
|
+
process.exit(0);
|
|
447
1040
|
}
|
|
448
|
-
|
|
1041
|
+
|
|
1042
|
+
// Handle global help
|
|
1043
|
+
if (globalFlags.help && !cleanArgs[0]) {
|
|
1044
|
+
printHelp(!config.noBanner);
|
|
1045
|
+
process.exit(0);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Handle completion command (special - doesn't need registry loaded normally)
|
|
1049
|
+
if (cleanArgs[0] === "completion") {
|
|
1050
|
+
process.exit(runCompletion(cleanArgs.slice(1)));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Load registry for command resolution
|
|
1054
|
+
const registry = getRegistry();
|
|
1055
|
+
const { COMMANDS, ALIAS_MAP, ALL_COMMANDS } = registry;
|
|
1056
|
+
|
|
449
1057
|
let cmd = cleanArgs[0];
|
|
450
|
-
//
|
|
451
|
-
if (!COMMANDS[cmd] && ALIAS_MAP[cmd]) {
|
|
1058
|
+
// Prefer exact match, then alias
|
|
1059
|
+
if (!COMMANDS[cmd] && ALIAS_MAP[cmd]) {
|
|
1060
|
+
cmd = ALIAS_MAP[cmd];
|
|
1061
|
+
}
|
|
452
1062
|
let cmdArgs = cleanArgs.slice(1);
|
|
453
|
-
|
|
454
|
-
//
|
|
1063
|
+
|
|
1064
|
+
// Pass --help to runner if specified with command
|
|
455
1065
|
if (globalFlags.help) cmdArgs = ["--help", ...cmdArgs];
|
|
456
|
-
|
|
1066
|
+
|
|
457
1067
|
// Pass standard flags to runners
|
|
458
|
-
cmdArgs = [...cmdArgs];
|
|
459
1068
|
if (globalFlags.json) cmdArgs.push("--json");
|
|
460
1069
|
if (globalFlags.ci) cmdArgs.push("--ci");
|
|
1070
|
+
if (globalFlags.noBanner) cmdArgs.push("--no-banner");
|
|
1071
|
+
if (globalFlags.quiet) cmdArgs.push("--quiet");
|
|
461
1072
|
if (globalFlags.path && globalFlags.path !== process.cwd()) {
|
|
462
1073
|
cmdArgs.push("--path", globalFlags.path);
|
|
463
1074
|
}
|
|
@@ -466,114 +1077,107 @@ async function main() {
|
|
|
466
1077
|
}
|
|
467
1078
|
if (globalFlags.verbose) cmdArgs.push("--verbose");
|
|
468
1079
|
if (globalFlags.strict) cmdArgs.push("--strict");
|
|
469
|
-
|
|
1080
|
+
|
|
1081
|
+
// Unknown command
|
|
470
1082
|
if (!COMMANDS[cmd]) {
|
|
471
1083
|
const suggestions = findSimilarCommands(cmd, ALL_COMMANDS);
|
|
472
1084
|
console.log(`\n${c.red}${sym.error}${c.reset} Unknown command: ${c.yellow}${cmd}${c.reset}`);
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (cmd === "replay" || cmd === "record") {
|
|
476
|
-
console.log(`\n${c.dim}replay is a PRO feature for session recording.${c.reset}`);
|
|
477
|
-
console.log(`${c.dim}Free alternative:${c.reset} ${c.cyan}vibecheck reality${c.reset} ${c.dim}(one-time runtime proof)${c.reset}`);
|
|
478
|
-
} else if (suggestions.length > 0) {
|
|
1085
|
+
|
|
1086
|
+
if (suggestions.length > 0) {
|
|
479
1087
|
console.log(`\n${c.dim}Did you mean:${c.reset}`);
|
|
480
|
-
suggestions.forEach(s => {
|
|
1088
|
+
suggestions.forEach((s) => {
|
|
1089
|
+
const actual = ALIAS_MAP[s] || s;
|
|
1090
|
+
const def = COMMANDS[actual];
|
|
1091
|
+
console.log(` ${c.cyan}vibecheck ${s}${c.reset} ${c.dim}${def?.description || ""}${c.reset}`);
|
|
1092
|
+
});
|
|
481
1093
|
}
|
|
482
1094
|
console.log(`\n${c.dim}Run 'vibecheck --help' for available commands.${c.reset}\n`);
|
|
483
|
-
process.exit(4);
|
|
1095
|
+
process.exit(4);
|
|
484
1096
|
}
|
|
485
|
-
|
|
486
|
-
// Generate runId
|
|
487
|
-
const
|
|
1097
|
+
|
|
1098
|
+
// Generate runId
|
|
1099
|
+
const cliOutput = getCliOutput();
|
|
1100
|
+
const runId = cliOutput.generateRunId();
|
|
488
1101
|
const runStart = new Date().toISOString();
|
|
489
|
-
|
|
1102
|
+
|
|
490
1103
|
const cmdDef = COMMANDS[cmd];
|
|
491
1104
|
let authInfo = { key: null };
|
|
492
|
-
|
|
1105
|
+
|
|
1106
|
+
// Auth check (unless skipAuth)
|
|
493
1107
|
if (!cmdDef.skipAuth) {
|
|
494
1108
|
const auth = getAuthModule();
|
|
495
1109
|
const { key } = auth.getApiKey();
|
|
496
1110
|
authInfo.key = key;
|
|
497
|
-
|
|
498
|
-
// Use entitlements-v2 for access control (NO BYPASS)
|
|
1111
|
+
|
|
499
1112
|
const access = await checkCommandAccess(cmd, cmdArgs, authInfo);
|
|
500
|
-
|
|
1113
|
+
|
|
501
1114
|
if (!access.allowed) {
|
|
502
1115
|
console.log(access.reason);
|
|
503
|
-
// Use proper exit code: 3 = feature not allowed
|
|
504
1116
|
process.exit(access.exitCode || 3);
|
|
505
1117
|
}
|
|
506
|
-
|
|
507
|
-
//
|
|
1118
|
+
|
|
1119
|
+
// Downgrade notice
|
|
508
1120
|
if (access.downgrade && !config.quiet) {
|
|
1121
|
+
const upsell = getUpsell();
|
|
509
1122
|
console.log(upsell.formatDowngrade(cmd, {
|
|
510
1123
|
currentTier: access.tier,
|
|
511
1124
|
effectiveMode: access.downgrade,
|
|
512
1125
|
caps: access.caps,
|
|
513
1126
|
}));
|
|
514
1127
|
}
|
|
515
|
-
|
|
516
|
-
//
|
|
517
|
-
if (!config.quiet) {
|
|
518
|
-
if (access.tier === "pro")
|
|
519
|
-
|
|
1128
|
+
|
|
1129
|
+
// Tier badge
|
|
1130
|
+
if (!config.quiet && !config.noBanner) {
|
|
1131
|
+
if (access.tier === "pro") {
|
|
1132
|
+
console.log(`${c.magenta}${sym.arrowRight} PRO${c.reset} ${c.dim}feature${c.reset}`);
|
|
1133
|
+
} else if (access.tier === "complete") {
|
|
1134
|
+
console.log(`${c.yellow}${sym.arrowRight} COMPLETE${c.reset} ${c.dim}feature${c.reset}`);
|
|
1135
|
+
}
|
|
520
1136
|
}
|
|
521
|
-
|
|
522
|
-
// Attach access info for runners to use
|
|
1137
|
+
|
|
523
1138
|
authInfo.access = access;
|
|
524
1139
|
}
|
|
525
|
-
|
|
526
|
-
|
|
1140
|
+
|
|
1141
|
+
// Update state
|
|
1142
|
+
state.runCount++;
|
|
1143
|
+
state.lastRun = Date.now();
|
|
527
1144
|
state.commandHistory = [...(state.commandHistory || []).slice(-99), { cmd, timestamp: Date.now() }];
|
|
528
1145
|
saveState(state);
|
|
529
|
-
|
|
1146
|
+
|
|
1147
|
+
// Check for updates (async, non-blocking for startup)
|
|
1148
|
+
let updatePromise = null;
|
|
1149
|
+
if (!config.quiet && !config.noBanner && !isCI()) {
|
|
1150
|
+
updatePromise = checkForUpdates(state, config);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
530
1153
|
let exitCode = 0;
|
|
1154
|
+
|
|
531
1155
|
try {
|
|
532
1156
|
const runner = getRunner(cmd);
|
|
533
|
-
if (!runner) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1157
|
+
if (!runner) {
|
|
1158
|
+
console.error(`${c.red}${sym.error}${c.reset} Failed to load runner for: ${cmd}`);
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const context = {
|
|
1163
|
+
repoRoot: globalFlags.path,
|
|
1164
|
+
config,
|
|
1165
|
+
state,
|
|
1166
|
+
authInfo,
|
|
1167
|
+
version: getVersion(),
|
|
541
1168
|
isCI: isCI(),
|
|
542
1169
|
runId,
|
|
543
|
-
runStart
|
|
1170
|
+
runStart,
|
|
544
1171
|
};
|
|
545
|
-
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
else if (cmdArgs[0] === "guard") { const { runCtxGuard } = require("./runners/runCtxGuard"); exitCode = await runCtxGuard.main(cmdArgs.slice(1)); }
|
|
555
|
-
else if (cmdArgs[0] === "diff") { const { main: ctxDiffMain } = require("./runners/runCtxDiff"); exitCode = await ctxDiffMain(cmdArgs.slice(1)); }
|
|
556
|
-
else if (cmdArgs[0] === "search") { const { runContext } = require("./runners/runContext"); exitCode = await runContext(["--search", ...cmdArgs.slice(1)]); }
|
|
557
|
-
else { exitCode = await runner(cmdArgs, context); }
|
|
558
|
-
break;
|
|
559
|
-
case "runtime":
|
|
560
|
-
exitCode = await runner(cmdArgs, context);
|
|
561
|
-
break;
|
|
562
|
-
case "export":
|
|
563
|
-
exitCode = await runner(cmdArgs, context);
|
|
564
|
-
break;
|
|
565
|
-
case "security":
|
|
566
|
-
exitCode = await runner(cmdArgs, context);
|
|
567
|
-
break;
|
|
568
|
-
case "install": exitCode = await runner(cmdArgs, context); break;
|
|
569
|
-
case "status": exitCode = await runner({ ...context, json: cmdArgs.includes("--json") }); break;
|
|
570
|
-
case "pr": exitCode = await runner(cmdArgs, context); break;
|
|
571
|
-
case "share": exitCode = await runner(cmdArgs, context); break;
|
|
572
|
-
default: exitCode = await runner(cmdArgs);
|
|
573
|
-
}
|
|
574
|
-
} catch (error) { console.error(formatError(error, config)); exitCode = 1; }
|
|
575
|
-
|
|
576
|
-
// Create manifest and receipt for paid commands
|
|
1172
|
+
|
|
1173
|
+
// Execute command - all commands use consistent runner signature
|
|
1174
|
+
exitCode = await runner(cmdArgs, context);
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
console.error(formatError(error, config));
|
|
1177
|
+
exitCode = 1;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Create receipt for paid commands
|
|
577
1181
|
if (cmdDef.tier !== "free" && runId) {
|
|
578
1182
|
try {
|
|
579
1183
|
const { createManifestAndReceipt } = require("./runners/lib/receipts");
|
|
@@ -585,24 +1189,59 @@ async function main() {
|
|
|
585
1189
|
exitCode,
|
|
586
1190
|
startTime: runStart,
|
|
587
1191
|
endTime: new Date().toISOString(),
|
|
588
|
-
projectPath:
|
|
1192
|
+
projectPath: globalFlags.path,
|
|
589
1193
|
});
|
|
590
1194
|
} catch (e) {
|
|
591
|
-
// Don't fail the command if receipt creation fails
|
|
592
1195
|
if (config.debug) console.error(`Failed to create receipt: ${e.message}`);
|
|
593
1196
|
}
|
|
594
1197
|
}
|
|
595
|
-
|
|
596
|
-
|
|
1198
|
+
|
|
1199
|
+
// Show update notice after command (if available)
|
|
1200
|
+
if (updatePromise) {
|
|
1201
|
+
const latestVersion = await updatePromise;
|
|
1202
|
+
if (latestVersion) {
|
|
1203
|
+
printUpdateNotice(latestVersion);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Debug timing
|
|
1208
|
+
if (config.debug) {
|
|
1209
|
+
console.log(`\n${c.dim}${sym.clock} Total: ${(performance.now() - startTime).toFixed(0)}ms${c.reset}`);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
597
1212
|
process.exit(exitCode);
|
|
598
1213
|
}
|
|
599
1214
|
|
|
600
1215
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
601
1216
|
// GRACEFUL SHUTDOWN
|
|
602
1217
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
603
|
-
process.on("SIGINT", () => {
|
|
604
|
-
|
|
605
|
-
process.
|
|
606
|
-
|
|
1218
|
+
process.on("SIGINT", () => {
|
|
1219
|
+
console.log(`\n${c.yellow}${sym.warning}${c.reset} Interrupted`);
|
|
1220
|
+
process.exit(130);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
process.on("SIGTERM", () => {
|
|
1224
|
+
console.log(`\n${c.yellow}${sym.warning}${c.reset} Terminated`);
|
|
1225
|
+
process.exit(143);
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
process.on("uncaughtException", (error) => {
|
|
1229
|
+
console.error(`\n${c.red}${sym.error} Uncaught Exception:${c.reset} ${error.message}`);
|
|
1230
|
+
if (process.env.VIBECHECK_DEBUG === "true") {
|
|
1231
|
+
console.error(c.dim + error.stack + c.reset);
|
|
1232
|
+
}
|
|
1233
|
+
process.exit(1);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
process.on("unhandledRejection", (reason) => {
|
|
1237
|
+
console.error(`\n${c.red}${sym.error} Unhandled Rejection:${c.reset} ${reason}`);
|
|
1238
|
+
process.exit(1);
|
|
1239
|
+
});
|
|
607
1240
|
|
|
608
|
-
|
|
1241
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1242
|
+
// RUN
|
|
1243
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1244
|
+
main().catch((error) => {
|
|
1245
|
+
console.error(`\n${c.red}${sym.error} Fatal:${c.reset} ${error.message}`);
|
|
1246
|
+
process.exit(1);
|
|
1247
|
+
});
|