@kaynooo/cli-doctor 0.8.0 → 0.10.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.
Files changed (3) hide show
  1. package/dist/cli-bin.js +277 -102
  2. package/dist/cli.js +276 -101
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -2,16 +2,37 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli.ts
5
- import process15 from "process";
5
+ import process18 from "process";
6
6
  // ../kit/src/args.ts
7
- import process5 from "process";
7
+ import process7 from "process";
8
8
  import { parseArgs } from "util";
9
9
 
10
10
  // ../kit/src/format.ts
11
- import process3 from "process";
11
+ import process5 from "process";
12
12
 
13
- // ../kit/src/style.ts
13
+ // ../kit/src/mode.ts
14
14
  import process2 from "process";
15
+ var MODES = new Set(["terminal", "ci", "agent", "pipe"]);
16
+ var AGENT_VARIABLES = ["CLAUDECODE", "AI_AGENT"];
17
+ function isSet(value) {
18
+ return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
19
+ }
20
+ function outputMode(stream = process2.stdout, env = process2.env) {
21
+ const chosen = env.KIT_MODE;
22
+ if (chosen !== undefined && MODES.has(chosen))
23
+ return chosen;
24
+ if (AGENT_VARIABLES.some((name) => isSet(env[name])))
25
+ return "agent";
26
+ if (stream.isTTY === true)
27
+ return "terminal";
28
+ return isSet(env.CI) ? "ci" : "pipe";
29
+ }
30
+ function isInteractive(streams = process2) {
31
+ return streams.stdin?.isTTY === true && streams.stderr?.isTTY === true;
32
+ }
33
+
34
+ // ../kit/src/style.ts
35
+ import process4 from "process";
15
36
 
16
37
  // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
