@kaynooo/cli-doctor 0.4.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 +19 -1
- package/dist/cli-bin.js +152 -77
- package/dist/cli.js +217 -82
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# doctor
|
|
2
2
|
|
|
3
|
-
`doctor` checks a host's @kaynooo/cli-* install in one call: one line per check, the command that fixes a failing one beside it.
|
|
3
|
+
`doctor` checks a host's @kaynooo/cli-* install in one call: one line per check, the command that fixes a failing one beside it. The package also ships a second bin, `cli` (`cli stats`, `cli list`, below), which is not `doctor`.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
bun install -g @kaynooo/cli-doctor
|
|
7
7
|
doctor # one line per check
|
|
8
8
|
doctor --json # [{check, ok, detail, fix?}]
|
|
9
|
+
doctor -q # only the failing checks, nothing when all pass
|
|
10
|
+
doctor --only tea --only bun # just these checks (a package such as cli-forgejo, tea, hooks, bun, completions)
|
|
11
|
+
doctor --fix --dry-run # what --fix would run
|
|
12
|
+
doctor --fix # run the fixes, then check again
|
|
9
13
|
```
|
|
10
14
|
|
|
11
15
|
| Check | Passes when | Fix |
|
|
@@ -17,6 +21,20 @@ doctor --json # [{check, ok, detail, fix?}]
|
|
|
17
21
|
|
|
18
22
|
A registry that does not answer leaves a package passing, with a note. Exit codes: 0 every check passed, 1 one failed, 2 a usage error.
|
|
19
23
|
|
|
24
|
+
## --fix
|
|
25
|
+
|
|
26
|
+
`--fix` runs the `fix:` lines it may: the `bun install -g` ones (joined into one call), `cli completions fish --install`, and `claude plugin install` or `disable`. It then checks again, prints the checks as they stand and exits on those. Nothing that installs a system package is run, and a line that only describes a step (`tea` from its releases page, adding a folder to the PATH, `bun upgrade`, which replaces Bun itself) is printed as `not run`. `--dry-run` prints each line as `would` and runs nothing (alone it implies `--fix`).
|
|
27
|
+
`--json` keeps its shape and prints the checks after the fixes, the fixes going to stderr; `--quiet` filters it to the failing ones.
|
|
28
|
+
|
|
29
|
+
## cli completions
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
cli completions fish --install # one file per installed bin in ~/.cache/fish/kaynooo-cli
|
|
33
|
+
source <(cli completions bash) # in ~/.bashrc
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Each bin completes itself through `<bin> __complete`. fish gets a file per bin (put the folder first in `fish_complete_path`), bash one script sourced from `~/.bashrc`. zsh has no script of its own: load `compinit` and `bashcompinit` in `~/.zshrc`, then the bash line above (not tested, zsh is not installed on the machine this was written on). The fish files are checked by `doctor`.
|
|
37
|
+
|
|
20
38
|
## cli stats
|
|
21
39
|
|
|
22
40
|
```sh
|
package/dist/cli-bin.js
CHANGED
|
@@ -2,17 +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
|
|
|
11
10
|
// ../kit/src/format.ts
|
|
12
|
-
import
|
|
11
|
+
import process3 from "process";
|
|
13
12
|
|
|
14
13
|
// ../kit/src/style.ts
|
|
15
|
-
import
|
|
14
|
+
import process2 from "process";
|
|
16
15
|
|
|
17
16
|
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
18
17
|
function x(s) {
|
|
@@ -276,10 +275,10 @@ function le(i = {}) {
|
|
|
276
275
|
var t = le();
|
|
277
276
|
|
|
278
277
|
// ../kit/src/style.ts
|
|
279
|
-
function takesControlCodes(stream, env =
|
|
278
|
+
function takesControlCodes(stream, env = process2.env) {
|
|
280
279
|
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
281
280
|
}
|
|
282
|
-
function paint(stream, env =
|
|
281
|
+
function paint(stream, env = process2.env) {
|
|
283
282
|
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
284
283
|
}
|
|
285
284
|
var STATUSES = {
|
|
@@ -312,7 +311,7 @@ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
|
312
311
|
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
313
312
|
}
|
|
314
313
|
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
315
|
-
const colors = paint(
|
|
314
|
+
const colors = paint(process3.stdout);
|
|
316
315
|
const lines = shown.map((row) => row.map((cell, index) => {
|
|
317
316
|
const width = widths[index] ?? 0;
|
|
318
317
|
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
@@ -344,24 +343,30 @@ function printJson(value) {
|
|
|
344
343
|
}
|
|
345
344
|
|
|
346
345
|
// ../kit/src/help.ts
|
|
347
|
-
import
|
|
346
|
+
import process4 from "process";
|
|
348
347
|
var OPTIONAL = /\[[^\]]+\]/g;
|
|
349
348
|
var USAGE_PREFIX = /^usage:\s*/i;
|
|
350
349
|
var FULL_STOP = /\.$/;
|
|
351
|
-
function usageLine(usage, colors = paint(
|
|
350
|
+
function usageLine(usage, colors = paint(process4.stdout)) {
|
|
352
351
|
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
353
352
|
}
|
|
354
|
-
function helpText(usage, options, colors = paint(
|
|
355
|
-
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
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(`
|
|
356
355
|
`);
|
|
357
356
|
}
|
|
358
|
-
function
|
|
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)) {
|
|
359
364
|
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
360
365
|
}
|
|
361
|
-
function wantsHelpJson(env =
|
|
366
|
+
function wantsHelpJson(env = process4.env) {
|
|
362
367
|
return env.KIT_HELP === "json";
|
|
363
368
|
}
|
|
364
|
-
function description(usage, { examples = [], replaces } = {}) {
|
|
369
|
+
function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
365
370
|
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
366
371
|
`);
|
|
367
372
|
const first = lines[0];
|
|
@@ -372,15 +377,17 @@ function description(usage, { examples = [], replaces } = {}) {
|
|
|
372
377
|
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
373
378
|
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
374
379
|
...replaces === undefined ? {} : { replaces },
|
|
375
|
-
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
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 } })) }
|
|
376
383
|
};
|
|
377
384
|
}
|
|
378
|
-
function exampleRows(examples, colors = paint(
|
|
385
|
+
function exampleRows(examples, colors = paint(process4.stdout)) {
|
|
379
386
|
if (examples.length === 0)
|
|
380
387
|
return [];
|
|
381
388
|
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
382
389
|
}
|
|
383
|
-
function optionRows(options, colors = paint(
|
|
390
|
+
function optionRows(options, colors = paint(process4.stdout)) {
|
|
384
391
|
const rows = Object.entries(options).map(([name, option]) => {
|
|
385
392
|
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
386
393
|
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
@@ -469,7 +476,7 @@ var OUTPUT_OPTIONS = {
|
|
|
469
476
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
470
477
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
471
478
|
};
|
|
472
|
-
function applyOutput(color, plain, env =
|
|
479
|
+
function applyOutput(color, plain, env = process5.env) {
|
|
473
480
|
const when = plain === true ? "never" : color ?? "auto";
|
|
474
481
|
if (when === "auto")
|
|
475
482
|
return;
|
|
@@ -492,7 +499,7 @@ function parse(args, usage, options, settings = {}) {
|
|
|
492
499
|
const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
|
|
493
500
|
let parsed;
|
|
494
501
|
try {
|
|
495
|
-
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 });
|
|
496
503
|
} catch (error) {
|
|
497
504
|
const message = parseError(error);
|
|
498
505
|
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
@@ -500,7 +507,7 @@ function parse(args, usage, options, settings = {}) {
|
|
|
500
507
|
const values = parsed.values;
|
|
501
508
|
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
502
509
|
if (values.help === true) {
|
|
503
|
-
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));
|
|
504
511
|
throw new HelpShown;
|
|
505
512
|
}
|
|
506
513
|
checkChoices(parsed.values, all);
|
|
@@ -542,6 +549,18 @@ function listed(options, allowNegative) {
|
|
|
542
549
|
return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
|
|
543
550
|
}));
|
|
544
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
|
+
}
|
|
545
564
|
function withBare(args, options) {
|
|
546
565
|
const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
|
|
547
566
|
if (bare.size === 0)
|
|
@@ -628,7 +647,7 @@ function nearestOption(message, options, allowNegative) {
|
|
|
628
647
|
}
|
|
629
648
|
// ../kit/src/process.ts
|
|
630
649
|
import { constants } from "os";
|
|
631
|
-
import
|
|
650
|
+
import process6 from "process";
|
|
632
651
|
var KILL_GRACE_MS = 2000;
|
|
633
652
|
function exitCodeOf(code, signal) {
|
|
634
653
|
if (code !== null)
|
|
@@ -648,7 +667,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
|
|
|
648
667
|
return;
|
|
649
668
|
try {
|
|
650
669
|
if (grouped)
|
|
651
|
-
|
|
670
|
+
process6.kill(-proc.pid, killing);
|
|
652
671
|
else
|
|
653
672
|
proc.kill(killing);
|
|
654
673
|
} catch {}
|
|
@@ -722,13 +741,15 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
|
|
|
722
741
|
var RESET_STYLE = `${ESC}[0m`;
|
|
723
742
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
724
743
|
// ../kit/src/cli.ts
|
|
725
|
-
import
|
|
744
|
+
import { readFileSync, realpathSync } from "fs";
|
|
745
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
746
|
+
import process8 from "process";
|
|
726
747
|
|
|
727
748
|
// ../kit/src/usage.ts
|
|
728
749
|
import { appendFileSync, mkdirSync } from "fs";
|
|
729
750
|
import { homedir } from "os";
|
|
730
751
|
import { dirname, isAbsolute, join } from "path";
|
|
731
|
-
import
|
|
752
|
+
import process7 from "process";
|
|
732
753
|
|
|
733
754
|
// ../kit/src/logs.ts
|
|
734
755
|
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -742,14 +763,14 @@ var usageError;
|
|
|
742
763
|
function noteUsageError(message) {
|
|
743
764
|
usageError = message;
|
|
744
765
|
}
|
|
745
|
-
function stateHome(env =
|
|
766
|
+
function stateHome(env = process7.env) {
|
|
746
767
|
const dir = env.XDG_STATE_HOME;
|
|
747
768
|
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
748
769
|
}
|
|
749
|
-
function usagePath(env =
|
|
770
|
+
function usagePath(env = process7.env) {
|
|
750
771
|
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
751
772
|
}
|
|
752
|
-
function usageRecorded(env =
|
|
773
|
+
function usageRecorded(env = process7.env) {
|
|
753
774
|
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
754
775
|
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
755
776
|
}
|
|
@@ -763,7 +784,7 @@ function usageEntry(bin, command, argv, code, now = new Date) {
|
|
|
763
784
|
usageError = undefined;
|
|
764
785
|
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
765
786
|
}
|
|
766
|
-
function recordUsage(entry, env =
|
|
787
|
+
function recordUsage(entry, env = process7.env) {
|
|
767
788
|
if (!usageRecorded(env))
|
|
768
789
|
return;
|
|
769
790
|
try {
|
|
@@ -890,8 +911,8 @@ async function help2({ path, header, summary: lazySummary, commands, defaultComm
|
|
|
890
911
|
`);
|
|
891
912
|
}
|
|
892
913
|
function printError(prefix, message, hint, cause) {
|
|
893
|
-
const colors = paint(
|
|
894
|
-
const styled = takesControlCodes(
|
|
914
|
+
const colors = paint(process8.stderr);
|
|
915
|
+
const styled = takesControlCodes(process8.stderr);
|
|
895
916
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
896
917
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
897
918
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -934,11 +955,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
934
955
|
}
|
|
935
956
|
}
|
|
936
957
|
if (name === undefined && level.path.length > 1) {
|
|
937
|
-
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]),
|
|
958
|
+
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stderr));
|
|
938
959
|
return 2;
|
|
939
960
|
}
|
|
940
961
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
941
|
-
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]),
|
|
962
|
+
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stdout));
|
|
942
963
|
return 0;
|
|
943
964
|
}
|
|
944
965
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -1054,10 +1075,22 @@ async function printCandidates(found) {
|
|
|
1054
1075
|
console.log(line);
|
|
1055
1076
|
return 0;
|
|
1056
1077
|
}
|
|
1057
|
-
|
|
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();
|
|
1058
1091
|
if (argv[0] === COMPLETE)
|
|
1059
1092
|
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
1060
|
-
const code = await dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(
|
|
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 });
|
|
1061
1094
|
if (cli.usage !== false) {
|
|
1062
1095
|
try {
|
|
1063
1096
|
recordUsage(usageEntry(cli.name, commandPath(scopeOf(cli.commands, cli.extraCommands, cli.aliases), argv), argv, code));
|
|
@@ -1080,11 +1113,11 @@ function commandPath(scope, argv) {
|
|
|
1080
1113
|
}
|
|
1081
1114
|
return path.length === 0 ? undefined : path.join(" ");
|
|
1082
1115
|
}
|
|
1083
|
-
async function runMain({ name, version, run, redact, usage }, argv =
|
|
1116
|
+
async function runMain({ name, version, run, redact, usage }, argv = process8.argv.slice(2)) {
|
|
1084
1117
|
if (argv[0] === COMPLETE)
|
|
1085
1118
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
1086
1119
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
1087
|
-
console.log(version);
|
|
1120
|
+
console.log(version ?? packageVersion());
|
|
1088
1121
|
return 0;
|
|
1089
1122
|
}
|
|
1090
1123
|
let code;
|
|
@@ -1093,8 +1126,11 @@ async function runMain({ name, version, run, redact, usage }, argv = process7.ar
|
|
|
1093
1126
|
} catch (error) {
|
|
1094
1127
|
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
1095
1128
|
}
|
|
1096
|
-
if (usage !== false)
|
|
1097
|
-
|
|
1129
|
+
if (usage !== false) {
|
|
1130
|
+
try {
|
|
1131
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
1132
|
+
} catch {}
|
|
1133
|
+
}
|
|
1098
1134
|
return code;
|
|
1099
1135
|
}
|
|
1100
1136
|
// ../kit/src/secrets.ts
|
|
@@ -1103,28 +1139,27 @@ var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "ses
|
|
|
1103
1139
|
var {Glob } = globalThis.Bun;
|
|
1104
1140
|
var SESSION_FILES = new Glob("*.json");
|
|
1105
1141
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
1106
|
-
// package.json
|
|
1107
|
-
var version = "0.4.0";
|
|
1108
|
-
|
|
1109
1142
|
// src/completions.ts
|
|
1110
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as
|
|
1111
|
-
import { join as
|
|
1112
|
-
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";
|
|
1113
1146
|
|
|
1114
1147
|
// src/checks.ts
|
|
1115
|
-
import { existsSync, readFileSync } from "fs";
|
|
1116
|
-
import { basename, join as
|
|
1148
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1149
|
+
import { basename, join as join3 } from "path";
|
|
1117
1150
|
var TOOLS = {
|
|
1151
|
+
"@kaynooo/cli-audit": ["audit"],
|
|
1118
1152
|
"@kaynooo/cli-base": ["base", "proof"],
|
|
1119
1153
|
"@kaynooo/cli-changelog": ["changelog"],
|
|
1120
|
-
"@kaynooo/cli-check": ["check", "fix", "errs"],
|
|
1154
|
+
"@kaynooo/cli-check": ["check", "fix", "errs", "j"],
|
|
1121
1155
|
"@kaynooo/cli-code": ["peek", "todo-scan", "hotspots"],
|
|
1122
1156
|
"@kaynooo/cli-db": ["db"],
|
|
1157
|
+
"@kaynooo/cli-docker": ["dc", "dlog", "dx", "sf"],
|
|
1123
1158
|
"@kaynooo/cli-doctor": ["doctor", "cli"],
|
|
1124
1159
|
"@kaynooo/cli-edit": ["sub"],
|
|
1125
1160
|
"@kaynooo/cli-env": ["ev", "redact"],
|
|
1126
1161
|
"@kaynooo/cli-forgejo": ["ft"],
|
|
1127
|
-
"@kaynooo/cli-git": ["gst", "gc"],
|
|
1162
|
+
"@kaynooo/cli-git": ["gst", "gc", "gb"],
|
|
1128
1163
|
"@kaynooo/cli-gitlab": ["gl"],
|
|
1129
1164
|
"@kaynooo/cli-guard": ["guard"],
|
|
1130
1165
|
"@kaynooo/cli-out": ["out"],
|
|
@@ -1132,6 +1167,7 @@ var TOOLS = {
|
|
|
1132
1167
|
"@kaynooo/cli-pick": ["pick"],
|
|
1133
1168
|
"@kaynooo/cli-pkg": ["pkg"],
|
|
1134
1169
|
"@kaynooo/cli-probe": ["probe"],
|
|
1170
|
+
"@kaynooo/cli-proc": ["pid", "ports", "procs"],
|
|
1135
1171
|
"@kaynooo/cli-repos": ["repos"],
|
|
1136
1172
|
"@kaynooo/cli-stack": ["stack"],
|
|
1137
1173
|
"@kaynooo/cli-sweep": ["sweep"],
|
|
@@ -1153,7 +1189,7 @@ function readJsonText(text) {
|
|
|
1153
1189
|
}
|
|
1154
1190
|
}
|
|
1155
1191
|
function readJson(path) {
|
|
1156
|
-
return existsSync(path) ? readJsonText(
|
|
1192
|
+
return existsSync(path) ? readJsonText(readFileSync2(path, "utf8")) : undefined;
|
|
1157
1193
|
}
|
|
1158
1194
|
function firstLine(text) {
|
|
1159
1195
|
const trimmed = text.trim();
|
|
@@ -1162,12 +1198,12 @@ function firstLine(text) {
|
|
|
1162
1198
|
return end === -1 ? trimmed : trimmed.slice(0, end);
|
|
1163
1199
|
}
|
|
1164
1200
|
function installed(host, name) {
|
|
1165
|
-
return readJson(
|
|
1201
|
+
return readJson(join3(host.globalModules, name, "package.json"));
|
|
1166
1202
|
}
|
|
1167
1203
|
function ownBin(host, name, bin) {
|
|
1168
1204
|
const declared = installed(host, name)?.bin;
|
|
1169
1205
|
const file = typeof declared === "string" ? declared : declared?.[bin];
|
|
1170
|
-
return file === undefined ? undefined :
|
|
1206
|
+
return file === undefined ? undefined : join3(host.globalModules, name, file);
|
|
1171
1207
|
}
|
|
1172
1208
|
async function checkTool(host, name, bins) {
|
|
1173
1209
|
const check = name.slice(name.indexOf("/") + 1);
|
|
@@ -1176,7 +1212,7 @@ async function checkTool(host, name, bins) {
|
|
|
1176
1212
|
return { check, ok: false, detail: "not installed", fix: `bun install -g ${name}` };
|
|
1177
1213
|
const off = bins.filter((bin) => host.which(bin) === null);
|
|
1178
1214
|
if (off.length > 0)
|
|
1179
|
-
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` };
|
|
1180
1216
|
const info = await host.run(["bun", "info", name, "version", "--no-cache"]);
|
|
1181
1217
|
const answered = info.code === 0 ? firstLine(info.stdout.trim().split(`
|
|
1182
1218
|
`).at(-1) ?? "") : "";
|
|
@@ -1203,9 +1239,9 @@ async function checkTea(host) {
|
|
|
1203
1239
|
return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
|
|
1204
1240
|
}
|
|
1205
1241
|
function checkHooks(host) {
|
|
1206
|
-
const settings = readJson(
|
|
1242
|
+
const settings = readJson(join3(host.home, ".claude", "settings.json")) ?? {};
|
|
1207
1243
|
const plugin = settings.enabledPlugins?.[PLUGIN] === true;
|
|
1208
|
-
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")));
|
|
1209
1245
|
if (plugin && mod !== undefined)
|
|
1210
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}` };
|
|
1211
1247
|
if (plugin)
|
|
@@ -1230,12 +1266,22 @@ function checkCompletions(host) {
|
|
|
1230
1266
|
return { check: "completions", ok: false, detail: `no fish completion in ${dir} for ${missing.join(", ")}`, fix: "cli completions fish --install" };
|
|
1231
1267
|
return { check: "completions", ok: true, detail: dir };
|
|
1232
1268
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
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)));
|
|
1235
1275
|
const tools = [];
|
|
1236
1276
|
for (let at = 0;at < entries.length; at += INFO_AT_ONCE)
|
|
1237
1277
|
tools.push(...await Promise.all(entries.slice(at, at + INFO_AT_ONCE).map(async ([name, bins]) => checkTool(host, name, bins))));
|
|
1238
|
-
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
|
+
];
|
|
1239
1285
|
}
|
|
1240
1286
|
|
|
1241
1287
|
// src/completions.ts
|
|
@@ -1252,8 +1298,28 @@ var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_c
|
|
|
1252
1298
|
end
|
|
1253
1299
|
end
|
|
1254
1300
|
end`;
|
|
1255
|
-
|
|
1256
|
-
|
|
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");
|
|
1257
1323
|
}
|
|
1258
1324
|
function installedBins(host) {
|
|
1259
1325
|
return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
|
|
@@ -1267,30 +1333,30 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
|
|
|
1267
1333
|
async function installCompletions(host, dir) {
|
|
1268
1334
|
const bins = installedBins(host);
|
|
1269
1335
|
mkdirSync2(dir, { recursive: true });
|
|
1270
|
-
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) &&
|
|
1336
|
+
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync3(join4(dir, name), "utf8").startsWith(MARKER));
|
|
1271
1337
|
for (const name of removed)
|
|
1272
|
-
rmSync(
|
|
1273
|
-
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))));
|
|
1274
1340
|
return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
|
|
1275
1341
|
}
|
|
1276
1342
|
function missingCompletions(host, dir) {
|
|
1277
|
-
return installedBins(host).filter((bin) => !existsSync2(
|
|
1343
|
+
return installedBins(host).filter((bin) => !existsSync2(join4(dir, `${bin}.fish`)));
|
|
1278
1344
|
}
|
|
1279
1345
|
|
|
1280
1346
|
// src/host.ts
|
|
1281
1347
|
import { homedir as homedir2 } from "os";
|
|
1282
|
-
import { join as
|
|
1283
|
-
import
|
|
1348
|
+
import { join as join5 } from "path";
|
|
1349
|
+
import process11 from "process";
|
|
1284
1350
|
var RUN_TIMEOUT_MS = 20000;
|
|
1285
1351
|
function hostHere() {
|
|
1286
1352
|
const home = homedir2();
|
|
1287
1353
|
return {
|
|
1288
1354
|
home,
|
|
1289
|
-
globalModules:
|
|
1355
|
+
globalModules: join5(process11.env.BUN_INSTALL ?? join5(home, ".bun"), "install", "global", "node_modules"),
|
|
1290
1356
|
bunVersion: Bun.version,
|
|
1291
1357
|
which: (bin) => Bun.which(bin),
|
|
1292
1358
|
run: async (argv, env) => {
|
|
1293
|
-
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 } } });
|
|
1294
1360
|
if (ran.timedOut)
|
|
1295
1361
|
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
1296
1362
|
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
@@ -1324,7 +1390,7 @@ var LIST_OPTIONS = {
|
|
|
1324
1390
|
json: jsonOption("[{bin, package, installed, synopsis?, summary?, replaces?, examples?}]")
|
|
1325
1391
|
};
|
|
1326
1392
|
function print(listed) {
|
|
1327
|
-
const { cyan, dim } = paint(
|
|
1393
|
+
const { cyan, dim } = paint(process12.stdout);
|
|
1328
1394
|
const width = Math.max(...listed.map((each) => each.bin.length));
|
|
1329
1395
|
for (const each of listed) {
|
|
1330
1396
|
const what = !each.installed ? dim(`not installed: bun install -g ${each.package}`) : `${each.summary ?? ""}${each.replaces === undefined ? "" : dim(`; instead of ${each.replaces}`)}`;
|
|
@@ -1340,18 +1406,28 @@ async function list(args, host = hostHere()) {
|
|
|
1340
1406
|
print(listed);
|
|
1341
1407
|
return 0;
|
|
1342
1408
|
}
|
|
1343
|
-
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
|
|
1344
1410
|
|
|
1345
|
-
Each bin completes itself through \`<bin> __complete\`; this writes the
|
|
1346
|
-
|
|
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).`;
|
|
1347
1415
|
var COMPLETIONS_OPTIONS = {
|
|
1348
1416
|
install: { type: "boolean", description: "write the files to the folder instead of printing them" },
|
|
1349
1417
|
dir: { type: "string", short: "d", description: "the folder to write to, instead of ~/.cache/fish/kaynooo-cli" }
|
|
1350
1418
|
};
|
|
1351
1419
|
async function completions(args, host = hostHere()) {
|
|
1352
|
-
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"]] });
|
|
1353
|
-
if (positionals[0]
|
|
1354
|
-
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
|
+
}
|
|
1355
1431
|
if (values.install !== true) {
|
|
1356
1432
|
console.log(installedBins(host).map(completionFile).join(`
|
|
1357
1433
|
`));
|
|
@@ -1392,9 +1468,8 @@ ${table(summary.usageErrors.map((each) => [`${each.bin}${each.command === undefi
|
|
|
1392
1468
|
return 0;
|
|
1393
1469
|
}
|
|
1394
1470
|
if (import.meta.main) {
|
|
1395
|
-
|
|
1471
|
+
process12.exitCode = await runCli({
|
|
1396
1472
|
name: "cli",
|
|
1397
|
-
version,
|
|
1398
1473
|
about: "the bins of the @kaynooo/cli-* packages, described by their own help",
|
|
1399
1474
|
replaces: "running each bin's --help to find the one that fits",
|
|
1400
1475
|
examples: ["cli list", ["cli list --json", "with each bin's synopsis and examples"]],
|
package/dist/cli.js
CHANGED
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli.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
|
|
|
11
10
|
// ../kit/src/format.ts
|
|
12
|
-
import
|
|
11
|
+
import process3 from "process";
|
|
13
12
|
|
|
14
13
|
// ../kit/src/style.ts
|
|
15
|
-
import
|
|
14
|
+
import process2 from "process";
|
|
16
15
|
|
|
17
16
|
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
18
17
|
function x(s) {
|
|
@@ -276,10 +275,10 @@ function le(i = {}) {
|
|
|
276
275
|
var t = le();
|
|
277
276
|
|
|
278
277
|
// ../kit/src/style.ts
|
|
279
|
-
function takesControlCodes(stream, env =
|
|
278
|
+
function takesControlCodes(stream, env = process2.env) {
|
|
280
279
|
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
281
280
|
}
|
|
282
|
-
function paint(stream, env =
|
|
281
|
+
function paint(stream, env = process2.env) {
|
|
283
282
|
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
284
283
|
}
|
|
285
284
|
var STATUSES = {
|
|
@@ -312,7 +311,7 @@ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
|
312
311
|
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
313
312
|
}
|
|
314
313
|
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
315
|
-
const colors = paint(
|
|
314
|
+
const colors = paint(process3.stdout);
|
|
316
315
|
const lines = shown.map((row) => row.map((cell, index) => {
|
|
317
316
|
const width = widths[index] ?? 0;
|
|
318
317
|
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
@@ -344,24 +343,30 @@ function printJson(value) {
|
|
|
344
343
|
}
|
|
345
344
|
|
|
346
345
|
// ../kit/src/help.ts
|
|
347
|
-
import
|
|
346
|
+
import process4 from "process";
|
|
348
347
|
var OPTIONAL = /\[[^\]]+\]/g;
|
|
349
348
|
var USAGE_PREFIX = /^usage:\s*/i;
|
|
350
349
|
var FULL_STOP = /\.$/;
|
|
351
|
-
function usageLine(usage, colors = paint(
|
|
350
|
+
function usageLine(usage, colors = paint(process4.stdout)) {
|
|
352
351
|
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
353
352
|
}
|
|
354
|
-
function helpText(usage, options, colors = paint(
|
|
355
|
-
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
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(`
|
|
356
355
|
`);
|
|
357
356
|
}
|
|
358
|
-
function
|
|
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)) {
|
|
359
364
|
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
360
365
|
}
|
|
361
|
-
function wantsHelpJson(env =
|
|
366
|
+
function wantsHelpJson(env = process4.env) {
|
|
362
367
|
return env.KIT_HELP === "json";
|
|
363
368
|
}
|
|
364
|
-
function description(usage, { examples = [], replaces } = {}) {
|
|
369
|
+
function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
365
370
|
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
366
371
|
`);
|
|
367
372
|
const first = lines[0];
|
|
@@ -372,15 +377,17 @@ function description(usage, { examples = [], replaces } = {}) {
|
|
|
372
377
|
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
373
378
|
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
374
379
|
...replaces === undefined ? {} : { replaces },
|
|
375
|
-
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
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 } })) }
|
|
376
383
|
};
|
|
377
384
|
}
|
|
378
|
-
function exampleRows(examples, colors = paint(
|
|
385
|
+
function exampleRows(examples, colors = paint(process4.stdout)) {
|
|
379
386
|
if (examples.length === 0)
|
|
380
387
|
return [];
|
|
381
388
|
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
382
389
|
}
|
|
383
|
-
function optionRows(options, colors = paint(
|
|
390
|
+
function optionRows(options, colors = paint(process4.stdout)) {
|
|
384
391
|
const rows = Object.entries(options).map(([name, option]) => {
|
|
385
392
|
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
386
393
|
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
@@ -469,7 +476,7 @@ var OUTPUT_OPTIONS = {
|
|
|
469
476
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
470
477
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
471
478
|
};
|
|
472
|
-
function applyOutput(color, plain, env =
|
|
479
|
+
function applyOutput(color, plain, env = process5.env) {
|
|
473
480
|
const when = plain === true ? "never" : color ?? "auto";
|
|
474
481
|
if (when === "auto")
|
|
475
482
|
return;
|
|
@@ -492,7 +499,7 @@ function parse(args, usage, options, settings = {}) {
|
|
|
492
499
|
const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
|
|
493
500
|
let parsed;
|
|
494
501
|
try {
|
|
495
|
-
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 });
|
|
496
503
|
} catch (error) {
|
|
497
504
|
const message = parseError(error);
|
|
498
505
|
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
@@ -500,7 +507,7 @@ function parse(args, usage, options, settings = {}) {
|
|
|
500
507
|
const values = parsed.values;
|
|
501
508
|
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
502
509
|
if (values.help === true) {
|
|
503
|
-
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));
|
|
504
511
|
throw new HelpShown;
|
|
505
512
|
}
|
|
506
513
|
checkChoices(parsed.values, all);
|
|
@@ -542,6 +549,18 @@ function listed(options, allowNegative) {
|
|
|
542
549
|
return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
|
|
543
550
|
}));
|
|
544
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
|
+
}
|
|
545
564
|
function withBare(args, options) {
|
|
546
565
|
const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
|
|
547
566
|
if (bare.size === 0)
|
|
@@ -628,7 +647,7 @@ function nearestOption(message, options, allowNegative) {
|
|
|
628
647
|
}
|
|
629
648
|
// ../kit/src/process.ts
|
|
630
649
|
import { constants } from "os";
|
|
631
|
-
import
|
|
650
|
+
import process6 from "process";
|
|
632
651
|
var KILL_GRACE_MS = 2000;
|
|
633
652
|
function exitCodeOf(code, signal) {
|
|
634
653
|
if (code !== null)
|
|
@@ -648,7 +667,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
|
|
|
648
667
|
return;
|
|
649
668
|
try {
|
|
650
669
|
if (grouped)
|
|
651
|
-
|
|
670
|
+
process6.kill(-proc.pid, killing);
|
|
652
671
|
else
|
|
653
672
|
proc.kill(killing);
|
|
654
673
|
} catch {}
|
|
@@ -722,13 +741,15 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
|
|
|
722
741
|
var RESET_STYLE = `${ESC}[0m`;
|
|
723
742
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
724
743
|
// ../kit/src/cli.ts
|
|
725
|
-
import
|
|
744
|
+
import { readFileSync, realpathSync } from "fs";
|
|
745
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
746
|
+
import process8 from "process";
|
|
726
747
|
|
|
727
748
|
// ../kit/src/usage.ts
|
|
728
749
|
import { appendFileSync, mkdirSync } from "fs";
|
|
729
750
|
import { homedir } from "os";
|
|
730
751
|
import { dirname, isAbsolute, join } from "path";
|
|
731
|
-
import
|
|
752
|
+
import process7 from "process";
|
|
732
753
|
|
|
733
754
|
// ../kit/src/logs.ts
|
|
734
755
|
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -742,14 +763,14 @@ var usageError;
|
|
|
742
763
|
function noteUsageError(message) {
|
|
743
764
|
usageError = message;
|
|
744
765
|
}
|
|
745
|
-
function stateHome(env =
|
|
766
|
+
function stateHome(env = process7.env) {
|
|
746
767
|
const dir = env.XDG_STATE_HOME;
|
|
747
768
|
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
748
769
|
}
|
|
749
|
-
function usagePath(env =
|
|
770
|
+
function usagePath(env = process7.env) {
|
|
750
771
|
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
751
772
|
}
|
|
752
|
-
function usageRecorded(env =
|
|
773
|
+
function usageRecorded(env = process7.env) {
|
|
753
774
|
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
754
775
|
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
755
776
|
}
|
|
@@ -763,7 +784,7 @@ function usageEntry(bin, command, argv, code, now = new Date) {
|
|
|
763
784
|
usageError = undefined;
|
|
764
785
|
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
765
786
|
}
|
|
766
|
-
function recordUsage(entry, env =
|
|
787
|
+
function recordUsage(entry, env = process7.env) {
|
|
767
788
|
if (!usageRecorded(env))
|
|
768
789
|
return;
|
|
769
790
|
try {
|
|
@@ -890,8 +911,8 @@ async function help2({ path, header, summary: lazySummary, commands, defaultComm
|
|
|
890
911
|
`);
|
|
891
912
|
}
|
|
892
913
|
function printError(prefix, message, hint, cause) {
|
|
893
|
-
const colors = paint(
|
|
894
|
-
const styled = takesControlCodes(
|
|
914
|
+
const colors = paint(process8.stderr);
|
|
915
|
+
const styled = takesControlCodes(process8.stderr);
|
|
895
916
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
896
917
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
897
918
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -934,11 +955,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
934
955
|
}
|
|
935
956
|
}
|
|
936
957
|
if (name === undefined && level.path.length > 1) {
|
|
937
|
-
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]),
|
|
958
|
+
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stderr));
|
|
938
959
|
return 2;
|
|
939
960
|
}
|
|
940
961
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
941
|
-
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]),
|
|
962
|
+
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stdout));
|
|
942
963
|
return 0;
|
|
943
964
|
}
|
|
944
965
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -1054,10 +1075,22 @@ async function printCandidates(found) {
|
|
|
1054
1075
|
console.log(line);
|
|
1055
1076
|
return 0;
|
|
1056
1077
|
}
|
|
1057
|
-
|
|
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();
|
|
1058
1091
|
if (argv[0] === COMPLETE)
|
|
1059
1092
|
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
1060
|
-
const code = await dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(
|
|
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 });
|
|
1061
1094
|
if (cli.usage !== false) {
|
|
1062
1095
|
try {
|
|
1063
1096
|
recordUsage(usageEntry(cli.name, commandPath(scopeOf(cli.commands, cli.extraCommands, cli.aliases), argv), argv, code));
|
|
@@ -1080,11 +1113,11 @@ function commandPath(scope, argv) {
|
|
|
1080
1113
|
}
|
|
1081
1114
|
return path.length === 0 ? undefined : path.join(" ");
|
|
1082
1115
|
}
|
|
1083
|
-
async function runMain({ name, version, run, redact, usage }, argv =
|
|
1116
|
+
async function runMain({ name, version, run, redact, usage }, argv = process8.argv.slice(2)) {
|
|
1084
1117
|
if (argv[0] === COMPLETE)
|
|
1085
1118
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
1086
1119
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
1087
|
-
console.log(version);
|
|
1120
|
+
console.log(version ?? packageVersion());
|
|
1088
1121
|
return 0;
|
|
1089
1122
|
}
|
|
1090
1123
|
let code;
|
|
@@ -1093,8 +1126,11 @@ async function runMain({ name, version, run, redact, usage }, argv = process7.ar
|
|
|
1093
1126
|
} catch (error) {
|
|
1094
1127
|
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
1095
1128
|
}
|
|
1096
|
-
if (usage !== false)
|
|
1097
|
-
|
|
1129
|
+
if (usage !== false) {
|
|
1130
|
+
try {
|
|
1131
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
1132
|
+
} catch {}
|
|
1133
|
+
}
|
|
1098
1134
|
return code;
|
|
1099
1135
|
}
|
|
1100
1136
|
// ../kit/src/secrets.ts
|
|
@@ -1103,17 +1139,14 @@ var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "ses
|
|
|
1103
1139
|
var {Glob } = globalThis.Bun;
|
|
1104
1140
|
var SESSION_FILES = new Glob("*.json");
|
|
1105
1141
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
1106
|
-
// package.json
|
|
1107
|
-
var version = "0.4.0";
|
|
1108
|
-
|
|
1109
1142
|
// src/checks.ts
|
|
1110
|
-
import { existsSync as existsSync2, readFileSync as
|
|
1111
|
-
import { basename, join as
|
|
1143
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
1144
|
+
import { basename, join as join4 } from "path";
|
|
1112
1145
|
|
|
1113
1146
|
// src/completions.ts
|
|
1114
|
-
import { existsSync, mkdirSync as mkdirSync2, readdirSync, readFileSync, rmSync } from "fs";
|
|
1115
|
-
import { join as
|
|
1116
|
-
import
|
|
1147
|
+
import { existsSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
1148
|
+
import { join as join3 } from "path";
|
|
1149
|
+
import process10 from "process";
|
|
1117
1150
|
var MARKER = "# Generated by `cli completions fish`";
|
|
1118
1151
|
var FISH_FILE = /\.fish$/;
|
|
1119
1152
|
var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_complete --argument-names bin --description 'what a @kaynooo/cli-* bin offers for the word being typed'
|
|
@@ -1127,8 +1160,28 @@ var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_c
|
|
|
1127
1160
|
end
|
|
1128
1161
|
end
|
|
1129
1162
|
end`;
|
|
1130
|
-
|
|
1131
|
-
|
|
1163
|
+
var BASH_FUNCTION = `__kaynooo_cli_complete() {
|
|
1164
|
+
local IFS=$'\\n' cur=\${COMP_WORDS[COMP_CWORD]} line words=
|
|
1165
|
+
local -a lines
|
|
1166
|
+
COMPREPLY=()
|
|
1167
|
+
lines=($("\${COMP_WORDS[0]}" __complete "\${COMP_WORDS[@]:1:COMP_CWORD}" 2>/dev/null))
|
|
1168
|
+
for line in "\${lines[@]}"; do
|
|
1169
|
+
if [[ $line == __files__ ]]; then
|
|
1170
|
+
COMPREPLY+=($(compgen -f -- "$cur"))
|
|
1171
|
+
else
|
|
1172
|
+
words+="\${line%%$'\\t'*}"$'\\n'
|
|
1173
|
+
fi
|
|
1174
|
+
done
|
|
1175
|
+
COMPREPLY+=($(compgen -W "$words" -- "$cur"))
|
|
1176
|
+
}`;
|
|
1177
|
+
function bashScript(bins) {
|
|
1178
|
+
return `# Generated by \`cli completions bash\`
|
|
1179
|
+
${BASH_FUNCTION}
|
|
1180
|
+
complete -F __kaynooo_cli_complete ${bins.join(" ")}
|
|
1181
|
+
`;
|
|
1182
|
+
}
|
|
1183
|
+
function completionDir(home, env = process10.env) {
|
|
1184
|
+
return join3(env.XDG_CACHE_HOME ?? join3(home, ".cache"), "fish", "kaynooo-cli");
|
|
1132
1185
|
}
|
|
1133
1186
|
function installedBins(host) {
|
|
1134
1187
|
return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
|
|
@@ -1142,28 +1195,30 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
|
|
|
1142
1195
|
async function installCompletions(host, dir) {
|
|
1143
1196
|
const bins = installedBins(host);
|
|
1144
1197
|
mkdirSync2(dir, { recursive: true });
|
|
1145
|
-
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) &&
|
|
1198
|
+
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync2(join3(dir, name), "utf8").startsWith(MARKER));
|
|
1146
1199
|
for (const name of removed)
|
|
1147
|
-
rmSync(
|
|
1148
|
-
await Promise.all(bins.map(async (bin) => Bun.write(
|
|
1200
|
+
rmSync(join3(dir, name));
|
|
1201
|
+
await Promise.all(bins.map(async (bin) => Bun.write(join3(dir, `${bin}.fish`), completionFile(bin))));
|
|
1149
1202
|
return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
|
|
1150
1203
|
}
|
|
1151
1204
|
function missingCompletions(host, dir) {
|
|
1152
|
-
return installedBins(host).filter((bin) => !existsSync(
|
|
1205
|
+
return installedBins(host).filter((bin) => !existsSync(join3(dir, `${bin}.fish`)));
|
|
1153
1206
|
}
|
|
1154
1207
|
|
|
1155
1208
|
// src/checks.ts
|
|
1156
1209
|
var TOOLS = {
|
|
1210
|
+
"@kaynooo/cli-audit": ["audit"],
|
|
1157
1211
|
"@kaynooo/cli-base": ["base", "proof"],
|
|
1158
1212
|
"@kaynooo/cli-changelog": ["changelog"],
|
|
1159
|
-
"@kaynooo/cli-check": ["check", "fix", "errs"],
|
|
1213
|
+
"@kaynooo/cli-check": ["check", "fix", "errs", "j"],
|
|
1160
1214
|
"@kaynooo/cli-code": ["peek", "todo-scan", "hotspots"],
|
|
1161
1215
|
"@kaynooo/cli-db": ["db"],
|
|
1216
|
+
"@kaynooo/cli-docker": ["dc", "dlog", "dx", "sf"],
|
|
1162
1217
|
"@kaynooo/cli-doctor": ["doctor", "cli"],
|
|
1163
1218
|
"@kaynooo/cli-edit": ["sub"],
|
|
1164
1219
|
"@kaynooo/cli-env": ["ev", "redact"],
|
|
1165
1220
|
"@kaynooo/cli-forgejo": ["ft"],
|
|
1166
|
-
"@kaynooo/cli-git": ["gst", "gc"],
|
|
1221
|
+
"@kaynooo/cli-git": ["gst", "gc", "gb"],
|
|
1167
1222
|
"@kaynooo/cli-gitlab": ["gl"],
|
|
1168
1223
|
"@kaynooo/cli-guard": ["guard"],
|
|
1169
1224
|
"@kaynooo/cli-out": ["out"],
|
|
@@ -1171,6 +1226,7 @@ var TOOLS = {
|
|
|
1171
1226
|
"@kaynooo/cli-pick": ["pick"],
|
|
1172
1227
|
"@kaynooo/cli-pkg": ["pkg"],
|
|
1173
1228
|
"@kaynooo/cli-probe": ["probe"],
|
|
1229
|
+
"@kaynooo/cli-proc": ["pid", "ports", "procs"],
|
|
1174
1230
|
"@kaynooo/cli-repos": ["repos"],
|
|
1175
1231
|
"@kaynooo/cli-stack": ["stack"],
|
|
1176
1232
|
"@kaynooo/cli-sweep": ["sweep"],
|
|
@@ -1192,7 +1248,7 @@ function readJsonText(text) {
|
|
|
1192
1248
|
}
|
|
1193
1249
|
}
|
|
1194
1250
|
function readJson(path) {
|
|
1195
|
-
return existsSync2(path) ? readJsonText(
|
|
1251
|
+
return existsSync2(path) ? readJsonText(readFileSync3(path, "utf8")) : undefined;
|
|
1196
1252
|
}
|
|
1197
1253
|
function firstLine(text) {
|
|
1198
1254
|
const trimmed = text.trim();
|
|
@@ -1201,12 +1257,12 @@ function firstLine(text) {
|
|
|
1201
1257
|
return end === -1 ? trimmed : trimmed.slice(0, end);
|
|
1202
1258
|
}
|
|
1203
1259
|
function installed(host, name) {
|
|
1204
|
-
return readJson(
|
|
1260
|
+
return readJson(join4(host.globalModules, name, "package.json"));
|
|
1205
1261
|
}
|
|
1206
1262
|
function ownBin(host, name, bin) {
|
|
1207
1263
|
const declared = installed(host, name)?.bin;
|
|
1208
1264
|
const file = typeof declared === "string" ? declared : declared?.[bin];
|
|
1209
|
-
return file === undefined ? undefined :
|
|
1265
|
+
return file === undefined ? undefined : join4(host.globalModules, name, file);
|
|
1210
1266
|
}
|
|
1211
1267
|
async function checkTool(host, name, bins) {
|
|
1212
1268
|
const check = name.slice(name.indexOf("/") + 1);
|
|
@@ -1215,7 +1271,7 @@ async function checkTool(host, name, bins) {
|
|
|
1215
1271
|
return { check, ok: false, detail: "not installed", fix: `bun install -g ${name}` };
|
|
1216
1272
|
const off = bins.filter((bin) => host.which(bin) === null);
|
|
1217
1273
|
if (off.length > 0)
|
|
1218
|
-
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${
|
|
1274
|
+
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join4(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
|
|
1219
1275
|
const info = await host.run(["bun", "info", name, "version", "--no-cache"]);
|
|
1220
1276
|
const answered = info.code === 0 ? firstLine(info.stdout.trim().split(`
|
|
1221
1277
|
`).at(-1) ?? "") : "";
|
|
@@ -1242,9 +1298,9 @@ async function checkTea(host) {
|
|
|
1242
1298
|
return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
|
|
1243
1299
|
}
|
|
1244
1300
|
function checkHooks(host) {
|
|
1245
|
-
const settings = readJson(
|
|
1301
|
+
const settings = readJson(join4(host.home, ".claude", "settings.json")) ?? {};
|
|
1246
1302
|
const plugin = settings.enabledPlugins?.[PLUGIN] === true;
|
|
1247
|
-
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(
|
|
1303
|
+
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(join4(dir, "hooks", "hooks.json")));
|
|
1248
1304
|
if (plugin && mod !== undefined)
|
|
1249
1305
|
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}` };
|
|
1250
1306
|
if (plugin)
|
|
@@ -1269,28 +1325,75 @@ function checkCompletions(host) {
|
|
|
1269
1325
|
return { check: "completions", ok: false, detail: `no fish completion in ${dir} for ${missing.join(", ")}`, fix: "cli completions fish --install" };
|
|
1270
1326
|
return { check: "completions", ok: true, detail: dir };
|
|
1271
1327
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1328
|
+
var toolCheck = (name) => name.slice(name.indexOf("/") + 1);
|
|
1329
|
+
var OTHER_CHECKS = ["tea", "hooks", "bun", "completions"];
|
|
1330
|
+
var CHECK_NAMES = [...Object.keys(TOOLS).map(toolCheck), ...OTHER_CHECKS];
|
|
1331
|
+
async function runChecks(host, only = []) {
|
|
1332
|
+
const wanted = (check) => only.length === 0 || only.includes(check);
|
|
1333
|
+
const entries = Object.entries(TOOLS).filter(([name]) => wanted(toolCheck(name)));
|
|
1274
1334
|
const tools = [];
|
|
1275
1335
|
for (let at = 0;at < entries.length; at += INFO_AT_ONCE)
|
|
1276
1336
|
tools.push(...await Promise.all(entries.slice(at, at + INFO_AT_ONCE).map(async ([name, bins]) => checkTool(host, name, bins))));
|
|
1277
|
-
return [
|
|
1337
|
+
return [
|
|
1338
|
+
...tools,
|
|
1339
|
+
...wanted("tea") ? [await checkTea(host)] : [],
|
|
1340
|
+
...wanted("hooks") ? [checkHooks(host)] : [],
|
|
1341
|
+
...wanted("bun") ? [checkBun(host)] : [],
|
|
1342
|
+
...wanted("completions") ? [checkCompletions(host)] : []
|
|
1343
|
+
];
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/fix.ts
|
|
1347
|
+
var INSTALL = /^bun install -g (\S+)$/;
|
|
1348
|
+
var RUNNABLE = /^(?:cli completions fish --install|claude plugin (?:install \S+ --marketplace \S+|disable \S+))$/;
|
|
1349
|
+
function planFixes(results) {
|
|
1350
|
+
const steps = [];
|
|
1351
|
+
const installs = [];
|
|
1352
|
+
for (const { ok, fix } of results) {
|
|
1353
|
+
if (ok || fix === undefined)
|
|
1354
|
+
continue;
|
|
1355
|
+
const install = INSTALL.exec(fix);
|
|
1356
|
+
if (install !== null)
|
|
1357
|
+
installs.push(install[1] ?? "");
|
|
1358
|
+
else
|
|
1359
|
+
steps.push({ command: fix, argv: RUNNABLE.test(fix) ? fix.split(" ") : undefined });
|
|
1360
|
+
}
|
|
1361
|
+
if (installs.length === 0)
|
|
1362
|
+
return steps;
|
|
1363
|
+
const command = `bun install -g ${installs.join(" ")}`;
|
|
1364
|
+
return [{ command, argv: command.split(" ") }, ...steps];
|
|
1365
|
+
}
|
|
1366
|
+
async function applyFixes(host, steps, dry) {
|
|
1367
|
+
const outcomes = [];
|
|
1368
|
+
for (const { command, argv } of steps) {
|
|
1369
|
+
if (argv === undefined) {
|
|
1370
|
+
outcomes.push({ command, status: "not run", detail: "not a command doctor runs: do it by hand" });
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (dry) {
|
|
1374
|
+
outcomes.push({ command, status: "dry-run" });
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
const ran = await host.run(argv);
|
|
1378
|
+
outcomes.push(ran.code === 0 ? { command, status: "ran" } : { command, status: "failed", detail: `exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}` });
|
|
1379
|
+
}
|
|
1380
|
+
return outcomes;
|
|
1278
1381
|
}
|
|
1279
1382
|
|
|
1280
1383
|
// src/host.ts
|
|
1281
1384
|
import { homedir as homedir2 } from "os";
|
|
1282
|
-
import { join as
|
|
1283
|
-
import
|
|
1385
|
+
import { join as join5 } from "path";
|
|
1386
|
+
import process11 from "process";
|
|
1284
1387
|
var RUN_TIMEOUT_MS = 20000;
|
|
1285
1388
|
function hostHere() {
|
|
1286
1389
|
const home = homedir2();
|
|
1287
1390
|
return {
|
|
1288
1391
|
home,
|
|
1289
|
-
globalModules:
|
|
1392
|
+
globalModules: join5(process11.env.BUN_INSTALL ?? join5(home, ".bun"), "install", "global", "node_modules"),
|
|
1290
1393
|
bunVersion: Bun.version,
|
|
1291
1394
|
which: (bin) => Bun.which(bin),
|
|
1292
1395
|
run: async (argv, env) => {
|
|
1293
|
-
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...
|
|
1396
|
+
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process11.env, ...env } } });
|
|
1294
1397
|
if (ran.timedOut)
|
|
1295
1398
|
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
1296
1399
|
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
@@ -1300,35 +1403,67 @@ function hostHere() {
|
|
|
1300
1403
|
}
|
|
1301
1404
|
|
|
1302
1405
|
// src/cli.ts
|
|
1303
|
-
var USAGE = `doctor [--json]: checks this host's @kaynooo/cli-* install, one line per check, the command that fixes a failing one beside it
|
|
1406
|
+
var USAGE = `doctor [--only <check>] [--quiet] [--fix [--dry-run]] [--json]: checks this host's @kaynooo/cli-* install, one line per check, the command that fixes a failing one beside it
|
|
1304
1407
|
|
|
1305
|
-
Each @kaynooo/cli-* package installed globally with its bins on the PATH and no older than the registry's latest; tea logged in to git.kaynooo.fr and reaching it; the Claude Code hooks wired through the cli-hooks plugin or the cli mod, not both; Bun at least the version the tools ask for.
|
|
1306
|
-
|
|
1307
|
-
|
|
1408
|
+
Each @kaynooo/cli-* package installed globally with its bins on the PATH and no older than the registry's latest; tea logged in to git.kaynooo.fr and reaching it; the Claude Code hooks wired through the cli-hooks plugin or the cli mod, not both; Bun at least the version the tools ask for; fish completions for every bin.
|
|
1409
|
+
--fix runs the printed fix: lines it may (global installs of the packages, the completions, the hooks plugin), the installs in one call, then checks again; a line that installs a system package or only describes a step is printed as not run. --dry-run shows what --fix would run.
|
|
1410
|
+
--quiet prints only the failing checks, nothing when all pass. --only limits the run to the checks named, e.g. --only tea --only bun.`;
|
|
1411
|
+
var EXITS = [[0, "every check passes (after --fix, the ones it fixed too)"], [1, "a check fails"], [2, "a usage error"]];
|
|
1412
|
+
var INSTALL2 = "bun install -g ";
|
|
1308
1413
|
var OPTIONS = {
|
|
1309
|
-
json: jsonOption("[{check, ok, detail, fix?}]")
|
|
1414
|
+
json: jsonOption("[{check, ok, detail, fix?}], as the checks stand after --fix; the fixes it ran go to stderr"),
|
|
1415
|
+
only: { type: "string", multiple: true, choices: CHECK_NAMES, description: "run only this check (a package such as cli-forgejo, tea, hooks, bun or completions), repeatable" },
|
|
1416
|
+
quiet: { type: "boolean", short: "q", description: "print only the failing checks" },
|
|
1417
|
+
fix: { type: "boolean", description: "run the fix: lines doctor may run, then check again" },
|
|
1418
|
+
"dry-run": { type: "boolean", description: "with --fix, show what would run without running it (alone, it implies --fix)" }
|
|
1310
1419
|
};
|
|
1311
1420
|
function print(results) {
|
|
1312
|
-
|
|
1421
|
+
if (results.length === 0)
|
|
1422
|
+
return;
|
|
1423
|
+
const { green, red, dim } = paint(process12.stdout);
|
|
1313
1424
|
const width = Math.max(...results.map((result) => result.check.length));
|
|
1314
1425
|
for (const { check, ok, detail, fix } of results)
|
|
1315
1426
|
console.log(`${ok ? green("ok ") : red("FAIL")} ${check.padEnd(width)} ${detail}${fix === undefined ? "" : ` ${dim("fix:")} ${fix}`}`);
|
|
1316
|
-
const installs = results.flatMap((result) => result.fix?.startsWith(
|
|
1427
|
+
const installs = results.flatMap((result) => result.fix?.startsWith(INSTALL2) === true ? [result.fix.slice(INSTALL2.length)] : []);
|
|
1317
1428
|
if (installs.length > 1)
|
|
1318
1429
|
console.log(`
|
|
1319
|
-
${dim("fix them all:")} ${
|
|
1430
|
+
${dim("fix them all:")} ${INSTALL2}${installs.join(" ")}`);
|
|
1431
|
+
}
|
|
1432
|
+
function printOutcomes(outcomes, log) {
|
|
1433
|
+
const { green, red, dim } = paint(process12.stdout);
|
|
1434
|
+
const marks = { ran: green("ran "), failed: red("failed "), "dry-run": dim("would "), "not run": dim("not run") };
|
|
1435
|
+
for (const { command, status, detail } of outcomes)
|
|
1436
|
+
log(`${marks[status]} ${command}${detail === undefined ? "" : ` ${dim(detail)}`}`);
|
|
1320
1437
|
}
|
|
1321
1438
|
async function main(args, host = hostHere()) {
|
|
1322
|
-
const { values } = parse(args, USAGE, OPTIONS, { positionals: [], replaces: "checking each bin's version, tea's login and the hooks by hand", examples: ["doctor", ["doctor --json", "the checks for a script"]] });
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1439
|
+
const { values } = parse(args, USAGE, OPTIONS, { positionals: [], exits: EXITS, replaces: "checking each bin's version, tea's login and the hooks by hand", examples: ["doctor", ["doctor --json", "the checks for a script"], ["doctor -q", "silent unless something fails"], ["doctor --fix --dry-run", "what --fix would run"]] });
|
|
1440
|
+
const only = values.only ?? [];
|
|
1441
|
+
const dry = values["dry-run"] === true;
|
|
1442
|
+
const json = values.json === true;
|
|
1443
|
+
let results = await runChecks(host, only);
|
|
1444
|
+
let fixed = false;
|
|
1445
|
+
if ((values.fix === true || dry) && results.some((result) => !result.ok)) {
|
|
1446
|
+
if (!json)
|
|
1447
|
+
print(values.quiet === true ? results.filter((result) => !result.ok) : results);
|
|
1448
|
+
const steps = planFixes(results);
|
|
1449
|
+
printOutcomes(await applyFixes(host, steps, dry), json ? console.error : console.log);
|
|
1450
|
+
if (dry)
|
|
1451
|
+
return 1;
|
|
1452
|
+
results = await runChecks(host, only);
|
|
1453
|
+
fixed = true;
|
|
1454
|
+
}
|
|
1455
|
+
const shown = values.quiet === true ? results.filter((result) => !result.ok) : results;
|
|
1456
|
+
if (fixed && !json && shown.length > 0)
|
|
1457
|
+
console.log(`
|
|
1458
|
+
after the fixes:`);
|
|
1459
|
+
if (json)
|
|
1460
|
+
printJson(shown);
|
|
1326
1461
|
else
|
|
1327
|
-
print(
|
|
1462
|
+
print(shown);
|
|
1328
1463
|
return results.every((result) => result.ok) ? 0 : 1;
|
|
1329
1464
|
}
|
|
1330
1465
|
if (import.meta.main)
|
|
1331
|
-
|
|
1466
|
+
process12.exitCode = await runMain({ name: "doctor", run: async (args) => main(args) });
|
|
1332
1467
|
export {
|
|
1333
1468
|
main
|
|
1334
1469
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaynooo/cli-doctor",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "doctor: checks the @kaynooo/cli-* install of a host: each bin's version against the registry, tea's login to Forgejo, the Claude Code hooks and the Bun floor; cli list: every bin in one line, read from its own help.",
|
|
6
6
|
"author": "Kaynooo <contact@kaynooo.fr>",
|
|
7
7
|
"license": "MIT",
|