@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 +65 -23
- package/dist/cli.js +65 -23
- package/package.json +2 -2
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).
|
|
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 (
|
|
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(
|
|
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 =
|
|
1810
|
-
const picked = await checklist("
|
|
1811
|
-
|
|
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
|
|
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" && !
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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", "
|
|
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).
|
|
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 (
|
|
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(
|
|
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 =
|
|
1808
|
-
const picked = await checklist("
|
|
1809
|
-
|
|
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
|
|
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" && !
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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", "
|
|
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.
|
|
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.
|
|
40
|
+
"@kaynooo/cli-kit": "0.23.0"
|
|
41
41
|
}
|
|
42
42
|
}
|