@kaynooo/cli-doctor 0.11.0 → 0.13.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/dist/cli-bin.js CHANGED
@@ -2,13 +2,13 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli-bin.ts
5
- import process18 from "process";
5
+ import process21 from "process";
6
6
  // ../kit/src/args.ts
7
- import process7 from "process";
7
+ import process9 from "process";
8
8
  import { parseArgs } from "util";
9
9
 
10
10
  // ../kit/src/format.ts
11
- import process5 from "process";
11
+ import process7 from "process";
12
12
 
13
13
  // ../kit/src/mode.ts
14
14
  import process2 from "process";
@@ -32,7 +32,7 @@ function isInteractive(streams = process2) {
32
32
  }
33
33
 
34
34
  // ../kit/src/style.ts
35
- import process4 from "process";
35
+ import process6 from "process";
36
36
 
37
37
  // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
38
38
  function x(s) {
@@ -295,28 +295,188 @@ function le(i = {}) {
295
295
  }
296
296
  var t = le();
297
297
 
298
+ // ../kit/src/theme.ts
299
+ import { existsSync as existsSync3, readFileSync, statSync } from "fs";
300
+ import { homedir as homedir2 } from "os";
301
+ import { join as join3, resolve as resolve2 } from "path";
302
+ import process5 from "process";
303
+
304
+ // ../kit/src/config.ts
305
+ import { existsSync } from "fs";
306
+ import { dirname, join, resolve } from "path";
307
+ import process3 from "process";
308
+ function findUp(names, from = process3.cwd(), stop = () => false) {
309
+ const wanted = typeof names === "string" ? [names] : names;
310
+ for (let dir = resolve(from);; dir = dirname(dir)) {
311
+ const found = wanted.map((name) => join(dir, name)).find((path) => existsSync(path));
312
+ if (found !== undefined)
313
+ return found;
314
+ if (stop(dir) || dirname(dir) === dir)
315
+ return;
316
+ }
317
+ }
318
+
298
319
  // ../kit/src/user-config.ts
320
+ import { existsSync as existsSync2 } from "fs";
299
321
  import { homedir } from "os";
300
- import { join } from "path";
301
- import process3 from "process";
322
+ import { join as join2 } from "path";
323
+ import process4 from "process";
302
324
  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");
325
+ const xdg = where.xdg ?? (where.home === undefined ? process4.env.XDG_CONFIG_HOME : undefined);
326
+ return xdg !== undefined && xdg !== "" ? xdg : join2(where.home ?? homedir(), ".config");
305
327
  }
306
328
  function userConfigPath(bin, where = {}) {
307
- return join(configBase(where), "kaynooo-cli", `${bin}.json`);
329
+ return join2(configBase(where), "kaynooo-cli", `${bin}.json`);
330
+ }
331
+ function userConfigReadPath(bin, legacy = [], where = {}) {
332
+ const current = userConfigPath(bin, where);
333
+ if (existsSync2(current))
334
+ return current;
335
+ const base = configBase(where);
336
+ return legacy.map((parts) => join2(base, ...parts)).find((path) => existsSync2(path)) ?? current;
308
337
  }
309
338
 
310
339
  // ../kit/src/theme.ts
340
+ var DEFAULT = {
341
+ colors: { accent: "cyan", id: "yellow", path: "blue", label: "dim", value: "bold", ok: "green", warn: "yellow", problem: "red", note: "dim", passed: "green", failed: "red", running: "cyan", queued: "yellow", skipped: "dim" },
342
+ glyphs: { ok: "\u2713", warn: "\u25B2", problem: "\u2717", note: "\u203A", passed: "\u2713", failed: "\u2717", running: "\u25CF", queued: "\u25CB", skipped: "-", bar: "\u2501" },
343
+ accents: ["cyan", "magenta", "green", "blue", "yellow", "redBright"],
344
+ bins: {}
345
+ };
346
+ var BRIGHT = { accent: "cyanBright", id: "yellowBright", path: "blueBright", label: "white", value: "bold whiteBright", ok: "greenBright", warn: "yellowBright", problem: "redBright", note: "white", passed: "greenBright", failed: "redBright", running: "cyanBright", queued: "yellowBright", skipped: "white" };
347
+ var MONO = { accent: "bold", id: "bold", path: "underline", label: "dim", value: "bold", ok: "bold", warn: "bold", problem: "bold", note: "dim", passed: "bold", failed: "bold", running: "bold", queued: "dim", skipped: "dim" };
348
+ var ASCII = { ok: "+", warn: "!", problem: "x", note: ">", passed: "+", failed: "x", running: "*", queued: "o", skipped: "-", bar: "#" };
349
+ var PRESETS = {
350
+ default: {},
351
+ "high-contrast": { colors: BRIGHT, accents: ["cyanBright", "magentaBright", "greenBright", "blueBright", "yellowBright", "redBright"] },
352
+ mono: { colors: MONO, accents: ["bold"] },
353
+ ascii: { glyphs: ASCII }
354
+ };
311
355
  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"]);
356
+ var HEX = /^#[0-9a-f]{6}$/i;
357
+ var CONTROL = /\p{Cc}/u;
312
358
  var MAX_FILE_BYTES = 64 * 1024;