17
38
  function x(s) {
@@ -274,22 +295,34 @@ function le(i = {}) {
274
295
  }
275
296
  var t = le();
276
297
 
298
+ // ../kit/src/user-config.ts
299
+ import { homedir } from "os";
300
+ import { join } from "path";
301
+ import process3 from "process";
302
+ function configBase(where = {}) {
303
+ const xdg = where.xdg ?? (where.home === undefined ? process3.env.XDG_CONFIG_HOME : undefined);
304
+ return xdg !== undefined && xdg !== "" ? xdg : join(where.home ?? homedir(), ".config");
305
+ }
306
+ function userConfigPath(bin, where = {}) {
307
+ return join(configBase(where), "kaynooo-cli", `${bin}.json`);
308
+ }
309
+
277
310
  // ../kit/src/theme.ts
278
311
  var STYLES = new Set(["bold", "dim", "italic", "underline", "inverse", "strikethrough", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "gray", "grey", "blackBright", "redBright", "greenBright", "yellowBright", "blueBright", "magentaBright", "cyanBright", "whiteBright"]);
279
312
  var MAX_FILE_BYTES = 64 * 1024;
280
313
  var warned = new Set;
281
314
 
282
315
  // ../kit/src/style.ts
283
- function takesControlCodes(stream, env = process2.env) {
284
- return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
316
+ function takesControlCodes(stream, env = process4.env) {
317
+ return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb" && outputMode(stream, env) === "terminal";
285
318
  }
286
- function paint(stream, env = process2.env) {
319
+ function paint(stream, env = process4.env) {
287
320
  return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
288
321
  }
289
322
  var STATUS_WORDS = {
290
323
  passed: ["passed", "success", "succeeded", "ok"],
291
324
  failed: ["failed", "failure", "error", "errored"],
292
- running: ["running", "in_progress", "started"],
325
+ running: ["running", "in_progress", "started", "holding"],
293
326
  queued: ["queued", "waiting", "pending", "created", "blocked", "scheduled"],
294
327
  skipped: ["skipped", "cancelled", "canceled", "manual"]
295
328
  };
@@ -308,8 +341,11 @@ var ESCAPE = String.fromCharCode(27);
308
341
  function textWidth(text) {
309
342
  return Bun.stringWidth(text);
310
343
  }
311
- function table(rows, { cellWidth, header = false, align = [], fit, stream = process3.stdout } = {}) {
344
+ function table(rows, { cellWidth, header = false, align = [], fit, stream = process5.stdout, env = process5.env } = {}) {
312
345
  const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
346
+ if (outputMode(stream, env) === "agent")
347
+ return cells.map((row) => row.map((cell) => Bun.stripANSI(cell).replaceAll(/[\t\r\n]+/g, " ")).join("\t")).join(`
348
+ `);
313
349
  const widths = [];
314
350
  for (const row of cells) {
315
351
  for (const [index, cell] of row.entries())
@@ -344,32 +380,35 @@ function narrow(rows, widths, limit) {
344
380
  }
345
381
  return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
346
382
  }
383
+ function jsonText(value, replacer, { stream = process5.stdout, env = process5.env } = {}) {
384
+ return JSON.stringify(value, replacer, outputMode(stream, env) === "agent" ? undefined : 2);
385
+ }
347
386
  function printJson(value) {
348
- console.log(JSON.stringify(value, undefined, 2));
387
+ console.log(jsonText(value));
349
388
  }
350
389
 
351
390
  // ../kit/src/help.ts
352
- import process4 from "process";
391
+ import process6 from "process";
353
392
  var OPTIONAL = /\[[^\]]+\]/g;
354
393
  var USAGE_PREFIX = /^usage:\s*/i;
355
394
  var FULL_STOP = /\.$/;
356
- function usageLine(usage, colors = paint(process4.stdout)) {
395
+ function usageLine(usage, colors = paint(process6.stdout)) {
357
396
  return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
358
397
  }
359
- function helpText(usage, options, colors = paint(process4.stdout), { examples = [], replaces, exits = [] } = {}) {
398
+ function helpText(usage, options, colors = paint(process6.stdout), { examples = [], replaces, exits = [] } = {}) {
360
399
  return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
361
400
  `);
362
401
  }
363
- function exitRows(exits, colors = paint(process4.stdout)) {
402
+ function exitRows(exits, colors = paint(process6.stdout)) {
364
403
  if (exits.length === 0)
365
404
  return [];
366
405
  const width = Math.max(...exits.map(([code]) => String(code).length));
367
406
  return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
368
407
  }
369
- function replacesRows(replaces, colors = paint(process4.stdout)) {
370
- return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
408
+ function replacesRows(replaces, colors = paint(process6.stdout)) {
409
+ return replaces === undefined || outputMode() === "agent" ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
371
410
  }
372
- function wantsHelpJson(env = process4.env) {
411
+ function wantsHelpJson(env = process6.env) {
373
412
  return env.KIT_HELP === "json";
374
413
  }
375
414
  function description(usage, { examples = [], replaces, exits } = {}, options) {
@@ -388,12 +427,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
388
427
  ...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
389
428
  };
390
429
  }
391
- function exampleRows(examples, colors = paint(process4.stdout)) {
392
- if (examples.length === 0)
430
+ function exampleRows(examples, colors = paint(process6.stdout)) {
431
+ if (examples.length === 0 || outputMode() === "agent")
393
432
  return [];
394
433
  return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
395
434
  }
396
- function optionRows(options, colors = paint(process4.stdout)) {
435
+ function optionRows(options, colors = paint(process6.stdout)) {
397
436
  const rows = Object.entries(options).map(([name, option]) => {
398
437
  const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
399
438
  const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
@@ -486,7 +525,7 @@ var OUTPUT_OPTIONS = {
486
525
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
487
526
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
488
527
  };
489
- function applyOutput(color, plain, env = process5.env) {
528
+ function applyOutput(color, plain, env = process7.env) {
490
529
  const when = plain === true ? "never" : color ?? "auto";
491
530
  if (when === "auto")
492
531
  return;
@@ -657,7 +696,7 @@ function nearestOption(message, options, allowNegative) {
657
696
  }
658
697
  // ../kit/src/process.ts
659
698
  import { constants } from "os";
660
- import process6 from "process";
699
+ import process8 from "process";
661
700
  var KILL_GRACE_MS = 2000;
662
701
  function exitCodeOf(code, signal) {
663
702
  if (code !== null)
@@ -677,7 +716,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
677
716
  return;
678
717
  try {
679
718
  if (grouped)
680
- process6.kill(-proc.pid, killing);
719
+ process8.kill(-proc.pid, killing);
681
720
  else
682
721
  proc.kill(killing);
683
722
  } catch {}
@@ -752,14 +791,14 @@ var RESET_STYLE = `${ESC}[0m`;
752
791
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
753
792
  // ../kit/src/cli.ts
754
793
  import { readFileSync, realpathSync } from "fs";
755
- import { dirname as dirname2, join as join2 } from "path";
756
- import process8 from "process";
794
+ import { dirname as dirname2, join as join3 } from "path";
795
+ import process10 from "process";
757
796
 
758
797
  // ../kit/src/usage.ts
759
798
  import { appendFileSync, mkdirSync } from "fs";
760
- import { homedir } from "os";
761
- import { dirname, isAbsolute, join } from "path";
762
- import process7 from "process";
799
+ import { homedir as homedir2 } from "os";
800
+ import { dirname, isAbsolute, join as join2 } from "path";
801
+ import process9 from "process";
763
802
 
764
803
  // ../kit/src/logs.ts
765
804
  var DAY_MS = 24 * 60 * 60 * 1000;
@@ -773,14 +812,14 @@ var usageError;
773
812
  function noteUsageError(message) {
774
813
  usageError = message;
775
814
  }
776
- function stateHome(env = process7.env) {
815
+ function stateHome(env = process9.env) {
777
816
  const dir = env.XDG_STATE_HOME;
778
- return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
817
+ return dir !== undefined && isAbsolute(dir) ? dir : join2(homedir2(), ".local", "state");
779
818
  }
780
- function usagePath(env = process7.env) {
781
- return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
819
+ function usagePath(env = process9.env) {
820
+ return join2(stateHome(env), CLI_FOLDER, "usage.jsonl");
782
821
  }
783
- function usageRecorded(env = process7.env) {
822
+ function usageRecorded(env = process9.env) {
784
823
  const off = env.KAYNOOO_CLI_NO_USAGE;
785
824
  return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
786
825
  }
@@ -794,7 +833,7 @@ function usageEntry(bin, command, argv, code, now = new Date) {
794
833
  usageError = undefined;
795
834
  return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
796
835
  }
797
- function recordUsage(entry, env = process7.env) {
836
+ function recordUsage(entry, env = process9.env) {
798
837
  if (!usageRecorded(env))
799
838
  return;
800
839
  try {
@@ -933,8 +972,8 @@ async function help2({ path, header, summary: lazySummary, commands, defaultComm
933
972
  `);
934
973
  }
935
974
  function printError(prefix, message, hint, cause) {
936
- const colors = paint(process8.stderr);
937
- const styled = takesControlCodes(process8.stderr);
975
+ const colors = paint(process10.stderr);
976
+ const styled = takesControlCodes(process10.stderr);
938
977
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
939
978
  const under = (label, text, tint) => text === undefined ? "" : `
940
979
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -977,11 +1016,11 @@ async function dispatch(level, typed, { version, redact }) {
977
1016
  }
978
1017
  }
979
1018
  if (name === undefined && level.path.length > 1) {
980
- console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stderr));
1019
+ console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process10.stderr));
981
1020
  return 2;
982
1021
  }
983
1022
  if (name === undefined || name === "help" || name === "--help" || name === "-h") {
984
- console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stdout));
1023
+ console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process10.stdout));
985
1024
  return 0;
986
1025
  }
987
1026
  if (level.path.length === 1 && (name === "--version" || name === "-v")) {
@@ -1100,7 +1139,7 @@ async function printCandidates(found) {
1100
1139
  function packageVersion(entry = Bun.main) {
1101
1140
  for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
1102
1141
  try {
1103
- const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
1142
+ const { version } = JSON.parse(readFileSync(join3(dir, "package.json"), "utf8"));
1104
1143
  if (typeof version === "string")
1105
1144
  return version;
1106
1145
  } catch {}
@@ -1108,7 +1147,7 @@ function packageVersion(entry = Bun.main) {
1108
1147
  return "0.0.0";
1109
1148
  }
1110
1149
  }
1111
- async function runCli(cli, argv = process8.argv.slice(2)) {
1150
+ async function runCli(cli, argv = process10.argv.slice(2)) {
1112
1151
  const version = cli.version ?? packageVersion();
1113
1152
  if (argv[0] === COMPLETE)
1114
1153
  return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
@@ -1135,7 +1174,7 @@ function commandPath(scope, argv) {
1135
1174
  }
1136
1175
  return path.length === 0 ? undefined : path.join(" ");
1137
1176
  }
1138
- async function runMain({ name, version, run, redact, usage }, argv = process8.argv.slice(2)) {
1177
+ async function runMain({ name, version, run, redact, usage }, argv = process10.argv.slice(2)) {
1139
1178
  if (argv[0] === COMPLETE)
1140
1179
  return printCandidates(leafCandidates(run, argv.slice(1)));
1141
1180
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
@@ -1161,12 +1200,59 @@ var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "ses
1161
1200
  var {Glob } = globalThis.Bun;
1162
1201
  var SESSION_FILES = new Glob("*.json");
1163
1202
  var ACCOUNT_DIRS = new Glob("*/sessions");
1203
+ // ../kit/src/workspace-root.ts
1204
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1205
+ import { homedir as homedir3 } from "os";
1206
+ import { dirname as dirname3, join as join4, resolve } from "path";
1207
+ import process11 from "process";
1208
+ function expandHome(path, home) {
1209
+ if (path === "~")
1210
+ return home;
1211
+ return path.startsWith("~/") ? join4(home, path.slice(2)) : path;
1212
+ }
1213
+ function configured(env, home) {
1214
+ const path = userConfigPath("config", { home, xdg: env.XDG_CONFIG_HOME });
1215
+ if (!existsSync(path))
1216
+ return;
1217
+ try {
1218
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1219
+ const folder = typeof parsed === "object" && parsed !== null ? parsed.workspace : undefined;
1220
+ return typeof folder === "string" && folder !== "" ? folder : undefined;
1221
+ } catch {
1222
+ return;
1223
+ }
1224
+ }
1225
+ function workspaceRoot({ given, env = process11.env, home = homedir3() } = {}) {
1226
+ const fromEnv = env.KAYNOOO_WORKSPACE === "" ? undefined : env.KAYNOOO_WORKSPACE;
1227
+ return resolve(expandHome(given ?? fromEnv ?? configured(env, home) ?? join4(home, "Workspace"), home));
1228
+ }
1229
+ function workspaceChosen({ env = process11.env, home = homedir3() } = {}) {
1230
+ return (env.KAYNOOO_WORKSPACE ?? "") !== "" || configured(env, home) !== undefined;
1231
+ }
1232
+ function saveWorkspace(folder, { env = process11.env, home = homedir3() } = {}) {
1233
+ const path = userConfigPath("config", { home, xdg: env.XDG_CONFIG_HOME });
1234
+ let current = {};
1235
+ if (existsSync(path)) {
1236
+ try {
1237
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1238
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
1239
+ return false;
1240
+ current = parsed;
1241
+ } catch {
1242
+ return false;
1243
+ }
1244
+ }
1245
+ mkdirSync2(dirname3(path), { recursive: true });
1246
+ writeFileSync(path, `${JSON.stringify({ ...current, workspace: folder }, undefined, 2)}
1247
+ `);
1248
+ return true;
1249
+ }
1164
1250
  // src/doctor.ts
1165
- import process14 from "process";
1251
+ import process17 from "process";
1166
1252
 
1167
1253
  // src/checks.ts
1168
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
1169
- import { basename, join as join4 } from "path";
1254
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
1255
+ import { basename, join as join6 } from "path";
1170
1256
 
1171
1257
  // src/catalog.ts
1172
1258
  var HOOKS_REQUIRES = "@kaynooo/cli-guard";
@@ -1202,9 +1288,9 @@ var NEEDS = {
1202
1288
  };
1203
1289
 
1204
1290
  // src/completions.ts
1205
- import { existsSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
1206
- import { join as join3 } from "path";
1207
- import process10 from "process";
1291
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync3, rmSync } from "fs";
1292
+ import { join as join5 } from "path";
1293
+ import process13 from "process";
1208
1294
  var MARKER = "# Generated by `cli completions fish`";
1209
1295
  var FISH_FILE = /\.fish$/;
1210
1296
  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'
@@ -1238,8 +1324,8 @@ ${BASH_FUNCTION}
1238
1324
  complete -F __kaynooo_cli_complete ${bins.join(" ")}
1239
1325
  `;
1240
1326
  }
1241
- function completionDir(home, env = process10.env) {
1242
- return join3(env.XDG_CACHE_HOME ?? join3(home, ".cache"), "fish", "kaynooo-cli");
1327
+ function completionDir(home, env = process13.env) {
1328
+ return join5(env.XDG_CACHE_HOME ?? join5(home, ".cache"), "fish", "kaynooo-cli");
1243
1329
  }
1244
1330
  function installedBins(host) {
1245
1331
  return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
@@ -1252,15 +1338,15 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
1252
1338
  }
1253
1339
  async function installCompletions(host, dir) {
1254
1340
  const bins = installedBins(host);
1255
- mkdirSync2(dir, { recursive: true });
1256
- const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync2(join3(dir, name), "utf8").startsWith(MARKER));
1341
+ mkdirSync3(dir, { recursive: true });
1342
+ const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync3(join5(dir, name), "utf8").startsWith(MARKER));
1257
1343
  for (const name of removed)
1258
- rmSync(join3(dir, name));
1259
- await Promise.all(bins.map(async (bin) => Bun.write(join3(dir, `${bin}.fish`), completionFile(bin))));
1344
+ rmSync(join5(dir, name));
1345
+ await Promise.all(bins.map(async (bin) => Bun.write(join5(dir, `${bin}.fish`), completionFile(bin))));
1260
1346
  return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
1261
1347
  }
1262
1348
  function missingCompletions(host, dir) {
1263
- return installedBins(host).filter((bin) => !existsSync(join3(dir, `${bin}.fish`)));
1349
+ return installedBins(host).filter((bin) => !existsSync2(join5(dir, `${bin}.fish`)));
1264
1350
  }
1265
1351
 
1266
1352
  // src/checks.ts
@@ -1304,7 +1390,7 @@ function readJsonText(text) {
1304
1390
  }
1305
1391
  }
1306
1392
  function readJson(path) {
1307
- return existsSync2(path) ? readJsonText(readFileSync3(path, "utf8")) : undefined;
1393
+ return existsSync3(path) ? readJsonText(readFileSync4(path, "utf8")) : undefined;
1308
1394
  }
1309
1395
  function firstLine(text) {
1310
1396
  const trimmed = text.trim();
@@ -1313,7 +1399,7 @@ function firstLine(text) {
1313
1399
  return end === -1 ? trimmed : trimmed.slice(0, end);
1314
1400
  }
1315
1401
  function installed(host, name) {
1316
- return readJson(join4(host.globalModules, name, "package.json"));
1402
+ return readJson(join6(host.globalModules, name, "package.json"));
1317
1403
  }
1318
1404
  function isInstalled(host, name) {
1319
1405
  return installed(host, name)?.version !== undefined;
@@ -1321,7 +1407,7 @@ function isInstalled(host, name) {
1321
1407
  function ownBin(host, name, bin) {
1322
1408
  const declared = installed(host, name)?.bin;
1323
1409
  const file = typeof declared === "string" ? declared : declared?.[bin];
1324
- return file === undefined ? undefined : join4(host.globalModules, name, file);
1410
+ return file === undefined ? undefined : join6(host.globalModules, name, file);
1325
1411
  }
1326
1412
  async function checkTool(host, name, bins) {
1327
1413
  const check = name.slice(name.indexOf("/") + 1);
@@ -1336,7 +1422,7 @@ async function checkTool(host, name, bins) {
1336
1422
  if (latest !== "" && Bun.semver.order(version, latest) < 0)
1337
1423
  return { check, ok: false, detail: `${version}, ${latest} released${off.length > 0 ? `, ${off.join(", ")} not on the PATH` : ""}`, fix: `bun install -g ${name}@${latest}` };
1338
1424
  if (off.length > 0)
1339
- return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join4(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
1425
+ return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join6(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
1340
1426
  if (latest === "")
1341
1427
  return { check, ok: true, detail: `${version}, the registry did not answer: ${firstLine(info.stderr) || answered || "no version"}` };
1342
1428
  return { check, ok: true, detail: version };
@@ -1357,9 +1443,9 @@ async function checkTea(host) {
1357
1443
  return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
1358
1444
  }
1359
1445
  function hooksWiring(host) {
1360
- const settings = readJson(join4(host.home, ".claude", "settings.json")) ?? {};
1446
+ const settings = readJson(join6(host.home, ".claude", "settings.json")) ?? {};
1361
1447
  const plugin = settings.enabledPlugins?.[PLUGIN] === true;
1362
- const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(join4(dir, "hooks", "hooks.json")));
1448
+ const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync3(join6(dir, "hooks", "hooks.json")));
1363
1449
  return { plugin, mod };
1364
1450
  }
1365
1451
  function checkHooks(host) {
@@ -1397,8 +1483,15 @@ function checkNeeds(host) {
1397
1483
  return { check: "needs", ok: true, detail: "every tool the installed packages need is on the PATH" };
1398
1484
  }
1399
1485
  var toolCheck = (name) => name.slice(name.indexOf("/") + 1);
1400
- var OTHER_CHECKS = ["tea", "hooks", "needs", "bun", "completions"];
1486
+ var OTHER_CHECKS = ["tea", "hooks", "needs", "bun", "completions", "workspace"];
1401
1487
  var FORGEJO = "@kaynooo/cli-forgejo";
1488
+ var WORKSPACE = "@kaynooo/cli-workspace";
1489
+ function checkWorkspace(host, env = process.env) {
1490
+ const folder = workspaceRoot({ home: host.home, env });
1491
+ if (existsSync3(folder))
1492
+ return { check: "workspace", ok: true, detail: folder };
1493
+ 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` };
1494
+ }
1402
1495
  var CHECK_NAMES = [...Object.keys(TOOLS).map(toolCheck), ...OTHER_CHECKS];
1403
1496
  async function runChecks(host, only = []) {
1404
1497
  const wanted = (check) => only.length === 0 || only.includes(check);
@@ -1414,7 +1507,8 @@ async function runChecks(host, only = []) {
1414
1507
  ...wanted("hooks") && hooks ? [checkHooks(host)] : [],
1415
1508
  ...wanted("needs") ? [checkNeeds(host)] : [],
1416
1509
  ...wanted("bun") ? [checkBun(host)] : [],
1417
- ...wanted("completions") ? [checkCompletions(host)] : []
1510
+ ...wanted("completions") ? [checkCompletions(host)] : [],
1511
+ ...wanted("workspace") && (only.length > 0 || isInstalled(host, WORKSPACE) || isInstalled(host, "@kaynooo/cli-pkg")) ? [checkWorkspace(host)] : []
1418
1512
  ];
1419
1513
  }
1420
1514
 
@@ -1456,19 +1550,19 @@ async function applyFixes(host, steps, dry) {
1456
1550
  }
1457
1551
 
1458
1552
  // src/host.ts
1459
- import { homedir as homedir2 } from "os";
1460
- import { join as join5 } from "path";
1461
- import process11 from "process";
1553
+ import { homedir as homedir4 } from "os";
1554
+ import { join as join7 } from "path";
1555
+ import process14 from "process";
1462
1556
  var RUN_TIMEOUT_MS = 20000;
1463
1557
  function hostHere() {
1464
- const home = homedir2();
1558
+ const home = homedir4();
1465
1559
  return {
1466
1560
  home,
1467
- globalModules: join5(process11.env.BUN_INSTALL ?? join5(home, ".bun"), "install", "global", "node_modules"),
1561
+ globalModules: join7(process14.env.BUN_INSTALL ?? join7(home, ".bun"), "install", "global", "node_modules"),
1468
1562
  bunVersion: Bun.version,
1469
1563
  which: (bin) => Bun.which(bin),
1470
1564
  run: async (argv, env) => {
1471
- const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process11.env, ...env } } });
1565
+ const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process14.env, ...env } } });
1472
1566
  if (ran.timedOut)
1473
1567
  return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
1474
1568
  const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
@@ -1478,10 +1572,10 @@ function hostHere() {
1478
1572
  }
1479
1573
 
1480
1574
  // src/install.ts
1481
- import process13 from "process";
1575
+ import process16 from "process";
1482
1576
 
1483
1577
  // ../kit/src/prompt.ts
1484
- import process12 from "process";
1578
+ import process15 from "process";
1485
1579
  var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
1486
1580
  var CONTROL = /^[\x00-\x1F\x7F]/;
1487
1581
  var WORDS = /\s+/;
@@ -1519,6 +1613,52 @@ async function checklist(question, choices, { initial = new Set, min = 0, toggle
1519
1613
  });
1520
1614
  return accepted ? checked : undefined;
1521
1615
  }
1616
+ async function confirm(question, { default: fallback, ...settings } = {}) {
1617
+ let answered = fallback;
1618
+ const choices = { true: "Y/n", false: "y/N", undefined: "y/n" }[String(fallback)];
1619
+ const accepted = await ask(question, settings, (colors, state) => [state === "asking" ? asking(colors, question, "", choices) : answer(colors, question, state, answered ? "yes" : "no")], (key) => {
1620
+ if (key === "cancel")
1621
+ return "cancel";
1622
+ if (key === "enter")
1623
+ return answered === undefined ? undefined : "enter";
1624
+ const letter = typeof key === "object" ? key.text.toLowerCase() : "";
1625
+ if (letter !== "y" && letter !== "n")
1626
+ return;
1627
+ answered = letter === "y";
1628
+ return "enter";
1629
+ });
1630
+ return accepted ? answered : undefined;
1631
+ }
1632
+ async function input2(question, { default: fallback, mask = false, validate, ...settings } = {}) {
1633
+ let value = "";
1634
+ let invalid;
1635
+ const taken = () => value === "" ? fallback ?? "" : value;
1636
+ const shown = () => mask ? "*".repeat([...taken()].length) : taken();
1637
+ const accepted = await ask(question, settings, (colors, state, { columns }) => {
1638
+ if (state !== "asking")
1639
+ return [answer(colors, question, state, shown())];
1640
+ const characters = [...shown()];
1641
+ const room = Math.max(columns - question.length - 5, 1);
1642
+ const tail = characters.length > room ? `\u2026${characters.slice(1 - room).join("")}` : shown();
1643
+ const typed = value === "" ? colors.dim(tail) : tail;
1644
+ return [`${colors.cyan("?")} ${question} ${typed}${colors.dim("\u2588")}`, ...invalid === undefined ? [] : [` ${colors.red(invalid)}`]];
1645
+ }, (key) => {
1646
+ if (key === "cancel")
1647
+ return "cancel";
1648
+ invalid = undefined;
1649
+ if (key === "enter") {
1650
+ invalid = validate?.(taken());
1651
+ return invalid === undefined ? "enter" : undefined;
1652
+ }
1653
+ if (key === "erase")
1654
+ value = [...value].slice(0, -1).join("");
1655
+ else if (typeof key === "object")
1656
+ value += key.text;
1657
+ return;
1658
+ });
1659
+ return accepted ? taken() : undefined;
1660
+ }
1661
+
1522
1662
  class List {
1523
1663
  choices;
1524
1664
  query = "";
@@ -1577,9 +1717,9 @@ function asking(colors, question, typed, keys) {
1577
1717
  function answer(colors, question, state, picked) {
1578
1718
  return state === "enter" ? `${colors.green("\u2713")} ${question} ${colors.cyan(picked)}` : `${colors.red("\u2717")} ${question} ${colors.dim("cancelled")}`;
1579
1719
  }
1580
- async function ask(question, { hint, terminal = process12 }, render, press) {
1720
+ async function ask(question, { hint, terminal = process15 }, render, press) {
1581
1721
  const { stdin, stderr } = terminal;
1582
- if (stdin.isTTY !== true || stderr.isTTY !== true)
1722
+ if (!isInteractive(terminal))
1583
1723
  fail(`cannot ask "${question}" off a terminal`, 2, hint);
1584
1724
  const colors = paint(stderr);
1585
1725
  let drawn = 0;
@@ -1629,18 +1769,19 @@ async function ask(question, { hint, terminal = process12 }, render, press) {
1629
1769
 
1630
1770
  // src/install.ts
1631
1771
  var HOOKS = "hooks";
1772
+ var PLUGIN_UNINSTALL = ["claude", "plugin", "uninstall", "cli-hooks"];
1632
1773
  var PLUGIN_INSTALL = ["claude", "plugin", "install", "cli-hooks", "--marketplace", `https://${FORGE}/kaynooo/cli.git`];
1633
1774
  var CLI_PREFIX = /^cli-/;
1634
1775
  var shortName = (name) => name.slice(name.indexOf("/") + 1).replace(CLI_PREFIX, "");
1635
1776
  function candidates2(host) {
1636
- const packages = Object.entries(TOOLS).filter(([name]) => !isInstalled(host, name)).map(([name, bins]) => {
1777
+ const packages = Object.entries(TOOLS).map(([name, bins]) => {
1637
1778
  const needs = (NEEDS[name] ?? []).map((need) => need.bin);
1638
- return { id: name, names: [shortName(name), name, `cli-${shortName(name)}`, ...bins], label: shortName(name), hint: `${bins.join(" ")}: ${ABOUT[name] ?? ""}${needs.length === 0 ? "" : `, needs ${needs.join(", ")}`}`, packages: [name] };
1779
+ return { id: name, names: [shortName(name), name, `cli-${shortName(name)}`, ...bins], label: shortName(name), hint: `${bins.join(" ")}: ${ABOUT[name] ?? ""}${needs.length === 0 ? "" : `, needs ${needs.join(", ")}`}`, packages: [name], installed: isInstalled(host, name) };
1639
1780
  });
1640
1781
  const { plugin, mod } = hooksWiring(host);
1641
- if (plugin || mod !== undefined)
1782
+ if (mod !== undefined && !plugin)
1642
1783
  return packages;
1643
- return [...packages, { id: HOOKS, names: [HOOKS, "plugin", "cli-hooks"], label: "hooks", hint: `the Claude Code plugin running guard on every tool call, needs ${HOOKS_REQUIRES}`, packages: isInstalled(host, HOOKS_REQUIRES) ? [] : [HOOKS_REQUIRES] }];
1784
+ return [...packages, { id: HOOKS, names: [HOOKS, "plugin", "cli-hooks"], label: "hooks", hint: `the Claude Code plugin running guard on every tool call, needs ${HOOKS_REQUIRES}`, packages: isInstalled(host, HOOKS_REQUIRES) ? [] : [HOOKS_REQUIRES], installed: plugin }];
1644
1785
  }
1645
1786
  function choose(pool, names) {
1646
1787
  const chosen = new Set;
@@ -1677,16 +1818,33 @@ function plan(host, picked, force) {
1677
1818
  function checksFor(packages, plugin) {
1678
1819
  return [...packages.map((name) => name.slice(name.indexOf("/") + 1)), ...plugin ? ["hooks"] : [], "needs", "completions", ...packages.includes("@kaynooo/cli-forgejo") ? ["tea"] : []];
1679
1820
  }
1680
- async function ask2(pool) {
1681
- if (process13.stdin.isTTY !== true || process13.stderr.isTTY !== true)
1821
+ async function ask2(all) {
1822
+ if (!isInteractive())
1682
1823
  badUsage("name what to install, or all: doctor --install forgejo check hooks (doctor --install --list shows the choices)");
1683
- const choices = pool.map((each) => ({ value: each, label: each.label, hint: each.hint }));
1684
- const picked = await checklist("install which?", choices, { hint: "name them instead: doctor --install forgejo check" });
1685
- return picked === undefined ? [] : [...picked];
1824
+ const choices = all.map((each) => ({ value: each, label: each.label, hint: `${each.installed ? "installed, " : ""}${each.hint}` }));
1825
+ const picked = await checklist("installed packages", choices, { initial: new Set(all.filter((each) => each.installed)), hint: "check to install, uncheck to remove: doctor --install forgejo check names them" });
1826
+ if (picked === undefined)
1827
+ return;
1828
+ const remove = all.filter((each) => !picked.has(each) && each.installed);
1829
+ if (remove.length > 0 && await confirm(`remove ${remove.map((each) => each.label).join(", ")}?`, { default: false }) !== true)
1830
+ return { add: all.filter((each) => picked.has(each) && !each.installed), remove: [] };
1831
+ return { add: all.filter((each) => picked.has(each) && !each.installed), remove };
1832
+ }
1833
+ function removal(all, remove) {
1834
+ const keepsGuard = all.some((each) => each.id === HOOKS && each.installed && !remove.includes(each));
1835
+ return { packages: [...new Set(remove.flatMap((each) => each.id === HOOKS ? [] : each.packages))].filter((name) => !(keepsGuard && name === HOOKS_REQUIRES)), plugin: remove.some((each) => each.id === HOOKS) };
1686
1836
  }
1687
- async function install(host, options, recheck) {
1688
- const pool = candidates2(host);
1689
- const { dim, green, red, yellow } = paint(process13.stdout);
1837
+ async function askWorkspace(host) {
1838
+ if (!isInteractive() || workspaceChosen({ home: host.home }))
1839
+ return;
1840
+ const folder = await input2("folder holding your repositories (repos, sweep and pkg bump look there)", { default: "~/Workspace" });
1841
+ if (folder !== undefined && folder !== "" && !saveWorkspace(folder, { home: host.home }))
1842
+ console.error(`could not write the workspace into ${userConfigPath("config", { home: host.home })}: it does not parse, fix it by hand`);
1843
+ }
1844
+ async function install(host, options, recheck, choosing = ask2) {
1845
+ const all = candidates2(host);
1846
+ const pool = all.filter((each) => !each.installed);
1847
+ const { dim, green, red, yellow } = paint(process16.stdout);
1690
1848
  if (options.list) {
1691
1849
  for (const each of pool)
1692
1850
  console.log(`${each.label.padEnd(12)} ${dim(each.hint)}`);
@@ -1694,20 +1852,23 @@ async function install(host, options, recheck) {
1694
1852
  console.log("everything is installed");
1695
1853
  return 0;
1696
1854
  }
1697
- if (pool.length === 0) {
1855
+ if (pool.length === 0 && options.names.length > 0) {
1698
1856
  console.log("everything is installed");
1699
1857
  return 0;
1700
1858
  }
1701
- const unknown = options.names.filter((name) => name !== "all" && !Object.values(candidatesAll(host)).some((each) => each.names.includes(name)));
1859
+ const unknown = options.names.filter((name) => name !== "all" && !all.some((each) => each.names.includes(name)));
1702
1860
  if (unknown.length > 0)
1703
1861
  badUsage(`nothing to install is named ${unknown.join(", ")}: doctor --install --list`);
1704
- const picked = options.names.length === 0 ? await ask2(pool) : choose(pool, options.names);
1862
+ const chosen = options.names.length === 0 ? await choosing(all) : { add: choose(pool, options.names), remove: [] };
1863
+ const picked = chosen?.add ?? [];
1705
1864
  const skipped = options.names.filter((name) => name !== "all" && !pool.some((each) => each.names.includes(name)));
1706
1865
  for (const name of skipped)
1707
1866
  console.log(`${dim("already")} ${name} is installed`);
1708
- if (picked.length === 0) {
1867
+ const removing = removal(all, chosen?.remove ?? []);
1868
+ const removes = removing.packages.length > 0 || removing.plugin;
1869
+ if (picked.length === 0 && !removes) {
1709
1870
  if (skipped.length === 0)
1710
- console.log("nothing picked");
1871
+ console.log("nothing changed");
1711
1872
  return 0;
1712
1873
  }
1713
1874
  const planned = plan(host, picked, options.force);
@@ -1715,13 +1876,20 @@ async function install(host, options, recheck) {
1715
1876
  console.log(`${red("blocked")} ${each.package} needs ${each.need} (${each.why}): ${each.how}; --force installs it anyway`);
1716
1877
  for (const each of planned.warnings)
1717
1878
  console.log(`${yellow("warn ")} ${each.package}: ${each.need} is missing, ${each.why}`);
1718
- if (planned.packages.length === 0 && !planned.plugin)
1879
+ if (planned.packages.length === 0 && !planned.plugin && !removes)
1719
1880
  return 1;
1881
+ if (!options.dryRun && planned.packages.some((name) => name === "@kaynooo/cli-workspace" || name === "@kaynooo/cli-pkg"))
1882
+ await askWorkspace(host);
1720
1883
  const command = ["bun", "install", "-g", ...planned.packages];
1721
1884
  if (planned.packages.length > 0)
1722
1885
  console.log(`${options.dryRun ? dim("would ") : green("install")} ${command.join(" ")}`);
1723
1886
  if (planned.plugin)
1724
1887
  console.log(`${options.dryRun ? dim("would ") : green("install")} ${PLUGIN_INSTALL.join(" ")}`);
1888
+ const uninstall = ["bun", "remove", "-g", ...removing.packages];
1889
+ if (removing.packages.length > 0)
1890
+ console.log(`${options.dryRun ? dim("would ") : green("remove ")} ${uninstall.join(" ")}`);
1891
+ if (removing.plugin)
1892
+ console.log(`${options.dryRun ? dim("would ") : green("remove ")} ${PLUGIN_UNINSTALL.join(" ")}`);
1725
1893
  if (options.dryRun)
1726
1894
  return planned.blocked.length > 0 ? 1 : 0;
1727
1895
  if (planned.packages.length > 0) {
@@ -1740,16 +1908,23 @@ async function install(host, options, recheck) {
1740
1908
  console.error(`${red("failed")} ${PLUGIN_INSTALL.join(" ")}: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
1741
1909
  }
1742
1910
  }
1911
+ if (removing.packages.length > 0) {
1912
+ const ran = await host.run(uninstall);
1913
+ if (ran.code !== 0) {
1914
+ console.error(`${red("failed")} bun remove -g: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
1915
+ return 1;
1916
+ }
1917
+ }
1918
+ if (removing.plugin && host.which("claude") !== null) {
1919
+ const ran = await host.run(PLUGIN_UNINSTALL);
1920
+ if (ran.code !== 0)
1921
+ console.error(`${red("failed")} ${PLUGIN_UNINSTALL.join(" ")}: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
1922
+ }
1743
1923
  console.log(`
1744
1924
  after the install:`);
1745
1925
  const code = await recheck(checksFor(planned.packages, planned.plugin));
1746
1926
  return planned.blocked.length > 0 ? 1 : code;
1747
1927
  }
1748
- function candidatesAll(host) {
1749
- const open = candidates2(host);
1750
- const done = Object.entries(TOOLS).filter(([name]) => isInstalled(host, name)).map(([name, bins]) => ({ id: name, names: [shortName(name), name, `cli-${shortName(name)}`, ...bins], label: shortName(name), hint: "", packages: [name] }));
1751
- return [...open, ...done];
1752
- }
1753
1928
 
1754
1929
  // src/doctor.ts
1755
1930
  var USAGE = `doctor [--only <check>] [--quiet] [--fix | --install [<name>...|all]] [--list] [--dry-run] [--force] [--json]: checks this host's @kaynooo/cli-* install, one line per check, the command that fixes a failing one beside it, and installs the packages it picks
@@ -1766,14 +1941,14 @@ var OPTIONS = {
1766
1941
  quiet: { type: "boolean", short: "q", description: "print only the failing checks" },
1767
1942
  fix: { type: "boolean", description: "run the fix: lines doctor may run, then check again" },
1768
1943
  "dry-run": { type: "boolean", description: "with --fix or --install, show what would run without running it (alone, it implies --fix)" },
1769
- install: { type: "boolean", description: "install the packages named, or picked from a checklist: forgejo, ft, hooks, all" },
1944
+ install: { type: "boolean", description: "install the packages named, or managed from a checklist, installed ones checked and unchecking removing them: forgejo, ft, hooks, all" },
1770
1945
  list: { type: "boolean", description: "with --install, print the choices" },
1771
1946
  force: { type: "boolean", description: "with --install, install a package even though a tool it requires is missing" }
1772
1947
  };
1773
1948
  function print(results) {
1774
1949
  if (results.length === 0)
1775
1950
  return;
1776
- const { green, red, dim } = paint(process14.stdout);
1951
+ const { green, red, dim } = paint(process17.stdout);
1777
1952
  const width = Math.max(...results.map((result) => result.check.length));
1778
1953
  for (const { check, ok, detail, fix } of results)
1779
1954
  console.log(`${ok ? green("ok ") : red("FAIL")} ${check.padEnd(width)} ${detail}${fix === undefined ? "" : ` ${dim("fix:")} ${fix}`}`);
@@ -1783,13 +1958,13 @@ function print(results) {
1783
1958
  ${dim("fix them all:")} ${INSTALL2}${installs.join(" ")}`);
1784
1959
  }
1785
1960
  function printOutcomes(outcomes, log) {
1786
- const { green, red, dim } = paint(process14.stdout);
1961
+ const { green, red, dim } = paint(process17.stdout);
1787
1962
  const marks = { ran: green("ran "), failed: red("failed "), "dry-run": dim("would "), "not run": dim("not run") };
1788
1963
  for (const { command, status, detail } of outcomes)
1789
1964
  log(`${marks[status]} ${command}${detail === undefined ? "" : ` ${dim(detail)}`}`);
1790
1965
  }
1791
1966
  async function main(args, host = hostHere()) {
1792
- const { values, positionals } = parse(args, USAGE, OPTIONS, { positionals: ["[name...]"], exits: EXITS, replaces: "checking each bin's version, tea's login and the hooks by hand", examples: ["doctor", ["doctor --json", "the checks for a script"], ["doctor -q", "silent unless something fails"], ["doctor --fix --dry-run", "what --fix would run"], ["doctor --install", "pick the packages to add"], ["doctor --install forgejo check hooks", "three named ones"], ["doctor --install all --dry-run", "what installing everything would run"]] });
1967
+ const { values, positionals } = parse(args, USAGE, OPTIONS, { positionals: ["[name...]"], exits: EXITS, replaces: "checking each bin's version, tea's login and the hooks by hand", examples: ["doctor", ["doctor --json", "the checks for a script"], ["doctor -q", "silent unless something fails"], ["doctor --fix --dry-run", "what --fix would run"], ["doctor --install", "check to install, uncheck to remove"], ["doctor --install forgejo check hooks", "three named ones"], ["doctor --install all --dry-run", "what installing everything would run"]] });
1793
1968
  const only = values.only ?? [];
1794
1969
  if (values.install !== true && (positionals.length > 0 || values.list === true || values.force === true))
1795
1970
  badUsage("names, --list and --force go with --install");
@@ -1823,9 +1998,9 @@ after the fixes:`);
1823
1998
  const absent = Object.keys(TOOLS).filter((name) => !isInstalled(host, name)).length;
1824
1999
  if (!json && only.length === 0 && absent > 0)
1825
2000
  console.log(`
1826
- ${paint(process14.stdout).dim(`${absent} packages not installed: doctor --install`)}`);
2001
+ ${paint(process17.stdout).dim(`${absent} packages not installed: doctor --install`)}`);
1827
2002
  return results.every((result) => result.ok) ? 0 : 1;
1828
2003
  }
1829
2004
 
1830
2005
  // src/cli.ts
1831
- process15.exitCode = await runMain({ name: "doctor", run: async (args) => main(args) });
2006
+ process18.exitCode = await runMain({ name: "doctor", run: async (args) => main(args) });