File size: 6,525 Bytes
eb3f11e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | // Root Commander help, global options, banner, version, and example formatting.
import type { Command } from "commander";
import { formatDocsLink } from "../../../packages/terminal-core/src/links.js";
import { isRich, theme } from "../../../packages/terminal-core/src/theme.js";
import { resolveCommitHash } from "../../infra/git-commit.js";
import { formatConsoleDiagnosticBlock } from "../../logging/json-console-line.js";
import { escapeRegExp } from "../../utils.js";
import { isRootVersionInvocation } from "../argv.js";
import { formatCliBannerLine, hasEmittedCliBanner } from "../banner.js";
import { CLI_NAME } from "../cli-name.js";
import { CLI_LOG_LEVEL_VALUES, parseCliLogLevelOption } from "../log-level-option.js";
import {
getCommanderErrorCommandNames,
getCommanderErrorCommandPath,
} from "./commander-parse-facts.js";
import { getCoreCliCommandsWithSubcommands } from "./core-command-descriptors.js";
import { formatCliParseErrorOutput } from "./error-output.js";
import { getSubCliCommandsWithSubcommands } from "./subcli-descriptors.js";
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 onboard", "Run guided setup for a local Gateway, workspace, auth, and channels."],
["openclaw setup", "Create the baseline config, workspace, and session folders."],
["openclaw configure", "Change models, Gateway, channels, plugins, skills, and health checks."],
["openclaw status", "Check Gateway, channel, model, and recent-session status."],
["openclaw doctor --fix", "Repair common config, service, plugin, and channel problems."],
["openclaw channels add", "Add or update a chat channel account with guided prompts."],
["openclaw channels status", "See connected messaging accounts and login state."],
["openclaw --dev gateway", "Run a dev Gateway (isolated state/config) on ws://127.0.0.1:19001."],
["openclaw gateway run --force", "Start the Gateway and replace anything bound to its port."],
["openclaw models status", "Show model/provider auth health before running agents."],
["openclaw plugins list", "Inspect enabled, disabled, and installed plugins."],
[
'openclaw agent --to +15555550123 --message "Run summary" --deliver',
"Run one agent turn through the Gateway and optionally deliver the reply.",
],
[
'openclaw message send --channel telegram --target @mychat --message "Hi"',
"Send via your Telegram bot.",
],
] as const;
export function formatProgramHelpOutput(str: string): string {
// Commander emits plain section labels; decorate them after command-specific help renders.
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:"));
}
export function configureProgramHelp(
program: Command,
ctx: { programVersion: string },
options?: { commandsWithSubcommands?: ReadonlySet<string> },
) {
const commandsWithSubcommands = new Set([
...ROOT_COMMANDS_WITH_SUBCOMMANDS,
...(options?.commandsWithSubcommands ?? []),
]);
program
.name(CLI_NAME)
.description("")
.version(ctx.programVersion)
.option(
"--container <name>",
"Run the CLI inside a running Podman/Docker container named <name> (default: env OPENCLAW_CONTAINER)",
)
.option(
"--dev",
"Dev profile: isolate state under ~/.openclaw-dev, default gateway port 19001, and shift derived ports (browser/canvas)",
)
.option(
"--profile <name>",
"Use a named profile (isolates OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATH under ~/.openclaw-<name>)",
)
.option(
"--log-level <level>",
`Global log level override for file + console (${CLI_LOG_LEVEL_VALUES})`,
parseCliLogLevelOption,
);
program.option("--no-color", "Disable ANSI colors", false);
program.helpOption("-h, --help", "Display help for command");
program.helpCommand("help [command]", "Display help for command");
program.configureHelp({
// sort options and subcommands alphabetically
sortSubcommands: true,
sortOptions: true,
optionTerm: (option) => theme.option(option.flags),
subcommandTerm: (cmd) => {
const isRootCommand = cmd.parent === program;
const hasSubcommands = isRootCommand && commandsWithSubcommands.has(cmd.name());
return theme.command(hasSubcommands ? `${cmd.name()} *` : cmd.name());
},
});
program.configureOutput({
writeOut: (str) => {
process.stdout.write(formatProgramHelpOutput(str));
},
writeErr: (str) => {
const message = formatProgramHelpOutput(str);
process.stderr.write(formatConsoleDiagnosticBlock({ level: "error", message }));
},
outputError: (str, write) => {
write(
formatCliParseErrorOutput(str, {
argv: process.argv,
commandPath: getCommanderErrorCommandPath(program),
commandNames: getCommanderErrorCommandNames(program),
}),
);
},
});
if (isRootVersionInvocation(process.argv)) {
const commit = resolveCommitHash({ moduleUrl: import.meta.url });
console.log(
commit ? `OpenClaw ${ctx.programVersion} (${commit})` : `OpenClaw ${ctx.programVersion}`,
);
process.exit(0);
}
program.addHelpText("beforeAll", () => {
if (hasEmittedCliBanner() || process.env.OPENCLAW_SUPPRESS_HELP_BANNER === "1") {
return "";
}
const rich = isRich();
const line = formatCliBannerLine(ctx.programVersion, { richTty: rich, mode: "default" });
return `\n${line}\n`;
});
const fmtExamples = EXAMPLES.map(
([cmd, desc]) => ` ${theme.command(cmd)}\n ${theme.muted(desc)}`,
).join("\n");
program.addHelpText("afterAll", ({ command }) => {
if (command !== program) {
return "";
}
const docs = formatDocsLink("/cli", "docs.openclaw.ai/cli");
return `\n${theme.heading("Examples:")}\n${fmtExamples}\n\n${theme.muted("Docs:")} ${docs}\n`;
});
}
|