@kaynooo/cli-doctor 0.3.0 → 0.5.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.
Files changed (4) hide show
  1. package/README.md +27 -1
  2. package/dist/cli-bin.js +398 -114
  3. package/dist/cli.js +431 -118
  4. package/package.json +2 -2
package/dist/cli-bin.js CHANGED
@@ -2,14 +2,16 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli-bin.ts
5
- import process8 from "process";
6
-
5
+ import process12 from "process";
7
6
  // ../kit/src/args.ts
8
- import process2 from "process";
7
+ import process5 from "process";
9
8
  import { parseArgs } from "util";
10
9
 
10
+ // ../kit/src/format.ts
11
+ import process3 from "process";
12
+
11
13
  // ../kit/src/style.ts
12
- import process from "process";
14
+ import process2 from "process";
13
15
 
14
16
  // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
15
17
  function x(s) {
@@ -273,10 +275,10 @@ function le(i = {}) {
273
275
  var t = le();
274
276
 
275
277
  // ../kit/src/style.ts
276
- function takesControlCodes(stream, env = process.env) {
278
+ function takesControlCodes(stream, env = process2.env) {
277
279
  return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
278
280
  }
279
- function paint(stream, env = process.env) {
281
+ function paint(stream, env = process2.env) {
280
282
  return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
281
283
  }
282
284
  var STATUSES = {
@@ -293,11 +295,110 @@ function upTo(text, separator) {
293
295
  const end = text.indexOf(separator);
294
296
  return end === -1 ? text : text.slice(0, end);
295
297
  }
298
+ function truncate(text, width) {
299
+ return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
300
+ }
301
+ var LINE_BREAKS = /\s*\n\s*/g;
296
302
  var ESCAPE = String.fromCharCode(27);
303
+ function textWidth(text) {
304
+ return Bun.stringWidth(text);
305
+ }
306
+ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
307
+ const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
308
+ const widths = [];
309
+ for (const row of cells) {
310
+ for (const [index, cell] of row.entries())
311
+ widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
312
+ }
313
+ const shown = fit === undefined ? cells : narrow(cells, widths, fit);
314
+ const colors = paint(process3.stdout);
315
+ const lines = shown.map((row) => row.map((cell, index) => {
316
+ const width = widths[index] ?? 0;
317
+ const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
318
+ if (align[index] === "right")
319
+ return `${gap}${cell}`;
320
+ return index === row.length - 1 ? cell : `${cell}${gap}`;
321
+ }).join(" ").trimEnd());
322
+ return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
323
+ `);
324
+ }
325
+ function narrow(rows, widths, limit) {
326
+ const used = () => widths.reduce((sum, width) => sum + width, 0) + 2 * Math.max(0, widths.length - 1);
327
+ const cuttable = (index) => rows.every((row) => !(row[index] ?? "").includes(ESCAPE));
328
+ const floor = 8;
329
+ while (used() > limit) {
330
+ let widest = -1;
331
+ for (const [index, width] of widths.entries()) {
332
+ if (cuttable(index) && width > floor && width > (widths[widest] ?? 0))
333
+ widest = index;
334
+ }
335
+ if (widest === -1)
336
+ break;
337
+ widths[widest] = Math.max(floor, (widths[widest] ?? 0) - (used() - limit));
338
+ }
339
+ return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
340
+ }
297
341
  function printJson(value) {
298
342
  console.log(JSON.stringify(value, undefined, 2));
299
343
  }
300
344
 
345
+ // ../kit/src/help.ts
346
+ import process4 from "process";
347
+ var OPTIONAL = /\[[^\]]+\]/g;
348
+ var USAGE_PREFIX = /^usage:\s*/i;
349
+ var FULL_STOP = /\.$/;
350
+ function usageLine(usage, colors = paint(process4.stdout)) {
351
+ return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
352
+ }
353
+ function helpText(usage, options, colors = paint(process4.stdout), { examples = [], replaces, exits = [] } = {}) {
354
+ return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
355
+ `);
356
+ }
357
+ function exitRows(exits, colors = paint(process4.stdout)) {
358
+ if (exits.length === 0)
359
+ return [];
360
+ const width = Math.max(...exits.map(([code]) => String(code).length));
361
+ return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
362
+ }
363
+ function replacesRows(replaces, colors = paint(process4.stdout)) {
364
+ return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
365
+ }
366
+ function wantsHelpJson(env = process4.env) {
367
+ return env.KIT_HELP === "json";
368
+ }
369
+ function description(usage, { examples = [], replaces, exits } = {}, options) {
370
+ const lines = usage.replace(USAGE_PREFIX, "").split(`
371
+ `);
372
+ const first = lines[0];
373
+ const colon = first.indexOf(": ");
374
+ const prose = lines[lines.indexOf("") + 1] ?? "";
375
+ const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
376
+ return {
377
+ synopsis: colon === -1 ? first : first.slice(0, colon),
378
+ summary: colon === -1 ? fallback : first.slice(colon + 2),
379
+ ...replaces === undefined ? {} : { replaces },
380
+ examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] }),
381
+ ...exits === undefined ? {} : { exits: exits.map(([code, meaning]) => ({ code, meaning })) },
382
+ ...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
383
+ };
384
+ }
385
+ function exampleRows(examples, colors = paint(process4.stdout)) {
386
+ if (examples.length === 0)
387
+ return [];
388
+ return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
389
+ }
390
+ function optionRows(options, colors = paint(process4.stdout)) {
391
+ const rows = Object.entries(options).map(([name, option]) => {
392
+ const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
393
+ const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
394
+ const placeholder = option.optional === undefined ? named : `[${named}]`;
395
+ const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
396
+ return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
397
+ });
398
+ const width = Math.max(0, ...rows.map((row) => row.width));
399
+ return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
400
+ }
401
+
301
402
  // ../kit/src/text.ts
