@kaynooo/cli-doctor 0.9.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.
package/dist/cli-bin.js CHANGED
@@ -1615,6 +1615,22 @@ async function checklist(question, choices, { initial = new Set, min = 0, toggle
1615
1615
  });
1616
1616
  return accepted ? checked : undefined;
1617
1617
  }
1618
+ async function confirm(question, { default: fallback, ...settings } = {}) {
1619
+ let answered = fallback;
1620
+ const choices = { true: "Y/n", false: "y/N", undefined: "y/n" }[String(fallback)];
1621
+ const accepted = await ask(question, settings, (colors, state) => [state === "asking" ? asking(colors, question, "", choices) : answer(colors, question, state, answered ? "yes" : "no")], (key) => {
1622
+ if (key === "cancel")
1623
+ return "cancel";
1624
+ if (key === "enter")
1625
+ return answered === undefined ? undefined : "enter";
1626
+ const letter = typeof key === "object" ? key.text.toLowerCase() : "";
1627
+ if (letter !== "y" && letter !== "n")
1628
+ return;
1629
+ answered = letter === "y";
1630
+ return "enter";
1631
+ });
1632
+ return accepted ? answered : undefined;
1633
+ }
1618
1634
  async function input2(question, { default: fallback, mask = false, validate, ...settings } = {}) {
1619
1635
  let value = "";
1620
1636
  let invalid;
@@ -1755,18 +1771,19 @@ async function ask(question, { hint, terminal = process15 }, render, press) {
1755
1771
 
1756
1772
  // src/install.ts
1757
1773
  var HOOKS = "hooks";
1774
+ var PLUGIN_UNINSTALL = ["claude", "plugin", "uninstall", "cli-hooks"];
1758
1775
  var PLUGIN_INSTALL = ["claude", "plugin", "install", "cli-hooks", "--marketplace", `https://${FORGE}/kaynooo/cli.git`];
1759
1776
  var CLI_PREFIX = /^cli-/;
1760
1777
  var shortName = (name) => name.slice(name.indexOf("/") + 1).replace(CLI_PREFIX, "");
1761
1778
  function candidates2(host) {
1762
- const packages = Object.entries(TOOLS).filter(([name]) => !isInstalled(host, name)).map(([name, bins]) => {
1779
+ const packages = Object.entries(TOOLS).map(([name, bins]) => {
1763
1780
  const needs = (NEEDS[name] ?? []).map((need) => need.bin);
1764
- return { id: name, names: [shortName(name), name, `cli-${shortName(name)}`, ...bins], label: shortName(name), hint: `${bins.join(" ")}: ${ABOUT[name] ?? ""}${needs.length === 0 ? "" : `, needs ${needs.join(", ")}`}`, packages: [name] };
1781
+ 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) };
1765
1782
  });
1766
1783
  const { plugin, mod } = hooksWiring(host);
1767
- if (plugin || mod !== undefined)
1784
+ if (mod !== undefined && !plugin)
1768
1785
  return packages;
1769
- return [...packages, { id: HOOKS, names: [HOOKS, "plugin", "cli-hooks"], label: "hooks", hint: `the Claude Code plugin running guard on every tool call, needs ${HOOKS_REQUIRES}`, packages: isInstalled(host, HOOKS_REQUIRES) ? [] : [HOOKS_REQUIRES] }];
1786
+ 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 }];
1770
1787
  }
