@kaynooo/cli-doctor 0.7.0 → 0.9.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 +9 -2
- package/dist/cli-bin.js +574 -88
- package/dist/cli.js +571 -87
- package/package.json +2 -2
package/dist/cli-bin.js
CHANGED
|
@@ -2,16 +2,37 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli-bin.ts
|
|
5
|
-
import
|
|
5
|
+
import process18 from "process";
|
|
6
6
|
// ../kit/src/args.ts
|
|
7
|
-
import
|
|
7
|
+
import process7 from "process";
|
|
8
8
|
import { parseArgs } from "util";
|
|
9
9
|
|
|
10
10
|
// ../kit/src/format.ts
|
|
11
|
-
import
|
|
11
|
+
import process5 from "process";
|
|
12
12
|
|
|
13
|
-
// ../kit/src/
|
|
13
|
+
// ../kit/src/mode.ts
|
|
14
14
|
import process2 from "process";
|
|
15
|
+
var MODES = new Set(["terminal", "ci", "agent", "pipe"]);
|
|
16
|
+
var AGENT_VARIABLES = ["CLAUDECODE", "AI_AGENT"];
|
|
17
|
+
function isSet(value) {
|
|
18
|
+
return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
19
|
+
}
|
|
20
|
+
function outputMode(stream = process2.stdout, env = process2.env) {
|
|
21
|
+
const chosen = env.KIT_MODE;
|
|
22
|
+
if (chosen !== undefined && MODES.has(chosen))
|
|
23
|
+
return chosen;
|
|
24
|
+
if (AGENT_VARIABLES.some((name) => isSet(env[name])))
|
|
25
|
+
return "agent";
|
|
26
|
+
if (stream.isTTY === true)
|
|
27
|
+
return "terminal";
|
|
28
|
+
return isSet(env.CI) ? "ci" : "pipe";
|
|
29
|
+
}
|
|
30
|
+
function isInteractive(streams = process2) {
|
|
31
|
+
return streams.stdin?.isTTY === true && streams.stderr?.isTTY === true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ../kit/src/style.ts
|
|
35
|
+
import process4 from "process";
|
|
15
36
|
|
|
16
37
|
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
17
38
|
function x(s) {
|
|
@@ -274,21 +295,38 @@ function le(i = {}) {
|
|
|
274
295
|
}
|
|
275
296
|
var t = le();
|
|
276
297
|
|
|
298
|
+
// ../kit/src/user-config.ts
|
|
299
|
+
import { homedir } from "os";
|
|
300
|
+
import { join } from "path";
|
|
301
|
+
import process3 from "process";
|
|
302
|
+
function configBase(where = {}) {
|
|
303
|
+
const xdg = where.xdg ?? (where.home === undefined ? process3.env.XDG_CONFIG_HOME : undefined);
|
|
304
|
+
return xdg !== undefined && xdg !== "" ? xdg : join(where.home ?? homedir(), ".config");
|
|
305
|
+
}
|
|
306
|
+
function userConfigPath(bin, where = {}) {
|
|
307
|
+
return join(configBase(where), "kaynooo-cli", `${bin}.json`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ../kit/src/theme.ts
|
|
311
|
+
var STYLES = new Set(["bold", "dim", "italic", "underline", "inverse", "strikethrough", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "gray", "grey", "blackBright", "redBright", "greenBright", "yellowBright", "blueBright", "magentaBright", "cyanBright", "whiteBright"]);
|
|
312
|
+
var MAX_FILE_BYTES = 64 * 1024;
|
|
313
|
+
var warned = new Set;
|
|
314
|
+
|
|
277
315
|
// ../kit/src/style.ts
|
|
278
|
-
function takesControlCodes(stream, env =
|
|
279
|
-
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
316
|
+
function takesControlCodes(stream, env = process4.env) {
|
|
317
|
+
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb" && outputMode(stream, env) === "terminal";
|
|
280
318
|
}
|
|
281
|
-
function paint(stream, env =
|
|
319
|
+
function paint(stream, env = process4.env) {
|
|
282
320
|
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
283
321
|
}
|
|
284
|
-
var
|
|
285
|
-
passed:
|
|
286
|
-
failed:
|
|
287
|
-
running:
|
|
288
|
-
queued:
|
|
289
|
-
skipped:
|
|
322
|
+
var STATUS_WORDS = {
|
|
323
|
+
passed: ["passed", "success", "succeeded", "ok"],
|
|
324
|
+
failed: ["failed", "failure", "error", "errored"],
|
|
325
|
+
running: ["running", "in_progress", "started", "holding"],
|
|
326
|
+
queued: ["queued", "waiting", "pending", "created", "blocked", "scheduled"],
|
|
327
|
+
skipped: ["skipped", "cancelled", "canceled", "manual"]
|
|
290
328
|
};
|
|
291
|
-
var STATUS_OF = new Map(Object.entries(
|
|
329
|
+
var STATUS_OF = new Map(Object.entries(STATUS_WORDS).flatMap(([status, words]) => words.map((word) => [word, status])));
|
|
292
330
|
|
|
293
331
|
// ../kit/src/format.ts
|
|
294
332
|
function upTo(text, separator) {
|
|
@@ -303,15 +341,18 @@ var ESCAPE = String.fromCharCode(27);
|
|
|
303
341
|
function textWidth(text) {
|
|
304
342
|
return Bun.stringWidth(text);
|
|
305
343
|
}
|
|
306
|
-
function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
344
|
+
function table(rows, { cellWidth, header = false, align = [], fit, stream = process5.stdout, env = process5.env } = {}) {
|
|
307
345
|
const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
|
|
346
|
+
if (outputMode(stream, env) === "agent")
|
|
347
|
+
return cells.map((row) => row.map((cell) => Bun.stripANSI(cell).replaceAll(/[\t\r\n]+/g, " ")).join("\t")).join(`
|
|
348
|
+
`);
|
|
308
349
|
const widths = [];
|
|
309
350
|
for (const row of cells) {
|
|
310
351
|
for (const [index, cell] of row.entries())
|
|
311
352
|
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
312
353
|
}
|
|
313
354
|
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
314
|
-
const colors = paint(
|
|
355
|
+
const colors = paint(stream);
|
|
315
356
|
const lines = shown.map((row) => row.map((cell, index) => {
|
|
316
357
|
const width = widths[index] ?? 0;
|
|
317
358
|
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
@@ -319,7 +360,8 @@ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
|
319
360
|
return `${gap}${cell}`;
|
|
320
361
|
return index === row.length - 1 ? cell : `${cell}${gap}`;
|
|
321
362
|
}).join(" ").trimEnd());
|
|
322
|
-
|
|
363
|
+
const rule = header && takesControlCodes(stream) && lines.length > 1 ? [colors.dim("\u2500".repeat(Math.max(...lines.map(textWidth))))] : [];
|
|
364
|
+
return lines.flatMap((line, index) => header && index === 0 ? [colors.bold(line), ...rule] : [line]).join(`
|
|
323
365
|
`);
|
|
324
366
|
}
|
|
325
367
|
function narrow(rows, widths, limit) {
|
|
@@ -338,32 +380,35 @@ function narrow(rows, widths, limit) {
|
|
|
338
380
|
}
|
|
339
381
|
return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
|
|
340
382
|
}
|
|
383
|
+
function jsonText(value, replacer, { stream = process5.stdout, env = process5.env } = {}) {
|
|
384
|
+
return JSON.stringify(value, replacer, outputMode(stream, env) === "agent" ? undefined : 2);
|
|
385
|
+
}
|
|
341
386
|
function printJson(value) {
|
|
342
|
-
console.log(
|
|
387
|
+
console.log(jsonText(value));
|
|
343
388
|
}
|
|
344
389
|
|
|
345
390
|
// ../kit/src/help.ts
|
|
346
|
-
import
|
|
391
|
+
import process6 from "process";
|
|
347
392
|
var OPTIONAL = /\[[^\]]+\]/g;
|
|
348
393
|
var USAGE_PREFIX = /^usage:\s*/i;
|
|
349
394
|
var FULL_STOP = /\.$/;
|
|
350
|
-
function usageLine(usage, colors = paint(
|
|
395
|
+
function usageLine(usage, colors = paint(process6.stdout)) {
|
|
351
396
|
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
352
397
|
}
|
|
353
|
-
function helpText(usage, options, colors = paint(
|
|
398
|
+
function helpText(usage, options, colors = paint(process6.stdout), { examples = [], replaces, exits = [] } = {}) {
|
|
354
399
|
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
|
|
355
400
|
`);
|
|
356
401
|
}
|
|
357
|
-
function exitRows(exits, colors = paint(
|
|
402
|
+
function exitRows(exits, colors = paint(process6.stdout)) {
|
|
358
403
|
if (exits.length === 0)
|
|
359
404
|
return [];
|
|
360
405
|
const width = Math.max(...exits.map(([code]) => String(code).length));
|
|
361
406
|
return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
|
|
362
407
|
}
|
|
363
|
-
function replacesRows(replaces, colors = paint(
|
|
364
|
-
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
408
|
+
function replacesRows(replaces, colors = paint(process6.stdout)) {
|
|
409
|
+
return replaces === undefined || outputMode() === "agent" ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
365
410
|
}
|
|
366
|
-
function wantsHelpJson(env =
|
|
411
|
+
function wantsHelpJson(env = process6.env) {
|
|
367
412
|
return env.KIT_HELP === "json";
|
|
368
413
|
}
|
|
369
414
|
function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
@@ -382,12 +427,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
|
382
427
|
...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
|
|
383
428
|
};
|
|
384
429
|
}
|
|
385
|
-
function exampleRows(examples, colors = paint(
|
|
386
|
-
if (examples.length === 0)
|
|
430
|
+
function exampleRows(examples, colors = paint(process6.stdout)) {
|
|
431
|
+
if (examples.length === 0 || outputMode() === "agent")
|
|
387
432
|
return [];
|
|
388
433
|
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
389
434
|
}
|
|
390
|
-
function optionRows(options, colors = paint(
|
|
435
|
+
function optionRows(options, colors = paint(process6.stdout)) {
|
|
391
436
|
const rows = Object.entries(options).map(([name, option]) => {
|
|
392
437
|
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
393
438
|
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
@@ -446,6 +491,10 @@ class Exit extends Error {
|
|
|
446
491
|
this.hint = hint;
|
|
447
492
|
}
|
|
448
493
|
}
|
|
494
|
+
function fail(message, code = 2, hint) {
|
|
495
|
+
throw new Exit(message, code, hint);
|
|
496
|
+
}
|
|
497
|
+
|
|
449
498
|
class UsageError extends Exit {
|
|
450
499
|
constructor(message, hint) {
|
|
451
500
|
super(message, 2, hint);
|
|
@@ -476,7 +525,7 @@ var OUTPUT_OPTIONS = {
|
|
|
476
525
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
477
526
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
478
527
|
};
|
|
479
|
-
function applyOutput(color, plain, env =
|
|
528
|
+
function applyOutput(color, plain, env = process7.env) {
|
|
480
529
|
const when = plain === true ? "never" : color ?? "auto";
|
|
481
530
|
if (when === "auto")
|
|
482
531
|
return;
|
|
@@ -647,7 +696,7 @@ function nearestOption(message, options, allowNegative) {
|
|
|
647
696
|
}
|
|
648
697
|
// ../kit/src/process.ts
|
|
649
698
|
import { constants } from "os";
|
|
650
|
-
import
|
|
699
|
+
import process8 from "process";
|
|
651
700
|
var KILL_GRACE_MS = 2000;
|
|
652
701
|
function exitCodeOf(code, signal) {
|
|
653
702
|
if (code !== null)
|
|
@@ -667,7 +716,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
|
|
|
667
716
|
return;
|
|
668
717
|
try {
|
|
669
718
|
if (grouped)
|
|
670
|
-
|
|
719
|
+
process8.kill(-proc.pid, killing);
|
|
671
720
|
else
|
|
672
721
|
proc.kill(killing);
|
|
673
722
|
} catch {}
|
|
@@ -742,14 +791,14 @@ var RESET_STYLE = `${ESC}[0m`;
|
|
|
742
791
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
743
792
|
// ../kit/src/cli.ts
|
|
744
793
|
import { readFileSync, realpathSync } from "fs";
|
|
745
|
-
import { dirname as dirname2, join as
|
|
746
|
-
import
|
|
794
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
795
|
+
import process10 from "process";
|
|
747
796
|
|
|
748
797
|
// ../kit/src/usage.ts
|
|
749
798
|
import { appendFileSync, mkdirSync } from "fs";
|
|
750
|
-
import { homedir } from "os";
|
|
751
|
-
import { dirname, isAbsolute, join } from "path";
|
|
752
|
-
import
|
|
799
|
+
import { homedir as homedir2 } from "os";
|
|
800
|
+
import { dirname, isAbsolute, join as join2 } from "path";
|
|
801
|
+
import process9 from "process";
|
|
753
802
|
|
|
754
803
|
// ../kit/src/logs.ts
|
|
755
804
|
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -763,14 +812,14 @@ var usageError;
|
|
|
763
812
|
function noteUsageError(message) {
|
|
764
813
|
usageError = message;
|
|
765
814
|
}
|
|
766
|
-
function stateHome(env =
|
|
815
|
+
function stateHome(env = process9.env) {
|
|
767
816
|
const dir = env.XDG_STATE_HOME;
|
|
768
|
-
return dir !== undefined && isAbsolute(dir) ? dir :
|
|
817
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join2(homedir2(), ".local", "state");
|
|
769
818
|
}
|
|
770
|
-
function usagePath(env =
|
|
771
|
-
return
|
|
819
|
+
function usagePath(env = process9.env) {
|
|
820
|
+
return join2(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
772
821
|
}
|
|
773
|
-
function usageRecorded(env =
|
|
822
|
+
function usageRecorded(env = process9.env) {
|
|
774
823
|
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
775
824
|
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
776
825
|
}
|
|
@@ -784,7 +833,7 @@ function usageEntry(bin, command, argv, code, now = new Date) {
|
|
|
784
833
|
usageError = undefined;
|
|
785
834
|
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
786
835
|
}
|
|
787
|
-
function recordUsage(entry, env =
|
|
836
|
+
function recordUsage(entry, env = process9.env) {
|
|
788
837
|
if (!usageRecorded(env))
|
|
789
838
|
return;
|
|
790
839
|
try {
|
|
@@ -923,8 +972,8 @@ async function help2({ path, header, summary: lazySummary, commands, defaultComm
|
|
|
923
972
|
`);
|
|
924
973
|
}
|
|
925
974
|
function printError(prefix, message, hint, cause) {
|
|
926
|
-
const colors = paint(
|
|
927
|
-
const styled = takesControlCodes(
|
|
975
|
+
const colors = paint(process10.stderr);
|
|
976
|
+
const styled = takesControlCodes(process10.stderr);
|
|
928
977
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
929
978
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
930
979
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -967,11 +1016,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
967
1016
|
}
|
|
968
1017
|
}
|
|
969
1018
|
if (name === undefined && level.path.length > 1) {
|
|
970
|
-
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]),
|
|
1019
|
+
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process10.stderr));
|
|
971
1020
|
return 2;
|
|
972
1021
|
}
|
|
973
1022
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
974
|
-
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]),
|
|
1023
|
+
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process10.stdout));
|
|
975
1024
|
return 0;
|
|
976
1025
|
}
|
|
977
1026
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -1090,7 +1139,7 @@ async function printCandidates(found) {
|
|
|
1090
1139
|
function packageVersion(entry = Bun.main) {
|
|
1091
1140
|
for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
|
|
1092
1141
|
try {
|
|
1093
|
-
const { version } = JSON.parse(readFileSync(
|
|
1142
|
+
const { version } = JSON.parse(readFileSync(join3(dir, "package.json"), "utf8"));
|
|
1094
1143
|
if (typeof version === "string")
|
|
1095
1144
|
return version;
|
|
1096
1145
|
} catch {}
|
|
@@ -1098,7 +1147,7 @@ function packageVersion(entry = Bun.main) {
|
|
|
1098
1147
|
return "0.0.0";
|
|
1099
1148
|
}
|
|
1100
1149
|
}
|
|
1101
|
-
async function runCli(cli, argv =
|
|
1150
|
+
async function runCli(cli, argv = process10.argv.slice(2)) {
|
|
1102
1151
|
const version = cli.version ?? packageVersion();
|
|
1103
1152
|
if (argv[0] === COMPLETE)
|
|
1104
1153
|
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
@@ -1125,7 +1174,7 @@ function commandPath(scope, argv) {
|
|
|
1125
1174
|
}
|
|
1126
1175
|
return path.length === 0 ? undefined : path.join(" ");
|
|
1127
1176
|
}
|
|
1128
|
-
async function runMain({ name, version, run, redact, usage }, argv =
|
|
1177
|
+
async function runMain({ name, version, run, redact, usage }, argv = process10.argv.slice(2)) {
|
|
1129
1178
|
if (argv[0] === COMPLETE)
|
|
1130
1179
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
1131
1180
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
@@ -1151,14 +1200,96 @@ var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "ses
|
|
|
1151
1200
|
var {Glob } = globalThis.Bun;
|
|
1152
1201
|
var SESSION_FILES = new Glob("*.json");
|
|
1153
1202
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
1203
|
+
// ../kit/src/workspace-root.ts
|
|
1204
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1205
|
+
import { homedir as homedir3 } from "os";
|
|
1206
|
+
import { dirname as dirname3, join as join4, resolve } from "path";
|
|
1207
|
+
import process11 from "process";
|
|
1208
|
+
function expandHome(path, home) {
|
|
1209
|
+
if (path === "~")
|
|
1210
|
+
return home;
|
|
1211
|
+
return path.startsWith("~/") ? join4(home, path.slice(2)) : path;
|
|
1212
|
+
}
|
|
1213
|
+
function configured(env, home) {
|
|
1214
|
+
const path = userConfigPath("config", { home, xdg: env.XDG_CONFIG_HOME });
|
|
1215
|
+
if (!existsSync(path))
|
|
1216
|
+
return;
|
|
1217
|
+
try {
|
|
1218
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
1219
|
+
const folder = typeof parsed === "object" && parsed !== null ? parsed.workspace : undefined;
|
|
1220
|
+
return typeof folder === "string" && folder !== "" ? folder : undefined;
|
|
1221
|
+
} catch {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
function workspaceRoot({ given, env = process11.env, home = homedir3() } = {}) {
|
|
1226
|
+
const fromEnv = env.KAYNOOO_WORKSPACE === "" ? undefined : env.KAYNOOO_WORKSPACE;
|
|
1227
|
+
return resolve(expandHome(given ?? fromEnv ?? configured(env, home) ?? join4(home, "Workspace"), home));
|
|
1228
|
+
}
|
|
1229
|
+
function workspaceChosen({ env = process11.env, home = homedir3() } = {}) {
|
|
1230
|
+
return (env.KAYNOOO_WORKSPACE ?? "") !== "" || configured(env, home) !== undefined;
|
|
1231
|
+
}
|
|
1232
|
+
function saveWorkspace(folder, { env = process11.env, home = homedir3() } = {}) {
|
|
1233
|
+
const path = userConfigPath("config", { home, xdg: env.XDG_CONFIG_HOME });
|
|
1234
|
+
let current = {};
|
|
1235
|
+
if (existsSync(path)) {
|
|
1236
|
+
try {
|
|
1237
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
1238
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
1239
|
+
return false;
|
|
1240
|
+
current = parsed;
|
|
1241
|
+
} catch {
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
1246
|
+
writeFileSync(path, `${JSON.stringify({ ...current, workspace: folder }, undefined, 2)}
|
|
1247
|
+
`);
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1154
1250
|
// src/completions.ts
|
|
1155
|
-
import { existsSync as
|
|
1156
|
-
import { join as
|
|
1157
|
-
import
|
|
1251
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync4, rmSync } from "fs";
|
|
1252
|
+
import { join as join6 } from "path";
|
|
1253
|
+
import process13 from "process";
|
|
1254
|
+
|
|
1255
|
+
// src/checks.ts
|
|
1256
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
1257
|
+
import { basename, join as join5 } from "path";
|
|
1258
|
+
|
|
1259
|
+
// src/catalog.ts
|
|
1260
|
+
var HOOKS_REQUIRES = "@kaynooo/cli-guard";
|
|
1261
|
+
var ABOUT = {
|
|
1262
|
+
"@kaynooo/cli-base": "run a command at a ref in a throwaway worktree, and prove a test fails before a change",
|
|
1263
|
+
"@kaynooo/cli-changelog": "release notes from conventional commits, published as a Forgejo release",
|
|
1264
|
+
"@kaynooo/cli-check": "the repo's check, lint and test steps, its fixers, error lines from logs and a recipe picker",
|
|
1265
|
+
"@kaynooo/cli-code": "source read by spans, TODO comments and the files worth splitting",
|
|
1266
|
+
"@kaynooo/cli-db": "read-only SQL against Postgres, MySQL and MariaDB, credentials never shown",
|
|
1267
|
+
"@kaynooo/cli-dev": "passwords, JWTs, timestamps, UUIDs, base64 and file hashes",
|
|
1268
|
+
"@kaynooo/cli-docker": "docker compose on the stack found here, service health, shells and Symfony commands",
|
|
1269
|
+
"@kaynooo/cli-doctor": "this install check, and the list of every bin",
|
|
1270
|
+
"@kaynooo/cli-edit": "replace text in files, writing nothing unless every match count is right",
|
|
1271
|
+
"@kaynooo/cli-env": ".env files listed and edited with secrets masked, and a secrets filter for pipes",
|
|
1272
|
+
"@kaynooo/cli-forgejo": "Forgejo CI runs, logs, releases and issue checklists",
|
|
1273
|
+
"@kaynooo/cli-git": "repository state in one line, commits of exact paths, a branch picker",
|
|
1274
|
+
"@kaynooo/cli-gitlab": "GitLab pipelines and their failed tests",
|
|
1275
|
+
"@kaynooo/cli-guard": "the safety rules a Claude Code Bash call is held to",
|
|
1276
|
+
"@kaynooo/cli-out": "shortens a noisy command's output, keeping the errors",
|
|
1277
|
+
"@kaynooo/cli-pick": "a fuzzy selector for pipes",
|
|
1278
|
+
"@kaynooo/cli-pkg": "vets npm packages before they are added, and bumps a lib in its consumers",
|
|
1279
|
+
"@kaynooo/cli-probe": "a site's HTTP version, redirects, timings and TLS",
|
|
1280
|
+
"@kaynooo/cli-proc": "what a process is, who listens on a port, leftover dev processes",
|
|
1281
|
+
"@kaynooo/cli-stack": "a stable port per service name, and a compose release audit",
|
|
1282
|
+
"@kaynooo/cli-view": "markdown, source and git diffs in the colours of the other bins",
|
|
1283
|
+
"@kaynooo/cli-wait": "wait for a line, a port, a URL or a process; a machine-wide slot for heavy jobs",
|
|
1284
|
+
"@kaynooo/cli-workspace": "the repositories under a root needing attention, reclaimable folders and known vulnerabilities"
|
|
1285
|
+
};
|
|
1286
|
+
var NEEDS = {
|
|
1287
|
+
"@kaynooo/cli-forgejo": [{ bin: "tea", why: "ft goes through it", how: "https://gitea.com/gitea/tea/releases" }],
|
|
1288
|
+
"@kaynooo/cli-gitlab": [{ bin: "glab", why: "gl goes through it", how: "https://gitlab.com/gitlab-org/cli" }],
|
|
1289
|
+
"@kaynooo/cli-docker": [{ bin: "docker", why: "dc, dlog, dx and sf run docker compose", how: "https://docs.docker.com/engine/install/" }]
|
|
1290
|
+
};
|
|
1158
1291
|
|
|
1159
1292
|
// src/checks.ts
|
|
1160
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1161
|
-
import { basename, join as join3 } from "path";
|
|
1162
1293
|
var TOOLS = {
|
|
1163
1294
|
"@kaynooo/cli-base": ["base", "proof"],
|
|
1164
1295
|
"@kaynooo/cli-changelog": ["changelog"],
|
|
@@ -1199,7 +1330,7 @@ function readJsonText(text) {
|
|
|
1199
1330
|
}
|
|
1200
1331
|
}
|
|
1201
1332
|
function readJson(path) {
|
|
1202
|
-
return
|
|
1333
|
+
return existsSync2(path) ? readJsonText(readFileSync3(path, "utf8")) : undefined;
|
|
1203
1334
|
}
|
|
1204
1335
|
function firstLine(text) {
|
|
1205
1336
|
const trimmed = text.trim();
|
|
@@ -1208,12 +1339,15 @@ function firstLine(text) {
|
|
|
1208
1339
|
return end === -1 ? trimmed : trimmed.slice(0, end);
|
|
1209
1340
|
}
|
|
1210
1341
|
function installed(host, name) {
|
|
1211
|
-
return readJson(
|
|
1342
|
+
return readJson(join5(host.globalModules, name, "package.json"));
|
|
1343
|
+
}
|
|
1344
|
+
function isInstalled(host, name) {
|
|
1345
|
+
return installed(host, name)?.version !== undefined;
|
|
1212
1346
|
}
|
|
1213
1347
|
function ownBin(host, name, bin) {
|
|
1214
1348
|
const declared = installed(host, name)?.bin;
|
|
1215
1349
|
const file = typeof declared === "string" ? declared : declared?.[bin];
|
|
1216
|
-
return file === undefined ? undefined :
|
|
1350
|
+
return file === undefined ? undefined : join5(host.globalModules, name, file);
|
|
1217
1351
|
}
|
|
1218
1352
|
async function checkTool(host, name, bins) {
|
|
1219
1353
|
const check = name.slice(name.indexOf("/") + 1);
|
|
@@ -1228,7 +1362,7 @@ async function checkTool(host, name, bins) {
|
|
|
1228
1362
|
if (latest !== "" && Bun.semver.order(version, latest) < 0)
|
|
1229
1363
|
return { check, ok: false, detail: `${version}, ${latest} released${off.length > 0 ? `, ${off.join(", ")} not on the PATH` : ""}`, fix: `bun install -g ${name}@${latest}` };
|
|
1230
1364
|
if (off.length > 0)
|
|
1231
|
-
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${
|
|
1365
|
+
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join5(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
|
|
1232
1366
|
if (latest === "")
|
|
1233
1367
|
return { check, ok: true, detail: `${version}, the registry did not answer: ${firstLine(info.stderr) || answered || "no version"}` };
|
|
1234
1368
|
return { check, ok: true, detail: version };
|
|
@@ -1248,10 +1382,16 @@ async function checkTea(host) {
|
|
|
1248
1382
|
return { check: "tea", ok: false, detail: `${forge} did not answer: ${firstLine(version.stderr || version.stdout)}`, fix: `tea login edit ${login.name} --token <token>, or check the network` };
|
|
1249
1383
|
return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
|
|
1250
1384
|
}
|
|
1251
|
-
function
|
|
1252
|
-
const settings = readJson(
|
|
1385
|
+
function hooksWiring(host) {
|
|
1386
|
+
const settings = readJson(join5(host.home, ".claude", "settings.json")) ?? {};
|
|
1253
1387
|
const plugin = settings.enabledPlugins?.[PLUGIN] === true;
|
|
1254
|
-
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" &&
|
|
1388
|
+
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(join5(dir, "hooks", "hooks.json")));
|
|
1389
|
+
return { plugin, mod };
|
|
1390
|
+
}
|
|
1391
|
+
function checkHooks(host) {
|
|
1392
|
+
const { plugin, mod } = hooksWiring(host);
|
|
1393
|
+
if ((plugin || mod !== undefined) && !isInstalled(host, HOOKS_REQUIRES))
|
|
1394
|
+
return { check: "hooks", ok: false, detail: `wired, but ${HOOKS_REQUIRES} is not installed, so every hook finds no guard`, fix: `bun install -g ${HOOKS_REQUIRES}` };
|
|
1255
1395
|
if (plugin && mod !== undefined)
|
|
1256
1396
|
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}` };
|
|
1257
1397
|
if (plugin)
|
|
@@ -1276,21 +1416,39 @@ function checkCompletions(host) {
|
|
|
1276
1416
|
return { check: "completions", ok: false, detail: `no fish completion in ${dir} for ${missing.join(", ")}`, fix: "cli completions fish --install" };
|
|
1277
1417
|
return { check: "completions", ok: true, detail: dir };
|
|
1278
1418
|
}
|
|
1419
|
+
function checkNeeds(host) {
|
|
1420
|
+
const missing = Object.entries(NEEDS).flatMap(([name, needs]) => isInstalled(host, name) ? needs.filter((need) => need.bin !== "tea" && host.which(need.bin) === null) : []);
|
|
1421
|
+
if (missing.length > 0)
|
|
1422
|
+
return { check: "needs", ok: false, detail: `missing ${missing.map((need) => `${need.bin} (${need.why})`).join(", ")}`, fix: `install ${missing.map((need) => `${need.bin}: ${need.how}`).join("; ")}` };
|
|
1423
|
+
return { check: "needs", ok: true, detail: "every tool the installed packages need is on the PATH" };
|
|
1424
|
+
}
|
|
1279
1425
|
var toolCheck = (name) => name.slice(name.indexOf("/") + 1);
|
|
1280
|
-
var OTHER_CHECKS = ["tea", "hooks", "bun", "completions"];
|
|
1426
|
+
var OTHER_CHECKS = ["tea", "hooks", "needs", "bun", "completions", "workspace"];
|
|
1427
|
+
var FORGEJO = "@kaynooo/cli-forgejo";
|
|
1428
|
+
var WORKSPACE = "@kaynooo/cli-workspace";
|
|
1429
|
+
function checkWorkspace(host, env = process.env) {
|
|
1430
|
+
const folder = workspaceRoot({ home: host.home, env });
|
|
1431
|
+
if (existsSync2(folder))
|
|
1432
|
+
return { check: "workspace", ok: true, detail: folder };
|
|
1433
|
+
return { check: "workspace", ok: false, detail: `${folder} does not exist`, fix: `mkdir -p ${folder}, or set "workspace" in ~/.config/kaynooo-cli/config.json to your folder` };
|
|
1434
|
+
}
|
|
1281
1435
|
var CHECK_NAMES = [...Object.keys(TOOLS).map(toolCheck), ...OTHER_CHECKS];
|
|
1282
1436
|
async function runChecks(host, only = []) {
|
|
1283
1437
|
const wanted = (check) => only.length === 0 || only.includes(check);
|
|
1284
|
-
const entries = Object.entries(TOOLS).filter(([name]) => wanted(toolCheck(name)));
|
|
1438
|
+
const entries = Object.entries(TOOLS).filter(([name]) => only.length === 0 ? isInstalled(host, name) : wanted(toolCheck(name)));
|
|
1439
|
+
const { plugin, mod } = hooksWiring(host);
|
|
1440
|
+
const hooks = only.length > 0 || plugin || mod !== undefined || isInstalled(host, HOOKS_REQUIRES);
|
|
1285
1441
|
const tools = [];
|
|
1286
1442
|
for (let at = 0;at < entries.length; at += INFO_AT_ONCE)
|
|
1287
1443
|
tools.push(...await Promise.all(entries.slice(at, at + INFO_AT_ONCE).map(async ([name, bins]) => checkTool(host, name, bins))));
|
|
1288
1444
|
return [
|
|
1289
1445
|
...tools,
|
|
1290
|
-
...wanted("tea") ? [await checkTea(host)] : [],
|
|
1291
|
-
...wanted("hooks") ? [checkHooks(host)] : [],
|
|
1446
|
+
...wanted("tea") && (only.length > 0 || isInstalled(host, FORGEJO)) ? [await checkTea(host)] : [],
|
|
1447
|
+
...wanted("hooks") && hooks ? [checkHooks(host)] : [],
|
|
1448
|
+
...wanted("needs") ? [checkNeeds(host)] : [],
|
|
1292
1449
|
...wanted("bun") ? [checkBun(host)] : [],
|
|
1293
|
-
...wanted("completions") ? [checkCompletions(host)] : []
|
|
1450
|
+
...wanted("completions") ? [checkCompletions(host)] : [],
|
|
1451
|
+
...wanted("workspace") && (only.length > 0 || isInstalled(host, WORKSPACE) || isInstalled(host, "@kaynooo/cli-pkg")) ? [checkWorkspace(host)] : []
|
|
1294
1452
|
];
|
|
1295
1453
|
}
|
|
1296
1454
|
|
|
@@ -1328,8 +1486,8 @@ ${BASH_FUNCTION}
|
|
|
1328
1486
|
complete -F __kaynooo_cli_complete ${bins.join(" ")}
|
|
1329
1487
|
`;
|
|
1330
1488
|
}
|
|
1331
|
-
function completionDir(home, env =
|
|
1332
|
-
return
|
|
1489
|
+
function completionDir(home, env = process13.env) {
|
|
1490
|
+
return join6(env.XDG_CACHE_HOME ?? join6(home, ".cache"), "fish", "kaynooo-cli");
|
|
1333
1491
|
}
|
|
1334
1492
|
function installedBins(host) {
|
|
1335
1493
|
return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
|
|
@@ -1342,19 +1500,19 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
|
|
|
1342
1500
|
}
|
|
1343
1501
|
async function installCompletions(host, dir) {
|
|
1344
1502
|
const bins = installedBins(host);
|
|
1345
|
-
|
|
1346
|
-
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) &&
|
|
1503
|
+
mkdirSync3(dir, { recursive: true });
|
|
1504
|
+
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync4(join6(dir, name), "utf8").startsWith(MARKER));
|
|
1347
1505
|
for (const name of removed)
|
|
1348
|
-
rmSync(
|
|
1349
|
-
await Promise.all(bins.map(async (bin) => Bun.write(
|
|
1506
|
+
rmSync(join6(dir, name));
|
|
1507
|
+
await Promise.all(bins.map(async (bin) => Bun.write(join6(dir, `${bin}.fish`), completionFile(bin))));
|
|
1350
1508
|
return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
|
|
1351
1509
|
}
|
|
1352
1510
|
function missingCompletions(host, dir) {
|
|
1353
|
-
return installedBins(host).filter((bin) => !
|
|
1511
|
+
return installedBins(host).filter((bin) => !existsSync3(join6(dir, `${bin}.fish`)));
|
|
1354
1512
|
}
|
|
1355
1513
|
|
|
1356
1514
|
// src/doctor.ts
|
|
1357
|
-
import
|
|
1515
|
+
import process17 from "process";
|
|
1358
1516
|
|
|
1359
1517
|
// src/fix.ts
|
|
1360
1518
|
var INSTALL = /^bun install -g (\S+)$/;
|
|
@@ -1394,19 +1552,19 @@ async function applyFixes(host, steps, dry) {
|
|
|
1394
1552
|
}
|
|
1395
1553
|
|
|
1396
1554
|
// src/host.ts
|
|
1397
|
-
import { homedir as
|
|
1398
|
-
import { join as
|
|
1399
|
-
import
|
|
1555
|
+
import { homedir as homedir4 } from "os";
|
|
1556
|
+
import { join as join7 } from "path";
|
|
1557
|
+
import process14 from "process";
|
|
1400
1558
|
var RUN_TIMEOUT_MS = 20000;
|
|
1401
1559
|
function hostHere() {
|
|
1402
|
-
const home =
|
|
1560
|
+
const home = homedir4();
|
|
1403
1561
|
return {
|
|
1404
1562
|
home,
|
|
1405
|
-
globalModules:
|
|
1563
|
+
globalModules: join7(process14.env.BUN_INSTALL ?? join7(home, ".bun"), "install", "global", "node_modules"),
|
|
1406
1564
|
bunVersion: Bun.version,
|
|
1407
1565
|
which: (bin) => Bun.which(bin),
|
|
1408
1566
|
run: async (argv, env) => {
|
|
1409
|
-
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...
|
|
1567
|
+
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process14.env, ...env } } });
|
|
1410
1568
|
if (ran.timedOut)
|
|
1411
1569
|
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
1412
1570
|
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
@@ -1415,11 +1573,325 @@ function hostHere() {
|
|
|
1415
1573
|
};
|
|
1416
1574
|
}
|
|
1417
1575
|
|
|
1576
|
+
// src/install.ts
|
|
1577
|
+
import process16 from "process";
|
|
1578
|
+
|
|
1579
|
+
// ../kit/src/prompt.ts
|
|
1580
|
+
import process15 from "process";
|
|
1581
|
+
var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
|
|
1582
|
+
var CONTROL = /^[\x00-\x1F\x7F]/;
|
|
1583
|
+
var WORDS = /\s+/;
|
|
1584
|
+
var MAX_ROWS = 10;
|
|
1585
|
+
var KEYS = { "\x1B[A": "up", "\x1BOA": "up", "\x1B[B": "down", "\x1BOB": "down", "\r": "enter", "\n": "enter", "\x03": "cancel", "\x1B": "cancel", "\x7F": "erase", "\b": "erase" };
|
|
1586
|
+
function readKeys(input) {
|
|
1587
|
+
return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => KEYS[sequence] ?? (CONTROL.test(sequence) ? [] : [{ text: sequence }]));
|
|
1588
|
+
}
|
|
1589
|
+
function flip(checked, value) {
|
|
1590
|
+
const next = new Set(checked);
|
|
1591
|
+
if (!next.delete(value))
|
|
1592
|
+
next.add(value);
|
|
1593
|
+
return next;
|
|
1594
|
+
}
|
|
1595
|
+
async function checklist(question, choices, { initial = new Set, min = 0, toggle = flip, ...settings } = {}) {
|
|
1596
|
+
const list = new List(choices, 0);
|
|
1597
|
+
let checked = initial;
|
|
1598
|
+
let invalid;
|
|
1599
|
+
const accepted = await ask(question, settings, (colors, state, rows) => state === "asking" ? [asking(colors, question, list.query, "type to filter, space toggles, enter accepts"), ...list.rows(colors, rows, (choice) => checked.has(choice.value) ? `${colors.cyan("\u25C9")} ` : `${colors.dim("\u25EF")} `), ...invalid === undefined ? [] : [` ${colors.red(invalid)}`]] : [answer(colors, question, state, choices.filter((choice) => checked.has(choice.value)).map((choice) => choice.label).join(", ") || "none")], (key) => {
|
|
1600
|
+
if (key === "cancel")
|
|
1601
|
+
return key;
|
|
1602
|
+
invalid = undefined;
|
|
1603
|
+
if (key === "enter") {
|
|
1604
|
+
invalid = checked.size < min ? `pick at least ${min}` : undefined;
|
|
1605
|
+
return invalid === undefined ? "enter" : undefined;
|
|
1606
|
+
}
|
|
1607
|
+
const current = list.current();
|
|
1608
|
+
if (typeof key === "object" && key.text === " ") {
|
|
1609
|
+
if (current !== undefined)
|
|
1610
|
+
checked = toggle(checked, current.value);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
list.press(key);
|
|
1614
|
+
return;
|
|
1615
|
+
});
|
|
1616
|
+
return accepted ? checked : undefined;
|
|
1617
|
+
}
|
|
1618
|
+
async function input2(question, { default: fallback, mask = false, validate, ...settings } = {}) {
|
|
1619
|
+
let value = "";
|
|
1620
|
+
let invalid;
|
|
1621
|
+
const taken = () => value === "" ? fallback ?? "" : value;
|
|
1622
|
+
const shown = () => mask ? "*".repeat([...taken()].length) : taken();
|
|
1623
|
+
const accepted = await ask(question, settings, (colors, state, { columns }) => {
|
|
1624
|
+
if (state !== "asking")
|
|
1625
|
+
return [answer(colors, question, state, shown())];
|
|
1626
|
+
const characters = [...shown()];
|
|
1627
|
+
const room = Math.max(columns - question.length - 5, 1);
|
|
1628
|
+
const tail = characters.length > room ? `\u2026${characters.slice(1 - room).join("")}` : shown();
|
|
1629
|
+
const typed = value === "" ? colors.dim(tail) : tail;
|
|
1630
|
+
return [`${colors.cyan("?")} ${question} ${typed}${colors.dim("\u2588")}`, ...invalid === undefined ? [] : [` ${colors.red(invalid)}`]];
|
|
1631
|
+
}, (key) => {
|
|
1632
|
+
if (key === "cancel")
|
|
1633
|
+
return "cancel";
|
|
1634
|
+
invalid = undefined;
|
|
1635
|
+
if (key === "enter") {
|
|
1636
|
+
invalid = validate?.(taken());
|
|
1637
|
+
return invalid === undefined ? "enter" : undefined;
|
|
1638
|
+
}
|
|
1639
|
+
if (key === "erase")
|
|
1640
|
+
value = [...value].slice(0, -1).join("");
|
|
1641
|
+
else if (typeof key === "object")
|
|
1642
|
+
value += key.text;
|
|
1643
|
+
return;
|
|
1644
|
+
});
|
|
1645
|
+
return accepted ? taken() : undefined;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
class List {
|
|
1649
|
+
choices;
|
|
1650
|
+
query = "";
|
|
1651
|
+
matches;
|
|
1652
|
+
cursor;
|
|
1653
|
+
top = 0;
|
|
1654
|
+
width;
|
|
1655
|
+
constructor(choices, initial) {
|
|
1656
|
+
this.choices = choices;
|
|
1657
|
+
this.matches = choices;
|
|
1658
|
+
this.cursor = Math.min(Math.max(initial, 0), choices.length - 1);
|
|
1659
|
+
this.width = Math.max(0, ...choices.map((choice) => choice.label.length));
|
|
1660
|
+
}
|
|
1661
|
+
current() {
|
|
1662
|
+
return this.matches[this.cursor];
|
|
1663
|
+
}
|
|
1664
|
+
press(key) {
|
|
1665
|
+
if (key === "up" || key === "down") {
|
|
1666
|
+
if (this.matches.length > 0)
|
|
1667
|
+
this.cursor = (this.cursor + (key === "up" ? this.matches.length - 1 : 1)) % this.matches.length;
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (key === "erase")
|
|
1671
|
+
this.filter([...this.query].slice(0, -1).join(""));
|
|
1672
|
+
else if (typeof key === "object")
|
|
1673
|
+
this.filter(this.query + key.text);
|
|
1674
|
+
}
|
|
1675
|
+
filter(query) {
|
|
1676
|
+
const words = query.toLowerCase().split(WORDS).filter((word) => word !== "");
|
|
1677
|
+
const current = this.current();
|
|
1678
|
+
this.query = query;
|
|
1679
|
+
this.matches = this.choices.filter(({ label, hint }) => words.every((word) => `${label} ${hint}`.toLowerCase().includes(word)));
|
|
1680
|
+
this.cursor = current === undefined ? 0 : Math.max(this.matches.indexOf(current), 0);
|
|
1681
|
+
this.top = 0;
|
|
1682
|
+
}
|
|
1683
|
+
rows(colors, { columns, rows }, marker) {
|
|
1684
|
+
if (this.matches.length === 0)
|
|
1685
|
+
return [colors.dim(" no match")];
|
|
1686
|
+
const height = Math.max(1, Math.min(MAX_ROWS, rows - 3, this.matches.length));
|
|
1687
|
+
this.top = Math.min(Math.max(this.top, this.cursor - height + 1), this.cursor);
|
|
1688
|
+
const shown = this.matches.slice(this.top, this.top + height).map((choice, index) => {
|
|
1689
|
+
const here = this.top + index === this.cursor;
|
|
1690
|
+
const mark = marker(choice);
|
|
1691
|
+
const head = `${here ? "\u276F" : " "} ${mark}`;
|
|
1692
|
+
const label = choice.label.padEnd(this.width);
|
|
1693
|
+
const room = columns - 5 - (mark === "" ? 0 : 2) - this.width;
|
|
1694
|
+
return `${here ? colors.cyan(head) : head}${here ? colors.cyan(label) : label} ${colors.dim(truncate(choice.hint, Math.max(room, 0)))}`;
|
|
1695
|
+
});
|
|
1696
|
+
const below = this.matches.length - this.top - height;
|
|
1697
|
+
return [...this.top > 0 ? [colors.dim(` \u2026 ${this.top} more`)] : [], ...shown, ...below > 0 ? [colors.dim(` \u2026 ${below} more`)] : []];
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
function asking(colors, question, typed, keys) {
|
|
1701
|
+
return `${colors.cyan("?")} ${question} ${typed === "" ? colors.dim(`(${keys})`) : typed}`;
|
|
1702
|
+
}
|
|
1703
|
+
function answer(colors, question, state, picked) {
|
|
1704
|
+
return state === "enter" ? `${colors.green("\u2713")} ${question} ${colors.cyan(picked)}` : `${colors.red("\u2717")} ${question} ${colors.dim("cancelled")}`;
|
|
1705
|
+
}
|
|
1706
|
+
async function ask(question, { hint, terminal = process15 }, render, press) {
|
|
1707
|
+
const { stdin, stderr } = terminal;
|
|
1708
|
+
if (!isInteractive(terminal))
|
|
1709
|
+
fail(`cannot ask "${question}" off a terminal`, 2, hint);
|
|
1710
|
+
const colors = paint(stderr);
|
|
1711
|
+
let drawn = 0;
|
|
1712
|
+
let outcome;
|
|
1713
|
+
const draw = (state) => {
|
|
1714
|
+
const frame = render(colors, state, { columns: stderr.columns || 80, rows: stderr.rows || 24 });
|
|
1715
|
+
stderr.write(`${drawn > 1 ? `\x1B[${drawn - 1}A` : ""}\r\x1B[0J${frame.join(`
|
|
1716
|
+
`)}`);
|
|
1717
|
+
drawn = frame.length;
|
|
1718
|
+
};
|
|
1719
|
+
try {
|
|
1720
|
+
stdin.setRawMode(true);
|
|
1721
|
+
stdin.resume();
|
|
1722
|
+
stderr.write("\x1B[?25l");
|
|
1723
|
+
draw("asking");
|
|
1724
|
+
return await new Promise((resolve) => {
|
|
1725
|
+
function onData(data) {
|
|
1726
|
+
for (const key of readKeys(data.toString())) {
|
|
1727
|
+
const pressed = press(key);
|
|
1728
|
+
if (pressed !== undefined) {
|
|
1729
|
+
settle(pressed);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
draw("asking");
|
|
1734
|
+
}
|
|
1735
|
+
function onEnd() {
|
|
1736
|
+
settle("cancel");
|
|
1737
|
+
}
|
|
1738
|
+
function settle(settled) {
|
|
1739
|
+
outcome = settled;
|
|
1740
|
+
stdin.off("data", onData);
|
|
1741
|
+
stdin.off("end", onEnd);
|
|
1742
|
+
resolve(settled === "enter");
|
|
1743
|
+
}
|
|
1744
|
+
stdin.on("data", onData);
|
|
1745
|
+
stdin.on("end", onEnd);
|
|
1746
|
+
});
|
|
1747
|
+
} finally {
|
|
1748
|
+
draw(outcome ?? "cancel");
|
|
1749
|
+
stderr.write(`
|
|
1750
|
+
\x1B[?25h`);
|
|
1751
|
+
stdin.setRawMode(false);
|
|
1752
|
+
stdin.pause();
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// src/install.ts
|
|
1757
|
+
var HOOKS = "hooks";
|
|
1758
|
+
var PLUGIN_INSTALL = ["claude", "plugin", "install", "cli-hooks", "--marketplace", `https://${FORGE}/kaynooo/cli.git`];
|
|
1759
|
+
var CLI_PREFIX = /^cli-/;
|
|
1760
|
+
var shortName = (name) => name.slice(name.indexOf("/") + 1).replace(CLI_PREFIX, "");
|
|
1761
|
+
function candidates2(host) {
|
|
1762
|
+
const packages = Object.entries(TOOLS).filter(([name]) => !isInstalled(host, name)).map(([name, bins]) => {
|
|
1763
|
+
const needs = (NEEDS[name] ?? []).map((need) => need.bin);
|
|
1764
|
+
return { id: name, names: [shortName(name), name, `cli-${shortName(name)}`, ...bins], label: shortName(name), hint: `${bins.join(" ")}: ${ABOUT[name] ?? ""}${needs.length === 0 ? "" : `, needs ${needs.join(", ")}`}`, packages: [name] };
|
|
1765
|
+
});
|
|
1766
|
+
const { plugin, mod } = hooksWiring(host);
|
|
1767
|
+
if (plugin || mod !== undefined)
|
|
1768
|
+
return packages;
|
|
1769
|
+
return [...packages, { id: HOOKS, names: [HOOKS, "plugin", "cli-hooks"], label: "hooks", hint: `the Claude Code plugin running guard on every tool call, needs ${HOOKS_REQUIRES}`, packages: isInstalled(host, HOOKS_REQUIRES) ? [] : [HOOKS_REQUIRES] }];
|
|
1770
|
+
}
|
|
1771
|
+
function choose(pool, names) {
|
|
1772
|
+
const chosen = new Set;
|
|
1773
|
+
for (const name of names) {
|
|
1774
|
+
if (name === "all") {
|
|
1775
|
+
for (const each of pool)
|
|
1776
|
+
chosen.add(each);
|
|
1777
|
+
continue;
|
|
1778
|
+
}
|
|
1779
|
+
const found = pool.find((each) => each.names.includes(name));
|
|
1780
|
+
if (found !== undefined)
|
|
1781
|
+
chosen.add(found);
|
|
1782
|
+
}
|
|
1783
|
+
return [...chosen];
|
|
1784
|
+
}
|
|
1785
|
+
function plan(host, picked, force) {
|
|
1786
|
+
const blocked = [];
|
|
1787
|
+
const warnings = [];
|
|
1788
|
+
const packages = [];
|
|
1789
|
+
for (const name of new Set(picked.flatMap((each) => each.packages))) {
|
|
1790
|
+
const lacking = (NEEDS[name] ?? []).filter((need) => host.which(need.bin) === null);
|
|
1791
|
+
if (lacking.length > 0 && !force) {
|
|
1792
|
+
for (const need of lacking)
|
|
1793
|
+
blocked.push({ package: shortName(name), need: need.bin, why: need.why, how: need.how });
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
for (const need of lacking)
|
|
1797
|
+
warnings.push({ package: shortName(name), need: need.bin, why: need.why });
|
|
1798
|
+
packages.push(name);
|
|
1799
|
+
}
|
|
1800
|
+
const guardBlocked = blocked.some((each) => `@kaynooo/cli-${each.package}` === HOOKS_REQUIRES);
|
|
1801
|
+
return { packages, plugin: picked.some((each) => each.id === HOOKS) && !guardBlocked, blocked, warnings };
|
|
1802
|
+
}
|
|
1803
|
+
function checksFor(packages, plugin) {
|
|
1804
|
+
return [...packages.map((name) => name.slice(name.indexOf("/") + 1)), ...plugin ? ["hooks"] : [], "needs", "completions", ...packages.includes("@kaynooo/cli-forgejo") ? ["tea"] : []];
|
|
1805
|
+
}
|
|
1806
|
+
async function ask2(pool) {
|
|
1807
|
+
if (!isInteractive())
|
|
1808
|
+
badUsage("name what to install, or all: doctor --install forgejo check hooks (doctor --install --list shows the choices)");
|
|
1809
|
+
const choices = pool.map((each) => ({ value: each, label: each.label, hint: each.hint }));
|
|
1810
|
+
const picked = await checklist("install which?", choices, { hint: "name them instead: doctor --install forgejo check" });
|
|
1811
|
+
return picked === undefined ? [] : [...picked];
|
|
1812
|
+
}
|
|
1813
|
+
async function askWorkspace(host) {
|
|
1814
|
+
if (!isInteractive() || workspaceChosen({ home: host.home }))
|
|
1815
|
+
return;
|
|
1816
|
+
const folder = await input2("folder holding your repositories (repos, sweep and pkg bump look there)", { default: "~/Workspace" });
|
|
1817
|
+
if (folder !== undefined && folder !== "" && !saveWorkspace(folder, { home: host.home }))
|
|
1818
|
+
console.error(`could not write the workspace into ${userConfigPath("config", { home: host.home })}: it does not parse, fix it by hand`);
|
|
1819
|
+
}
|
|
1820
|
+
async function install(host, options, recheck) {
|
|
1821
|
+
const pool = candidates2(host);
|
|
1822
|
+
const { dim, green, red, yellow } = paint(process16.stdout);
|
|
1823
|
+
if (options.list) {
|
|
1824
|
+
for (const each of pool)
|
|
1825
|
+
console.log(`${each.label.padEnd(12)} ${dim(each.hint)}`);
|
|
1826
|
+
if (pool.length === 0)
|
|
1827
|
+
console.log("everything is installed");
|
|
1828
|
+
return 0;
|
|
1829
|
+
}
|
|
1830
|
+
if (pool.length === 0) {
|
|
1831
|
+
console.log("everything is installed");
|
|
1832
|
+
return 0;
|
|
1833
|
+
}
|
|
1834
|
+
const unknown = options.names.filter((name) => name !== "all" && !Object.values(candidatesAll(host)).some((each) => each.names.includes(name)));
|
|
1835
|
+
if (unknown.length > 0)
|
|
1836
|
+
badUsage(`nothing to install is named ${unknown.join(", ")}: doctor --install --list`);
|
|
1837
|
+
const picked = options.names.length === 0 ? await ask2(pool) : choose(pool, options.names);
|
|
1838
|
+
const skipped = options.names.filter((name) => name !== "all" && !pool.some((each) => each.names.includes(name)));
|
|
1839
|
+
for (const name of skipped)
|
|
1840
|
+
console.log(`${dim("already")} ${name} is installed`);
|
|
1841
|
+
if (picked.length === 0) {
|
|
1842
|
+
if (skipped.length === 0)
|
|
1843
|
+
console.log("nothing picked");
|
|
1844
|
+
return 0;
|
|
1845
|
+
}
|
|
1846
|
+
const planned = plan(host, picked, options.force);
|
|
1847
|
+
for (const each of planned.blocked)
|
|
1848
|
+
console.log(`${red("blocked")} ${each.package} needs ${each.need} (${each.why}): ${each.how}; --force installs it anyway`);
|
|
1849
|
+
for (const each of planned.warnings)
|
|
1850
|
+
console.log(`${yellow("warn ")} ${each.package}: ${each.need} is missing, ${each.why}`);
|
|
1851
|
+
if (planned.packages.length === 0 && !planned.plugin)
|
|
1852
|
+
return 1;
|
|
1853
|
+
if (!options.dryRun && planned.packages.some((name) => name === "@kaynooo/cli-workspace" || name === "@kaynooo/cli-pkg"))
|
|
1854
|
+
await askWorkspace(host);
|
|
1855
|
+
const command = ["bun", "install", "-g", ...planned.packages];
|
|
1856
|
+
if (planned.packages.length > 0)
|
|
1857
|
+
console.log(`${options.dryRun ? dim("would ") : green("install")} ${command.join(" ")}`);
|
|
1858
|
+
if (planned.plugin)
|
|
1859
|
+
console.log(`${options.dryRun ? dim("would ") : green("install")} ${PLUGIN_INSTALL.join(" ")}`);
|
|
1860
|
+
if (options.dryRun)
|
|
1861
|
+
return planned.blocked.length > 0 ? 1 : 0;
|
|
1862
|
+
if (planned.packages.length > 0) {
|
|
1863
|
+
const ran = await host.run(command);
|
|
1864
|
+
if (ran.code !== 0) {
|
|
1865
|
+
console.error(`${red("failed")} bun install -g: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
|
|
1866
|
+
return 1;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (planned.plugin) {
|
|
1870
|
+
if (host.which("claude") === null) {
|
|
1871
|
+
console.log(`${dim("not run")} claude is not on the PATH: ${PLUGIN_INSTALL.join(" ")}`);
|
|
1872
|
+
} else {
|
|
1873
|
+
const ran = await host.run(PLUGIN_INSTALL);
|
|
1874
|
+
if (ran.code !== 0)
|
|
1875
|
+
console.error(`${red("failed")} ${PLUGIN_INSTALL.join(" ")}: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
console.log(`
|
|
1879
|
+
after the install:`);
|
|
1880
|
+
const code = await recheck(checksFor(planned.packages, planned.plugin));
|
|
1881
|
+
return planned.blocked.length > 0 ? 1 : code;
|
|
1882
|
+
}
|
|
1883
|
+
function candidatesAll(host) {
|
|
1884
|
+
const open = candidates2(host);
|
|
1885
|
+
const done = Object.entries(TOOLS).filter(([name]) => isInstalled(host, name)).map(([name, bins]) => ({ id: name, names: [shortName(name), name, `cli-${shortName(name)}`, ...bins], label: shortName(name), hint: "", packages: [name] }));
|
|
1886
|
+
return [...open, ...done];
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1418
1889
|
// src/doctor.ts
|
|
1419
|
-
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
|
|
1890
|
+
var USAGE = `doctor [--only <check>] [--quiet] [--fix | --install [<name>...|all]] [--list] [--dry-run] [--force] [--json]: checks this host's @kaynooo/cli-* install, one line per check, the command that fixes a failing one beside it, and installs the packages it picks
|
|
1420
1891
|
|
|
1421
|
-
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.
|
|
1892
|
+
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 when ft is installed; the system tools the installed packages need (glab, docker); the Claude Code hooks wired through the cli-hooks plugin or the cli mod, not both, with guard installed; Bun at least the version the tools ask for; fish completions for every bin. A package not installed is no failure, the last line counts them.
|
|
1422
1893
|
--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.
|
|
1894
|
+
--install adds packages: the names given (a package such as forgejo, one of its bins such as ft, hooks for the Claude Code plugin, or all), else a checklist on a terminal. Each is checked against what it needs first: a missing required tool (tea for ft, glab for gl, docker for dc) leaves that package out unless --force, and hooks brings guard along. doctor never installs a system package. Then it checks and fixes what it installed. --list prints the choices, --dry-run what would be installed.
|
|
1423
1895
|
--quiet prints only the failing checks, nothing when all pass. --only limits the run to the checks named, e.g. --only tea --only bun.`;
|
|
1424
1896
|
var EXITS = [[0, "every check passes (after --fix, the ones it fixed too)"], [1, "a check fails"], [2, "a usage error"]];
|
|
1425
1897
|
var INSTALL2 = "bun install -g ";
|
|
@@ -1428,12 +1900,15 @@ var OPTIONS = {
|
|
|
1428
1900
|
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" },
|
|
1429
1901
|
quiet: { type: "boolean", short: "q", description: "print only the failing checks" },
|
|
1430
1902
|
fix: { type: "boolean", description: "run the fix: lines doctor may run, then check again" },
|
|
1431
|
-
"dry-run": { type: "boolean", description: "with --fix, show what would run without running it (alone, it implies --fix)" }
|
|
1903
|
+
"dry-run": { type: "boolean", description: "with --fix or --install, show what would run without running it (alone, it implies --fix)" },
|
|
1904
|
+
install: { type: "boolean", description: "install the packages named, or picked from a checklist: forgejo, ft, hooks, all" },
|
|
1905
|
+
list: { type: "boolean", description: "with --install, print the choices" },
|
|
1906
|
+
force: { type: "boolean", description: "with --install, install a package even though a tool it requires is missing" }
|
|
1432
1907
|
};
|
|
1433
1908
|
function print(results) {
|
|
1434
1909
|
if (results.length === 0)
|
|
1435
1910
|
return;
|
|
1436
|
-
const { green, red, dim } = paint(
|
|
1911
|
+
const { green, red, dim } = paint(process17.stdout);
|
|
1437
1912
|
const width = Math.max(...results.map((result) => result.check.length));
|
|
1438
1913
|
for (const { check, ok, detail, fix } of results)
|
|
1439
1914
|
console.log(`${ok ? green("ok ") : red("FAIL")} ${check.padEnd(width)} ${detail}${fix === undefined ? "" : ` ${dim("fix:")} ${fix}`}`);
|
|
@@ -1443,14 +1918,21 @@ function print(results) {
|
|
|
1443
1918
|
${dim("fix them all:")} ${INSTALL2}${installs.join(" ")}`);
|
|
1444
1919
|
}
|
|
1445
1920
|
function printOutcomes(outcomes, log) {
|
|
1446
|
-
const { green, red, dim } = paint(
|
|
1921
|
+
const { green, red, dim } = paint(process17.stdout);
|
|
1447
1922
|
const marks = { ran: green("ran "), failed: red("failed "), "dry-run": dim("would "), "not run": dim("not run") };
|
|
1448
1923
|
for (const { command, status, detail } of outcomes)
|
|
1449
1924
|
log(`${marks[status]} ${command}${detail === undefined ? "" : ` ${dim(detail)}`}`);
|
|
1450
1925
|
}
|
|
1451
1926
|
async function main(args, host = hostHere()) {
|
|
1452
|
-
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"]] });
|
|
1927
|
+
const { values, positionals } = parse(args, USAGE, OPTIONS, { positionals: ["[name...]"], 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"], ["doctor --install", "pick the packages to add"], ["doctor --install forgejo check hooks", "three named ones"], ["doctor --install all --dry-run", "what installing everything would run"]] });
|
|
1453
1928
|
const only = values.only ?? [];
|
|
1929
|
+
if (values.install !== true && (positionals.length > 0 || values.list === true || values.force === true))
|
|
1930
|
+
badUsage("names, --list and --force go with --install");
|
|
1931
|
+
if (values.install === true) {
|
|
1932
|
+
if (values.fix === true || only.length > 0 || values.json === true || values.quiet === true)
|
|
1933
|
+
badUsage("--install does not combine with --fix, --only, --json or --quiet");
|
|
1934
|
+
return install(host, { names: positionals, list: values.list === true, dryRun: values["dry-run"] === true, force: values.force === true }, async (checks) => main(["--fix", ...checks.flatMap((check) => ["--only", check])], host));
|
|
1935
|
+
}
|
|
1454
1936
|
const dry = values["dry-run"] === true;
|
|
1455
1937
|
const json = values.json === true;
|
|
1456
1938
|
let results = await runChecks(host, only);
|
|
@@ -1473,6 +1955,10 @@ after the fixes:`);
|
|
|
1473
1955
|
printJson(shown);
|
|
1474
1956
|
else
|
|
1475
1957
|
print(shown);
|
|
1958
|
+
const absent = Object.keys(TOOLS).filter((name) => !isInstalled(host, name)).length;
|
|
1959
|
+
if (!json && only.length === 0 && absent > 0)
|
|
1960
|
+
console.log(`
|
|
1961
|
+
${paint(process17.stdout).dim(`${absent} packages not installed: doctor --install`)}`);
|
|
1476
1962
|
return results.every((result) => result.ok) ? 0 : 1;
|
|
1477
1963
|
}
|
|
1478
1964
|
|
|
@@ -1501,7 +1987,7 @@ var LIST_OPTIONS = {
|
|
|
1501
1987
|
json: jsonOption("[{bin, package, installed, synopsis?, summary?, replaces?, examples?}]")
|
|
1502
1988
|
};
|
|
1503
1989
|
function print2(listed) {
|
|
1504
|
-
const { cyan, dim } = paint(
|
|
1990
|
+
const { cyan, dim } = paint(process18.stdout);
|
|
1505
1991
|
const width = Math.max(...listed.map((each) => each.bin.length));
|
|
1506
1992
|
for (const each of listed) {
|
|
1507
1993
|
const what = !each.installed ? dim(`not installed: bun install -g ${each.package}`) : `${each.summary ?? ""}${each.replaces === undefined ? "" : dim(`; instead of ${each.replaces}`)}`;
|
|
@@ -1584,7 +2070,7 @@ ${table(summary.usageErrors.map((each) => [`${each.bin}${each.command === undefi
|
|
|
1584
2070
|
return 0;
|
|
1585
2071
|
}
|
|
1586
2072
|
if (import.meta.main) {
|
|
1587
|
-
|
|
2073
|
+
process18.exitCode = await runCli({
|
|
1588
2074
|
name: "cli",
|
|
1589
2075
|
about: "the bins of the @kaynooo/cli-* packages, described by their own help",
|
|
1590
2076
|
replaces: "running each bin's --help to find the one that fits",
|