302
403
  var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
303
404
  function distance(a, b) {
@@ -317,8 +418,21 @@ function closest(name, names) {
317
418
  const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
318
419
  return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
319
420
  }
421
+ // ../kit/src/input.ts
422
+ var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
423
+ var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
424
+ function duration(value, name, fallback, unit = "s") {
425
+ if (value === undefined)
426
+ return fallback;
427
+ const match = DURATION.exec(value.trim());
428
+ const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
429
+ if (ms < 1)
430
+ badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
431
+ return ms;
432
+ }
320
433
 
321
434
  // ../kit/src/args.ts
435
+ var DASHES = /^-+/;
322
436
  function jsonOption(holds) {
323
437
  return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
324
438
  }
@@ -358,58 +472,11 @@ class Completing {
358
472
  }
359
473
  }
360
474
  var completion = { active: false };
361
- var OPTIONAL = /\[[^\]]+\]/g;
362
- var USAGE_PREFIX = /^usage:\s*/i;
363
- var FULL_STOP = /\.$/;
364
- var DASHES = /^-+/;
365
- function usageLine(usage, colors = paint(process2.stdout)) {
366
- return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
367
- }
368
- function helpText(usage, options, colors = paint(process2.stdout), { examples = [], replaces } = {}) {
369
- return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
370
- `);
371
- }
372
- function replacesRows(replaces, colors = paint(process2.stdout)) {
373
- return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
374
- }
375
- function wantsHelpJson(env = process2.env) {
376
- return env.KIT_HELP === "json";
377
- }
378
- function description(usage, { examples = [], replaces } = {}) {
379
- const lines = usage.replace(USAGE_PREFIX, "").split(`
380
- `);
381
- const first = lines[0];
382
- const colon = first.indexOf(": ");
383
- const prose = lines[lines.indexOf("") + 1] ?? "";
384
- const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
385
- return {
386
- synopsis: colon === -1 ? first : first.slice(0, colon),
387
- summary: colon === -1 ? fallback : first.slice(colon + 2),
388
- ...replaces === undefined ? {} : { replaces },
389
- examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
390
- };
391
- }
392
- function exampleRows(examples, colors = paint(process2.stdout)) {
393
- if (examples.length === 0)
394
- return [];
395
- return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
396
- }
397
- function optionRows(options, colors = paint(process2.stdout)) {
398
- const rows = Object.entries(options).map(([name, option]) => {
399
- const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
400
- const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
401
- const placeholder = option.optional === undefined ? named : `[${named}]`;
402
- const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
403
- return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
404
- });
405
- const width = Math.max(0, ...rows.map((row) => row.width));
406
- return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
407
- }
408
475
  var OUTPUT_OPTIONS = {
409
476
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
410
477
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
411
478
  };
412
- function applyOutput(color, plain, env = process2.env) {
479
+ function applyOutput(color, plain, env = process5.env) {
413
480
  const when = plain === true ? "never" : color ?? "auto";
414
481
  if (when === "auto")
415
482
  return;
@@ -432,7 +499,7 @@ function parse(args, usage, options, settings = {}) {
432
499
  const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
433
500
  let parsed;
434
501
  try {
435
- parsed = parseArgs({ args: joinDashValues(withBare(own, all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
502
+ parsed = parseArgs({ args: joinDashValues(withBare(withAliases(own, all), all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
436
503
  } catch (error) {
437
504
  const message = parseError(error);
438
505
  throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
@@ -440,7 +507,7 @@ function parse(args, usage, options, settings = {}) {
440
507
  const values = parsed.values;
441
508
  applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
442
509
  if (values.help === true) {
443
- console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, listing, undefined, settings));
510
+ console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings, listing)) : helpText(usage, listing, undefined, settings));
444
511
  throw new HelpShown;
445
512
  }
446
513
  checkChoices(parsed.values, all);
@@ -482,6 +549,18 @@ function listed(options, allowNegative) {
482
549
  return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
483
550
  }));
484
551
  }
552
+ function withAliases(args, options) {
553
+ const named = new Map(Object.entries(options).flatMap(([name, { aliases = [] }]) => aliases.map((alias) => [`--${alias}`, `--${name}`])));
554
+ if (named.size === 0)
555
+ return args;
556
+ const separator = args.indexOf("--");
557
+ const own = separator === -1 ? args : args.slice(0, separator);
558
+ return [...own.map((arg) => {
559
+ const at = arg.indexOf("=");
560
+ const current = named.get(at === -1 ? arg : arg.slice(0, at));
561
+ return current === undefined ? arg : `${current}${at === -1 ? "" : arg.slice(at)}`;
562
+ }), ...separator === -1 ? [] : args.slice(separator)];
563
+ }
485
564
  function withBare(args, options) {
486
565
  const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
487
566
  if (bare.size === 0)
@@ -568,7 +647,7 @@ function nearestOption(message, options, allowNegative) {
568
647
  }
569
648
  // ../kit/src/process.ts
570
649
  import { constants } from "os";
571
- import process3 from "process";
650
+ import process6 from "process";
572
651
  var KILL_GRACE_MS = 2000;
573
652
  function exitCodeOf(code, signal) {
574
653
  if (code !== null)
@@ -588,7 +667,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
588
667
  return;
589
668
  try {
590
669
  if (grouped)
591
- process3.kill(-proc.pid, killing);
670
+ process6.kill(-proc.pid, killing);
592
671
  else
593
672
  proc.kill(killing);
594
673
  } catch {}
@@ -662,7 +741,97 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
662
741
  var RESET_STYLE = `${ESC}[0m`;
663
742
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
664
743
  // ../kit/src/cli.ts
665
- import process4 from "process";
744
+ import { readFileSync, realpathSync } from "fs";
745
+ import { dirname as dirname2, join as join2 } from "path";
746
+ import process8 from "process";
747
+
748
+ // ../kit/src/usage.ts
749
+ import { appendFileSync, mkdirSync } from "fs";
750
+ import { homedir } from "os";
751
+ import { dirname, isAbsolute, join } from "path";
752
+ import process7 from "process";
753
+
754
+ // ../kit/src/logs.ts
755
+ var DAY_MS = 24 * 60 * 60 * 1000;
756
+ var CLI_FOLDER = "kaynooo-cli";
757
+
758
+ // ../kit/src/usage.ts
759
+ var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
760
+ var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
761
+ var LONGEST_NAME = 40;
762
+ var usageError;
763
+ function noteUsageError(message) {
764
+ usageError = message;
765
+ }
766
+ function stateHome(env = process7.env) {
767
+ const dir = env.XDG_STATE_HOME;
768
+ return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
769
+ }
770
+ function usagePath(env = process7.env) {
771
+ return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
772
+ }
773
+ function usageRecorded(env = process7.env) {
774
+ const off = env.KAYNOOO_CLI_NO_USAGE;
775
+ return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
776
+ }
777
+ function flagNames(argv) {
778
+ const end = argv.indexOf("--");
779
+ const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
780
+ return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
781
+ }
782
+ function usageEntry(bin, command, argv, code, now = new Date) {
783
+ const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
784
+ usageError = undefined;
785
+ return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
786
+ }
787
+ function recordUsage(entry, env = process7.env) {
788
+ if (!usageRecorded(env))
789
+ return;
790
+ try {
791
+ const path = usagePath(env);
792
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
793
+ appendFileSync(path, `${JSON.stringify(entry)}
794
+ `, { mode: 384 });
795
+ } catch {}
796
+ }
797
+ async function readUsage(path = usagePath(), since = 0) {
798
+ const file = Bun.file(path);
799
+ if (!await file.exists())
800
+ return [];
801
+ return (await file.text()).split(`
802
+ `).flatMap((line) => {
803
+ try {
804
+ const entry = JSON.parse(line);
805
+ return Date.parse(entry.at) >= since ? [entry] : [];
806
+ } catch {
807
+ return [];
808
+ }
809
+ });
810
+ }
811
+ function summarizeUsage(entries) {
812
+ const bins = new Map;
813
+ const errors = new Map;
814
+ for (const entry of entries) {
815
+ const bin = bins.get(entry.bin) ?? { runs: 0, failed: 0, usageErrors: 0 };
816
+ bin.runs++;
817
+ bin.failed += entry.code === 0 ? 0 : 1;
818
+ bin.usageErrors += entry.code === 2 ? 1 : 0;
819
+ bins.set(entry.bin, bin);
820
+ if (entry.code === 2 && entry.unknown !== undefined) {
821
+ const key = `${entry.bin} ${entry.command ?? ""} ${entry.unknown}`;
822
+ const error = errors.get(key) ?? { bin: entry.bin, ...entry.command === undefined ? {} : { command: entry.command }, unknown: entry.unknown, count: 0 };
823
+ error.count++;
824
+ errors.set(key, error);
825
+ }
826
+ }
827
+ return {
828
+ runs: entries.length,
829
+ bins: [...bins].map(([bin, counts]) => ({ bin, ...counts })).toSorted((a, b) => b.runs - a.runs),
830
+ usageErrors: [...errors.values()].toSorted((a, b) => b.count - a.count)
831
+ };
832
+ }
833
+
834
+ // ../kit/src/cli.ts
666
835
  var LEVEL_OPTIONS = {
667
836
  ...OUTPUT_OPTIONS,
668
837
  help: { type: "boolean", short: "h", description: "show this help" }
@@ -715,7 +884,7 @@ async function expand(scope, words) {
715
884
  }
716
885
  return { ...scope, commands, lazy: [] };
717
886
  }
718
- async function help({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
887
+ async function help2({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
719
888
  const colors = paint(stream);
720
889
  const prefix = path.join(" ");
721
890
  const summary = await resolveSummary(lazySummary);
@@ -742,8 +911,8 @@ async function help({ path, header, summary: lazySummary, commands, defaultComma
742
911
  `);
743
912
  }
