@variantlab/cli 0.1.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.
@@ -0,0 +1,664 @@
1
+ #!/usr/bin/env node
2
+ import { access, mkdir, writeFile, readFile } from 'fs/promises';
3
+ import { resolve, dirname } from 'path';
4
+ import { watch } from 'fs';
5
+ import { validateConfig, ConfigValidationError, explain, createEngine } from '@variantlab/core';
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // src/utils/printer.ts
18
+ function supportsColor() {
19
+ if (typeof process === "undefined") return false;
20
+ if (process.env.NO_COLOR !== void 0) return false;
21
+ if (process.env.FORCE_COLOR !== void 0) return true;
22
+ if (process.stdout && "isTTY" in process.stdout) return !!process.stdout.isTTY;
23
+ return false;
24
+ }
25
+ function wrap(code, text) {
26
+ return useColor ? `${code}${text}${RESET}` : text;
27
+ }
28
+ function success(msg) {
29
+ console.log(`${color.green("\u2713")} ${msg}`);
30
+ }
31
+ function info(msg) {
32
+ console.log(`${color.blue("\u2139")} ${msg}`);
33
+ }
34
+ function error(msg) {
35
+ console.error(`${color.red("\u2717")} ${msg}`);
36
+ }
37
+ function verbose(msg, isVerbose) {
38
+ if (isVerbose) console.log(`${color.dim(` ${msg}`)}`);
39
+ }
40
+ var ESC, RESET, codes, useColor, color;
41
+ var init_printer = __esm({
42
+ "src/utils/printer.ts"() {
43
+ ESC = "\x1B[";
44
+ RESET = `${ESC}0m`;
45
+ codes = {
46
+ red: `${ESC}31m`,
47
+ green: `${ESC}32m`,
48
+ yellow: `${ESC}33m`,
49
+ blue: `${ESC}34m`,
50
+ magenta: `${ESC}35m`,
51
+ cyan: `${ESC}36m`,
52
+ gray: `${ESC}90m`,
53
+ bold: `${ESC}1m`,
54
+ dim: `${ESC}2m`
55
+ };
56
+ useColor = supportsColor();
57
+ color = {
58
+ red: (t) => wrap(codes.red, t),
59
+ green: (t) => wrap(codes.green, t),
60
+ yellow: (t) => wrap(codes.yellow, t),
61
+ blue: (t) => wrap(codes.blue, t),
62
+ magenta: (t) => wrap(codes.magenta, t),
63
+ cyan: (t) => wrap(codes.cyan, t),
64
+ gray: (t) => wrap(codes.gray, t),
65
+ bold: (t) => wrap(codes.bold, t),
66
+ dim: (t) => wrap(codes.dim, t)
67
+ };
68
+ }
69
+ });
70
+ async function readTextFile(path) {
71
+ return readFile(path, "utf-8");
72
+ }
73
+ async function writeTextFile(path, content) {
74
+ await mkdir(dirname(path), { recursive: true });
75
+ await writeFile(path, content, "utf-8");
76
+ }
77
+ async function fileExists(path) {
78
+ try {
79
+ await access(path);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ var init_file = __esm({
86
+ "src/utils/file.ts"() {
87
+ }
88
+ });
89
+
90
+ // src/commands/init.ts
91
+ var init_exports = {};
92
+ __export(init_exports, {
93
+ init: () => init
94
+ });
95
+ async function init(options = {}) {
96
+ const configPath = resolve("experiments.json");
97
+ if (!options.force && await fileExists(configPath)) {
98
+ error("experiments.json already exists. Use --force to overwrite.");
99
+ return 1;
100
+ }
101
+ try {
102
+ await writeTextFile(configPath, STARTER_CONFIG);
103
+ success("Created experiments.json");
104
+ } catch (err) {
105
+ error(`Failed to write experiments.json: ${err.message}`);
106
+ return 3;
107
+ }
108
+ const pkgPath = resolve("package.json");
109
+ if (await fileExists(pkgPath)) {
110
+ try {
111
+ const raw = await readTextFile(pkgPath);
112
+ const pkg = JSON.parse(raw);
113
+ const scripts = pkg.scripts ?? {};
114
+ if (!scripts["variantlab:generate"]) {
115
+ scripts["variantlab:generate"] = "variantlab generate";
116
+ pkg.scripts = scripts;
117
+ await writeTextFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
118
+ `);
119
+ success('Added "variantlab:generate" script to package.json');
120
+ }
121
+ } catch {
122
+ verbose("Could not update package.json (non-fatal)", true);
123
+ }
124
+ }
125
+ console.log("");
126
+ info("Next steps:");
127
+ console.log(" 1. Edit experiments.json to define your experiments");
128
+ console.log(" 2. Run: variantlab generate");
129
+ console.log(" 3. Import the generated types in your app");
130
+ console.log("");
131
+ return 0;
132
+ }
133
+ var STARTER_CONFIG;
134
+ var init_init = __esm({
135
+ "src/commands/init.ts"() {
136
+ init_file();
137
+ init_printer();
138
+ STARTER_CONFIG = `{
139
+ "$schema": "https://variantlab.dev/schemas/experiments.schema.json",
140
+ "version": 1,
141
+ "experiments": [
142
+ {
143
+ "id": "welcome-message",
144
+ "name": "Welcome message copy",
145
+ "type": "value",
146
+ "default": "hello",
147
+ "variants": [
148
+ { "id": "hello", "label": "Hello", "value": "Hello, welcome!" },
149
+ { "id": "hey", "label": "Hey", "value": "Hey there, glad you're here!" }
150
+ ]
151
+ },
152
+ {
153
+ "id": "hero-layout",
154
+ "name": "Hero section layout",
155
+ "type": "render",
156
+ "default": "centered",
157
+ "variants": [
158
+ { "id": "centered", "label": "Centered" },
159
+ { "id": "split", "label": "Split view" }
160
+ ]
161
+ }
162
+ ]
163
+ }
164
+ `;
165
+ }
166
+ });
167
+
168
+ // src/commands/generate.ts
169
+ var generate_exports = {};
170
+ __export(generate_exports, {
171
+ generate: () => generate
172
+ });
173
+ async function generate(options = {}) {
174
+ const configPath = resolve(options.config ?? "experiments.json");
175
+ const outPath = resolve(options.out ?? "src/variantlab.generated.ts");
176
+ const code = await runGenerate(configPath, outPath, options.verbose ?? false);
177
+ if (code !== 0) return code;
178
+ if (options.watch) {
179
+ info(`Watching ${configPath} for changes...`);
180
+ let debounce = null;
181
+ watch(configPath, () => {
182
+ if (debounce) clearTimeout(debounce);
183
+ debounce = setTimeout(async () => {
184
+ info("Config changed, regenerating...");
185
+ await runGenerate(configPath, outPath, options.verbose ?? false);
186
+ }, 100);
187
+ });
188
+ await new Promise(() => {
189
+ });
190
+ }
191
+ return 0;
192
+ }
193
+ async function runGenerate(configPath, outPath, isVerbose) {
194
+ if (!await fileExists(configPath)) {
195
+ error(`Config not found: ${configPath}`);
196
+ return 1;
197
+ }
198
+ let raw;
199
+ try {
200
+ raw = await readTextFile(configPath);
201
+ } catch (err) {
202
+ error(`Failed to read config: ${err.message}`);
203
+ return 3;
204
+ }
205
+ let parsed;
206
+ try {
207
+ parsed = JSON.parse(raw);
208
+ } catch (err) {
209
+ error(`Invalid JSON: ${err.message}`);
210
+ return 2;
211
+ }
212
+ let config;
213
+ try {
214
+ config = validateConfig(parsed);
215
+ } catch (err) {
216
+ error(`Validation failed: ${err.message}`);
217
+ return 2;
218
+ }
219
+ verbose(`Loaded ${config.experiments.length} experiment(s)`, isVerbose);
220
+ const output = emitTypeScript(config);
221
+ try {
222
+ await writeTextFile(outPath, output);
223
+ } catch (err) {
224
+ error(`Failed to write output: ${err.message}`);
225
+ return 3;
226
+ }
227
+ success(`Generated ${outPath}`);
228
+ return 0;
229
+ }
230
+ function emitTypeScript(config) {
231
+ const lines = [
232
+ "// AUTO-GENERATED by @variantlab/cli",
233
+ `// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
234
+ "// DO NOT EDIT BY HAND",
235
+ "// Regenerate with: variantlab generate",
236
+ "",
237
+ "export interface VariantLabExperiments {"
238
+ ];
239
+ for (const exp of config.experiments) {
240
+ if (exp.name) {
241
+ lines.push(` /** ${escapeComment(exp.name)} */`);
242
+ }
243
+ lines.push(` "${exp.id}": {`);
244
+ lines.push(` type: "${exp.type ?? "render"}";`);
245
+ lines.push(` variants: ${variantUnion(exp)};`);
246
+ lines.push(` default: "${exp.default}";`);
247
+ if (exp.type === "value") {
248
+ lines.push(` value: ${valueUnion(exp)};`);
249
+ }
250
+ lines.push(" };");
251
+ }
252
+ lines.push("}");
253
+ lines.push("");
254
+ lines.push('declare module "@variantlab/core" {');
255
+ lines.push(" interface VariantLabRegistry extends VariantLabExperiments {}");
256
+ lines.push("}");
257
+ lines.push("");
258
+ lines.push("export type ExperimentId = keyof VariantLabExperiments;");
259
+ lines.push("");
260
+ lines.push("export type VariantId<T extends ExperimentId> =");
261
+ lines.push(' VariantLabExperiments[T]["variants"];');
262
+ lines.push("");
263
+ lines.push("export type VariantValueType<T extends ExperimentId> =");
264
+ lines.push(" VariantLabExperiments[T] extends { value: infer V } ? V : never;");
265
+ lines.push("");
266
+ return lines.join("\n");
267
+ }
268
+ function variantUnion(exp) {
269
+ return exp.variants.map((v) => `"${escapeStr(v.id)}"`).join(" | ");
270
+ }
271
+ function valueUnion(exp) {
272
+ const values = exp.variants.map((v) => v.value).filter((v) => v !== void 0);
273
+ if (values.length === 0) return "never";
274
+ return values.map((v) => {
275
+ if (typeof v === "string") return `"${escapeStr(v)}"`;
276
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
277
+ return "unknown";
278
+ }).join(" | ");
279
+ }
280
+ function escapeStr(s) {
281
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
282
+ }
283
+ function escapeComment(s) {
284
+ return s.replace(/\*\//g, "* /");
285
+ }
286
+ var init_generate = __esm({
287
+ "src/commands/generate.ts"() {
288
+ init_file();
289
+ init_printer();
290
+ }
291
+ });
292
+
293
+ // src/commands/validate.ts
294
+ var validate_exports = {};
295
+ __export(validate_exports, {
296
+ validate: () => validate
297
+ });
298
+ async function validate(configPathArg, options = {}) {
299
+ const configPath = resolve(configPathArg ?? "experiments.json");
300
+ if (!await fileExists(configPath)) {
301
+ error(`Config not found: ${configPath}`);
302
+ return 1;
303
+ }
304
+ let raw;
305
+ try {
306
+ raw = await readTextFile(configPath);
307
+ } catch (err) {
308
+ error(`Failed to read config: ${err.message}`);
309
+ return 3;
310
+ }
311
+ let parsed;
312
+ try {
313
+ parsed = JSON.parse(raw);
314
+ } catch (err) {
315
+ error(`Invalid JSON in ${configPath}`);
316
+ error(err.message);
317
+ return 2;
318
+ }
319
+ try {
320
+ const config = validateConfig(parsed);
321
+ success(`Valid config: ${config.experiments.length} experiment(s)`);
322
+ if (options.verbose) {
323
+ for (const exp of config.experiments) {
324
+ console.log(
325
+ ` ${color.cyan(exp.id)} \u2014 ${exp.variants.length} variants, assignment: ${exp.assignment ?? "default"}, status: ${exp.status ?? "active"}`
326
+ );
327
+ }
328
+ }
329
+ return 0;
330
+ } catch (err) {
331
+ if (err instanceof ConfigValidationError) {
332
+ error(`Validation failed with ${err.issues.length} issue(s):`);
333
+ console.log("");
334
+ for (const issue of err.issues) {
335
+ console.log(` ${color.red(issue.code)} at ${color.yellow(issue.path)}`);
336
+ console.log(` ${issue.message}`);
337
+ }
338
+ console.log("");
339
+ return 2;
340
+ }
341
+ error(`Unexpected error: ${err.message}`);
342
+ return 2;
343
+ }
344
+ }
345
+ var init_validate = __esm({
346
+ "src/commands/validate.ts"() {
347
+ init_file();
348
+ init_printer();
349
+ init_printer();
350
+ }
351
+ });
352
+
353
+ // src/commands/eval.ts
354
+ var eval_exports = {};
355
+ __export(eval_exports, {
356
+ evalCommand: () => evalCommand
357
+ });
358
+ async function evalCommand(configPathArg, options = {}) {
359
+ const configPath = resolve(configPathArg ?? "experiments.json");
360
+ if (!options.experiment) {
361
+ error("Missing required flag: --experiment <id>");
362
+ return 4;
363
+ }
364
+ if (!await fileExists(configPath)) {
365
+ error(`Config not found: ${configPath}`);
366
+ return 1;
367
+ }
368
+ let config;
369
+ try {
370
+ const raw = await readTextFile(configPath);
371
+ const parsed = JSON.parse(raw);
372
+ config = validateConfig(parsed);
373
+ } catch (err) {
374
+ if (err instanceof ConfigValidationError) {
375
+ error(`Invalid config: ${err.issues.length} issue(s)`);
376
+ return 2;
377
+ }
378
+ error(`Failed to load config: ${err.message}`);
379
+ return err instanceof SyntaxError ? 2 : 3;
380
+ }
381
+ let context = {};
382
+ if (options.contextFile) {
383
+ try {
384
+ const raw = await readTextFile(resolve(options.contextFile));
385
+ context = JSON.parse(raw);
386
+ } catch (err) {
387
+ error(`Failed to load context file: ${err.message}`);
388
+ return 3;
389
+ }
390
+ } else if (options.context) {
391
+ try {
392
+ context = JSON.parse(options.context);
393
+ } catch (err) {
394
+ error(`Invalid context JSON: ${err.message}`);
395
+ return 4;
396
+ }
397
+ }
398
+ const experiment = config.experiments.find((e) => e.id === options.experiment);
399
+ if (!experiment) {
400
+ error(`Experiment not found: ${options.experiment}`);
401
+ const ids = config.experiments.map((e) => e.id);
402
+ if (ids.length > 0) {
403
+ info(`Available experiments: ${ids.join(", ")}`);
404
+ }
405
+ return 4;
406
+ }
407
+ console.log(color.bold(`Experiment: ${experiment.id}`));
408
+ console.log(` Name: ${experiment.name}`);
409
+ console.log(` Type: ${experiment.type ?? "render"}`);
410
+ console.log(` Assignment: ${experiment.assignment ?? "default"}`);
411
+ console.log(` Default: ${experiment.default}`);
412
+ console.log(` Variants: ${experiment.variants.map((v) => v.id).join(", ")}`);
413
+ console.log("");
414
+ console.log(color.bold("Context:"));
415
+ if (Object.keys(context).length === 0) {
416
+ console.log(" (empty)");
417
+ } else {
418
+ for (const [key, val] of Object.entries(context)) {
419
+ console.log(` ${key}: ${JSON.stringify(val)}`);
420
+ }
421
+ }
422
+ console.log("");
423
+ console.log(color.bold("Targeting trace:"));
424
+ const result = explain(experiment, context);
425
+ if (result.steps.length === 0) {
426
+ console.log(" (no targeting rules)");
427
+ } else {
428
+ for (const step of result.steps) {
429
+ const icon = step.matched ? color.green("PASS") : color.red("FAIL");
430
+ const detail = step.detail ? ` \u2014 ${step.detail}` : "";
431
+ console.log(` ${icon} ${step.field}${detail}`);
432
+ }
433
+ }
434
+ console.log("");
435
+ const engine = createEngine(config, { context });
436
+ const variantId = engine.getVariant(experiment.id);
437
+ const variant = experiment.variants.find((v) => v.id === variantId);
438
+ console.log(color.bold("Result:"));
439
+ console.log(` Targeted: ${result.matched ? color.green("yes") : color.red("no")}`);
440
+ console.log(` Variant: ${color.cyan(variantId)}`);
441
+ if (variant?.value !== void 0) {
442
+ console.log(` Value: ${JSON.stringify(variant.value)}`);
443
+ }
444
+ if (!result.matched && result.reason) {
445
+ console.log(` Reason: failed on ${color.yellow(result.reason)}`);
446
+ }
447
+ console.log("");
448
+ engine.dispose();
449
+ return 0;
450
+ }
451
+ var init_eval = __esm({
452
+ "src/commands/eval.ts"() {
453
+ init_file();
454
+ init_printer();
455
+ init_printer();
456
+ }
457
+ });
458
+
459
+ // src/utils/arg-parser.ts
460
+ function parseArgs(argv) {
461
+ const flags = {};
462
+ const positionals = [];
463
+ let command;
464
+ let i = 0;
465
+ while (i < argv.length) {
466
+ const arg = argv[i];
467
+ if (arg === "--") {
468
+ positionals.push(...argv.slice(i + 1));
469
+ break;
470
+ }
471
+ if (arg.startsWith("--")) {
472
+ const eqIdx = arg.indexOf("=");
473
+ if (eqIdx !== -1) {
474
+ const key = camelCase(arg.slice(2, eqIdx));
475
+ flags[key] = arg.slice(eqIdx + 1);
476
+ } else {
477
+ const raw = arg.slice(2);
478
+ if (raw.startsWith("no-")) {
479
+ flags[camelCase(raw.slice(3))] = false;
480
+ } else {
481
+ const key = camelCase(raw);
482
+ const next = argv[i + 1];
483
+ if (next !== void 0 && !next.startsWith("-")) {
484
+ flags[key] = next;
485
+ i++;
486
+ } else {
487
+ flags[key] = true;
488
+ }
489
+ }
490
+ }
491
+ } else if (arg.startsWith("-") && arg.length === 2) {
492
+ const key = arg[1];
493
+ const next = argv[i + 1];
494
+ if (next !== void 0 && !next.startsWith("-")) {
495
+ flags[key] = next;
496
+ i++;
497
+ } else {
498
+ flags[key] = true;
499
+ }
500
+ } else if (command === void 0 && !arg.startsWith("-")) {
501
+ command = arg;
502
+ } else {
503
+ positionals.push(arg);
504
+ }
505
+ i++;
506
+ }
507
+ return { flags, positionals, command };
508
+ }
509
+ function camelCase(s) {
510
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
511
+ }
512
+
513
+ // src/bin/variantlab.ts
514
+ init_printer();
515
+ var VERSION = "0.0.0";
516
+ var USAGE = `
517
+ Usage: variantlab <command> [options]
518
+
519
+ Commands:
520
+ init Scaffold experiments.json in the current directory
521
+ generate Generate TypeScript from experiments.json
522
+ validate [path] Validate an experiments.json config file
523
+ eval [path] Evaluate an experiment against a context
524
+
525
+ Options:
526
+ --help Show this help message
527
+ --version Show version
528
+ --verbose Enable verbose output
529
+
530
+ Run "variantlab <command> --help" for command-specific help.
531
+ `.trim();
532
+ var INIT_HELP = `
533
+ Usage: variantlab init [options]
534
+
535
+ Scaffold a starter experiments.json in the current directory.
536
+
537
+ Options:
538
+ --force Overwrite existing experiments.json
539
+ --verbose Enable verbose output
540
+ --help Show this help message
541
+ `.trim();
542
+ var GENERATE_HELP = `
543
+ Usage: variantlab generate [options]
544
+
545
+ Generate TypeScript types from experiments.json.
546
+
547
+ Options:
548
+ --config <path> Path to experiments.json (default: ./experiments.json)
549
+ --out <path> Output path (default: ./src/variantlab.generated.ts)
550
+ --watch Watch for config changes and regenerate
551
+ --verbose Enable verbose output
552
+ --help Show this help message
553
+ `.trim();
554
+ var VALIDATE_HELP = `
555
+ Usage: variantlab validate [path] [options]
556
+
557
+ Validate an experiments.json config file.
558
+
559
+ Arguments:
560
+ path Path to config file (default: ./experiments.json)
561
+
562
+ Options:
563
+ --verbose Show experiment details on success
564
+ --help Show this help message
565
+ `.trim();
566
+ var EVAL_HELP = `
567
+ Usage: variantlab eval [path] [options]
568
+
569
+ Evaluate an experiment against a context and show targeting trace.
570
+
571
+ Arguments:
572
+ path Path to config file (default: ./experiments.json)
573
+
574
+ Options:
575
+ --experiment <id> Experiment ID to evaluate (required)
576
+ --context '<json>' Inline JSON context
577
+ --context-file <path> Path to a JSON context file
578
+ --verbose Enable verbose output
579
+ --help Show this help message
580
+ `.trim();
581
+ async function main() {
582
+ const args = parseArgs(process.argv.slice(2));
583
+ if (args.flags.version) {
584
+ console.log(VERSION);
585
+ process.exit(0);
586
+ }
587
+ if (!args.command || args.flags.help) {
588
+ if (!args.command) {
589
+ console.log(USAGE);
590
+ process.exit(args.flags.help ? 0 : 4);
591
+ }
592
+ }
593
+ const isVerbose = args.flags.verbose === true;
594
+ switch (args.command) {
595
+ case "init": {
596
+ if (args.flags.help) {
597
+ console.log(INIT_HELP);
598
+ process.exit(0);
599
+ }
600
+ const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
601
+ const code = await init2({
602
+ force: args.flags.force === true,
603
+ verbose: isVerbose
604
+ });
605
+ process.exit(code);
606
+ break;
607
+ }
608
+ case "generate": {
609
+ if (args.flags.help) {
610
+ console.log(GENERATE_HELP);
611
+ process.exit(0);
612
+ }
613
+ const { generate: generate2 } = await Promise.resolve().then(() => (init_generate(), generate_exports));
614
+ const code = await generate2({
615
+ config: typeof args.flags.config === "string" ? args.flags.config : void 0,
616
+ out: typeof args.flags.out === "string" ? args.flags.out : void 0,
617
+ watch: args.flags.watch === true,
618
+ verbose: isVerbose
619
+ });
620
+ process.exit(code);
621
+ break;
622
+ }
623
+ case "validate": {
624
+ if (args.flags.help) {
625
+ console.log(VALIDATE_HELP);
626
+ process.exit(0);
627
+ }
628
+ const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
629
+ const configPath = args.positionals[0];
630
+ const code = await validate2(typeof configPath === "string" ? configPath : void 0, {
631
+ verbose: isVerbose
632
+ });
633
+ process.exit(code);
634
+ break;
635
+ }
636
+ case "eval": {
637
+ if (args.flags.help) {
638
+ console.log(EVAL_HELP);
639
+ process.exit(0);
640
+ }
641
+ const { evalCommand: evalCommand2 } = await Promise.resolve().then(() => (init_eval(), eval_exports));
642
+ const configPath = args.positionals[0];
643
+ const code = await evalCommand2(typeof configPath === "string" ? configPath : void 0, {
644
+ experiment: typeof args.flags.experiment === "string" ? args.flags.experiment : void 0,
645
+ context: typeof args.flags.context === "string" ? args.flags.context : void 0,
646
+ contextFile: typeof args.flags.contextFile === "string" ? args.flags.contextFile : void 0,
647
+ verbose: isVerbose
648
+ });
649
+ process.exit(code);
650
+ break;
651
+ }
652
+ default:
653
+ error(`Unknown command: ${args.command}`);
654
+ console.log("");
655
+ console.log(USAGE);
656
+ process.exit(4);
657
+ }
658
+ }
659
+ main().catch((err) => {
660
+ error(err.message);
661
+ process.exit(1);
662
+ });
663
+ //# sourceMappingURL=variantlab.js.map
664
+ //# sourceMappingURL=variantlab.js.map