CLI: improve command descriptions in help output (#18486)

* CLI: clarify config vs configure descriptions

* CLI: improve top-level command descriptions

* CLI: make direct command help more descriptive

* CLI: add commands hint to root help

* CLI: show root help hint in implicit help output

* CLI: add help example for command-specific help

* CLI: tweak root subcommand marker spacing

* CLI: mark clawbot as subcommand root in help

* CLI: derive subcommand markers from registry metadata

* CLI: escape help regex CLI name
This commit is contained in:
Benjamin Jesuiter
2026-02-16 22:06:25 +01:00
committed by GitHub
parent 05a83b9e97
commit b25f334fa2
15 changed files with 288 additions and 69 deletions

View File

@@ -2,12 +2,23 @@ import type { Command } from "commander";
import type { ProgramContext } from "./context.js";
import { formatDocsLink } from "../../terminal/links.js";
import { isRich, theme } from "../../terminal/theme.js";
import { escapeRegExp } from "../../utils.js";
import { formatCliBannerLine, hasEmittedCliBanner } from "../banner.js";
import { replaceCliName, resolveCliName } from "../cli-name.js";
import { getCoreCliCommandsWithSubcommands } from "./command-registry.js";
import { getSubCliCommandsWithSubcommands } from "./register.subclis.js";
const CLI_NAME = resolveCliName();
const CLI_NAME_PATTERN = escapeRegExp(CLI_NAME);
const ROOT_COMMANDS_WITH_SUBCOMMANDS = new Set([
...getCoreCliCommandsWithSubcommands(),
...getSubCliCommandsWithSubcommands(),
]);
const ROOT_COMMANDS_HINT =
"Hint: commands suffixed with * have subcommands. Run <command> --help for details.";
const EXAMPLES = [
["openclaw models --help", "Show detailed help for the models command."],
[
"openclaw channels login --verbose",
"Link personal WhatsApp Web and show QR + connection logs.",
@@ -51,18 +62,36 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) {
sortSubcommands: true,
sortOptions: true,
optionTerm: (option) => theme.option(option.flags),
subcommandTerm: (cmd) => theme.command(cmd.name()),
subcommandTerm: (cmd) => {
const isRootCommand = cmd.parent === program;
const hasSubcommands = isRootCommand && ROOT_COMMANDS_WITH_SUBCOMMANDS.has(cmd.name());
return theme.command(hasSubcommands ? `${cmd.name()} *` : cmd.name());
},
});
const formatHelpOutput = (str: string) => {
let output = str;
const isRootHelp = new RegExp(
`^Usage:\\s+${CLI_NAME_PATTERN}\\s+\\[options\\]\\s+\\[command\\]\\s*$`,
"m",
).test(output);
if (isRootHelp && /^Commands:/m.test(output)) {
output = output.replace(/^Commands:/m, `Commands:\n ${theme.muted(ROOT_COMMANDS_HINT)}`);
}
return output
.replace(/^Usage:/gm, theme.heading("Usage:"))
.replace(/^Options:/gm, theme.heading("Options:"))
.replace(/^Commands:/gm, theme.heading("Commands:"));
};
program.configureOutput({
writeOut: (str) => {
const colored = str
.replace(/^Usage:/gm, theme.heading("Usage:"))
.replace(/^Options:/gm, theme.heading("Options:"))
.replace(/^Commands:/gm, theme.heading("Commands:"));
process.stdout.write(colored);
process.stdout.write(formatHelpOutput(str));
},
writeErr: (str) => {
process.stderr.write(formatHelpOutput(str));
},
writeErr: (str) => process.stderr.write(str),
outputError: (str, write) => write(theme.error(str)),
});