744
913
  function printError(prefix, message, hint, cause) {
745
- const colors = paint(process4.stderr);
746
- const styled = takesControlCodes(process4.stderr);
914
+ const colors = paint(process8.stderr);
915
+ const styled = takesControlCodes(process8.stderr);
747
916
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
748
917
  const under = (label, text, tint) => text === undefined ? "" : `
749
918
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -762,6 +931,8 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
762
931
  const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
763
932
  const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
764
933
  const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
934
+ if (error instanceof UsageError)
935
+ noteUsageError(error.message);
765
936
  const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
766
937
  report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
767
938
  return error instanceof Exit ? error.code : 1;
@@ -784,11 +955,11 @@ async function dispatch(level, typed, { version, redact }) {
784
955
  }
785
956
  }
786
957
  if (name === undefined && level.path.length > 1) {
787
- console.error(await help(await expand(level, [...level.path.slice(1), ...argv]), process4.stderr));
958
+ console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stderr));
788
959
  return 2;
789
960
  }
790
961
  if (name === undefined || name === "help" || name === "--help" || name === "-h") {
791
- console.log(await help(await expand(level, [...level.path.slice(1), ...argv]), process4.stdout));
962
+ console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stdout));
792
963
  return 0;
793
964
  }
794
965
  if (level.path.length === 1 && (name === "--version" || name === "-v")) {
@@ -904,59 +1075,102 @@ async function printCandidates(found) {
904
1075
  console.log(line);
905
1076
  return 0;
906
1077
  }
907
- async function runCli(cli, argv = process4.argv.slice(2)) {
1078
+ function packageVersion(entry = Bun.main) {
1079
+ for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
1080
+ try {
1081
+ const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
1082
+ if (typeof version === "string")
1083
+ return version;
1084
+ } catch {}
1085
+ if (dirname2(dir) === dir)
1086
+ return "0.0.0";
1087
+ }
1088
+ }
1089
+ async function runCli(cli, argv = process8.argv.slice(2)) {
1090
+ const version = cli.version ?? packageVersion();
908
1091
  if (argv[0] === COMPLETE)
909
1092
  return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
910
- return dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${about}`, summary: cli.about, ...scopeOf(cli.commands, cli.extraCommands, cli.aliases), defaultCommand: cli.defaultCommand, runBare: cli.runBare === true, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
1093
+ const code = await dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(version)} ${about}`, summary: cli.about, ...scopeOf(cli.commands, cli.extraCommands, cli.aliases), defaultCommand: cli.defaultCommand, runBare: cli.runBare === true, examples: cli.examples, replaces: cli.replaces }, argv, { version, redact: cli.redact });
1094
+ if (cli.usage !== false) {
1095
+ try {
1096
+ recordUsage(usageEntry(cli.name, commandPath(scopeOf(cli.commands, cli.extraCommands, cli.aliases), argv), argv, code));
1097
+ } catch {}
1098
+ }
1099
+ return code;
1100
+ }
1101
+ function commandPath(scope, argv) {
1102
+ const path = [];
1103
+ let level = scope;
1104
+ for (const word of argv.filter((each) => !each.startsWith("-"))) {
1105
+ const name = level.aliases !== undefined && Object.hasOwn(level.aliases, word) ? level.aliases[word] ?? word : word;
1106
+ const command = Object.hasOwn(level.commands, name) ? level.commands[name] : undefined;
1107
+ if (command === undefined)
1108
+ break;
1109
+ path.push(word);
1110
+ if (!("commands" in command))
1111
+ break;
1112
+ level = scopeOf(command.commands, command.extraCommands, command.aliases);
1113
+ }
1114
+ return path.length === 0 ? undefined : path.join(" ");
911
1115
  }
912
- async function runMain({ name, version, run, redact }, argv = process4.argv.slice(2)) {
1116
+ async function runMain({ name, version, run, redact, usage }, argv = process8.argv.slice(2)) {
913
1117
  if (argv[0] === COMPLETE)
914
1118
  return printCandidates(leafCandidates(run, argv.slice(1)));
915
1119
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
916
- console.log(version);
1120
+ console.log(version ?? packageVersion());
917
1121
  return 0;
918
1122
  }
1123
+ let code;
919
1124
  try {
920
- return await run(argv);
1125
+ code = await run(argv);
921
1126
  } catch (error) {
922
- return reportThrown(name, error, redact, wantsJson(argv));
1127
+ code = reportThrown(name, error, redact, wantsJson(argv));
923
1128
  }
1129
+ if (usage !== false) {
1130
+ try {
1131
+ recordUsage(usageEntry(name, undefined, argv, code));
1132
+ } catch {}
1133
+ }
1134
+ return code;
924
1135
  }
925
- // ../kit/src/logs.ts
926
- var DAY_MS = 24 * 60 * 60 * 1000;
927
1136
  // ../kit/src/secrets.ts
928
1137
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
929
1138
  // ../kit/src/sessions.ts
930
1139
  var {Glob } = globalThis.Bun;
931
1140
  var SESSION_FILES = new Glob("*.json");
932
1141
  var ACCOUNT_DIRS = new Glob("*/sessions");
933
- // package.json
934
- var version = "0.3.0";
935
-
936
1142
  // src/completions.ts
937
- import { existsSync as existsSync2, mkdirSync, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
938
- import { join as join2 } from "path";
939
- import process6 from "process";
1143
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, rmSync } from "fs";
1144
+ import { join as join4 } from "path";
1145
+ import process10 from "process";
940
1146
 
941
1147
  // src/checks.ts
942
- import { existsSync, readFileSync } from "fs";
943
- import { basename, join } from "path";
1148
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
1149
+ import { basename, join as join3 } from "path";
944
1150
  var TOOLS = {
1151
+ "@kaynooo/cli-audit": ["audit"],
945
1152
  "@kaynooo/cli-base": ["base", "proof"],
946
1153
  "@kaynooo/cli-changelog": ["changelog"],
947
- "@kaynooo/cli-check": ["check", "fix", "errs"],
1154
+ "@kaynooo/cli-check": ["check", "fix", "errs", "j"],
948
1155
  "@kaynooo/cli-code": ["peek", "todo-scan", "hotspots"],
949
1156
  "@kaynooo/cli-db": ["db"],
1157
+ "@kaynooo/cli-docker": ["dc", "dlog", "dx", "sf"],
950
1158
  "@kaynooo/cli-doctor": ["doctor", "cli"],
951
1159
  "@kaynooo/cli-edit": ["sub"],
952
1160
  "@kaynooo/cli-env": ["ev", "redact"],
953
1161
  "@kaynooo/cli-forgejo": ["ft"],
954
- "@kaynooo/cli-git": ["gst", "gc"],
1162
+ "@kaynooo/cli-git": ["gst", "gc", "gb"],
955
1163
  "@kaynooo/cli-gitlab": ["gl"],
956
1164
  "@kaynooo/cli-guard": ["guard"],
957
1165
  "@kaynooo/cli-out": ["out"],
1166
+ "@kaynooo/cli-dev": ["dev"],
1167
+ "@kaynooo/cli-pick": ["pick"],
958
1168
  "@kaynooo/cli-pkg": ["pkg"],
1169
+ "@kaynooo/cli-probe": ["probe"],
1170
+ "@kaynooo/cli-proc": ["pid", "ports", "procs"],
1171
+ "@kaynooo/cli-repos": ["repos"],
959
1172
  "@kaynooo/cli-stack": ["stack"],
1173
+ "@kaynooo/cli-sweep": ["sweep"],
960
1174
  "@kaynooo/cli-view": ["view"],
961
1175
  "@kaynooo/cli-wait": ["waitfor", "slot"]
962
1176
  };
@@ -975,7 +1189,7 @@ function readJsonText(text) {
975
1189
  }
976
1190
  }
977
1191
  function readJson(path) {
978
- return existsSync(path) ? readJsonText(readFileSync(path, "utf8")) : undefined;
1192
+ return existsSync(path) ? readJsonText(readFileSync2(path, "utf8")) : undefined;
979
1193
  }
980
1194
  function firstLine(text) {
981
1195
  const trimmed = text.trim();
@@ -984,12 +1198,12 @@ function firstLine(text) {
984
1198
  return end === -1 ? trimmed : trimmed.slice(0, end);
985
1199
  }
986
1200
  function installed(host, name) {
987
- return readJson(join(host.globalModules, name, "package.json"));
1201
+ return readJson(join3(host.globalModules, name, "package.json"));
988
1202
  }
989
1203
  function ownBin(host, name, bin) {
990
1204
  const declared = installed(host, name)?.bin;
991
1205
  const file = typeof declared === "string" ? declared : declared?.[bin];
992
- return file === undefined ? undefined : join(host.globalModules, name, file);
1206
+ return file === undefined ? undefined : join3(host.globalModules, name, file);
993
1207
  }
994
1208
  async function checkTool(host, name, bins) {
995
1209
  const check = name.slice(name.indexOf("/") + 1);
@@ -998,7 +1212,7 @@ async function checkTool(host, name, bins) {
998
1212
  return { check, ok: false, detail: "not installed", fix: `bun install -g ${name}` };
999
1213
  const off = bins.filter((bin) => host.which(bin) === null);
1000
1214
  if (off.length > 0)
1001
- return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
1215
+ return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join3(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
1002
1216
  const info = await host.run(["bun", "info", name, "version", "--no-cache"]);
1003
1217
  const answered = info.code === 0 ? firstLine(info.stdout.trim().split(`
