@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.
- package/README.md +27 -1
- package/dist/cli-bin.js +398 -114
- package/dist/cli.js +431 -118
- 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
|
|
6
|
-
|
|
5
|
+
import process12 from "process";
|
|
7
6
|
// ../kit/src/args.ts
|
|
8
|
-
import
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
746
|
-
const styled = takesControlCodes(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1125
|
+
code = await run(argv);
|
|
921
1126
|
} catch (error) {
|
|
922
|
-
|
|
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
|
|
938
|
-
import { join as
|
|
939
|
-
import
|
|
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(
|
|
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(
|
|
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 :
|
|
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 ${
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1056
|
-
|
|
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 [
|
|
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
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1092
|
-
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) &&
|
|
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(
|
|
1095
|
-
await Promise.all(bins.map(async (bin) => Bun.write(
|
|
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(
|
|
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
|
|
1105
|
-
import
|
|
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 =
|
|
1352
|
+
const home = homedir2();
|
|
1109
1353
|
return {
|
|
1110
1354
|
home,
|
|
1111
|
-
globalModules:
|
|
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: { ...
|
|
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(
|
|
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>]:
|
|
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
|
|
1168
|
-
|
|
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]
|
|
1176
|
-
badUsage("
|
|
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
|
-
|
|
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
|
};
|