359
+ var SPACES = /\s+/;
360
+ function isColorSpec(spec) {
361
+ if (typeof spec === "number")
362
+ return Number.isInteger(spec) && spec >= 0 && spec <= 255;
363
+ return typeof spec === "string" && spec.trim() !== "" && spec.trim().split(SPACES).every((token) => STYLES.has(token) || HEX.test(token));
364
+ }
365
+ function isRecord(value) {
366
+ return typeof value === "object" && value !== null && !Array.isArray(value);
367
+ }
368
+ function readThemeFile(path, warn) {
369
+ try {
370
+ const stat = statSync(path, { throwIfNoEntry: false });
371
+ if (stat === undefined)
372
+ return;
373
+ if (!stat.isFile() || stat.size > MAX_FILE_BYTES) {
374
+ warn(`${path} is not a regular file under ${MAX_FILE_BYTES / 1024} KiB, theme ignored`);
375
+ return;
376
+ }
377
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
378
+ if (isRecord(parsed))
379
+ return parsed;
380
+ warn(`${path} is not a JSON object, theme ignored`);
381
+ } catch (error) {
382
+ warn(`${path} does not parse (${error instanceof Error ? error.message : String(error)}), theme ignored`);
383
+ }
384
+ return;
385
+ }
386
+ function applyPreset(theme, name, warn, where) {
387
+ if (name === undefined)
388
+ return;
389
+ if (typeof name !== "string" || !Object.hasOwn(PRESETS, name)) {
390
+ warn(`${where}: no preset ${String(name)}, one of ${Object.keys(PRESETS).join(", ")}`);
391
+ return;
392
+ }
393
+ const preset = PRESETS[name];
394
+ Object.assign(theme.colors, preset.colors);
395
+ Object.assign(theme.glyphs, preset.glyphs);
396
+ if (preset.accents !== undefined)
397
+ theme.accents = [...preset.accents];
398
+ return name;
399
+ }
400
+ function applyFile(theme, file, where, warn) {
401
+ if (isRecord(file.colors)) {
402
+ for (const [key, value] of Object.entries(file.colors)) {
403
+ if (!Object.hasOwn(DEFAULT.colors, key))
404
+ warn(`${where}: no colour ${key}, one of ${Object.keys(DEFAULT.colors).join(", ")}`);
405
+ else if (!isColorSpec(value))
406
+ warn(`${where}: colour ${key} is not an ANSI name, \`#rrggbb\` or a number up to 255`);
407
+ else
408
+ theme.colors[key] = value;
409
+ }
410
+ }
411
+ if (isRecord(file.glyphs)) {
412
+ for (const [key, value] of Object.entries(file.glyphs)) {
413
+ if (!Object.hasOwn(DEFAULT.glyphs, key))
414
+ warn(`${where}: no glyph ${key}, one of ${Object.keys(DEFAULT.glyphs).join(", ")}`);
415
+ else if (typeof value !== "string" || CONTROL.test(value) || Bun.stringWidth(value) !== 1)
416
+ warn(`${where}: glyph ${key} must be one visible character wide, with no control character`);
417
+ else
418
+ theme.glyphs[key] = value;
419
+ }
420
+ }
421
+ if (Array.isArray(file.accents) && file.accents.length > 0 && file.accents.every(isColorSpec))
422
+ theme.accents = [...file.accents];
423
+ else if (file.accents !== undefined)
424
+ warn(`${where}: accents must be a non-empty list of colours`);
425
+ if (isRecord(file.bins)) {
426
+ for (const [bin, value] of Object.entries(file.bins)) {
427
+ if (isColorSpec(value))
428
+ theme.bins[bin] = value;
429
+ else
430
+ warn(`${where}: bin ${bin} has no valid colour`);
431
+ }
432
+ }
433
+ }
434
+ function endsWalk(home) {
435
+ return (dir) => dir === resolve2(home) || existsSync3(join3(dir, ".git"));
436
+ }
313
437
  var warned = new Set;
438
+ function warnOnce(message) {
439
+ if (warned.has(message))
440
+ return;
441
+ warned.add(message);
442
+ console.error(`warning: theme ${message}`);
443
+ }
444
+ function isPath(chosen) {
445
+ return chosen.includes("/") || chosen.endsWith(".json");
446
+ }
447
+ function loadTheme({ env = process5.env, from = process5.cwd(), home = homedir2(), warn = warnOnce } = {}) {
448
+ const theme = { colors: { ...DEFAULT.colors }, glyphs: { ...DEFAULT.glyphs }, accents: [...DEFAULT.accents], bins: Object.create(null) };
449
+ const sources = [];
450
+ const chosen = env.KIT_THEME ?? "";
451
+ const wanted = chosen === "" ? [userConfigPath("theme", { home, xdg: env.XDG_CONFIG_HOME }), findUp(join3(".kaynooo", "theme.json"), from, endsWalk(home))] : [];
452
+ if (isPath(chosen))
453
+ wanted.push(resolve2(from, chosen));
454
+ const files = [];
455
+ for (const path of wanted) {
456
+ const file = path === undefined ? undefined : readThemeFile(path, warn);
457
+ if (path !== undefined && file !== undefined)
458
+ files.push({ path, file });
459
+ }
460
+ let preset = "default";
461
+ if (chosen !== "" && !isPath(chosen)) {
462
+ preset = applyPreset(theme, chosen, warn, "KIT_THEME") ?? preset;
463
+ } else {
464
+ const named = files.findLast(({ file }) => file.preset !== undefined);
465
+ if (named !== undefined)
466
+ preset = applyPreset(theme, named.file.preset, warn, named.path) ?? preset;
467
+ }
468
+ for (const { path, file } of files) {
469
+ applyFile(theme, file, path, warn);
470
+ sources.push(path);
471
+ }
472
+ return { theme, sources, preset };
473
+ }
314
474
 
315
475
  // ../kit/src/style.ts