1004
1218
  `).at(-1) ?? "") : "";
@@ -1025,9 +1239,9 @@ async function checkTea(host) {
1025
1239
  return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
1026
1240
  }
1027
1241
  function checkHooks(host) {
1028
- const settings = readJson(join(host.home, ".claude", "settings.json")) ?? {};
1242
+ const settings = readJson(join3(host.home, ".claude", "settings.json")) ?? {};
1029
1243
  const plugin = settings.enabledPlugins?.[PLUGIN] === true;
1030
- const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync(join(dir, "hooks", "hooks.json")));
1244
+ const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync(join3(dir, "hooks", "hooks.json")));
1031
1245
  if (plugin && mod !== undefined)
1032
1246
  return { check: "hooks", ok: false, detail: `both the ${PLUGIN} plugin and the mod in ${mod} run guard, so every call is checked twice`, fix: `claude plugin disable ${PLUGIN}` };
1033
1247
  if (plugin)
@@ -1052,12 +1266,22 @@ function checkCompletions(host) {
1052
1266
  return { check: "completions", ok: false, detail: `no fish completion in ${dir} for ${missing.join(", ")}`, fix: "cli completions fish --install" };
1053
1267
  return { check: "completions", ok: true, detail: dir };
1054
1268
  }
1055
- async function runChecks(host) {
1056
- const entries = Object.entries(TOOLS);
1269
+ var toolCheck = (name) => name.slice(name.indexOf("/") + 1);
1270
+ var OTHER_CHECKS = ["tea", "hooks", "bun", "completions"];
1271
+ var CHECK_NAMES = [...Object.keys(TOOLS).map(toolCheck), ...OTHER_CHECKS];
1272
+ async function runChecks(host, only = []) {
1273
+ const wanted = (check) => only.length === 0 || only.includes(check);
1274
+ const entries = Object.entries(TOOLS).filter(([name]) => wanted(toolCheck(name)));
1057
1275
  const tools = [];
1058
1276
  for (let at = 0;at < entries.length; at += INFO_AT_ONCE)
1059
1277
  tools.push(...await Promise.all(entries.slice(at, at + INFO_AT_ONCE).map(async ([name, bins]) => checkTool(host, name, bins))));
1060
- return [...tools, await checkTea(host), checkHooks(host), checkBun(host), checkCompletions(host)];
1278
+ return [
1279
+ ...tools,
1280
+ ...wanted("tea") ? [await checkTea(host)] : [],
1281
+ ...wanted("hooks") ? [checkHooks(host)] : [],
1282
+ ...wanted("bun") ? [checkBun(host)] : [],
1283
+ ...wanted("completions") ? [checkCompletions(host)] : []
1284
+ ];
1061
1285
  }
1062
1286
 
1063
1287
  // src/completions.ts
@@ -1074,8 +1298,28 @@ var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_c
1074
1298
  end
1075
1299
  end
1076
1300
  end`;
