@kaynooo/cli-doctor 0.3.0 → 0.4.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 +8 -0
- package/dist/cli-bin.js +300 -91
- package/dist/cli.js +268 -90
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,6 +17,14 @@ doctor --json # [{check, ok, detail, fix?}]
|
|
|
17
17
|
|
|
18
18
|
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
19
|
|
|
20
|
+
## cli stats
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
cli stats [--since 7d] [--json] # runs per bin, failures, and the options usage errors did not know
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Every bin records each run in `~/.local/state/kaynooo-cli/usage.jsonl`: the bin, its command, the flag names it saw (never values or arguments), the exit code and, on a usage error, the unknown option. `KAYNOOO_CLI_NO_USAGE=1` turns it off; `guard` never records.
|
|
27
|
+
|
|
20
28
|
## cli list
|
|
21
29
|
|
|
22
30
|
```sh
|
package/dist/cli-bin.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli-bin.ts
|
|
5
|
-
import
|
|
5
|
+
import process11 from "process";
|
|
6
6
|
|
|
7
7
|
// ../kit/src/args.ts
|
|
8
|
-
import
|
|
8
|
+
import process4 from "process";
|
|
9
9
|
import { parseArgs } from "util";
|
|
10
10
|
|
|
11
|
+
// ../kit/src/format.ts
|
|
12
|
+
import process2 from "process";
|
|
13
|
+
|
|
11
14
|
// ../kit/src/style.ts
|
|
12
15
|
import process from "process";
|
|
13
16
|
|
|
@@ -293,11 +296,102 @@ function upTo(text, separator) {
|
|
|
293
296
|
const end = text.indexOf(separator);
|
|
294
297
|
return end === -1 ? text : text.slice(0, end);
|
|
295
298
|
}
|
|
299
|
+
function truncate(text, width) {
|
|
300
|
+
return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
301
|
+
}
|
|
302
|
+
var LINE_BREAKS = /\s*\n\s*/g;
|
|
296
303
|
var ESCAPE = String.fromCharCode(27);
|
|
304
|
+
function textWidth(text) {
|
|
305
|
+
return Bun.stringWidth(text);
|
|
306
|
+
}
|
|
307
|
+
function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
308
|
+
const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
|
|
309
|
+
const widths = [];
|
|
310
|
+
for (const row of cells) {
|
|
311
|
+
for (const [index, cell] of row.entries())
|
|
312
|
+
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
313
|
+
}
|
|
314
|
+
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
315
|
+
const colors = paint(process2.stdout);
|
|
316
|
+
const lines = shown.map((row) => row.map((cell, index) => {
|
|
317
|
+
const width = widths[index] ?? 0;
|
|
318
|
+
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
319
|
+
if (align[index] === "right")
|
|
320
|
+
return `${gap}${cell}`;
|
|
321
|
+
return index === row.length - 1 ? cell : `${cell}${gap}`;
|
|
322
|
+
}).join(" ").trimEnd());
|
|
323
|
+
return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
|
|
324
|
+
`);
|
|
325
|
+
}
|
|
326
|
+
function narrow(rows, widths, limit) {
|
|
327
|
+
const used = () => widths.reduce((sum, width) => sum + width, 0) + 2 * Math.max(0, widths.length - 1);
|
|
328
|
+
const cuttable = (index) => rows.every((row) => !(row[index] ?? "").includes(ESCAPE));
|
|
329
|
+
const floor = 8;
|
|
330
|
+
while (used() > limit) {
|
|
331
|
+
let widest = -1;
|
|
332
|
+
for (const [index, width] of widths.entries()) {
|
|
333
|
+
if (cuttable(index) && width > floor && width > (widths[widest] ?? 0))
|
|
334
|
+
widest = index;
|
|
335
|
+
}
|
|
336
|
+
if (widest === -1)
|
|
337
|
+
break;
|
|
338
|
+
widths[widest] = Math.max(floor, (widths[widest] ?? 0) - (used() - limit));
|
|
339
|
+
}
|
|
340
|
+
return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
|
|
341
|
+
}
|
|
297
342
|
function printJson(value) {
|
|
298
343
|
console.log(JSON.stringify(value, undefined, 2));
|
|
299
344
|
}
|
|
300
345
|
|
|
346
|
+
// ../kit/src/help.ts
|
|
347
|
+
import process3 from "process";
|
|
348
|
+
var OPTIONAL = /\[[^\]]+\]/g;
|
|
349
|
+
var USAGE_PREFIX = /^usage:\s*/i;
|
|
350
|
+
var FULL_STOP = /\.$/;
|
|
351
|
+
function usageLine(usage, colors = paint(process3.stdout)) {
|
|
352
|
+
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
353
|
+
}
|
|
354
|
+
function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces } = {}) {
|
|
355
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
356
|
+
`);
|
|
357
|
+
}
|
|
358
|
+
function replacesRows(replaces, colors = paint(process3.stdout)) {
|
|
359
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
360
|
+
}
|
|
361
|
+
function wantsHelpJson(env = process3.env) {
|
|
362
|
+
return env.KIT_HELP === "json";
|
|
363
|
+
}
|
|
364
|
+
function description(usage, { examples = [], replaces } = {}) {
|
|
365
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
366
|
+
`);
|
|
367
|
+
const first = lines[0];
|
|
368
|
+
const colon = first.indexOf(": ");
|
|
369
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
370
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
371
|
+
return {
|
|
372
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
373
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
374
|
+
...replaces === undefined ? {} : { replaces },
|
|
375
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function exampleRows(examples, colors = paint(process3.stdout)) {
|
|
379
|
+
if (examples.length === 0)
|
|
380
|
+
return [];
|
|
381
|
+
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
382
|
+
}
|
|
383
|
+
function optionRows(options, colors = paint(process3.stdout)) {
|
|
384
|
+
const rows = Object.entries(options).map(([name, option]) => {
|
|
385
|
+
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
386
|
+
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
387
|
+
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
388
|
+
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
389
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
390
|
+
});
|
|
391
|
+
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
392
|
+
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
301
395
|
// ../kit/src/text.ts
|
|
302
396
|
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
303
397
|
function distance(a, b) {
|
|
@@ -317,8 +411,21 @@ function closest(name, names) {
|
|
|
317
411
|
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
318
412
|
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
319
413
|
}
|
|
414
|
+
// ../kit/src/input.ts
|
|
415
|
+
var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
|
|
416
|
+
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
417
|
+
function duration(value, name, fallback, unit = "s") {
|
|
418
|
+
if (value === undefined)
|
|
419
|
+
return fallback;
|
|
420
|
+
const match = DURATION.exec(value.trim());
|
|
421
|
+
const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
|
|
422
|
+
if (ms < 1)
|
|
423
|
+
badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
|
|
424
|
+
return ms;
|
|
425
|
+
}
|
|
320
426
|
|
|
321
427
|
// ../kit/src/args.ts
|
|
428
|
+
var DASHES = /^-+/;
|
|
322
429
|
function jsonOption(holds) {
|
|
323
430
|
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
324
431
|
}
|
|
@@ -358,58 +465,11 @@ class Completing {
|
|
|
358
465
|
}
|
|
359
466
|
}
|
|
360
467
|
var completion = { active: false };
|
|
361
|
-
var OPTIONAL = /\[[^\]]+\]/g;
|
|
362
|
-
var USAGE_PREFIX = /^usage:\s*/i;
|
|
363
|
-
var FULL_STOP = /\.$/;
|
|
364
|
-
var DASHES = /^-+/;
|
|
365
|
-
function usageLine(usage, colors = paint(process2.stdout)) {
|
|
366
|
-
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
367
|
-
}
|
|
368
|
-
function helpText(usage, options, colors = paint(process2.stdout), { examples = [], replaces } = {}) {
|
|
369
|
-
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
370
|
-
`);
|
|
371
|
-
}
|
|
372
|
-
function replacesRows(replaces, colors = paint(process2.stdout)) {
|
|
373
|
-
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
374
|
-
}
|
|
375
|
-
function wantsHelpJson(env = process2.env) {
|
|
376
|
-
return env.KIT_HELP === "json";
|
|
377
|
-
}
|
|
378
|
-
function description(usage, { examples = [], replaces } = {}) {
|
|
379
|
-
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
380
|
-
`);
|
|
381
|
-
const first = lines[0];
|
|
382
|
-
const colon = first.indexOf(": ");
|
|
383
|
-
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
384
|
-
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
385
|
-
return {
|
|
386
|
-
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
387
|
-
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
388
|
-
...replaces === undefined ? {} : { replaces },
|
|
389
|
-
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
function exampleRows(examples, colors = paint(process2.stdout)) {
|
|
393
|
-
if (examples.length === 0)
|
|
394
|
-
return [];
|
|
395
|
-
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
396
|
-
}
|
|
397
|
-
function optionRows(options, colors = paint(process2.stdout)) {
|
|
398
|
-
const rows = Object.entries(options).map(([name, option]) => {
|
|
399
|
-
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
400
|
-
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
401
|
-
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
402
|
-
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
403
|
-
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
404
|
-
});
|
|
405
|
-
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
406
|
-
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
407
|
-
}
|
|
408
468
|
var OUTPUT_OPTIONS = {
|
|
409
469
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
410
470
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
411
471
|
};
|
|
412
|
-
function applyOutput(color, plain, env =
|
|
472
|
+
function applyOutput(color, plain, env = process4.env) {
|
|
413
473
|
const when = plain === true ? "never" : color ?? "auto";
|
|
414
474
|
if (when === "auto")
|
|
415
475
|
return;
|
|
@@ -568,7 +628,7 @@ function nearestOption(message, options, allowNegative) {
|
|
|
568
628
|
}
|
|
569
629
|
// ../kit/src/process.ts
|
|
570
630
|
import { constants } from "os";
|
|
571
|
-
import
|
|
631
|
+
import process5 from "process";
|
|
572
632
|
var KILL_GRACE_MS = 2000;
|
|
573
633
|
function exitCodeOf(code, signal) {
|
|
574
634
|
if (code !== null)
|
|
@@ -588,7 +648,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
|
|
|
588
648
|
return;
|
|
589
649
|
try {
|
|
590
650
|
if (grouped)
|
|
591
|
-
|
|
651
|
+
process5.kill(-proc.pid, killing);
|
|
592
652
|
else
|
|
593
653
|
proc.kill(killing);
|
|
594
654
|
} catch {}
|
|
@@ -662,7 +722,95 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
|
|
|
662
722
|
var RESET_STYLE = `${ESC}[0m`;
|
|
663
723
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
664
724
|
// ../kit/src/cli.ts
|
|
665
|
-
import
|
|
725
|
+
import process7 from "process";
|
|
726
|
+
|
|
727
|
+
// ../kit/src/usage.ts
|
|
728
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
729
|
+
import { homedir } from "os";
|
|
730
|
+
import { dirname, isAbsolute, join } from "path";
|
|
731
|
+
import process6 from "process";
|
|
732
|
+
|
|
733
|
+
// ../kit/src/logs.ts
|
|
734
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
735
|
+
var CLI_FOLDER = "kaynooo-cli";
|
|
736
|
+
|
|
737
|
+
// ../kit/src/usage.ts
|
|
738
|
+
var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
|
|
739
|
+
var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
|
|
740
|
+
var LONGEST_NAME = 40;
|
|
741
|
+
var usageError;
|
|
742
|
+
function noteUsageError(message) {
|
|
743
|
+
usageError = message;
|
|
744
|
+
}
|
|
745
|
+
function stateHome(env = process6.env) {
|
|
746
|
+
const dir = env.XDG_STATE_HOME;
|
|
747
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
748
|
+
}
|
|
749
|
+
function usagePath(env = process6.env) {
|
|
750
|
+
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
751
|
+
}
|
|
752
|
+
function usageRecorded(env = process6.env) {
|
|
753
|
+
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
754
|
+
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
755
|
+
}
|
|
756
|
+
function flagNames(argv) {
|
|
757
|
+
const end = argv.indexOf("--");
|
|
758
|
+
const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
|
|
759
|
+
return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
|
|
760
|
+
}
|
|
761
|
+
function usageEntry(bin, command, argv, code, now = new Date) {
|
|
762
|
+
const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
|
|
763
|
+
usageError = undefined;
|
|
764
|
+
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
765
|
+
}
|
|
766
|
+
function recordUsage(entry, env = process6.env) {
|
|
767
|
+
if (!usageRecorded(env))
|
|
768
|
+
return;
|
|
769
|
+
try {
|
|
770
|
+
const path = usagePath(env);
|
|
771
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
772
|
+
appendFileSync(path, `${JSON.stringify(entry)}
|
|
773
|
+
`, { mode: 384 });
|
|
774
|
+
} catch {}
|
|
775
|
+
}
|
|
776
|
+
async function readUsage(path = usagePath(), since = 0) {
|
|
777
|
+
const file = Bun.file(path);
|
|
778
|
+
if (!await file.exists())
|
|
779
|
+
return [];
|
|
780
|
+
return (await file.text()).split(`
|
|
781
|
+
`).flatMap((line) => {
|
|
782
|
+
try {
|
|
783
|
+
const entry = JSON.parse(line);
|
|
784
|
+
return Date.parse(entry.at) >= since ? [entry] : [];
|
|
785
|
+
} catch {
|
|
786
|
+
return [];
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function summarizeUsage(entries) {
|
|
791
|
+
const bins = new Map;
|
|
792
|
+
const errors = new Map;
|
|
793
|
+
for (const entry of entries) {
|
|
794
|
+
const bin = bins.get(entry.bin) ?? { runs: 0, failed: 0, usageErrors: 0 };
|
|
795
|
+
bin.runs++;
|
|
796
|
+
bin.failed += entry.code === 0 ? 0 : 1;
|
|
797
|
+
bin.usageErrors += entry.code === 2 ? 1 : 0;
|
|
798
|
+
bins.set(entry.bin, bin);
|
|
799
|
+
if (entry.code === 2 && entry.unknown !== undefined) {
|
|
800
|
+
const key = `${entry.bin} ${entry.command ?? ""} ${entry.unknown}`;
|
|
801
|
+
const error = errors.get(key) ?? { bin: entry.bin, ...entry.command === undefined ? {} : { command: entry.command }, unknown: entry.unknown, count: 0 };
|
|
802
|
+
error.count++;
|
|
803
|
+
errors.set(key, error);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
runs: entries.length,
|
|
808
|
+
bins: [...bins].map(([bin, counts]) => ({ bin, ...counts })).toSorted((a, b) => b.runs - a.runs),
|
|
809
|
+
usageErrors: [...errors.values()].toSorted((a, b) => b.count - a.count)
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// ../kit/src/cli.ts
|
|
666
814
|
var LEVEL_OPTIONS = {
|
|
667
815
|
...OUTPUT_OPTIONS,
|
|
668
816
|
help: { type: "boolean", short: "h", description: "show this help" }
|
|
@@ -715,7 +863,7 @@ async function expand(scope, words) {
|
|
|
715
863
|
}
|
|
716
864
|
return { ...scope, commands, lazy: [] };
|
|
717
865
|
}
|
|
718
|
-
async function
|
|
866
|
+
async function help2({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
|
|
719
867
|
const colors = paint(stream);
|
|
720
868
|
const prefix = path.join(" ");
|
|
721
869
|
const summary = await resolveSummary(lazySummary);
|
|
@@ -742,8 +890,8 @@ async function help({ path, header, summary: lazySummary, commands, defaultComma
|
|
|
742
890
|
`);
|
|
743
891
|
}
|
|
744
892
|
function printError(prefix, message, hint, cause) {
|
|
745
|
-
const colors = paint(
|
|
746
|
-
const styled = takesControlCodes(
|
|
893
|
+
const colors = paint(process7.stderr);
|
|
894
|
+
const styled = takesControlCodes(process7.stderr);
|
|
747
895
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
748
896
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
749
897
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -762,6 +910,8 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
|
762
910
|
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
763
911
|
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
764
912
|
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
913
|
+
if (error instanceof UsageError)
|
|
914
|
+
noteUsageError(error.message);
|
|
765
915
|
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
766
916
|
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
767
917
|
return error instanceof Exit ? error.code : 1;
|
|
@@ -784,11 +934,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
784
934
|
}
|
|
785
935
|
}
|
|
786
936
|
if (name === undefined && level.path.length > 1) {
|
|
787
|
-
console.error(await
|
|
937
|
+
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process7.stderr));
|
|
788
938
|
return 2;
|
|
789
939
|
}
|
|
790
940
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
791
|
-
console.log(await
|
|
941
|
+
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process7.stdout));
|
|
792
942
|
return 0;
|
|
793
943
|
}
|
|
794
944
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -904,26 +1054,49 @@ async function printCandidates(found) {
|
|
|
904
1054
|
console.log(line);
|
|
905
1055
|
return 0;
|
|
906
1056
|
}
|
|
907
|
-
async function runCli(cli, argv =
|
|
1057
|
+
async function runCli(cli, argv = process7.argv.slice(2)) {
|
|
908
1058
|
if (argv[0] === COMPLETE)
|
|
909
1059
|
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
910
|
-
|
|
1060
|
+
const code = await dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${about}`, summary: cli.about, ...scopeOf(cli.commands, cli.extraCommands, cli.aliases), defaultCommand: cli.defaultCommand, runBare: cli.runBare === true, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
|
|
1061
|
+
if (cli.usage !== false) {
|
|
1062
|
+
try {
|
|
1063
|
+
recordUsage(usageEntry(cli.name, commandPath(scopeOf(cli.commands, cli.extraCommands, cli.aliases), argv), argv, code));
|
|
1064
|
+
} catch {}
|
|
1065
|
+
}
|
|
1066
|
+
return code;
|
|
1067
|
+
}
|
|
1068
|
+
function commandPath(scope, argv) {
|
|
1069
|
+
const path = [];
|
|
1070
|
+
let level = scope;
|
|
1071
|
+
for (const word of argv.filter((each) => !each.startsWith("-"))) {
|
|
1072
|
+
const name = level.aliases !== undefined && Object.hasOwn(level.aliases, word) ? level.aliases[word] ?? word : word;
|
|
1073
|
+
const command = Object.hasOwn(level.commands, name) ? level.commands[name] : undefined;
|
|
1074
|
+
if (command === undefined)
|
|
1075
|
+
break;
|
|
1076
|
+
path.push(word);
|
|
1077
|
+
if (!("commands" in command))
|
|
1078
|
+
break;
|
|
1079
|
+
level = scopeOf(command.commands, command.extraCommands, command.aliases);
|
|
1080
|
+
}
|
|
1081
|
+
return path.length === 0 ? undefined : path.join(" ");
|
|
911
1082
|
}
|
|
912
|
-
async function runMain({ name, version, run, redact }, argv =
|
|
1083
|
+
async function runMain({ name, version, run, redact, usage }, argv = process7.argv.slice(2)) {
|
|
913
1084
|
if (argv[0] === COMPLETE)
|
|
914
1085
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
915
1086
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
916
1087
|
console.log(version);
|
|
917
1088
|
return 0;
|
|
918
1089
|
}
|
|
1090
|
+
let code;
|
|
919
1091
|
try {
|
|
920
|
-
|
|
1092
|
+
code = await run(argv);
|
|
921
1093
|
} catch (error) {
|
|
922
|
-
|
|
1094
|
+
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
923
1095
|
}
|
|
1096
|
+
if (usage !== false)
|
|
1097
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
1098
|
+
return code;
|
|
924
1099
|
}
|
|
925
|
-
// ../kit/src/logs.ts
|
|
926
|
-
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
927
1100
|
// ../kit/src/secrets.ts
|
|
928
1101
|
var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
|
|
929
1102
|
// ../kit/src/sessions.ts
|
|
@@ -931,16 +1104,16 @@ var {Glob } = globalThis.Bun;
|
|
|
931
1104
|
var SESSION_FILES = new Glob("*.json");
|
|
932
1105
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
933
1106
|
// package.json
|
|
934
|
-
var version = "0.
|
|
1107
|
+
var version = "0.4.0";
|
|
935
1108
|
|
|
936
1109
|
// src/completions.ts
|
|
937
|
-
import { existsSync as existsSync2, mkdirSync, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
938
|
-
import { join as
|
|
939
|
-
import
|
|
1110
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
1111
|
+
import { join as join3 } from "path";
|
|
1112
|
+
import process9 from "process";
|
|
940
1113
|
|
|
941
1114
|
// src/checks.ts
|
|
942
1115
|
import { existsSync, readFileSync } from "fs";
|
|
943
|
-
import { basename, join } from "path";
|
|
1116
|
+
import { basename, join as join2 } from "path";
|
|
944
1117
|
var TOOLS = {
|
|
945
1118
|
"@kaynooo/cli-base": ["base", "proof"],
|
|
946
1119
|
"@kaynooo/cli-changelog": ["changelog"],
|
|
@@ -955,8 +1128,13 @@ var TOOLS = {
|
|
|
955
1128
|
"@kaynooo/cli-gitlab": ["gl"],
|
|
956
1129
|
"@kaynooo/cli-guard": ["guard"],
|
|
957
1130
|
"@kaynooo/cli-out": ["out"],
|
|
1131
|
+
"@kaynooo/cli-dev": ["dev"],
|
|
1132
|
+
"@kaynooo/cli-pick": ["pick"],
|
|
958
1133
|
"@kaynooo/cli-pkg": ["pkg"],
|
|
1134
|
+
"@kaynooo/cli-probe": ["probe"],
|
|
1135
|
+
"@kaynooo/cli-repos": ["repos"],
|
|
959
1136
|
"@kaynooo/cli-stack": ["stack"],
|
|
1137
|
+
"@kaynooo/cli-sweep": ["sweep"],
|
|
960
1138
|
"@kaynooo/cli-view": ["view"],
|
|
961
1139
|
"@kaynooo/cli-wait": ["waitfor", "slot"]
|
|
962
1140
|
};
|
|
@@ -984,12 +1162,12 @@ function firstLine(text) {
|
|
|
984
1162
|
return end === -1 ? trimmed : trimmed.slice(0, end);
|
|
985
1163
|
}
|
|
986
1164
|
function installed(host, name) {
|
|
987
|
-
return readJson(
|
|
1165
|
+
return readJson(join2(host.globalModules, name, "package.json"));
|
|
988
1166
|
}
|
|
989
1167
|
function ownBin(host, name, bin) {
|
|
990
1168
|
const declared = installed(host, name)?.bin;
|
|
991
1169
|
const file = typeof declared === "string" ? declared : declared?.[bin];
|
|
992
|
-
return file === undefined ? undefined :
|
|
1170
|
+
return file === undefined ? undefined : join2(host.globalModules, name, file);
|
|
993
1171
|
}
|
|
994
1172
|
async function checkTool(host, name, bins) {
|
|
995
1173
|
const check = name.slice(name.indexOf("/") + 1);
|
|
@@ -998,7 +1176,7 @@ async function checkTool(host, name, bins) {
|
|
|
998
1176
|
return { check, ok: false, detail: "not installed", fix: `bun install -g ${name}` };
|
|
999
1177
|
const off = bins.filter((bin) => host.which(bin) === null);
|
|
1000
1178
|
if (off.length > 0)
|
|
1001
|
-
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${
|
|
1179
|
+
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join2(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
|
|
1002
1180
|
const info = await host.run(["bun", "info", name, "version", "--no-cache"]);
|
|
1003
1181
|
const answered = info.code === 0 ? firstLine(info.stdout.trim().split(`
|
|
1004
1182
|
`).at(-1) ?? "") : "";
|
|
@@ -1025,9 +1203,9 @@ async function checkTea(host) {
|
|
|
1025
1203
|
return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
|
|
1026
1204
|
}
|
|
1027
1205
|
function checkHooks(host) {
|
|
1028
|
-
const settings = readJson(
|
|
1206
|
+
const settings = readJson(join2(host.home, ".claude", "settings.json")) ?? {};
|
|
1029
1207
|
const plugin = settings.enabledPlugins?.[PLUGIN] === true;
|
|
1030
|
-
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync(
|
|
1208
|
+
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync(join2(dir, "hooks", "hooks.json")));
|
|
1031
1209
|
if (plugin && mod !== undefined)
|
|
1032
1210
|
return { check: "hooks", ok: false, detail: `both the ${PLUGIN} plugin and the mod in ${mod} run guard, so every call is checked twice`, fix: `claude plugin disable ${PLUGIN}` };
|
|
1033
1211
|
if (plugin)
|
|
@@ -1074,8 +1252,8 @@ var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_c
|
|
|
1074
1252
|
end
|
|
1075
1253
|
end
|
|
1076
1254
|
end`;
|
|
1077
|
-
function completionDir(home, env =
|
|
1078
|
-
return
|
|
1255
|
+
function completionDir(home, env = process9.env) {
|
|
1256
|
+
return join3(env.XDG_CACHE_HOME ?? join3(home, ".cache"), "fish", "kaynooo-cli");
|
|
1079
1257
|
}
|
|
1080
1258
|
function installedBins(host) {
|
|
1081
1259
|
return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
|
|
@@ -1088,31 +1266,31 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
|
|
|
1088
1266
|
}
|
|
1089
1267
|
async function installCompletions(host, dir) {
|
|
1090
1268
|
const bins = installedBins(host);
|
|
1091
|
-
|
|
1092
|
-
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync2(
|
|
1269
|
+
mkdirSync2(dir, { recursive: true });
|
|
1270
|
+
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync2(join3(dir, name), "utf8").startsWith(MARKER));
|
|
1093
1271
|
for (const name of removed)
|
|
1094
|
-
rmSync(
|
|
1095
|
-
await Promise.all(bins.map(async (bin) => Bun.write(
|
|
1272
|
+
rmSync(join3(dir, name));
|
|
1273
|
+
await Promise.all(bins.map(async (bin) => Bun.write(join3(dir, `${bin}.fish`), completionFile(bin))));
|
|
1096
1274
|
return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
|
|
1097
1275
|
}
|
|
1098
1276
|
function missingCompletions(host, dir) {
|
|
1099
|
-
return installedBins(host).filter((bin) => !existsSync2(
|
|
1277
|
+
return installedBins(host).filter((bin) => !existsSync2(join3(dir, `${bin}.fish`)));
|
|
1100
1278
|
}
|
|
1101
1279
|
|
|
1102
1280
|
// src/host.ts
|
|
1103
|
-
import { homedir } from "os";
|
|
1104
|
-
import { join as
|
|
1105
|
-
import
|
|
1281
|
+
import { homedir as homedir2 } from "os";
|
|
1282
|
+
import { join as join4 } from "path";
|
|
1283
|
+
import process10 from "process";
|
|
1106
1284
|
var RUN_TIMEOUT_MS = 20000;
|
|
1107
1285
|
function hostHere() {
|
|
1108
|
-
const home =
|
|
1286
|
+
const home = homedir2();
|
|
1109
1287
|
return {
|
|
1110
1288
|
home,
|
|
1111
|
-
globalModules:
|
|
1289
|
+
globalModules: join4(process10.env.BUN_INSTALL ?? join4(home, ".bun"), "install", "global", "node_modules"),
|
|
1112
1290
|
bunVersion: Bun.version,
|
|
1113
1291
|
which: (bin) => Bun.which(bin),
|
|
1114
1292
|
run: async (argv, env) => {
|
|
1115
|
-
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...
|
|
1293
|
+
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process10.env, ...env } } });
|
|
1116
1294
|
if (ran.timedOut)
|
|
1117
1295
|
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
1118
1296
|
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
@@ -1146,7 +1324,7 @@ var LIST_OPTIONS = {
|
|
|
1146
1324
|
json: jsonOption("[{bin, package, installed, synopsis?, summary?, replaces?, examples?}]")
|
|
1147
1325
|
};
|
|
1148
1326
|
function print(listed) {
|
|
1149
|
-
const { cyan, dim } = paint(
|
|
1327
|
+
const { cyan, dim } = paint(process11.stdout);
|
|
1150
1328
|
const width = Math.max(...listed.map((each) => each.bin.length));
|
|
1151
1329
|
for (const each of listed) {
|
|
1152
1330
|
const what = !each.installed ? dim(`not installed: bun install -g ${each.package}`) : `${each.summary ?? ""}${each.replaces === undefined ? "" : dim(`; instead of ${each.replaces}`)}`;
|
|
@@ -1184,8 +1362,37 @@ async function completions(args, host = hostHere()) {
|
|
|
1184
1362
|
console.log(`wrote ${written.length} completions to ${dir}${removed.length === 0 ? "" : `, took out ${removed.join(", ")}`}`);
|
|
1185
1363
|
return 0;
|
|
1186
1364
|
}
|
|
1365
|
+
var STATS_USAGE = `cli stats [--since 7d] [--json]: how often each bin ran, and the usage errors by the option it did not know
|
|
1366
|
+
|
|
1367
|
+
Every bin records each run in ~/.local/state/kaynooo-cli/usage.jsonl (or $XDG_STATE_HOME): the bin, its command, the names of the flags it saw (never a value or an argument), the exit code and, on a usage error, the unknown option. Set KAYNOOO_CLI_NO_USAGE=1 to stop recording; \`guard\` never records.
|
|
1368
|
+
Exits 0 when it printed the summary, 2 on a usage error.`;
|
|
1369
|
+
var STATS_OPTIONS = {
|
|
1370
|
+
since: { type: "string", description: "only the runs this recent (7d, 12h)" },
|
|
1371
|
+
json: jsonOption("{runs, bins: [{bin, runs, failed, usageErrors}], usageErrors: [{bin, command?, unknown, count}]}")
|
|
1372
|
+
};
|
|
1373
|
+
async function stats(args, path = usagePath()) {
|
|
1374
|
+
const { values } = parse(args, STATS_USAGE, STATS_OPTIONS, { positionals: [], examples: ["cli stats", ["cli stats --since 7d", "the last week"]] });
|
|
1375
|
+
const window = values.since === undefined ? undefined : duration(values.since, "since", 0, "h");
|
|
1376
|
+
const summary = summarizeUsage(await readUsage(path, window === undefined ? 0 : Date.now() - window));
|
|
1377
|
+
if (values.json === true) {
|
|
1378
|
+
printJson(summary);
|
|
1379
|
+
return 0;
|
|
1380
|
+
}
|
|
1381
|
+
if (summary.runs === 0) {
|
|
1382
|
+
console.log("no run recorded");
|
|
1383
|
+
return 0;
|
|
1384
|
+
}
|
|
1385
|
+
console.log(`${summary.runs} runs
|
|
1386
|
+
|
|
1387
|
+
${table(summary.bins.map((each) => [each.bin, String(each.runs), `${each.failed} failed`, `${each.usageErrors} usage errors`]))}`);
|
|
1388
|
+
if (summary.usageErrors.length > 0)
|
|
1389
|
+
console.log(`
|
|
1390
|
+
unknown options
|
|
1391
|
+
${table(summary.usageErrors.map((each) => [`${each.bin}${each.command === undefined ? "" : ` ${each.command}`}`, each.unknown, `${each.count}x`]))}`);
|
|
1392
|
+
return 0;
|
|
1393
|
+
}
|
|
1187
1394
|
if (import.meta.main) {
|
|
1188
|
-
|
|
1395
|
+
process11.exitCode = await runCli({
|
|
1189
1396
|
name: "cli",
|
|
1190
1397
|
version,
|
|
1191
1398
|
about: "the bins of the @kaynooo/cli-* packages, described by their own help",
|
|
@@ -1193,11 +1400,13 @@ if (import.meta.main) {
|
|
|
1193
1400
|
examples: ["cli list", ["cli list --json", "with each bin's synopsis and examples"]],
|
|
1194
1401
|
commands: {
|
|
1195
1402
|
list: { summary: "every bin in one line: what it does and what it is reached for instead of", run: async (args) => list(args) },
|
|
1403
|
+
stats: { summary: "how often each bin ran and the options usage errors did not know", run: async (args) => stats(args) },
|
|
1196
1404
|
completions: { summary: "fish completions for every installed bin, printed or written to the folder fish reads", run: async (args) => completions(args) }
|
|
1197
1405
|
}
|
|
1198
1406
|
});
|
|
1199
1407
|
}
|
|
1200
1408
|
export {
|
|
1201
1409
|
completions,
|
|
1202
|
-
list
|
|
1410
|
+
list,
|
|
1411
|
+
stats
|
|
1203
1412
|
};
|
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import
|
|
5
|
+
import process11 from "process";
|
|
6
6
|
|
|
7
7
|
// ../kit/src/args.ts
|
|
8
|
-
import
|
|
8
|
+
import process4 from "process";
|
|
9
9
|
import { parseArgs } from "util";
|
|
10
10
|
|
|
11
|
+
// ../kit/src/format.ts
|
|
12
|
+
import process2 from "process";
|
|
13
|
+
|
|
11
14
|
// ../kit/src/style.ts
|
|
12
15
|
import process from "process";
|
|
13
16
|
|
|
@@ -293,11 +296,102 @@ function upTo(text, separator) {
|
|
|
293
296
|
const end = text.indexOf(separator);
|
|
294
297
|
return end === -1 ? text : text.slice(0, end);
|
|
295
298
|
}
|
|
299
|
+
function truncate(text, width) {
|
|
300
|
+
return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
301
|
+
}
|
|
302
|
+
var LINE_BREAKS = /\s*\n\s*/g;
|
|
296
303
|
var ESCAPE = String.fromCharCode(27);
|
|
304
|
+
function textWidth(text) {
|
|
305
|
+
return Bun.stringWidth(text);
|
|
306
|
+
}
|
|
307
|
+
function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
308
|
+
const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
|
|
309
|
+
const widths = [];
|
|
310
|
+
for (const row of cells) {
|
|
311
|
+
for (const [index, cell] of row.entries())
|
|
312
|
+
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
313
|
+
}
|
|
314
|
+
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
315
|
+
const colors = paint(process2.stdout);
|
|
316
|
+
const lines = shown.map((row) => row.map((cell, index) => {
|
|
317
|
+
const width = widths[index] ?? 0;
|
|
318
|
+
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
319
|
+
if (align[index] === "right")
|
|
320
|
+
return `${gap}${cell}`;
|
|
321
|
+
return index === row.length - 1 ? cell : `${cell}${gap}`;
|
|
322
|
+
}).join(" ").trimEnd());
|
|
323
|
+
return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
|
|
324
|
+
`);
|
|
325
|
+
}
|
|
326
|
+
function narrow(rows, widths, limit) {
|
|
327
|
+
const used = () => widths.reduce((sum, width) => sum + width, 0) + 2 * Math.max(0, widths.length - 1);
|
|
328
|
+
const cuttable = (index) => rows.every((row) => !(row[index] ?? "").includes(ESCAPE));
|
|
329
|
+
const floor = 8;
|
|
330
|
+
while (used() > limit) {
|
|
331
|
+
let widest = -1;
|
|
332
|
+
for (const [index, width] of widths.entries()) {
|
|
333
|
+
if (cuttable(index) && width > floor && width > (widths[widest] ?? 0))
|
|
334
|
+
widest = index;
|
|
335
|
+
}
|
|
336
|
+
if (widest === -1)
|
|
337
|
+
break;
|
|
338
|
+
widths[widest] = Math.max(floor, (widths[widest] ?? 0) - (used() - limit));
|
|
339
|
+
}
|
|
340
|
+
return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
|
|
341
|
+
}
|
|
297
342
|
function printJson(value) {
|
|
298
343
|
console.log(JSON.stringify(value, undefined, 2));
|
|
299
344
|
}
|
|
300
345
|
|
|
346
|
+
// ../kit/src/help.ts
|
|
347
|
+
import process3 from "process";
|
|
348
|
+
var OPTIONAL = /\[[^\]]+\]/g;
|
|
349
|
+
var USAGE_PREFIX = /^usage:\s*/i;
|
|
350
|
+
var FULL_STOP = /\.$/;
|
|
351
|
+
function usageLine(usage, colors = paint(process3.stdout)) {
|
|
352
|
+
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
353
|
+
}
|
|
354
|
+
function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces } = {}) {
|
|
355
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
356
|
+
`);
|
|
357
|
+
}
|
|
358
|
+
function replacesRows(replaces, colors = paint(process3.stdout)) {
|
|
359
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
360
|
+
}
|
|
361
|
+
function wantsHelpJson(env = process3.env) {
|
|
362
|
+
return env.KIT_HELP === "json";
|
|
363
|
+
}
|
|
364
|
+
function description(usage, { examples = [], replaces } = {}) {
|
|
365
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
366
|
+
`);
|
|
367
|
+
const first = lines[0];
|
|
368
|
+
const colon = first.indexOf(": ");
|
|
369
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
370
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
371
|
+
return {
|
|
372
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
373
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
374
|
+
...replaces === undefined ? {} : { replaces },
|
|
375
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function exampleRows(examples, colors = paint(process3.stdout)) {
|
|
379
|
+
if (examples.length === 0)
|
|
380
|
+
return [];
|
|
381
|
+
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
382
|
+
}
|
|
383
|
+
function optionRows(options, colors = paint(process3.stdout)) {
|
|
384
|
+
const rows = Object.entries(options).map(([name, option]) => {
|
|
385
|
+
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
386
|
+
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
387
|
+
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
388
|
+
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
389
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
390
|
+
});
|
|
391
|
+
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
392
|
+
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
301
395
|
// ../kit/src/text.ts
|
|
302
396
|
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
303
397
|
function distance(a, b) {
|
|
@@ -317,8 +411,21 @@ function closest(name, names) {
|
|
|
317
411
|
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
318
412
|
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
319
413
|
}
|
|
414
|
+
// ../kit/src/input.ts
|
|
415
|
+
var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
|
|
416
|
+
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
417
|
+
function duration(value, name, fallback, unit = "s") {
|
|
418
|
+
if (value === undefined)
|
|
419
|
+
return fallback;
|
|
420
|
+
const match = DURATION.exec(value.trim());
|
|
421
|
+
const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
|
|
422
|
+
if (ms < 1)
|
|
423
|
+
badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
|
|
424
|
+
return ms;
|
|
425
|
+
}
|
|
320
426
|
|
|
321
427
|
// ../kit/src/args.ts
|
|
428
|
+
var DASHES = /^-+/;
|
|
322
429
|
function jsonOption(holds) {
|
|
323
430
|
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
324
431
|
}
|
|
@@ -358,58 +465,11 @@ class Completing {
|
|
|
358
465
|
}
|
|
359
466
|
}
|
|
360
467
|
var completion = { active: false };
|
|
361
|
-
var OPTIONAL = /\[[^\]]+\]/g;
|
|
362
|
-
var USAGE_PREFIX = /^usage:\s*/i;
|
|
363
|
-
var FULL_STOP = /\.$/;
|
|
364
|
-
var DASHES = /^-+/;
|
|
365
|
-
function usageLine(usage, colors = paint(process2.stdout)) {
|
|
366
|
-
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
367
|
-
}
|
|
368
|
-
function helpText(usage, options, colors = paint(process2.stdout), { examples = [], replaces } = {}) {
|
|
369
|
-
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
370
|
-
`);
|
|
371
|
-
}
|
|
372
|
-
function replacesRows(replaces, colors = paint(process2.stdout)) {
|
|
373
|
-
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
374
|
-
}
|
|
375
|
-
function wantsHelpJson(env = process2.env) {
|
|
376
|
-
return env.KIT_HELP === "json";
|
|
377
|
-
}
|
|
378
|
-
function description(usage, { examples = [], replaces } = {}) {
|
|
379
|
-
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
380
|
-
`);
|
|
381
|
-
const first = lines[0];
|
|
382
|
-
const colon = first.indexOf(": ");
|
|
383
|
-
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
384
|
-
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
385
|
-
return {
|
|
386
|
-
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
387
|
-
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
388
|
-
...replaces === undefined ? {} : { replaces },
|
|
389
|
-
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
function exampleRows(examples, colors = paint(process2.stdout)) {
|
|
393
|
-
if (examples.length === 0)
|
|
394
|
-
return [];
|
|
395
|
-
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
396
|
-
}
|
|
397
|
-
function optionRows(options, colors = paint(process2.stdout)) {
|
|
398
|
-
const rows = Object.entries(options).map(([name, option]) => {
|
|
399
|
-
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
400
|
-
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
401
|
-
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
402
|
-
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
403
|
-
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
404
|
-
});
|
|
405
|
-
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
406
|
-
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
407
|
-
}
|
|
408
468
|
var OUTPUT_OPTIONS = {
|
|
409
469
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
410
470
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
411
471
|
};
|
|
412
|
-
function applyOutput(color, plain, env =
|
|
472
|
+
function applyOutput(color, plain, env = process4.env) {
|
|
413
473
|
const when = plain === true ? "never" : color ?? "auto";
|
|
414
474
|
if (when === "auto")
|
|
415
475
|
return;
|
|
@@ -568,7 +628,7 @@ function nearestOption(message, options, allowNegative) {
|
|
|
568
628
|
}
|
|
569
629
|
// ../kit/src/process.ts
|
|
570
630
|
import { constants } from "os";
|
|
571
|
-
import
|
|
631
|
+
import process5 from "process";
|
|
572
632
|
var KILL_GRACE_MS = 2000;
|
|
573
633
|
function exitCodeOf(code, signal) {
|
|
574
634
|
if (code !== null)
|
|
@@ -588,7 +648,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
|
|
|
588
648
|
return;
|
|
589
649
|
try {
|
|
590
650
|
if (grouped)
|
|
591
|
-
|
|
651
|
+
process5.kill(-proc.pid, killing);
|
|
592
652
|
else
|
|
593
653
|
proc.kill(killing);
|
|
594
654
|
} catch {}
|
|
@@ -662,7 +722,95 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
|
|
|
662
722
|
var RESET_STYLE = `${ESC}[0m`;
|
|
663
723
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
664
724
|
// ../kit/src/cli.ts
|
|
665
|
-
import
|
|
725
|
+
import process7 from "process";
|
|
726
|
+
|
|
727
|
+
// ../kit/src/usage.ts
|
|
728
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
729
|
+
import { homedir } from "os";
|
|
730
|
+
import { dirname, isAbsolute, join } from "path";
|
|
731
|
+
import process6 from "process";
|
|
732
|
+
|
|
733
|
+
// ../kit/src/logs.ts
|
|
734
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
735
|
+
var CLI_FOLDER = "kaynooo-cli";
|
|
736
|
+
|
|
737
|
+
// ../kit/src/usage.ts
|
|
738
|
+
var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
|
|
739
|
+
var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
|
|
740
|
+
var LONGEST_NAME = 40;
|
|
741
|
+
var usageError;
|
|
742
|
+
function noteUsageError(message) {
|
|
743
|
+
usageError = message;
|
|
744
|
+
}
|
|
745
|
+
function stateHome(env = process6.env) {
|
|
746
|
+
const dir = env.XDG_STATE_HOME;
|
|
747
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
748
|
+
}
|
|
749
|
+
function usagePath(env = process6.env) {
|
|
750
|
+
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
751
|
+
}
|
|
752
|
+
function usageRecorded(env = process6.env) {
|
|
753
|
+
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
754
|
+
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
755
|
+
}
|
|
756
|
+
function flagNames(argv) {
|
|
757
|
+
const end = argv.indexOf("--");
|
|
758
|
+
const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
|
|
759
|
+
return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
|
|
760
|
+
}
|
|
761
|
+
function usageEntry(bin, command, argv, code, now = new Date) {
|
|
762
|
+
const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
|
|
763
|
+
usageError = undefined;
|
|
764
|
+
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
765
|
+
}
|
|
766
|
+
function recordUsage(entry, env = process6.env) {
|
|
767
|
+
if (!usageRecorded(env))
|
|
768
|
+
return;
|
|
769
|
+
try {
|
|
770
|
+
const path = usagePath(env);
|
|
771
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
772
|
+
appendFileSync(path, `${JSON.stringify(entry)}
|
|
773
|
+
`, { mode: 384 });
|
|
774
|
+
} catch {}
|
|
775
|
+
}
|
|
776
|
+
async function readUsage(path = usagePath(), since = 0) {
|
|
777
|
+
const file = Bun.file(path);
|
|
778
|
+
if (!await file.exists())
|
|
779
|
+
return [];
|
|
780
|
+
return (await file.text()).split(`
|
|
781
|
+
`).flatMap((line) => {
|
|
782
|
+
try {
|
|
783
|
+
const entry = JSON.parse(line);
|
|
784
|
+
return Date.parse(entry.at) >= since ? [entry] : [];
|
|
785
|
+
} catch {
|
|
786
|
+
return [];
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function summarizeUsage(entries) {
|
|
791
|
+
const bins = new Map;
|
|
792
|
+
const errors = new Map;
|
|
793
|
+
for (const entry of entries) {
|
|
794
|
+
const bin = bins.get(entry.bin) ?? { runs: 0, failed: 0, usageErrors: 0 };
|
|
795
|
+
bin.runs++;
|
|
796
|
+
bin.failed += entry.code === 0 ? 0 : 1;
|
|
797
|
+
bin.usageErrors += entry.code === 2 ? 1 : 0;
|
|
798
|
+
bins.set(entry.bin, bin);
|
|
799
|
+
if (entry.code === 2 && entry.unknown !== undefined) {
|
|
800
|
+
const key = `${entry.bin} ${entry.command ?? ""} ${entry.unknown}`;
|
|
801
|
+
const error = errors.get(key) ?? { bin: entry.bin, ...entry.command === undefined ? {} : { command: entry.command }, unknown: entry.unknown, count: 0 };
|
|
802
|
+
error.count++;
|
|
803
|
+
errors.set(key, error);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
runs: entries.length,
|
|
808
|
+
bins: [...bins].map(([bin, counts]) => ({ bin, ...counts })).toSorted((a, b) => b.runs - a.runs),
|
|
809
|
+
usageErrors: [...errors.values()].toSorted((a, b) => b.count - a.count)
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// ../kit/src/cli.ts
|
|
666
814
|
var LEVEL_OPTIONS = {
|
|
667
815
|
...OUTPUT_OPTIONS,
|
|
668
816
|
help: { type: "boolean", short: "h", description: "show this help" }
|
|
@@ -715,7 +863,7 @@ async function expand(scope, words) {
|
|
|
715
863
|
}
|
|
716
864
|
return { ...scope, commands, lazy: [] };
|
|
717
865
|
}
|
|
718
|
-
async function
|
|
866
|
+
async function help2({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
|
|
719
867
|
const colors = paint(stream);
|
|
720
868
|
const prefix = path.join(" ");
|
|
721
869
|
const summary = await resolveSummary(lazySummary);
|
|
@@ -742,8 +890,8 @@ async function help({ path, header, summary: lazySummary, commands, defaultComma
|
|
|
742
890
|
`);
|
|
743
891
|
}
|
|
744
892
|
function printError(prefix, message, hint, cause) {
|
|
745
|
-
const colors = paint(
|
|
746
|
-
const styled = takesControlCodes(
|
|
893
|
+
const colors = paint(process7.stderr);
|
|
894
|
+
const styled = takesControlCodes(process7.stderr);
|
|
747
895
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
748
896
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
749
897
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -762,6 +910,8 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
|
762
910
|
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
763
911
|
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
764
912
|
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
913
|
+
if (error instanceof UsageError)
|
|
914
|
+
noteUsageError(error.message);
|
|
765
915
|
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
766
916
|
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
767
917
|
return error instanceof Exit ? error.code : 1;
|
|
@@ -784,11 +934,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
784
934
|
}
|
|
785
935
|
}
|
|
786
936
|
if (name === undefined && level.path.length > 1) {
|
|
787
|
-
console.error(await
|
|
937
|
+
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process7.stderr));
|
|
788
938
|
return 2;
|
|
789
939
|
}
|
|
790
940
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
791
|
-
console.log(await
|
|
941
|
+
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process7.stdout));
|
|
792
942
|
return 0;
|
|
793
943
|
}
|
|
794
944
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -904,26 +1054,49 @@ async function printCandidates(found) {
|
|
|
904
1054
|
console.log(line);
|
|
905
1055
|
return 0;
|
|
906
1056
|
}
|
|
907
|
-
async function runCli(cli, argv =
|
|
1057
|
+
async function runCli(cli, argv = process7.argv.slice(2)) {
|
|
908
1058
|
if (argv[0] === COMPLETE)
|
|
909
1059
|
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
910
|
-
|
|
1060
|
+
const code = await dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${about}`, summary: cli.about, ...scopeOf(cli.commands, cli.extraCommands, cli.aliases), defaultCommand: cli.defaultCommand, runBare: cli.runBare === true, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
|
|
1061
|
+
if (cli.usage !== false) {
|
|
1062
|
+
try {
|
|
1063
|
+
recordUsage(usageEntry(cli.name, commandPath(scopeOf(cli.commands, cli.extraCommands, cli.aliases), argv), argv, code));
|
|
1064
|
+
} catch {}
|
|
1065
|
+
}
|
|
1066
|
+
return code;
|
|
1067
|
+
}
|
|
1068
|
+
function commandPath(scope, argv) {
|
|
1069
|
+
const path = [];
|
|
1070
|
+
let level = scope;
|
|
1071
|
+
for (const word of argv.filter((each) => !each.startsWith("-"))) {
|
|
1072
|
+
const name = level.aliases !== undefined && Object.hasOwn(level.aliases, word) ? level.aliases[word] ?? word : word;
|
|
1073
|
+
const command = Object.hasOwn(level.commands, name) ? level.commands[name] : undefined;
|
|
1074
|
+
if (command === undefined)
|
|
1075
|
+
break;
|
|
1076
|
+
path.push(word);
|
|
1077
|
+
if (!("commands" in command))
|
|
1078
|
+
break;
|
|
1079
|
+
level = scopeOf(command.commands, command.extraCommands, command.aliases);
|
|
1080
|
+
}
|
|
1081
|
+
return path.length === 0 ? undefined : path.join(" ");
|
|
911
1082
|
}
|
|
912
|
-
async function runMain({ name, version, run, redact }, argv =
|
|
1083
|
+
async function runMain({ name, version, run, redact, usage }, argv = process7.argv.slice(2)) {
|
|
913
1084
|
if (argv[0] === COMPLETE)
|
|
914
1085
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
915
1086
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
916
1087
|
console.log(version);
|
|
917
1088
|
return 0;
|
|
918
1089
|
}
|
|
1090
|
+
let code;
|
|
919
1091
|
try {
|
|
920
|
-
|
|
1092
|
+
code = await run(argv);
|
|
921
1093
|
} catch (error) {
|
|
922
|
-
|
|
1094
|
+
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
923
1095
|
}
|
|
1096
|
+
if (usage !== false)
|
|
1097
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
1098
|
+
return code;
|
|
924
1099
|
}
|
|
925
|
-
// ../kit/src/logs.ts
|
|
926
|
-
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
927
1100
|
// ../kit/src/secrets.ts
|
|
928
1101
|
var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
|
|
929
1102
|
// ../kit/src/sessions.ts
|
|
@@ -931,16 +1104,16 @@ var {Glob } = globalThis.Bun;
|
|
|
931
1104
|
var SESSION_FILES = new Glob("*.json");
|
|
932
1105
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
933
1106
|
// package.json
|
|
934
|
-
var version = "0.
|
|
1107
|
+
var version = "0.4.0";
|
|
935
1108
|
|
|
936
1109
|
// src/checks.ts
|
|
937
1110
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
938
|
-
import { basename, join as
|
|
1111
|
+
import { basename, join as join3 } from "path";
|
|
939
1112
|
|
|
940
1113
|
// src/completions.ts
|
|
941
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
942
|
-
import { join } from "path";
|
|
943
|
-
import
|
|
1114
|
+
import { existsSync, mkdirSync as mkdirSync2, readdirSync, readFileSync, rmSync } from "fs";
|
|
1115
|
+
import { join as join2 } from "path";
|
|
1116
|
+
import process9 from "process";
|
|
944
1117
|
var MARKER = "# Generated by `cli completions fish`";
|
|
945
1118
|
var FISH_FILE = /\.fish$/;
|
|
946
1119
|
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'
|
|
@@ -954,8 +1127,8 @@ var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_c
|
|
|
954
1127
|
end
|
|
955
1128
|
end
|
|
956
1129
|
end`;
|
|
957
|
-
function completionDir(home, env =
|
|
958
|
-
return
|
|
1130
|
+
function completionDir(home, env = process9.env) {
|
|
1131
|
+
return join2(env.XDG_CACHE_HOME ?? join2(home, ".cache"), "fish", "kaynooo-cli");
|
|
959
1132
|
}
|
|
960
1133
|
function installedBins(host) {
|
|
961
1134
|
return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
|
|
@@ -968,15 +1141,15 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
|
|
|
968
1141
|
}
|
|
969
1142
|
async function installCompletions(host, dir) {
|
|
970
1143
|
const bins = installedBins(host);
|
|
971
|
-
|
|
972
|
-
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync(
|
|
1144
|
+
mkdirSync2(dir, { recursive: true });
|
|
1145
|
+
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync(join2(dir, name), "utf8").startsWith(MARKER));
|
|
973
1146
|
for (const name of removed)
|
|
974
|
-
rmSync(
|
|
975
|
-
await Promise.all(bins.map(async (bin) => Bun.write(
|
|
1147
|
+
rmSync(join2(dir, name));
|
|
1148
|
+
await Promise.all(bins.map(async (bin) => Bun.write(join2(dir, `${bin}.fish`), completionFile(bin))));
|
|
976
1149
|
return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
|
|
977
1150
|
}
|
|
978
1151
|
function missingCompletions(host, dir) {
|
|
979
|
-
return installedBins(host).filter((bin) => !existsSync(
|
|
1152
|
+
return installedBins(host).filter((bin) => !existsSync(join2(dir, `${bin}.fish`)));
|
|
980
1153
|
}
|
|
981
1154
|
|
|
982
1155
|
// src/checks.ts
|
|
@@ -994,8 +1167,13 @@ var TOOLS = {
|
|
|
994
1167
|
"@kaynooo/cli-gitlab": ["gl"],
|
|
995
1168
|
"@kaynooo/cli-guard": ["guard"],
|
|
996
1169
|
"@kaynooo/cli-out": ["out"],
|
|
1170
|
+
"@kaynooo/cli-dev": ["dev"],
|
|
1171
|
+
"@kaynooo/cli-pick": ["pick"],
|
|
997
1172
|
"@kaynooo/cli-pkg": ["pkg"],
|
|
1173
|
+
"@kaynooo/cli-probe": ["probe"],
|
|
1174
|
+
"@kaynooo/cli-repos": ["repos"],
|
|
998
1175
|
"@kaynooo/cli-stack": ["stack"],
|
|
1176
|
+
"@kaynooo/cli-sweep": ["sweep"],
|
|
999
1177
|
"@kaynooo/cli-view": ["view"],
|
|
1000
1178
|
"@kaynooo/cli-wait": ["waitfor", "slot"]
|
|
1001
1179
|
};
|
|
@@ -1023,12 +1201,12 @@ function firstLine(text) {
|
|
|
1023
1201
|
return end === -1 ? trimmed : trimmed.slice(0, end);
|
|
1024
1202
|
}
|
|
1025
1203
|
function installed(host, name) {
|
|
1026
|
-
return readJson(
|
|
1204
|
+
return readJson(join3(host.globalModules, name, "package.json"));
|
|
1027
1205
|
}
|
|
1028
1206
|
function ownBin(host, name, bin) {
|
|
1029
1207
|
const declared = installed(host, name)?.bin;
|
|
1030
1208
|
const file = typeof declared === "string" ? declared : declared?.[bin];
|
|
1031
|
-
return file === undefined ? undefined :
|
|
1209
|
+
return file === undefined ? undefined : join3(host.globalModules, name, file);
|
|
1032
1210
|
}
|
|
1033
1211
|
async function checkTool(host, name, bins) {
|
|
1034
1212
|
const check = name.slice(name.indexOf("/") + 1);
|
|
@@ -1037,7 +1215,7 @@ async function checkTool(host, name, bins) {
|
|
|
1037
1215
|
return { check, ok: false, detail: "not installed", fix: `bun install -g ${name}` };
|
|
1038
1216
|
const off = bins.filter((bin) => host.which(bin) === null);
|
|
1039
1217
|
if (off.length > 0)
|
|
1040
|
-
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${
|
|
1218
|
+
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join3(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
|
|
1041
1219
|
const info = await host.run(["bun", "info", name, "version", "--no-cache"]);
|
|
1042
1220
|
const answered = info.code === 0 ? firstLine(info.stdout.trim().split(`
|
|
1043
1221
|
`).at(-1) ?? "") : "";
|
|
@@ -1064,9 +1242,9 @@ async function checkTea(host) {
|
|
|
1064
1242
|
return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
|
|
1065
1243
|
}
|
|
1066
1244
|
function checkHooks(host) {
|
|
1067
|
-
const settings = readJson(
|
|
1245
|
+
const settings = readJson(join3(host.home, ".claude", "settings.json")) ?? {};
|
|
1068
1246
|
const plugin = settings.enabledPlugins?.[PLUGIN] === true;
|
|
1069
|
-
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(
|
|
1247
|
+
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(join3(dir, "hooks", "hooks.json")));
|
|
1070
1248
|
if (plugin && mod !== undefined)
|
|
1071
1249
|
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}` };
|
|
1072
1250
|
if (plugin)
|
|
@@ -1100,19 +1278,19 @@ async function runChecks(host) {
|
|
|
1100
1278
|
}
|
|
1101
1279
|
|
|
1102
1280
|
// src/host.ts
|
|
1103
|
-
import { homedir } from "os";
|
|
1104
|
-
import { join as
|
|
1105
|
-
import
|
|
1281
|
+
import { homedir as homedir2 } from "os";
|
|
1282
|
+
import { join as join4 } from "path";
|
|
1283
|
+
import process10 from "process";
|
|
1106
1284
|
var RUN_TIMEOUT_MS = 20000;
|
|
1107
1285
|
function hostHere() {
|
|
1108
|
-
const home =
|
|
1286
|
+
const home = homedir2();
|
|
1109
1287
|
return {
|
|
1110
1288
|
home,
|
|
1111
|
-
globalModules:
|
|
1289
|
+
globalModules: join4(process10.env.BUN_INSTALL ?? join4(home, ".bun"), "install", "global", "node_modules"),
|
|
1112
1290
|
bunVersion: Bun.version,
|
|
1113
1291
|
which: (bin) => Bun.which(bin),
|
|
1114
1292
|
run: async (argv, env) => {
|
|
1115
|
-
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...
|
|
1293
|
+
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process10.env, ...env } } });
|
|
1116
1294
|
if (ran.timedOut)
|
|
1117
1295
|
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
1118
1296
|
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
@@ -1131,7 +1309,7 @@ var OPTIONS = {
|
|
|
1131
1309
|
json: jsonOption("[{check, ok, detail, fix?}]")
|
|
1132
1310
|
};
|
|
1133
1311
|
function print(results) {
|
|
1134
|
-
const { green, red, dim } = paint(
|
|
1312
|
+
const { green, red, dim } = paint(process11.stdout);
|
|
1135
1313
|
const width = Math.max(...results.map((result) => result.check.length));
|
|
1136
1314
|
for (const { check, ok, detail, fix } of results)
|
|
1137
1315
|
console.log(`${ok ? green("ok ") : red("FAIL")} ${check.padEnd(width)} ${detail}${fix === undefined ? "" : ` ${dim("fix:")} ${fix}`}`);
|
|
@@ -1150,7 +1328,7 @@ async function main(args, host = hostHere()) {
|
|
|
1150
1328
|
return results.every((result) => result.ok) ? 0 : 1;
|
|
1151
1329
|
}
|
|
1152
1330
|
if (import.meta.main)
|
|
1153
|
-
|
|
1331
|
+
process11.exitCode = await runMain({ name: "doctor", version, run: async (args) => main(args) });
|
|
1154
1332
|
export {
|
|
1155
1333
|
main
|
|
1156
1334
|
};
|
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.4.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",
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
"doctor": "$npm_execpath src/cli.ts"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@kaynooo/cli-kit": "0.
|
|
39
|
+
"@kaynooo/cli-kit": "0.20.0"
|
|
40
40
|
}
|
|
41
41
|
}
|