316
- function takesControlCodes(stream, env = process4.env) {
476
+ function takesControlCodes(stream, env = process6.env) {
317
477
  return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb" && outputMode(stream, env) === "terminal";
318
478
  }
319
- function paint(stream, env = process4.env) {
479
+ function paint(stream, env = process6.env) {
320
480
  return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
321
481
  }
322
482
  var STATUS_WORDS = {
@@ -341,7 +501,7 @@ var ESCAPE = String.fromCharCode(27);
341
501
  function textWidth(text) {
342
502
  return Bun.stringWidth(text);
343
503
  }
344
- function table(rows, { cellWidth, header = false, align = [], fit, stream = process5.stdout, env = process5.env } = {}) {
504
+ function table(rows, { cellWidth, header = false, align = [], fit, stream = process7.stdout, env = process7.env } = {}) {
345
505
  const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
346
506
  if (outputMode(stream, env) === "agent")
347
507
  return cells.map((row) => row.map((cell) => Bun.stripANSI(cell).replaceAll(/[\t\r\n]+/g, " ")).join("\t")).join(`
@@ -380,7 +540,7 @@ function narrow(rows, widths, limit) {
380
540
  }
381
541
  return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
382
542
  }
383
- function jsonText(value, replacer, { stream = process5.stdout, env = process5.env } = {}) {
543
+ function jsonText(value, replacer, { stream = process7.stdout, env = process7.env } = {}) {
384
544
  return JSON.stringify(value, replacer, outputMode(stream, env) === "agent" ? undefined : 2);
385
545
  }
386
546
  function printJson(value) {
@@ -388,27 +548,27 @@ function printJson(value) {
388
548
  }
389
549
 
390
550
  // ../kit/src/help.ts
391
- import process6 from "process";
551
+ import process8 from "process";
392
552
  var OPTIONAL = /\[[^\]]+\]/g;
393
553
  var USAGE_PREFIX = /^usage:\s*/i;
394
554
  var FULL_STOP = /\.$/;
395
- function usageLine(usage, colors = paint(process6.stdout)) {
555
+ function usageLine(usage, colors = paint(process8.stdout)) {
396
556
  return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
397
557
  }
398
- function helpText(usage, options, colors = paint(process6.stdout), { examples = [], replaces, exits = [] } = {}) {
558
+ function helpText(usage, options, colors = paint(process8.stdout), { examples = [], replaces, exits = [] } = {}) {
399
559
  return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
400
560
  `);
401
561
  }
402
- function exitRows(exits, colors = paint(process6.stdout)) {
562
+ function exitRows(exits, colors = paint(process8.stdout)) {
403
563
  if (exits.length === 0)
404
564
  return [];
405
565
  const width = Math.max(...exits.map(([code]) => String(code).length));
406
566
  return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
407
567
  }
408
- function replacesRows(replaces, colors = paint(process6.stdout)) {
568
+ function replacesRows(replaces, colors = paint(process8.stdout)) {
409
569
  return replaces === undefined || outputMode() === "agent" ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
410
570
  }
411
- function wantsHelpJson(env = process6.env) {
571
+ function wantsHelpJson(env = process8.env) {
412
572
  return env.KIT_HELP === "json";
413
573
  }
414
574
  function description(usage, { examples = [], replaces, exits } = {}, options) {
@@ -427,12 +587,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
427
587
  ...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
428
588
  };
429
589
  }
430
- function exampleRows(examples, colors = paint(process6.stdout)) {
590
+ function exampleRows(examples, colors = paint(process8.stdout)) {
431
591
  if (examples.length === 0 || outputMode() === "agent")
432
592
  return [];
433
593
  return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
434
594
  }
435
- function optionRows(options, colors = paint(process6.stdout)) {
595
+ function optionRows(options, colors = paint(process8.stdout)) {
436
596
  const rows = Object.entries(options).map(([name, option]) => {
437
597
  const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
438
598
  const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
@@ -524,7 +684,7 @@ var OUTPUT_OPTIONS = {
524
684
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
525
685
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
526
686
  };
527
- function applyOutput(color, plain, env = process7.env) {
687
+ function applyOutput(color, plain, env = process9.env) {
528
688
  const when = plain === true ? "never" : color ?? "auto";
529
689
  if (when === "auto")
530
690
  return;
@@ -695,7 +855,7 @@ function nearestOption(message, options, allowNegative) {
695
855
  }
696
856
  // ../kit/src/process.ts
697
857
  import { constants } from "os";
698
- import process8 from "process";
858
+ import process10 from "process";
699
859
  var KILL_GRACE_MS = 2000;
700
860
  function exitCodeOf(code, signal) {
701
861
  if (code !== null)
@@ -715,7 +875,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
715
875
  return;
716
876
  try {
717
877
  if (grouped)
718
- process8.kill(-proc.pid, killing);
878
+ process10.kill(-proc.pid, killing);
719
879
  else
720
880
  proc.kill(killing);
721
881
  } catch {}
@@ -789,15 +949,15 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
789
949
  var RESET_STYLE = `${ESC}[0m`;
790
950
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
791
951
  // ../kit/src/cli.ts
792
- import { readFileSync, realpathSync } from "fs";
793
- import { dirname as dirname2, join as join3 } from "path";
794
- import process10 from "process";
952
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
953
+ import { dirname as dirname3, join as join5 } from "path";
954
+ import process12 from "process";
795
955
 
796
956
  // ../kit/src/usage.ts
797
957
  import { appendFileSync, mkdirSync } from "fs";
798
- import { homedir as homedir2 } from "os";
799
- import { dirname, isAbsolute, join as join2 } from "path";
800
- import process9 from "process";
958
+ import { homedir as homedir3 } from "os";
959
+ import { dirname as dirname2, isAbsolute, join as join4 } from "path";
960
+ import process11 from "process";
801
961
 
802
962
  // ../kit/src/logs.ts
803
963
  var DAY_MS = 24 * 60 * 60 * 1000;
@@ -811,14 +971,14 @@ var usageError;
811
971
  function noteUsageError(message) {
812
972
  usageError = message;
813
973
  }
814
- function stateHome(env = process9.env) {
974
+ function stateHome(env = process11.env) {
815
975
  const dir = env.XDG_STATE_HOME;
816
- return dir !== undefined && isAbsolute(dir) ? dir : join2(homedir2(), ".local", "state");
976
+ return dir !== undefined && isAbsolute(dir) ? dir : join4(homedir3(), ".local", "state");
817
977
  }
818
- function usagePath(env = process9.env) {
819
- return join2(stateHome(env), CLI_FOLDER, "usage.jsonl");
978
+ function usagePath(env = process11.env) {
979
+ return join4(stateHome(env), CLI_FOLDER, "usage.jsonl");
820
980
  }
821
- function usageRecorded(env = process9.env) {
981
+ function usageRecorded(env = process11.env) {
822
982
  const off = env.KAYNOOO_CLI_NO_USAGE;
823
983
  return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
824
984
  }
@@ -832,12 +992,12 @@ function usageEntry(bin, command, argv, code, now = new Date) {
832
992
  usageError = undefined;
833
993
  return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
834
994
  }
835
- function recordUsage(entry, env = process9.env) {
995
+ function recordUsage(entry, env = process11.env) {
836
996
  if (!usageRecorded(env))
837
997
  return;
838
998
  try {
839
999
  const path = usagePath(env);
840
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
1000
+ mkdirSync(dirname2(path), { recursive: true, mode: 448 });
841
1001
  appendFileSync(path, `${JSON.stringify(entry)}
842
1002
  `, { mode: 384 });
843
1003
  } catch {}
@@ -971,8 +1131,8 @@ async function help2({ path, header, summary: lazySummary, commands, defaultComm
971
1131
  `);
972
1132
  }
973
1133
  function printError(prefix, message, hint, cause) {
974
- const colors = paint(process10.stderr);
975
- const styled = takesControlCodes(process10.stderr);
1134
+ const colors = paint(process12.stderr);
1135
+ const styled = takesControlCodes(process12.stderr);
976
1136
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
977
1137
  const under = (label, text, tint) => text === undefined ? "" : `
978
1138
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -1015,11 +1175,11 @@ async function dispatch(level, typed, { version, redact }) {
1015
1175
  }
1016
1176
  }
1017
1177
  if (name === undefined && level.path.length > 1) {
1018
- console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process10.stderr));
1178
+ console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process12.stderr));
1019
1179
  return 2;
1020
1180
  }
1021
1181
  if (name === undefined || name === "help" || name === "--help" || name === "-h") {
1022
- console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process10.stdout));
1182
+ console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process12.stdout));
1023
1183
  return 0;
1024
1184
  }
1025
1185
  if (level.path.length === 1 && (name === "--version" || name === "-v")) {
@@ -1136,17 +1296,17 @@ async function printCandidates(found) {
1136
1296
  return 0;
1137
1297
  }
1138
1298
  function packageVersion(entry = Bun.main) {
1139
- for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
1299
+ for (let dir = dirname3(realpathSync(entry));; dir = dirname3(dir)) {
1140
1300
  try {
1141
- const { version } = JSON.parse(readFileSync(join3(dir, "package.json"), "utf8"));
1301
+ const { version } = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf8"));
1142
1302
  if (typeof version === "string")
1143
1303
  return version;
1144
1304
  } catch {}
1145
- if (dirname2(dir) === dir)
1305
+ if (dirname3(dir) === dir)
1146
1306
  return "0.0.0";
1147
1307
  }
1148
1308
  }
1149
- async function runCli(cli, argv = process10.argv.slice(2)) {
1309
+ async function runCli(cli, argv = process12.argv.slice(2)) {
1150
1310
  const version = cli.version ?? packageVersion();
1151
1311
  if (argv[0] === COMPLETE)
1152
1312
  return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
@@ -1173,7 +1333,7 @@ function commandPath(scope, argv) {
1173
1333
  }
1174
1334
  return path.length === 0 ? undefined : path.join(" ");
1175
1335
  }
1176
- async function runMain({ name, version, run, redact, usage }, argv = process10.argv.slice(2)) {
1336
+ async function runMain({ name, version, run, redact, usage }, argv = process12.argv.slice(2)) {
1177
1337
  if (argv[0] === COMPLETE)
1178
1338
  return printCandidates(leafCandidates(run, argv.slice(1)));
1179
1339
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
@@ -1193,6 +1353,8 @@ async function runMain({ name, version, run, redact, usage }, argv = process10.a
1193
1353
  }
1194
1354
  return code;
1195
1355
  }
1356
+ // ../kit/src/folders.ts
1357
+ var SKIPPED_FOLDERS = new Set(["node_modules", "vendor", "dist", "build", "target", "coverage", "venv", "__pycache__"]);
1196
1358
  // ../kit/src/secrets.ts
1197
1359
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
1198
1360
  // ../kit/src/sessions.ts
@@ -1200,40 +1362,40 @@ var {Glob } = globalThis.Bun;
1200
1362
  var SESSION_FILES = new Glob("*.json");
1201
1363
  var ACCOUNT_DIRS = new Glob("*/sessions");
1202
1364
  // ../kit/src/workspace-root.ts
1203
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1204
- import { homedir as homedir3 } from "os";
1205
- import { dirname as dirname3, join as join4, resolve } from "path";
1206
- import process11 from "process";
1365
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
1366
+ import { homedir as homedir4 } from "os";
1367
+ import { dirname as dirname4, join as join6, resolve as resolve3 } from "path";
1368
+ import process13 from "process";
1207
1369
  function expandHome(path, home) {
1208
1370
  if (path === "~")
1209
1371
  return home;
1210
- return path.startsWith("~/") ? join4(home, path.slice(2)) : path;
1372
+ return path.startsWith("~/") ? join6(home, path.slice(2)) : path;
1211
1373
  }
1212
1374
  function configured(env, home) {
1213
1375
  const path = userConfigPath("config", { home, xdg: env.XDG_CONFIG_HOME });
1214
- if (!existsSync(path))
1376
+ if (!existsSync4(path))
1215
1377
  return;
1216
1378
  try {
1217
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
1379
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
1218
1380
  const folder = typeof parsed === "object" && parsed !== null ? parsed.workspace : undefined;
1219
1381
  return typeof folder === "string" && folder !== "" ? folder : undefined;
1220
1382
  } catch {
1221
1383
  return;
1222
1384
  }
1223
1385
  }
1224
- function workspaceRoot({ given, env = process11.env, home = homedir3() } = {}) {
1386
+ function workspaceRoot({ given, env = process13.env, home = homedir4() } = {}) {
1225
1387
  const fromEnv = env.KAYNOOO_WORKSPACE === "" ? undefined : env.KAYNOOO_WORKSPACE;
1226
- return resolve(expandHome(given ?? fromEnv ?? configured(env, home) ?? join4(home, "Workspace"), home));
1388
+ return resolve3(expandHome(given ?? fromEnv ?? configured(env, home) ?? join6(home, "Workspace"), home));
1227
1389
  }
1228
- function workspaceChosen({ env = process11.env, home = homedir3() } = {}) {
1390
+ function workspaceChosen({ env = process13.env, home = homedir4() } = {}) {
1229
1391
  return (env.KAYNOOO_WORKSPACE ?? "") !== "" || configured(env, home) !== undefined;
1230
1392
  }
1231
- function saveWorkspace(folder, { env = process11.env, home = homedir3() } = {}) {
1393
+ function saveWorkspace(folder, { env = process13.env, home = homedir4() } = {}) {
1232
1394
  const path = userConfigPath("config", { home, xdg: env.XDG_CONFIG_HOME });
1233
1395
  let current = {};
1234
- if (existsSync(path)) {
1396
+ if (existsSync4(path)) {
1235
1397
  try {
1236
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
1398
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
1237
1399
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
1238
1400
  return false;
1239
1401
  current = parsed;
@@ -1241,19 +1403,19 @@ function saveWorkspace(folder, { env = process11.env, home = homedir3() } = {})
1241
1403
  return false;
1242
1404
  }
1243
1405
  }
1244
- mkdirSync2(dirname3(path), { recursive: true });
1406
+ mkdirSync2(dirname4(path), { recursive: true });
1245
1407
  writeFileSync(path, `${JSON.stringify({ ...current, workspace: folder }, undefined, 2)}
1246
1408
  `);
1247
1409
  return true;
1248
1410
  }
1249
1411
  // src/completions.ts
1250
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync4, rmSync } from "fs";
1251
- import { join as join6 } from "path";
1252
- import process13 from "process";
1412
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, rmSync } from "fs";
1413
+ import { join as join8 } from "path";
1414
+ import process15 from "process";
1253
1415
 
1254
1416
  // src/checks.ts
1255
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
1256
- import { basename, join as join5 } from "path";
1417
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1418
+ import { basename, join as join7 } from "path";
1257
1419
 
1258
1420
  // src/catalog.ts
1259
1421
  var HOOKS_REQUIRES = "@kaynooo/cli-guard";
@@ -1329,7 +1491,7 @@ function readJsonText(text) {
1329
1491
  }
1330
1492
  }
1331
1493
  function readJson(path) {
1332
- return existsSync2(path) ? readJsonText(readFileSync3(path, "utf8")) : undefined;
1494
+ return existsSync5(path) ? readJsonText(readFileSync4(path, "utf8")) : undefined;
1333
1495
  }
1334
1496
  function firstLine(text) {
1335
1497
  const trimmed = text.trim();
@@ -1338,7 +1500,7 @@ function firstLine(text) {
1338
1500
  return end === -1 ? trimmed : trimmed.slice(0, end);
1339
1501
  }
1340
1502
  function installed(host, name) {
1341
- return readJson(join5(host.globalModules, name, "package.json"));
1503
+ return readJson(join7(host.globalModules, name, "package.json"));
1342
1504
  }
1343
1505
  function isInstalled(host, name) {
1344
1506
  return installed(host, name)?.version !== undefined;
@@ -1346,7 +1508,7 @@ function isInstalled(host, name) {
1346
1508
  function ownBin(host, name, bin) {
1347
1509
  const declared = installed(host, name)?.bin;
1348
1510
  const file = typeof declared === "string" ? declared : declared?.[bin];
1349
- return file === undefined ? undefined : join5(host.globalModules, name, file);
1511
+ return file === undefined ? undefined : join7(host.globalModules, name, file);
1350
1512
  }
1351
1513
  async function checkTool(host, name, bins) {
1352
1514
  const check = name.slice(name.indexOf("/") + 1);
@@ -1361,17 +1523,29 @@ async function checkTool(host, name, bins) {
1361
1523
  if (latest !== "" && Bun.semver.order(version, latest) < 0)
1362
1524
  return { check, ok: false, detail: `${version}, ${latest} released${off.length > 0 ? `, ${off.join(", ")} not on the PATH` : ""}`, fix: `bun install -g ${name}@${latest}` };
1363
1525
  if (off.length > 0)
1364
- return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join5(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
1526
+ return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join7(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
1365
1527
  if (latest === "")
1366
1528
  return { check, ok: true, detail: `${version}, the registry did not answer: ${firstLine(info.stderr) || answered || "no version"}` };
1367
1529
  return { check, ok: true, detail: version };
1368
1530
  }
1531
+ function extraForgeHosts(home) {
1532
+ try {
1533
+ const path = userConfigReadPath("doctor", [], { home, xdg: process.env.XDG_CONFIG_HOME });
1534
+ if (!existsSync5(path))
1535
+ return [];
1536
+ const raw = Bun.JSONC.parse(readFileSync4(path, "utf8"));
1537
+ return Array.isArray(raw?.forgeHosts) ? raw.forgeHosts.filter((each) => typeof each === "string") : [];
1538
+ } catch {
1539
+ return [];
1540
+ }
1541
+ }
1369
1542
  async function checkTea(host) {
1370
1543
  if (host.which("tea") === null)
1371
1544
  return { check: "tea", ok: false, detail: "not installed, and ft goes through it", fix: "install tea from https://gitea.com/gitea/tea/releases" };
1372
1545
  const logins = await host.run(["tea", "login", "list", "-o", "json"]);
1373
1546
  const parsed = logins.code === 0 ? readJsonText(logins.stdout) : undefined;
1374
- const login = parsed?.find((each) => URL.canParse(each.url) && FORGE_HOSTS.has(new URL(each.url).host));
1547
+ const hosts = new Set([...FORGE_HOSTS, ...extraForgeHosts(host.home)]);
1548
+ const login = parsed?.find((each) => URL.canParse(each.url) && hosts.has(new URL(each.url).host));
1375
1549
  if (login === undefined)
1376
1550
  return { check: "tea", ok: false, detail: `no login to ${FORGE}`, fix: `tea login add --url https://${FORGE} --oauth` };
1377
1551
  const version = await host.run(["tea", "api", "--login", login.name, "version"]);
@@ -1382,9 +1556,9 @@ async function checkTea(host) {
1382
1556
  return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
1383
1557
  }
1384
1558
  function hooksWiring(host) {
1385
- const settings = readJson(join5(host.home, ".claude", "settings.json")) ?? {};
1559
+ const settings = readJson(join7(host.home, ".claude", "settings.json")) ?? {};
1386
1560
  const plugin = settings.enabledPlugins?.[PLUGIN] === true;
1387
- 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")));
1561
+ const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync5(join7(dir, "hooks", "hooks.json")));
1388
1562
  return { plugin, mod };
1389
1563
  }
1390
1564
  function checkHooks(host) {
@@ -1427,7 +1601,7 @@ var FORGEJO = "@kaynooo/cli-forgejo";
1427
1601
  var WORKSPACE = "@kaynooo/cli-workspace";
1428
1602
  function checkWorkspace(host, env = process.env) {
1429
1603
  const folder = workspaceRoot({ home: host.home, env });
1430
- if (existsSync2(folder))
1604
+ if (existsSync5(folder))
1431
1605
  return { check: "workspace", ok: true, detail: folder };
1432
1606
  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` };
1433
1607
  }
@@ -1479,14 +1653,36 @@ var BASH_FUNCTION = `__kaynooo_cli_complete() {
1479
1653
  done
1480
1654
  COMPREPLY+=($(compgen -W "$words" -- "$cur"))
1481
1655
  }`;
1656
+ var ZSH_FUNCTION = `__kaynooo_cli_complete() {
1657
+ local line
1658
+ local -a described plain
1659
+ for line in \${(f)"$("\${words[1]}" __complete "\${(@)words[2,CURRENT]}" 2>/dev/null)"}; do
1660
+ if [[ $line == __files__ ]]; then
1661
+ _files
1662
+ elif [[ $line == *$'\\t'* ]]; then
1663
+ described+=("\${\${line%%$'\\t'*}//:/\\\\:}:\${line#*$'\\t'}")
1664
+ else
1665
+ plain+=("$line")
1666
+ fi
1667
+ done
1668
+ (( \${#described} )) && _describe -V value described
1669
+ (( \${#plain} )) && compadd -- "\${plain[@]}"
1670
+ return 0
1671
+ }`;
1672
+ function zshScript(bins) {
1673
+ return `# Generated by \`cli completions zsh\`
1674
+ ${ZSH_FUNCTION}
1675
+ compdef __kaynooo_cli_complete ${bins.join(" ")}
1676
+ `;
1677
+ }
1482
1678
  function bashScript(bins) {
1483
1679
  return `# Generated by \`cli completions bash\`
1484
1680
  ${BASH_FUNCTION}
1485
1681
  complete -F __kaynooo_cli_complete ${bins.join(" ")}
1486
1682
  `;
1487
1683
  }
1488
- function completionDir(home, env = process13.env) {
1489
- return join6(env.XDG_CACHE_HOME ?? join6(home, ".cache"), "fish", "kaynooo-cli");
1684
+ function completionDir(home, env = process15.env) {
1685
+ return join8(env.XDG_CACHE_HOME ?? join8(home, ".cache"), "fish", "kaynooo-cli");
1490
1686
  }
1491
1687
  function installedBins(host) {
1492
1688
  return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
@@ -1500,18 +1696,55 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
1500
1696
  async function installCompletions(host, dir) {
1501
1697
  const bins = installedBins(host);
1502
1698
  mkdirSync3(dir, { recursive: true });
1503
- const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync4(join6(dir, name), "utf8").startsWith(MARKER));
1699
+ const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync5(join8(dir, name), "utf8").startsWith(MARKER));
1504
1700
  for (const name of removed)
1505
- rmSync(join6(dir, name));
1506
- await Promise.all(bins.map(async (bin) => Bun.write(join6(dir, `${bin}.fish`), completionFile(bin))));
1701
+ rmSync(join8(dir, name));
1702
+ await Promise.all(bins.map(async (bin) => Bun.write(join8(dir, `${bin}.fish`), completionFile(bin))));
1507
1703
  return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
1508
1704
  }
1509
1705
  function missingCompletions(host, dir) {
1510
- return installedBins(host).filter((bin) => !existsSync3(join6(dir, `${bin}.fish`)));
1706
+ return installedBins(host).filter((bin) => !existsSync6(join8(dir, `${bin}.fish`)));
1707
+ }
1708
+
1709
+ // src/config-report.ts
1710
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1711
+ import { join as join9, resolve as resolve4 } from "path";
1712
+ import process16 from "process";
1713
+ var USER_FILES = [["config", []], ["theme", []], ["doctor", []], ["ft", [["ft", "config.json"]]], ["db", [["db", "config.json"]]], ["pkg", []], ["pkg-excluded", []], ["repos", []]];
1714
+ var PROJECT_FILES = [join9(".kaynooo", "theme.json"), ".pkgrc.json"];
1715
+ var ENV = ["KAYNOOO_WORKSPACE", "KAYNOOO_CLI_NO_USAGE", "KIT_MODE", "KIT_THEME", "KIT_HELP", "CI", "CLAUDECODE", "AI_AGENT", "NO_COLOR", "FORCE_COLOR", "NODE_DISABLE_COLORS", "COLORTERM", "TERM", "BROWSER", "EDITOR", "VISUAL", "SLOT_DIR", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_STATE_HOME", "XDG_RUNTIME_DIR", "BUN_INSTALL", "COMPOSE_FILE", "COMPOSE_PATH_SEPARATOR", "CLAUDE_CONFIG_DIR", "CLAUDE_CODE_TMPDIR", "GITHUB_TOKEN", "DISCORD_WEBHOOK"];
1716
+ var SECRETS = new Set(["GITHUB_TOKEN", "DISCORD_WEBHOOK"]);
1717
+ function fileStatus(path) {
1718
+ if (!existsSync7(path))
1719
+ return "absent";
1720
+ try {
1721
+ Bun.JSONC.parse(readFileSync6(path, "utf8"));
1722
+ return "set";
1723
+ } catch {
1724
+ return "invalid";
1725
+ }
1726
+ }
1727
+ function configReport(host, env = process16.env, from = process16.cwd()) {
1728
+ const sources = USER_FILES.map(([bin, legacy]) => {
1729
+ const where = userConfigReadPath(bin, legacy, { home: host.home, xdg: env.XDG_CONFIG_HOME });
1730
+ return { kind: "file", name: `${bin}.json`, where, status: fileStatus(where) };
1731
+ });
1732
+ for (const name of PROJECT_FILES) {
1733
+ const where = findUp(name, from, (dir) => dir === resolve4(host.home) || existsSync7(join9(dir, ".git")));
1734
+ sources.push({ kind: "file", name, where: where ?? `${name} (none found upward from ${from})`, status: where === undefined ? "absent" : fileStatus(where) });
1735
+ }
1736
+ for (const name of ENV) {
1737
+ const value = env[name];
1738
+ sources.push({ kind: "env", name, where: "environment", status: value === undefined || value === "" ? "absent" : "set", ...value === undefined || value === "" ? {} : { value: SECRETS.has(name) ? "(hidden)" : value } });
1739
+ }
1740
+ const { sources: themes, preset } = loadTheme({ env, from, home: host.home, warn: () => {
1741
+ return;
1742
+ } });
1743
+ return { workspace: workspaceRoot({ env, home: host.home }), theme: { preset, sources: themes }, sources };
1511
1744
  }
1512
1745
 
1513
1746
  // src/doctor.ts
1514
- import process17 from "process";
1747
+ import process20 from "process";
1515
1748
 
1516
1749
  // src/fix.ts
1517
1750
  var INSTALL = /^bun install -g (\S+)$/;
@@ -1551,19 +1784,19 @@ async function applyFixes(host, steps, dry) {
1551
1784
  }
1552
1785
 
1553
1786
  // src/host.ts
1554
- import { homedir as homedir4 } from "os";
1555
- import { join as join7 } from "path";
1556
- import process14 from "process";
1787
+ import { homedir as homedir5 } from "os";
1788
+ import { join as join10 } from "path";
1789
+ import process17 from "process";
1557
1790
  var RUN_TIMEOUT_MS = 20000;
1558
1791
  function hostHere() {
1559
- const home = homedir4();
1792
+ const home = homedir5();
1560
1793
  return {
1561
1794
  home,
1562
- globalModules: join7(process14.env.BUN_INSTALL ?? join7(home, ".bun"), "install", "global", "node_modules"),
1795
+ globalModules: join10(process17.env.BUN_INSTALL ?? join10(home, ".bun"), "install", "global", "node_modules"),
1563
1796
  bunVersion: Bun.version,
1564
1797
  which: (bin) => Bun.which(bin),
1565
1798
  run: async (argv, env) => {
1566
- const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process14.env, ...env } } });
1799
+ const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process17.env, ...env } } });
1567
1800
  if (ran.timedOut)
1568
1801
  return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
1569
1802
  const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
@@ -1573,19 +1806,53 @@ function hostHere() {
1573
1806
  }
1574
1807
 
1575
1808
  // src/install.ts
1576
- import process16 from "process";
1809
+ import process19 from "process";
1577
1810
 
1578
1811
  // ../kit/src/prompt.ts
1579
- import process15 from "process";
1580
- var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
1581
- var CONTROL = /^[\x00-\x1F\x7F]/;
1582
- var WORDS = /\s+/;
1583
- var MAX_ROWS = 10;
1812
+ import process18 from "process";
1813
+
1814
+ // ../kit/src/picker/score.ts
1815
+ var BOUNDARY_AFTER = new Set([" ", "/", "-", "_", ".", ":", "\\", "(", "[", ",", "\t"]);
1816
+ var NONE = Number.NEGATIVE_INFINITY;
1817
+
1818
+ // ../kit/src/picker/state.ts
1819
+ var IS_CONTROL = /[\x00-\x1F\x7F-\x9F]/;
1584
1820
  var PASTE_START = "\x1B[200~";
1585
1821
  var PASTE_END = "\x1B[201~";
1586
- var KEYS = { "\x1B[A": "up", "\x1BOA": "up", "\x1B[B": "down", "\x1BOB": "down", "\x1B[5~": "pageup", "\x1B[6~": "pagedown", "\x1B[H": "home", "\x1B[1~": "home", "\x1BOH": "home", "\x1B[F": "end", "\x1B[4~": "end", "\x1BOF": "end", "\r": "enter", "\n": "enter", "\x03": "cancel", "\x1B": "cancel", "\x7F": "erase", "\b": "erase" };
1587
- function readKeys(input, paste = { active: false }) {
1822
+ var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|\x1B[\s\S]|[\s\S]/gu;
1823
+ var KEYS = {
1824
+ "\x1B[A": "up",
1825
+ "\x1BOA": "up",
1826
+ "\x10": "up",
1827
+ "\x1B[B": "down",
1828
+ "\x1BOB": "down",
1829
+ "\x0E": "down",
1830
+ "\x1B[5~": "pageup",
1831
+ "\x1B[6~": "pagedown",
1832
+ "\x1B[H": "home",
1833
+ "\x1B[1~": "home",
1834
+ "\x1BOH": "home",
1835
+ "\x1B[F": "end",
1836
+ "\x1B[4~": "end",
1837
+ "\x1BOF": "end",
1838
+ "\r": "enter",
1839
+ "\n": "enter",
1840
+ "\x03": "cancel",
1841
+ "\x1B": "cancel",
1842
+ "\x7F": "erase",
1843
+ "\b": "erase",
1844
+ "\x15": "clear",
1845
+ "\x17": "word",
1846
+ "\t": "tab",
1847
+ "\x1F": "help",
1848
+ "\x1BOP": "help",
1849
+ "\x1B[11~": "help"
1850
+ };
1851
+ function parseKeys(input, paste = { active: false }, ctrls = []) {
1852
+ const codes = new Map(ctrls.map((name) => [String.fromCharCode(name.codePointAt(name.length - 1) - 96), name]));
1588
1853
  return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => {
1854
+ const named = codes.get(sequence);
1855
+ const key = named === undefined ? KEYS[sequence] : { ctrl: named };
1589
1856
  if (sequence === PASTE_START || sequence === PASTE_END) {
1590
1857
  paste.active = sequence === PASTE_START;
1591
1858
  return [];
@@ -1593,9 +1860,19 @@ function readKeys(input, paste = { active: false }) {
1593
1860
  if (paste.active && (sequence === "\r" || sequence === `
1594
1861
  `))
1595
1862
  return [];
1596
- return KEYS[sequence] ?? (CONTROL.test(sequence) ? [] : [{ text: sequence }]);
1863
+ if (key !== undefined)
1864
+ return [key];
1865
+ return IS_CONTROL.test(sequence) || sequence.startsWith("\x1B") ? [] : [{ text: sequence }];
1597
1866
  });
1598
1867
  }
1868
+
1869
+ // ../kit/src/prompt.ts
1870
+ var WORDS = /\s+/;
1871
+ var MAX_ROWS = 10;
1872
+ var PROMPT_KEYS = new Set(["up", "down", "pageup", "pagedown", "home", "end", "enter", "cancel", "erase"]);
1873
+ function readKeys(input, paste = { active: false }) {
1874
+ return parseKeys(input, paste).filter((key) => typeof key === "object" ? "text" in key : PROMPT_KEYS.has(key));
1875
+ }
1599
1876
  function flip(checked, value) {
1600
1877
  const next = new Set(checked);
1601
1878
  if (!next.delete(value))
@@ -1735,7 +2012,7 @@ function asking(colors, question, typed, keys) {
1735
2012
  function answer(colors, question, state, picked) {
1736
2013
  return state === "enter" ? `${colors.green("\u2713")} ${question} ${colors.cyan(picked)}` : `${colors.red("\u2717")} ${question} ${colors.dim("cancelled")}`;
1737
2014
  }
1738
- async function ask(question, { hint, terminal = process15 }, render, press) {
2015
+ async function ask(question, { hint, terminal = process18 }, render, press) {
1739
2016
  const { stdin, stderr } = terminal;
1740
2017
  if (!isInteractive(terminal))
1741
2018
  fail(`cannot ask "${question}" off a terminal`, 2, hint);
@@ -1870,7 +2147,7 @@ async function askWorkspace(host) {
1870
2147
  async function install(host, options, recheck, choosing = ask2) {
1871
2148
  const all = candidates2(host);
1872
2149
  const pool = all.filter((each) => !each.installed);
1873
- const { dim, green, red, yellow } = paint(process16.stdout);
2150
+ const { dim, green, red, yellow } = paint(process19.stdout);
1874
2151
  if (options.list) {
1875
2152
  for (const each of pool)
1876
2153
  console.log(`${each.label.padEnd(12)} ${dim(each.hint)}`);
@@ -1978,7 +2255,7 @@ var OPTIONS = {
1978
2255
  function print(results) {
1979
2256
  if (results.length === 0)
1980
2257
  return;
1981
- const { green, red, dim } = paint(process17.stdout);
2258
+ const { green, red, dim } = paint(process20.stdout);
1982
2259
  const width = Math.max(...results.map((result) => result.check.length));
1983
2260
  for (const { check, ok, detail, fix } of results)
1984
2261
  console.log(`${ok ? green("ok ") : red("FAIL")} ${check.padEnd(width)} ${detail}${fix === undefined ? "" : ` ${dim("fix:")} ${fix}`}`);
@@ -1988,7 +2265,7 @@ function print(results) {
1988
2265
  ${dim("fix them all:")} ${INSTALL2}${installs.join(" ")}`);
1989
2266
  }
1990
2267
  function printOutcomes(outcomes, log) {
1991
- const { green, red, dim } = paint(process17.stdout);
2268
+ const { green, red, dim } = paint(process20.stdout);
1992
2269
  const marks = { ran: green("ran "), failed: red("failed "), "dry-run": dim("would "), "not run": dim("not run") };
1993
2270
  for (const { command, status, detail } of outcomes)
1994
2271
  log(`${marks[status]} ${command}${detail === undefined ? "" : ` ${dim(detail)}`}`);
@@ -2028,7 +2305,7 @@ after the fixes:`);
2028
2305
  const absent = Object.keys(TOOLS).filter((name) => !isInstalled(host, name)).length;
2029
2306
  if (!json && only.length === 0 && absent > 0)
2030
2307
  console.log(`
2031
- ${paint(process17.stdout).dim(`${absent} packages not installed: doctor --install`)}`);
2308
+ ${paint(process20.stdout).dim(`${absent} packages not installed: doctor --install`)}`);
2032
2309
  return results.every((result) => result.ok) ? 0 : 1;
2033
2310
  }
2034
2311
 
@@ -2057,7 +2334,7 @@ var LIST_OPTIONS = {
2057
2334
  json: jsonOption("[{bin, package, installed, synopsis?, summary?, replaces?, examples?}]")
2058
2335
  };
2059
2336
  function print2(listed) {
2060
- const { cyan, dim } = paint(process18.stdout);
2337
+ const { cyan, dim } = paint(process21.stdout);
2061
2338
  const width = Math.max(...listed.map((each) => each.bin.length));
2062
2339
  for (const each of listed) {
2063
2340
  const what = !each.installed ? dim(`not installed: bun install -g ${each.package}`) : `${each.summary ?? ""}${each.replaces === undefined ? "" : dim(`; instead of ${each.replaces}`)}`;
@@ -2073,26 +2350,24 @@ async function list(args, host = hostHere()) {
2073
2350
  print2(listed);
2074
2351
  return 0;
2075
2352
  }
2076
- var COMPLETIONS_USAGE = `cli completions <fish|bash> [--install] [--dir <folder>]: shell completions for every installed bin of the @kaynooo/cli-* packages
2353
+ var COMPLETIONS_USAGE = `cli completions <fish|bash|zsh> [--install] [--dir <folder>]: shell completions for every installed bin of the @kaynooo/cli-* packages
2077
2354
 
2078
2355
  Each bin completes itself through \`<bin> __complete\`; this writes the glue a shell needs per bin.
2079
2356
  fish: without --install the files print on stdout, with it they go to ~/.cache/fish/kaynooo-cli (or $XDG_CACHE_HOME), one <bin>.fish each, the stale ones this command wrote taken out. That folder has to be in fish_complete_path, first so a completion fish ships for the same name does not win: \`set -p fish_complete_path ~/.cache/fish/kaynooo-cli\`.
2080
2357
  bash: one script on stdout, sourced from ~/.bashrc with \`source <(cli completions bash)\`; --install and --dir are fish's.
2081
- zsh: has no script of its own, it reads the bash one through bashcompinit: \`autoload -U +X compinit && compinit\`, \`autoload -U +X bashcompinit && bashcompinit\`, then the bash line above in ~/.zshrc (untested, zsh is not installed on the machine this was written on).`;
2358
+ zsh: one script on stdout like bash's, after compinit in ~/.zshrc: \`autoload -U +X compinit && compinit\`, then \`source <(cli completions zsh)\` (written without zsh at hand to run it, so untested).`;
2082
2359
  var COMPLETIONS_OPTIONS = {
2083
2360
  install: { type: "boolean", description: "write the files to the folder instead of printing them" },
2084
2361
  dir: { type: "string", short: "d", description: "the folder to write to, instead of ~/.cache/fish/kaynooo-cli" }
2085
2362
  };
2086
2363
  async function completions(args, host = hostHere()) {
2087
- const { values, positionals } = parse(args, COMPLETIONS_USAGE, COMPLETIONS_OPTIONS, { positionals: ["<shell>"], exits: [[0, "it printed or wrote them"], [2, "a usage error or a shell without a script"]], candidates: { "<shell>": () => ["fish", "bash"] }, examples: ["cli completions fish", ["cli completions fish --install", "write one file per bin"], ["source <(cli completions bash)", "in ~/.bashrc"]] });
2088
- if (positionals[0] === "zsh")
2089
- badUsage("zsh has no script of its own: it reads the bash one through bashcompinit, see cli completions --help");
2090
- if (positionals[0] !== "fish" && positionals[0] !== "bash")
2091
- badUsage("completions exist for fish and bash: cli completions fish");
2092
- if (positionals[0] === "bash") {
2364
+ const { values, positionals } = parse(args, COMPLETIONS_USAGE, COMPLETIONS_OPTIONS, { positionals: ["<shell>"], exits: [[0, "it printed or wrote them"], [2, "a usage error or a shell without a script"]], candidates: { "<shell>": () => ["fish", "bash", "zsh"] }, examples: ["cli completions fish", ["cli completions fish --install", "write one file per bin"], ["source <(cli completions bash)", "in ~/.bashrc"], ["source <(cli completions zsh)", "in ~/.zshrc after compinit"]] });
2365
+ if (positionals[0] !== "fish" && positionals[0] !== "bash" && positionals[0] !== "zsh")
2366
+ badUsage("completions exist for fish, bash and zsh: cli completions fish");
2367
+ if (positionals[0] === "bash" || positionals[0] === "zsh") {
2093
2368
  if (values.install === true || values.dir !== undefined)
2094
- badUsage("--install and --dir are for fish: source the bash script instead");
2095
- console.log(bashScript(installedBins(host)).trimEnd());
2369
+ badUsage(`--install and --dir are for fish: source the ${positionals[0]} script instead`);
2370
+ console.log((positionals[0] === "zsh" ? zshScript : bashScript)(installedBins(host)).trimEnd());
2096
2371
  return 0;
2097
2372
  }
2098
2373
  if (values.install !== true) {
@@ -2113,6 +2388,31 @@ var STATS_OPTIONS = {
2113
2388
  since: { type: "string", description: "only the runs this recent (7d, 12h)" },
2114
2389
  json: jsonOption("{runs, bins: [{bin, runs, failed, usageErrors}], commands: [{bin, command, runs, failed, usageErrors}], usageErrors: [{bin, command?, unknown, count}]}")
2115
2390
  };
2391
+ var CONFIG_USAGE = `cli config [--json]: where every bin looks for its config and which environment variables are set
2392
+
2393
+ Lists each user and project config file with the path read and whether it is there (set, absent, or invalid when it does not parse), then the main environment variables that change a bin's behaviour (docs/config.md has the rest); a token or webhook shows as hidden. Ends on the workspace folder and the theme in force. docs/config.md in the repository describes every key.
2394
+ Exits 0 when it printed the report, 2 on a usage error.`;
2395
+ var CONFIG_OPTIONS = {
2396
+ json: jsonOption("{workspace, theme: {preset, sources}, sources: [{kind, name, where, status, value?}]}")
2397
+ };
2398
+ function config2(args, host = hostHere()) {
2399
+ const { values } = parse(args, CONFIG_USAGE, CONFIG_OPTIONS, { positionals: [], examples: ["cli config", ["cli config --json", "for a script"]] });
2400
+ const report = configReport(host);
2401
+ if (values.json === true) {
2402
+ printJson(report);
2403
+ return 0;
2404
+ }
2405
+ const { dim } = paint(process21.stdout);
2406
+ console.log(table(report.sources.filter((each) => each.kind === "file").map((each) => [each.name, each.status === "absent" ? dim(each.status) : each.status, dim(each.where)])));
2407
+ const variables = report.sources.filter((each) => each.kind === "env" && each.status === "set");
2408
+ console.log(variables.length === 0 ? `
2409
+ ${dim("no variable set")}` : `
2410
+ ${table(variables.map((each) => [each.name, each.value ?? ""]))}`);
2411
+ console.log(`
2412
+ workspace ${report.workspace}
2413
+ theme ${report.theme.preset}${report.theme.sources.length === 0 ? "" : `, from ${report.theme.sources.join(", ")}`}`);
2414
+ return 0;
2415
+ }
2116
2416
  async function stats(args, path = usagePath()) {
2117
2417
  const { values } = parse(args, STATS_USAGE, STATS_OPTIONS, { positionals: [], examples: ["cli stats", ["cli stats --since 7d", "the last week"]] });
2118
2418
  const window = values.since === undefined ? undefined : duration(values.since, "since", 0, "h");
@@ -2140,7 +2440,7 @@ ${table(summary.usageErrors.map((each) => [`${each.bin}${each.command === undefi
2140
2440
  return 0;
2141
2441
  }
2142
2442
  if (import.meta.main) {
2143
- process18.exitCode = await runCli({
2443
+ process21.exitCode = await runCli({
2144
2444
  name: "cli",
2145
2445
  about: "the bins of the @kaynooo/cli-* packages, described by their own help",
2146
2446
  replaces: "running each bin's --help to find the one that fits",
@@ -2148,6 +2448,7 @@ if (import.meta.main) {
2148
2448
  commands: {
2149
2449
  doctor: { summary: "checks this host's install of the bins, one line per check, the command that fixes a failing one beside it", run: async (args) => main(args) },
2150
2450
  list: { summary: "every bin in one line: what it does and what it is reached for instead of", run: async (args) => list(args) },
2451
+ config: { summary: "where every bin looks for its config and which environment variables are set", run: async (args) => Promise.resolve(config2(args)) },
2151
2452
  stats: { summary: "how often each bin ran and the options usage errors did not know", run: async (args) => stats(args) },
2152
2453
  completions: { summary: "fish completions for every installed bin, printed or written to the folder fish reads", run: async (args) => completions(args) }
2153
2454
  }
@@ -2155,6 +2456,7 @@ if (import.meta.main) {
2155
2456
  }
2156
2457
  export {
2157
2458
  completions,
2459
+ config2 as config,
2158
2460
  list,
2159
2461
  stats
2160
2462
  };