1077
- function completionDir(home, env = process6.env) {
1078
- return join2(env.XDG_CACHE_HOME ?? join2(home, ".cache"), "fish", "kaynooo-cli");
1301
+ var BASH_FUNCTION = `__kaynooo_cli_complete() {
1302
+ local IFS=$'\\n' cur=\${COMP_WORDS[COMP_CWORD]} line words=
1303
+ local -a lines
1304
+ COMPREPLY=()
1305
+ lines=($("\${COMP_WORDS[0]}" __complete "\${COMP_WORDS[@]:1:COMP_CWORD}" 2>/dev/null))
1306
+ for line in "\${lines[@]}"; do
1307
+ if [[ $line == __files__ ]]; then
1308
+ COMPREPLY+=($(compgen -f -- "$cur"))
1309
+ else
1310
+ words+="\${line%%$'\\t'*}"$'\\n'
1311
+ fi
1312
+ done
1313
+ COMPREPLY+=($(compgen -W "$words" -- "$cur"))
1314
+ }`;
1315
+ function bashScript(bins) {
1316
+ return `# Generated by \`cli completions bash\`
1317
+ ${BASH_FUNCTION}
1318
+ complete -F __kaynooo_cli_complete ${bins.join(" ")}
1319
+ `;
1320
+ }
1321
+ function completionDir(home, env = process10.env) {
1322
+ return join4(env.XDG_CACHE_HOME ?? join4(home, ".cache"), "fish", "kaynooo-cli");
1079
1323
  }
1080
1324
  function installedBins(host) {
1081
1325
  return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
@@ -1088,31 +1332,31 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
1088
1332
  }
1089
1333
  async function installCompletions(host, dir) {
1090
1334
  const bins = installedBins(host);
1091
- mkdirSync(dir, { recursive: true });
1092
- const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync2(join2(dir, name), "utf8").startsWith(MARKER));
1335
+ mkdirSync2(dir, { recursive: true });
1336
+ const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync3(join4(dir, name), "utf8").startsWith(MARKER));
1093
1337
  for (const name of removed)