1771
1788
  function choose(pool, names) {
1772
1789
  const chosen = new Set;
@@ -1803,12 +1820,21 @@ function plan(host, picked, force) {
1803
1820
  function checksFor(packages, plugin) {
1804
1821
  return [...packages.map((name) => name.slice(name.indexOf("/") + 1)), ...plugin ? ["hooks"] : [], "needs", "completions", ...packages.includes("@kaynooo/cli-forgejo") ? ["tea"] : []];
1805
1822
  }
1806
- async function ask2(pool) {
1823
+ async function ask2(all) {
1807
1824
  if (!isInteractive())
1808
1825
  badUsage("name what to install, or all: doctor --install forgejo check hooks (doctor --install --list shows the choices)");
1809
- const choices = pool.map((each) => ({ value: each, label: each.label, hint: each.hint }));
1810
- const picked = await checklist("install which?", choices, { hint: "name them instead: doctor --install forgejo check" });
1811
- return picked === undefined ? [] : [...picked];
1826
+ const choices = all.map((each) => ({ value: each, label: each.label, hint: `${each.installed ? "installed, " : ""}${each.hint}` }));
1827
+ 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" });
1828
+ if (picked === undefined)
1829
+ return;
1830
+ const remove = all.filter((each) => !picked.has(each) && each.installed);
1831
+ if (remove.length > 0 && await confirm(`remove ${remove.map((each) => each.label).join(", ")}?`, { default: false }) !== true)
1832
+ return { add: all.filter((each) => picked.has(each) && !each.installed), remove: [] };
1833
+ return { add: all.filter((each) => picked.has(each) && !each.installed), remove };
1834
+ }
1835
+ function removal(all, remove) {
1836
+ const keepsGuard = all.some((each) => each.id === HOOKS && each.installed && !remove.includes(each));
1837
+ 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) };
1812
1838
  }
1813
1839
  async function askWorkspace(host) {
1814
1840
  if (!isInteractive() || workspaceChosen({ home: host.home }))
@@ -1817,8 +1843,9 @@ async function askWorkspace(host) {
1817
1843
  if (folder !== undefined && folder !== "" && !saveWorkspace(folder, { home: host.home }))
1818
1844
  console.error(`could not write the workspace into ${userConfigPath("config", { home: host.home })}: it does not parse, fix it by hand`);
1819
1845
  }
1820
- async function install(host, options, recheck) {
1821
- const pool = candidates2(host);
1846
+ async function install(host, options, recheck, choosing = ask2) {
1847
+ const all = candidates2(host);
1848
+ const pool = all.filter((each) => !each.installed);
1822
1849
  const { dim, green, red, yellow } = paint(process16.stdout);
1823
1850
  if (options.list) {
1824
1851
  for (const each of pool)
@@ -1827,20 +1854,23 @@ async function install(host, options, recheck) {
1827
1854
  console.log("everything is installed");
1828
1855
  return 0;
1829
1856
  }
1830
- if (pool.length === 0) {
1857
+ if (pool.length === 0 && options.names.length > 0) {
1831
1858
  console.log("everything is installed");
1832
1859
  return 0;
1833
1860
  }
1834
- const unknown = options.names.filter((name) => name !== "all" && !Object.values(candidatesAll(host)).some((each) => each.names.includes(name)));
1861
+ const unknown = options.names.filter((name) => name !== "all" && !all.some((each) => each.names.includes(name)));
1835
1862
  if (unknown.length > 0)
1836
1863
  badUsage(`nothing to install is named ${unknown.join(", ")}: doctor --install --list`);
1837
- const picked = options.names.length === 0 ? await ask2(pool) : choose(pool, options.names);
1864
+ const chosen = options.names.length === 0 ? await choosing(all) : { add: choose(pool, options.names), remove: [] };
1865
+ const picked = chosen?.add ?? [];
1838
1866
  const skipped = options.names.filter((name) => name !== "all" && !pool.some((each) => each.names.includes(name)));
1839
1867
  for (const name of skipped)
1840
1868
  console.log(`${dim("already")} ${name} is installed`);
1841
- if (picked.length === 0) {
1869
+ const removing = removal(all, chosen?.remove ?? []);
1870
+ const removes = removing.packages.length > 0 || removing.plugin;
1871
+ if (picked.length === 0 && !removes) {
1842
1872
  if (skipped.length === 0)
1843
- console.log("nothing picked");
1873
+ console.log("nothing changed");
1844
1874
  return 0;
1845
1875
  }
1846
1876
  const planned = plan(host, picked, options.force);
@@ -1848,7 +1878,7 @@ async function install(host, options, recheck) {
1848
1878
  console.log(`${red("blocked")} ${each.package} needs ${each.need} (${each.why}): ${each.how}; --force installs it anyway`);
1849
1879
  for (const each of planned.warnings)
1850
1880
  console.log(`${yellow("warn ")} ${each.package}: ${each.need} is missing, ${each.why}`);
1851
- if (planned.packages.length === 0 && !planned.plugin)
1881
+ if (planned.packages.length === 0 && !planned.plugin && !removes)
1852
1882
  return 1;
1853
1883
  if (!options.dryRun && planned.packages.some((name) => name === "@kaynooo/cli-workspace" || name === "@kaynooo/cli-pkg"))
1854
1884
  await askWorkspace(host);
@@ -1857,6 +1887,11 @@ async function install(host, options, recheck) {
1857
1887
  console.log(`${options.dryRun ? dim("would ") : green("install")} ${command.join(" ")}`);
1858
1888
  if (planned.plugin)
1859
1889
  console.log(`${options.dryRun ? dim("would ") : green("install")} ${PLUGIN_INSTALL.join(" ")}`);
1890
+ const uninstall = ["bun", "remove", "-g", ...removing.packages];
1891
+ if (removing.packages.length > 0)
1892
+ console.log(`${options.dryRun ? dim("would ") : green("remove ")} ${uninstall.join(" ")}`);
1893
+ if (removing.plugin)
1894
+ console.log(`${options.dryRun ? dim("would ") : green("remove ")} ${PLUGIN_UNINSTALL.join(" ")}`);
1860
1895
  if (options.dryRun)
1861
1896
  return planned.blocked.length > 0 ? 1 : 0;
1862
1897
  if (planned.packages.length > 0) {
@@ -1875,16 +1910,23 @@ async function install(host, options, recheck) {
1875
1910
  console.error(`${red("failed")} ${PLUGIN_INSTALL.join(" ")}: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
1876
1911
  }
1877
1912
  }
1913
+ if (removing.packages.length > 0) {
1914
+ const ran = await host.run(uninstall);
1915
+ if (ran.code !== 0) {
1916
+ console.error(`${red("failed")} bun remove -g: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
1917
+ return 1;
1918
+ }
1919
+ }
1920
+ if (removing.plugin && host.which("claude") !== null) {
1921
+ const ran = await host.run(PLUGIN_UNINSTALL);
1922
+ if (ran.code !== 0)
1923
+ console.error(`${red("failed")} ${PLUGIN_UNINSTALL.join(" ")}: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
1924
+ }
1878
1925
  console.log(`
1879
1926
  after the install:`);
1880
1927
  const code = await recheck(checksFor(planned.packages, planned.plugin));
1881
1928
  return planned.blocked.length > 0 ? 1 : code;
1882
1929
  }
1883
- function candidatesAll(host) {
1884
- const open = candidates2(host);
1885
- const done = Object.entries(TOOLS).filter(([name]) => isInstalled(host, name)).map(([name, bins]) => ({ id: name, names: [shortName(name), name, `cli-${shortName(name)}`, ...bins], label: shortName(name), hint: "", packages: [name] }));
1886
- return [...open, ...done];
1887
- }
1888
1930
 
1889
1931
  // src/doctor.ts
1890
1932
  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
@@ -1901,7 +1943,7 @@ var OPTIONS = {
1901
1943
  quiet: { type: "boolean", short: "q", description: "print only the failing checks" },
1902
1944
  fix: { type: "boolean", description: "run the fix: lines doctor may run, then check again" },
1903
1945
  "dry-run": { type: "boolean", description: "with --fix or --install, show what would run without running it (alone, it implies --fix)" },
1904
- install: { type: "boolean", description: "install the packages named, or picked from a checklist: forgejo, ft, hooks, all" },
1946
+ install: { type: "boolean", description: "install the packages named, or managed from a checklist, installed ones checked and unchecking removing them: forgejo, ft, hooks, all" },
1905
1947
  list: { type: "boolean", description: "with --install, print the choices" },
1906
1948
  force: { type: "boolean", description: "with --install, install a package even though a tool it requires is missing" }
1907
1949
  };
@@ -1924,7 +1966,7 @@ function printOutcomes(outcomes, log) {
1924
1966
  log(`${marks[status]} ${command}${detail === undefined ? "" : ` ${dim(detail)}`}`);
1925
1967
  }
1926
1968
  async function main(args, host = hostHere()) {
1927
- const { values, positionals } = parse(args, USAGE, OPTIONS, { positionals: ["[name...]"], exits: EXITS, replaces: "checking each bin's version, tea's login and the hooks by hand", examples: ["doctor", ["doctor --json", "the checks for a script"], ["doctor -q", "silent unless something fails"], ["doctor --fix --dry-run", "what --fix would run"], ["doctor --install", "pick the packages to add"], ["doctor --install forgejo check hooks", "three named ones"], ["doctor --install all --dry-run", "what installing everything would run"]] });
1969
+ 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"]] });
1928
1970
  const only = values.only ?? [];
1929
1971
  if (values.install !== true && (positionals.length > 0 || values.list === true || values.force === true))
1930
1972
  badUsage("names, --list and --force go with --install");
package/dist/cli.js CHANGED
@@ -1613,6 +1613,22 @@ async function checklist(question, choices, { initial = new Set, min = 0, toggle
1613
1613
  });
1614
1614
  return accepted ? checked : undefined;
1615
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
+ }
1616
1632
  async function input2(question, { default: fallback, mask = false, validate, ...settings } = {}) {
1617
1633
  let value = "";
1618
1634
  let invalid;
@@ -1753,18 +1769,19 @@ async function ask(question, { hint, terminal = process15 }, render, press) {
1753
1769
 
1754
1770
  // src/install.ts
1755
1771
  var HOOKS = "hooks";
1772
+ var PLUGIN_UNINSTALL = ["claude", "plugin", "uninstall", "cli-hooks"];
1756
1773
  var PLUGIN_INSTALL = ["claude", "plugin", "install", "cli-hooks", "--marketplace", `https://${FORGE}/kaynooo/cli.git`];
1757
1774
  var CLI_PREFIX = /^cli-/;
1758
1775
  var shortName = (name) => name.slice(name.indexOf("/") + 1).replace(CLI_PREFIX, "");
1759
1776
  function candidates2(host) {
1760
- const packages = Object.entries(TOOLS).filter(([name]) => !isInstalled(host, name)).map(([name, bins]) => {
1777
+ const packages = Object.entries(TOOLS).map(([name, bins]) => {
1761
1778
  const needs = (NEEDS[name] ?? []).map((need) => need.bin);
1762
- 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) };
1763
1780
  });
1764
1781
  const { plugin, mod } = hooksWiring(host);
1765
- if (plugin || mod !== undefined)
1782
+ if (mod !== undefined && !plugin)
1766
1783
  return packages;
1767
- 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 }];
1768
1785
  }
1769
1786
  function choose(pool, names) {
1770
1787
  const chosen = new Set;
@@ -1801,12 +1818,21 @@ function plan(host, picked, force) {
1801
1818
  function checksFor(packages, plugin) {
1802
1819
  return [...packages.map((name) => name.slice(name.indexOf("/") + 1)), ...plugin ? ["hooks"] : [], "needs", "completions", ...packages.includes("@kaynooo/cli-forgejo") ? ["tea"] : []];
1803
1820
  }
1804
- async function ask2(pool) {
1821
+ async function ask2(all) {
1805
1822
  if (!isInteractive())
1806
1823
  badUsage("name what to install, or all: doctor --install forgejo check hooks (doctor --install --list shows the choices)");
1807
- const choices = pool.map((each) => ({ value: each, label: each.label, hint: each.hint }));
1808
- const picked = await checklist("install which?", choices, { hint: "name them instead: doctor --install forgejo check" });
1809
- 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) };
1810
1836
  }
1811
1837
  async function askWorkspace(host) {
1812
1838
  if (!isInteractive() || workspaceChosen({ home: host.home }))
@@ -1815,8 +1841,9 @@ async function askWorkspace(host) {
1815
1841
  if (folder !== undefined && folder !== "" && !saveWorkspace(folder, { home: host.home }))
1816
1842
  console.error(`could not write the workspace into ${userConfigPath("config", { home: host.home })}: it does not parse, fix it by hand`);
1817
1843
  }
1818
- async function install(host, options, recheck) {
1819
- const pool = candidates2(host);
1844
+ async function install(host, options, recheck, choosing = ask2) {
1845
+ const all = candidates2(host);
1846
+ const pool = all.filter((each) => !each.installed);
1820
1847
  const { dim, green, red, yellow } = paint(process16.stdout);
1821
1848
  if (options.list) {
1822
1849
  for (const each of pool)
@@ -1825,20 +1852,23 @@ async function install(host, options, recheck) {
1825
1852
  console.log("everything is installed");
1826
1853
  return 0;
1827
1854
  }
1828
- if (pool.length === 0) {
1855
+ if (pool.length === 0 && options.names.length > 0) {
1829
1856
  console.log("everything is installed");
1830
1857
  return 0;
1831
1858
  }
1832
- 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)));
1833
1860
  if (unknown.length > 0)
1834
1861
  badUsage(`nothing to install is named ${unknown.join(", ")}: doctor --install --list`);
1835
- 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 ?? [];
1836
1864
  const skipped = options.names.filter((name) => name !== "all" && !pool.some((each) => each.names.includes(name)));
1837
1865
  for (const name of skipped)
1838
1866
  console.log(`${dim("already")} ${name} is installed`);
1839
- 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) {
1840
1870
  if (skipped.length === 0)
1841
- console.log("nothing picked");
1871
+ console.log("nothing changed");
1842
1872
  return 0;
1843
1873
  }
1844
1874
  const planned = plan(host, picked, options.force);
@@ -1846,7 +1876,7 @@ async function install(host, options, recheck) {
1846
1876
  console.log(`${red("blocked")} ${each.package} needs ${each.need} (${each.why}): ${each.how}; --force installs it anyway`);
1847
1877
  for (const each of planned.warnings)
1848
1878
  console.log(`${yellow("warn ")} ${each.package}: ${each.need} is missing, ${each.why}`);
1849
- if (planned.packages.length === 0 && !planned.plugin)
1879
+ if (planned.packages.length === 0 && !planned.plugin && !removes)
1850
1880
  return 1;
1851
1881
  if (!options.dryRun && planned.packages.some((name) => name === "@kaynooo/cli-workspace" || name === "@kaynooo/cli-pkg"))
1852
1882
  await askWorkspace(host);
@@ -1855,6 +1885,11 @@ async function install(host, options, recheck) {
1855
1885
  console.log(`${options.dryRun ? dim("would ") : green("install")} ${command.join(" ")}`);
1856
1886
  if (planned.plugin)
1857
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(" ")}`);
1858
1893
  if (options.dryRun)
1859
1894
  return planned.blocked.length > 0 ? 1 : 0;
1860
1895
  if (planned.packages.length > 0) {
@@ -1873,16 +1908,23 @@ async function install(host, options, recheck) {
1873
1908
  console.error(`${red("failed")} ${PLUGIN_INSTALL.join(" ")}: exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}`);
1874
1909
  }
1875
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
+ }
1876
1923
  console.log(`
1877
1924
  after the install:`);
1878
1925
  const code = await recheck(checksFor(planned.packages, planned.plugin));
1879
1926
  return planned.blocked.length > 0 ? 1 : code;
1880
1927
  }
1881
- function candidatesAll(host) {
1882
- const open = candidates2(host);
1883
- 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] }));
1884
- return [...open, ...done];
1885
- }
1886
1928
 
1887
1929
  // src/doctor.ts
1888
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
@@ -1899,7 +1941,7 @@ var OPTIONS = {
1899
1941
  quiet: { type: "boolean", short: "q", description: "print only the failing checks" },
1900
1942
  fix: { type: "boolean", description: "run the fix: lines doctor may run, then check again" },
1901
1943
  "dry-run": { type: "boolean", description: "with --fix or --install, show what would run without running it (alone, it implies --fix)" },
1902
- 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" },
1903
1945
  list: { type: "boolean", description: "with --install, print the choices" },
1904
1946
  force: { type: "boolean", description: "with --install, install a package even though a tool it requires is missing" }
1905
1947
  };
@@ -1922,7 +1964,7 @@ function printOutcomes(outcomes, log) {
1922
1964
  log(`${marks[status]} ${command}${detail === undefined ? "" : ` ${dim(detail)}`}`);
1923
1965
  }
1924
1966
  async function main(args, host = hostHere()) {
1925
- 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"]] });
1926
1968
  const only = values.only ?? [];
1927
1969
  if (values.install !== true && (positionals.length > 0 || values.list === true || values.force === true))
1928
1970
  badUsage("names, --list and --force go with --install");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kaynooo/cli-doctor",
3
3
  "type": "module",
4
- "version": "0.9.0",
4
+ "version": "0.10.0",
5
5
  "description": "doctor: checks the @kaynooo/cli-* install of a host: each bin's version against the registry, tea's login to Forgejo, the Claude Code hooks and the Bun floor; cli list: every bin in one line, read from its own help.",
6
6
  "author": "Kaynooo <contact@kaynooo.fr>",
7
7
  "license": "MIT",
@@ -37,6 +37,6 @@
37
37
  "doctor": "$npm_execpath src/cli.ts"
38
38
  },
39
39
  "devDependencies": {
40
- "@kaynooo/cli-kit": "0.22.0"
40
+ "@kaynooo/cli-kit": "0.23.0"
41
41
  }
42
42
  }