1094
- rmSync(join2(dir, name));
1095
- await Promise.all(bins.map(async (bin) => Bun.write(join2(dir, `${bin}.fish`), completionFile(bin))));
1338
+ rmSync(join4(dir, name));
1339
+ await Promise.all(bins.map(async (bin) => Bun.write(join4(dir, `${bin}.fish`), completionFile(bin))));
1096
1340
  return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
1097
1341
  }
1098
1342
  function missingCompletions(host, dir) {
1099
- return installedBins(host).filter((bin) => !existsSync2(join2(dir, `${bin}.fish`)));
1343
+ return installedBins(host).filter((bin) => !existsSync2(join4(dir, `${bin}.fish`)));
1100
1344
  }
1101
1345
 
1102
1346
  // src/host.ts
1103
- import { homedir } from "os";
1104
- import { join as join3 } from "path";
1105
- import process7 from "process";
1347
+ import { homedir as homedir2 } from "os";
1348
+ import { join as join5 } from "path";
1349
+ import process11 from "process";
1106
1350
  var RUN_TIMEOUT_MS = 20000;
1107
1351
  function hostHere() {
1108
- const home = homedir();
1352
+ const home = homedir2();
1109
1353
  return {
1110
1354
  home,
1111
- globalModules: join3(process7.env.BUN_INSTALL ?? join3(home, ".bun"), "install", "global", "node_modules"),
1355
+ globalModules: join5(process11.env.BUN_INSTALL ?? join5(home, ".bun"), "install", "global", "node_modules"),
1112
1356
  bunVersion: Bun.version,
1113
1357
  which: (bin) => Bun.which(bin),
1114
1358
  run: async (argv, env) => {
1115
- const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process7.env, ...env } } });
1359
+ const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process11.env, ...env } } });
1116
1360
  if (ran.timedOut)
1117
1361
  return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
1118
1362
  const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
@@ -1146,7 +1390,7 @@ var LIST_OPTIONS = {
1146
1390
  json: jsonOption("[{bin, package, installed, synopsis?, summary?, replaces?, examples?}]")
1147
1391
  };
1148
1392
  function print(listed) {
1149
- const { cyan, dim } = paint(process8.stdout);
1393
+ const { cyan, dim } = paint(process12.stdout);
1150
1394
  const width = Math.max(...listed.map((each) => each.bin.length));
1151
1395
  for (const each of listed) {
1152
1396
  const what = !each.installed ? dim(`not installed: bun install -g ${each.package}`) : `${each.summary ?? ""}${each.replaces === undefined ? "" : dim(`; instead of ${each.replaces}`)}`;
@@ -1162,18 +1406,28 @@ async function list(args, host = hostHere()) {
1162
1406
  print(listed);
1163
1407
  return 0;
1164
1408
  }
1165
- var COMPLETIONS_USAGE = `cli completions fish [--install] [--dir <folder>]: fish completions for every installed bin of the @kaynooo/cli-* packages
1409
+ var COMPLETIONS_USAGE = `cli completions <fish|bash> [--install] [--dir <folder>]: shell completions for every installed bin of the @kaynooo/cli-* packages
1166
1410
 
1167
- Each bin completes itself through \`<bin> __complete\`; this writes the one line fish needs per bin. Without --install the files print on stdout, with it they go to ~/.cache/fish/kaynooo-cli (or $XDG_CACHE_HOME), one <bin>.fish each, the stale ones this command wrote taken out. That folder has to be in fish_complete_path, first so a completion fish ships for the same name does not win: \`set -p fish_complete_path ~/.cache/fish/kaynooo-cli\`.
1168
- Exits 0 when it printed or wrote them, 2 on a usage error.`;
1411
+ Each bin completes itself through \`<bin> __complete\`; this writes the glue a shell needs per bin.
1412
+ fish: without --install the files print on stdout, with it they go to ~/.cache/fish/kaynooo-cli (or $XDG_CACHE_HOME), one <bin>.fish each, the stale ones this command wrote taken out. That folder has to be in fish_complete_path, first so a completion fish ships for the same name does not win: \`set -p fish_complete_path ~/.cache/fish/kaynooo-cli\`.
1413
+ bash: one script on stdout, sourced from ~/.bashrc with \`source <(cli completions bash)\`; --install and --dir are fish's.
1414
+ zsh: has no script of its own, it reads the bash one through bashcompinit: \`autoload -U +X compinit && compinit\`, \`autoload -U +X bashcompinit && bashcompinit\`, then the bash line above in ~/.zshrc (untested, zsh is not installed on the machine this was written on).`;
1169
1415
  var COMPLETIONS_OPTIONS = {
1170
1416
  install: { type: "boolean", description: "write the files to the folder instead of printing them" },
1171
1417
  dir: { type: "string", short: "d", description: "the folder to write to, instead of ~/.cache/fish/kaynooo-cli" }
1172
1418
  };
1173
1419
  async function completions(args, host = hostHere()) {
1174
- const { values, positionals } = parse(args, COMPLETIONS_USAGE, COMPLETIONS_OPTIONS, { positionals: ["<shell>"], candidates: { "<shell>": () => ["fish"] }, examples: ["cli completions fish", ["cli completions fish --install", "write one file per bin"]] });
1175
- if (positionals[0] !== "fish")
1176
- badUsage("only fish is supported: cli completions fish");
1420
+ const { values, positionals } = parse(args, COMPLETIONS_USAGE, COMPLETIONS_OPTIONS, { positionals: ["<shell>"], exits: [[0, "it printed or wrote them"], [2, "a usage error or a shell without a script"]], candidates: { "<shell>": () => ["fish", "bash"] }, examples: ["cli completions fish", ["cli completions fish --install", "write one file per bin"], ["source <(cli completions bash)", "in ~/.bashrc"]] });
1421
+ if (positionals[0] === "zsh")
1422
+ badUsage("zsh has no script of its own: it reads the bash one through bashcompinit, see cli completions --help");
1423
+ if (positionals[0] !== "fish" && positionals[0] !== "bash")
1424
+ badUsage("completions exist for fish and bash: cli completions fish");
1425
+ if (positionals[0] === "bash") {
1426
+ if (values.install === true || values.dir !== undefined)
1427
+ badUsage("--install and --dir are for fish: source the bash script instead");
1428
+ console.log(bashScript(installedBins(host)).trimEnd());
1429
+ return 0;
1430
+ }
1177
1431
  if (values.install !== true) {
1178
1432
  console.log(installedBins(host).map(completionFile).join(`
1179
1433
  `));
@@ -1184,20 +1438,50 @@ async function completions(args, host = hostHere()) {
1184
1438
  console.log(`wrote ${written.length} completions to ${dir}${removed.length === 0 ? "" : `, took out ${removed.join(", ")}`}`);
1185
1439
  return 0;
1186
1440
  }
1441
+ var STATS_USAGE = `cli stats [--since 7d] [--json]: how often each bin ran, and the usage errors by the option it did not know
1442
+
1443
+ Every bin records each run in ~/.local/state/kaynooo-cli/usage.jsonl (or $XDG_STATE_HOME): the bin, its command, the names of the flags it saw (never a value or an argument), the exit code and, on a usage error, the unknown option. Set KAYNOOO_CLI_NO_USAGE=1 to stop recording; \`guard\` never records.
1444
+ Exits 0 when it printed the summary, 2 on a usage error.`;
1445
+ var STATS_OPTIONS = {
1446
+ since: { type: "string", description: "only the runs this recent (7d, 12h)" },
1447
+ json: jsonOption("{runs, bins: [{bin, runs, failed, usageErrors}], usageErrors: [{bin, command?, unknown, count}]}")
1448
+ };
1449
+ async function stats(args, path = usagePath()) {
1450
+ const { values } = parse(args, STATS_USAGE, STATS_OPTIONS, { positionals: [], examples: ["cli stats", ["cli stats --since 7d", "the last week"]] });
1451
+ const window = values.since === undefined ? undefined : duration(values.since, "since", 0, "h");
1452
+ const summary = summarizeUsage(await readUsage(path, window === undefined ? 0 : Date.now() - window));
1453
+ if (values.json === true) {
1454
+ printJson(summary);
1455
+ return 0;
1456
+ }
1457
+ if (summary.runs === 0) {
1458
+ console.log("no run recorded");
1459
+ return 0;
1460
+ }
1461
+ console.log(`${summary.runs} runs
1462
+
1463
+ ${table(summary.bins.map((each) => [each.bin, String(each.runs), `${each.failed} failed`, `${each.usageErrors} usage errors`]))}`);
1464
+ if (summary.usageErrors.length > 0)
1465
+ console.log(`
1466
+ unknown options
1467
+ ${table(summary.usageErrors.map((each) => [`${each.bin}${each.command === undefined ? "" : ` ${each.command}`}`, each.unknown, `${each.count}x`]))}`);
1468
+ return 0;
1469
+ }
1187
1470
  if (import.meta.main) {
1188
- process8.exitCode = await runCli({
1471
+ process12.exitCode = await runCli({
1189
1472
  name: "cli",
1190
- version,
1191
1473
  about: "the bins of the @kaynooo/cli-* packages, described by their own help",
1192
1474
  replaces: "running each bin's --help to find the one that fits",
1193
1475
  examples: ["cli list", ["cli list --json", "with each bin's synopsis and examples"]],
1194
1476
  commands: {
1195
1477
  list: { summary: "every bin in one line: what it does and what it is reached for instead of", run: async (args) => list(args) },
1478
+ stats: { summary: "how often each bin ran and the options usage errors did not know", run: async (args) => stats(args) },
1196
1479
  completions: { summary: "fish completions for every installed bin, printed or written to the folder fish reads", run: async (args) => completions(args) }
1197
1480
  }
1198
1481
  });
1199
1482
  }
1200
1483
  export {
1201
1484
  completions,
1202
- list
1485
+ list,
1486
+ stats
1203
1487
  };