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

@@ -16,6 +16,7 @@ import { runChannelLogin, runChannelLogout } from "./channel-auth.js";
import { formatCliChannelOptions } from "./channel-options.js";
import { runCommandWithRuntime } from "./cli-utils.js";
import { hasExplicitOptions } from "./command-options.js";
import { formatHelpExamples } from "./help-format.js";
const optionNamesAdd = [
"channel",
@@ -70,11 +71,19 @@ export function registerChannelsCli(program: Command) {
const channelNames = formatCliChannelOptions();
const channels = program
.command("channels")
.description("Manage chat channel accounts")
.description("Manage connected chat channels and accounts")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink(
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw channels list", "List configured channels and auth profiles."],
["openclaw channels status --probe", "Run channel status checks and probes."],
[
"openclaw channels add --channel telegram --token <token>",
"Add or update a channel account non-interactively.",
],
["openclaw channels login --channel whatsapp", "Link a WhatsApp Web account."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink(
"/cli/channels",
"docs.openclaw.ai/cli/channels",
)}\n`,

View File

@@ -280,7 +280,9 @@ export async function runConfigUnset(opts: { path: string; runtime?: RuntimeEnv
export function registerConfigCli(program: Command) {
const cmd = program
.command("config")
.description("Config helpers (get/set/unset). Run without subcommand for the wizard.")
.description(
"Non-interactive config helpers (get/set/unset). Run without subcommand for the setup wizard.",
)
.addHelpText(
"after",
() =>

View File

@@ -8,6 +8,7 @@ import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { renderTable } from "../terminal/table.js";
import { theme } from "../terminal/theme.js";
import { formatHelpExamples } from "./help-format.js";
function parseLimit(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) {
@@ -64,11 +65,22 @@ function printDirectoryList(params: {
export function registerDirectoryCli(program: Command) {
const directory = program
.command("directory")
.description("Directory lookups (self, peers, groups) for channels that support it")
.description("Lookup contact and group IDs (self, peers, groups) for supported chat channels")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink(
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw directory self --channel slack", "Show the connected account identity."],
[
'openclaw directory peers list --channel slack --query "alice"',
"Search contact/user IDs by name.",
],
["openclaw directory groups list --channel discord", "List available groups/channels."],
[
"openclaw directory groups members --channel discord --group-id <id>",
"List members for a specific group.",
],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink(
"/cli/directory",
"docs.openclaw.ai/cli/directory",
)}\n`,

View File

@@ -13,6 +13,7 @@ import { colorize, isRich, theme } from "../../terminal/theme.js";
import { formatTokenCount, formatUsd } from "../../utils/usage-format.js";
import { runCommandWithRuntime } from "../cli-utils.js";
import { addGatewayServiceCommands } from "../daemon-cli.js";
import { formatHelpExamples } from "../help-format.js";
import { withProgress } from "../progress.js";
import { callGatewayCli, gatewayCallOpts } from "./call.js";
import {
@@ -75,11 +76,16 @@ export function registerGatewayCli(program: Command) {
const gateway = addGatewayRunCommand(
program
.command("gateway")
.description("Run the WebSocket Gateway")
.description("Run, inspect, and query the WebSocket Gateway")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.openclaw.ai/cli/gateway")}\n`,
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw gateway run", "Run the gateway in the foreground."],
["openclaw gateway status", "Show service status and probe reachability."],
["openclaw gateway discover", "Find local and wide-area gateway beacons."],
["openclaw gateway call health", "Call a gateway RPC method directly."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.openclaw.ai/cli/gateway")}\n`,
),
);

View File

@@ -15,6 +15,7 @@ import { formatDocsLink } from "../terminal/links.js";
import { colorize, isRich, theme } from "../terminal/theme.js";
import { shortenHomeInString, shortenHomePath } from "../utils.js";
import { formatErrorMessage, withManager } from "./cli-utils.js";
import { formatHelpExamples } from "./help-format.js";
import { withProgress, withProgressTotals } from "./progress.js";
type MemoryCommandOptions = {
@@ -515,11 +516,16 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
export function registerMemoryCli(program: Command) {
const memory = program
.command("memory")
.description("Memory search tools")
.description("Search, inspect, and reindex memory files")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/memory", "docs.openclaw.ai/cli/memory")}\n`,
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw memory status", "Show index and provider status."],
["openclaw memory index --force", "Force a full reindex."],
['openclaw memory search --query "deployment notes"', "Search indexed memory entries."],
["openclaw memory status --json", "Output machine-readable JSON."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/memory", "docs.openclaw.ai/cli/memory")}\n`,
);
memory

View File

@@ -4,6 +4,7 @@ import { runNodeHost } from "../../node-host/runner.js";
import { formatDocsLink } from "../../terminal/links.js";
import { theme } from "../../terminal/theme.js";
import { parsePort } from "../daemon-cli/shared.js";
import { formatHelpExamples } from "../help-format.js";
import {
runNodeDaemonInstall,
runNodeDaemonRestart,
@@ -20,11 +21,19 @@ function parsePortWithFallback(value: unknown, fallback: number): number {
export function registerNodeCli(program: Command) {
const node = program
.command("node")
.description("Run a headless node host (system.run/system.which)")
.description("Run and manage the headless node host service")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/node", "docs.openclaw.ai/cli/node")}\n`,
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
[
"openclaw node run --host 127.0.0.1 --port 18789",
"Run the node host in the foreground.",
],
["openclaw node status", "Check node host service status."],
["openclaw node install", "Install the node host service."],
["openclaw node restart", "Restart the installed node host service."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/node", "docs.openclaw.ai/cli/node")}\n`,
);
node

View File

@@ -1,6 +1,7 @@
import type { Command } from "commander";
import { formatDocsLink } from "../../terminal/links.js";
import { theme } from "../../terminal/theme.js";
import { formatHelpExamples } from "../help-format.js";
import { registerNodesCameraCommands } from "./register.camera.js";
import { registerNodesCanvasCommands } from "./register.canvas.js";
import { registerNodesInvokeCommands } from "./register.invoke.js";
@@ -13,11 +14,16 @@ import { registerNodesStatusCommands } from "./register.status.js";
export function registerNodesCli(program: Command) {
const nodes = program
.command("nodes")
.description("Manage gateway-owned node pairing")
.description("Manage gateway-owned nodes (pairing, status, invoke, and media)")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/nodes", "docs.openclaw.ai/cli/nodes")}\n`,
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw nodes status", "List known nodes with live status."],
["openclaw nodes pairing pending", "Show pending node pairing requests."],
['openclaw nodes run --node <id> --raw "uname -a"', "Run a shell command on a node."],
["openclaw nodes camera snap --node <id>", "Capture a photo from a node camera."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/nodes", "docs.openclaw.ai/cli/nodes")}\n`,
);
registerNodesStatusCommands(nodes);

View File

@@ -163,7 +163,7 @@ function logSlotWarnings(warnings: string[]) {
export function registerPluginsCli(program: Command) {
const plugins = program
.command("plugins")
.description("Manage OpenClaw plugins/extensions")
.description("Manage OpenClaw plugins and extensions")
.addHelpText(
"after",
() =>

View File

@@ -15,8 +15,14 @@ export type CommandRegistration = {
register: (params: CommandRegisterParams) => void;
};
type CoreCliCommandDescriptor = {
name: string;
description: string;
hasSubcommands: boolean;
};
type CoreCliEntry = {
commands: Array<{ name: string; description: string }>;
commands: CoreCliCommandDescriptor[];
register: (params: CommandRegisterParams) => Promise<void> | void;
};
@@ -27,30 +33,59 @@ const shouldRegisterCorePrimaryOnly = (argv: string[]) => {
return true;
};
// Note for humans and agents:
// If you update the list of commands, also check whether they have subcommands
// and set the flag accordingly.
const coreEntries: CoreCliEntry[] = [
{
commands: [{ name: "setup", description: "Setup helpers" }],
commands: [
{
name: "setup",
description: "Initialize local config and agent workspace",
hasSubcommands: false,
},
],
register: async ({ program }) => {
const mod = await import("./register.setup.js");
mod.registerSetupCommand(program);
},
},
{
commands: [{ name: "onboard", description: "Onboarding helpers" }],
commands: [
{
name: "onboard",
description: "Interactive onboarding wizard for gateway, workspace, and skills",
hasSubcommands: false,
},
],
register: async ({ program }) => {
const mod = await import("./register.onboard.js");
mod.registerOnboardCommand(program);
},
},
{
commands: [{ name: "configure", description: "Configure wizard" }],
commands: [
{
name: "configure",
description:
"Interactive setup wizard for credentials, channels, gateway, and agent defaults",
hasSubcommands: false,
},
],
register: async ({ program }) => {
const mod = await import("./register.configure.js");
mod.registerConfigureCommand(program);
},
},
{
commands: [{ name: "config", description: "Config helpers" }],
commands: [
{
name: "config",
description:
"Non-interactive config helpers (get/set/unset). Default: starts setup wizard.",
hasSubcommands: true,
},
],
register: async ({ program }) => {
const mod = await import("../config-cli.js");
mod.registerConfigCli(program);
@@ -58,12 +93,25 @@ const coreEntries: CoreCliEntry[] = [
},
{
commands: [
{ name: "doctor", description: "Health checks + quick fixes for the gateway and channels" },
{ name: "dashboard", description: "Open the Control UI with your current token" },
{ name: "reset", description: "Reset local config/state (keeps the CLI installed)" },
{
name: "doctor",
description: "Health checks + quick fixes for the gateway and channels",
hasSubcommands: false,
},
{
name: "dashboard",
description: "Open the Control UI with your current token",
hasSubcommands: false,
},
{
name: "reset",
description: "Reset local config/state (keeps the CLI installed)",
hasSubcommands: false,
},
{
name: "uninstall",
description: "Uninstall the gateway service + local data (CLI remains)",
hasSubcommands: false,
},
],
register: async ({ program }) => {
@@ -72,14 +120,26 @@ const coreEntries: CoreCliEntry[] = [
},
},
{
commands: [{ name: "message", description: "Send, read, and manage messages" }],
commands: [
{
name: "message",
description: "Send, read, and manage messages",
hasSubcommands: true,
},
],
register: async ({ program, ctx }) => {
const mod = await import("./register.message.js");
mod.registerMessageCommands(program, ctx);
},
},
{
commands: [{ name: "memory", description: "Memory commands" }],
commands: [
{
name: "memory",
description: "Search and reindex memory files",
hasSubcommands: true,
},
],
register: async ({ program }) => {
const mod = await import("../memory-cli.js");
mod.registerMemoryCli(program);
@@ -87,19 +147,41 @@ const coreEntries: CoreCliEntry[] = [
},
{
commands: [
{ name: "agent", description: "Agent commands" },
{ name: "agents", description: "Manage isolated agents" },
{
name: "agent",
description: "Run one agent turn via the Gateway",
hasSubcommands: false,
},
{
name: "agents",
description: "Manage isolated agents (workspaces, auth, routing)",
hasSubcommands: true,
},
],
register: async ({ program, ctx }) => {
const mod = await import("./register.agent.js");
mod.registerAgentCommands(program, { agentChannelOptions: ctx.agentChannelOptions });
mod.registerAgentCommands(program, {
agentChannelOptions: ctx.agentChannelOptions,
});
},
},
{
commands: [
{ name: "status", description: "Gateway status" },
{ name: "health", description: "Gateway health" },
{ name: "sessions", description: "Session management" },
{
name: "status",
description: "Show channel health and recent session recipients",
hasSubcommands: false,
},
{
name: "health",
description: "Fetch health from the running gateway",
hasSubcommands: false,
},
{
name: "sessions",
description: "List stored conversation sessions",
hasSubcommands: false,
},
],
register: async ({ program }) => {
const mod = await import("./register.status-health-sessions.js");
@@ -107,7 +189,13 @@ const coreEntries: CoreCliEntry[] = [
},
},
{
commands: [{ name: "browser", description: "Browser tools" }],
commands: [
{
name: "browser",
description: "Manage OpenClaw's dedicated browser (Chrome/Chromium)",
hasSubcommands: true,
},
],
register: async ({ program }) => {
const mod = await import("../browser-cli.js");
mod.registerBrowserCli(program);
@@ -115,21 +203,32 @@ const coreEntries: CoreCliEntry[] = [
},
];
export function getCoreCliCommandNames(): string[] {
function collectCoreCliCommandNames(predicate?: (command: CoreCliCommandDescriptor) => boolean) {
const seen = new Set<string>();
const names: string[] = [];
for (const entry of coreEntries) {
for (const cmd of entry.commands) {
if (seen.has(cmd.name)) {
for (const command of entry.commands) {
if (predicate && !predicate(command)) {
continue;
}
seen.add(cmd.name);
names.push(cmd.name);
if (seen.has(command.name)) {
continue;
}
seen.add(command.name);
names.push(command.name);
}
}
return names;
}
export function getCoreCliCommandNames(): string[] {
return collectCoreCliCommandNames();
}
export function getCoreCliCommandsWithSubcommands(): string[] {
return collectCoreCliCommandNames((command) => command.hasSubcommands);
}
function removeCommand(program: Command, command: Command) {
const commands = program.commands as Command[];
const index = commands.indexOf(command);
@@ -142,7 +241,7 @@ function registerLazyCoreCommand(
program: Command,
ctx: ProgramContext,
entry: CoreCliEntry,
command: { name: string; description: string },
command: CoreCliCommandDescriptor,
) {
const placeholder = program.command(command.name).description(command.description);
placeholder.allowUnknownOption(true);

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)),
});

View File

@@ -11,7 +11,7 @@ import { runCommandWithRuntime } from "../cli-utils.js";
export function registerConfigureCommand(program: Command) {
program
.command("configure")
.description("Interactive prompt to set up credentials, devices, and agent defaults")
.description("Interactive setup wizard for credentials, channels, gateway, and agent defaults")
.addHelpText(
"after",
() =>

View File

@@ -24,7 +24,7 @@ import { registerMessageThreadCommands } from "./message/register.thread.js";
export function registerMessageCommands(program: Command, ctx: ProgramContext) {
const message = program
.command("message")
.description("Send messages and channel actions")
.description("Send, read, and manage messages and channel actions")
.addHelpText(
"after",
() =>

View File

@@ -9,6 +9,7 @@ type SubCliRegistrar = (program: Command) => Promise<void> | void;
type SubCliEntry = {
name: string;
description: string;
hasSubcommands: boolean;
register: SubCliRegistrar;
};
@@ -31,10 +32,14 @@ const loadConfig = async (): Promise<OpenClawConfig> => {
return mod.loadConfig();
};
// Note for humans and agents:
// If you update the list of commands, also check whether they have subcommands
// and set the flag accordingly.
const entries: SubCliEntry[] = [
{
name: "acp",
description: "Agent Control Protocol tools",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../acp-cli.js");
mod.registerAcpCli(program);
@@ -42,7 +47,8 @@ const entries: SubCliEntry[] = [
},
{
name: "gateway",
description: "Gateway control",
description: "Run, inspect, and query the WebSocket Gateway",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../gateway-cli.js");
mod.registerGatewayCli(program);
@@ -51,6 +57,7 @@ const entries: SubCliEntry[] = [
{
name: "daemon",
description: "Gateway service (legacy alias)",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../daemon-cli.js");
mod.registerDaemonCli(program);
@@ -58,7 +65,8 @@ const entries: SubCliEntry[] = [
},
{
name: "logs",
description: "Gateway logs",
description: "Tail gateway file logs via RPC",
hasSubcommands: false,
register: async (program) => {
const mod = await import("../logs-cli.js");
mod.registerLogsCli(program);
@@ -67,6 +75,7 @@ const entries: SubCliEntry[] = [
{
name: "system",
description: "System events, heartbeat, and presence",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../system-cli.js");
mod.registerSystemCli(program);
@@ -74,7 +83,8 @@ const entries: SubCliEntry[] = [
},
{
name: "models",
description: "Model configuration",
description: "Discover, scan, and configure models",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../models-cli.js");
mod.registerModelsCli(program);
@@ -82,7 +92,8 @@ const entries: SubCliEntry[] = [
},
{
name: "approvals",
description: "Exec approvals",
description: "Manage exec approvals (gateway or node host)",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../exec-approvals-cli.js");
mod.registerExecApprovalsCli(program);
@@ -90,7 +101,8 @@ const entries: SubCliEntry[] = [
},
{
name: "nodes",
description: "Node commands",
description: "Manage gateway-owned node pairing and node commands",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../nodes-cli.js");
mod.registerNodesCli(program);
@@ -99,6 +111,7 @@ const entries: SubCliEntry[] = [
{
name: "devices",
description: "Device pairing + token management",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../devices-cli.js");
mod.registerDevicesCli(program);
@@ -106,7 +119,8 @@ const entries: SubCliEntry[] = [
},
{
name: "node",
description: "Node control",
description: "Run and manage the headless node host service",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../node-cli.js");
mod.registerNodeCli(program);
@@ -114,7 +128,8 @@ const entries: SubCliEntry[] = [
},
{
name: "sandbox",
description: "Sandbox tools",
description: "Manage sandbox containers for agent isolation",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../sandbox-cli.js");
mod.registerSandboxCli(program);
@@ -122,7 +137,8 @@ const entries: SubCliEntry[] = [
},
{
name: "tui",
description: "Terminal UI",
description: "Open a terminal UI connected to the Gateway",
hasSubcommands: false,
register: async (program) => {
const mod = await import("../tui-cli.js");
mod.registerTuiCli(program);
@@ -130,7 +146,8 @@ const entries: SubCliEntry[] = [
},
{
name: "cron",
description: "Cron scheduler",
description: "Manage cron jobs via the Gateway scheduler",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../cron-cli.js");
mod.registerCronCli(program);
@@ -138,7 +155,8 @@ const entries: SubCliEntry[] = [
},
{
name: "dns",
description: "DNS helpers",
description: "DNS helpers for wide-area discovery (Tailscale + CoreDNS)",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../dns-cli.js");
mod.registerDnsCli(program);
@@ -146,7 +164,8 @@ const entries: SubCliEntry[] = [
},
{
name: "docs",
description: "Docs helpers",
description: "Search the live OpenClaw docs",
hasSubcommands: false,
register: async (program) => {
const mod = await import("../docs-cli.js");
mod.registerDocsCli(program);
@@ -154,7 +173,8 @@ const entries: SubCliEntry[] = [
},
{
name: "hooks",
description: "Hooks tooling",
description: "Manage internal agent hooks",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../hooks-cli.js");
mod.registerHooksCli(program);
@@ -162,7 +182,8 @@ const entries: SubCliEntry[] = [
},
{
name: "webhooks",
description: "Webhook helpers",
description: "Webhook helpers and integrations",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../webhooks-cli.js");
mod.registerWebhooksCli(program);
@@ -171,6 +192,7 @@ const entries: SubCliEntry[] = [
{
name: "qr",
description: "Generate iOS pairing QR/setup code",
hasSubcommands: false,
register: async (program) => {
const mod = await import("../qr-cli.js");
mod.registerQrCli(program);
@@ -179,6 +201,7 @@ const entries: SubCliEntry[] = [
{
name: "clawbot",
description: "Legacy clawbot command aliases",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../clawbot-cli.js");
mod.registerClawbotCli(program);
@@ -186,7 +209,8 @@ const entries: SubCliEntry[] = [
},
{
name: "pairing",
description: "Pairing helpers",
description: "Secure DM pairing (approve inbound requests)",
hasSubcommands: true,
register: async (program) => {
// Initialize plugins before registering pairing CLI.
// The pairing CLI calls listPairingChannels() at registration time,
@@ -199,7 +223,8 @@ const entries: SubCliEntry[] = [
},
{
name: "plugins",
description: "Plugin management",
description: "Manage OpenClaw plugins and extensions",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../plugins-cli.js");
mod.registerPluginsCli(program);
@@ -209,7 +234,8 @@ const entries: SubCliEntry[] = [
},
{
name: "channels",
description: "Channel management",
description: "Manage connected chat channels (Telegram, Discord, etc.)",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../channels-cli.js");
mod.registerChannelsCli(program);
@@ -217,7 +243,8 @@ const entries: SubCliEntry[] = [
},
{
name: "directory",
description: "Directory commands",
description: "Lookup contact and group IDs (self, peers, groups) for supported chat channels",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../directory-cli.js");
mod.registerDirectoryCli(program);
@@ -225,7 +252,8 @@ const entries: SubCliEntry[] = [
},
{
name: "security",
description: "Security helpers",
description: "Security tools and local config audits",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../security-cli.js");
mod.registerSecurityCli(program);
@@ -233,7 +261,8 @@ const entries: SubCliEntry[] = [
},
{
name: "skills",
description: "Skills management",
description: "List and inspect available skills",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../skills-cli.js");
mod.registerSkillsCli(program);
@@ -241,7 +270,8 @@ const entries: SubCliEntry[] = [
},
{
name: "update",
description: "CLI update helpers",
description: "Update OpenClaw and inspect update channel status",
hasSubcommands: true,
register: async (program) => {
const mod = await import("../update-cli.js");
mod.registerUpdateCli(program);
@@ -250,6 +280,7 @@ const entries: SubCliEntry[] = [
{
name: "completion",
description: "Generate shell completion script",
hasSubcommands: false,
register: async (program) => {
const mod = await import("../completion-cli.js");
mod.registerCompletionCli(program);
@@ -261,6 +292,10 @@ export function getSubCliEntries(): SubCliEntry[] {
return entries;
}
export function getSubCliCommandsWithSubcommands(): string[] {
return entries.filter((entry) => entry.hasSubcommands).map((entry) => entry.name);
}
function removeCommand(program: Command, command: Command) {
const commands = program.commands as Command[];
const index = commands.indexOf(command);

View File

@@ -7,6 +7,7 @@ import { formatDocsLink } from "../terminal/links.js";
import { isRich, theme } from "../terminal/theme.js";
import { shortenHomeInString, shortenHomePath } from "../utils.js";
import { formatCliCommand } from "./command-format.js";
import { formatHelpExamples } from "./help-format.js";
type SecurityAuditOptions = {
json?: boolean;
@@ -29,11 +30,16 @@ function formatSummary(summary: { critical: number; warn: number; info: number }
export function registerSecurityCli(program: Command) {
const security = program
.command("security")
.description("Security tools (audit)")
.description("Audit local config and state for common security foot-guns")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.openclaw.ai/cli/security")}\n`,
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw security audit", "Run a local security audit."],
["openclaw security audit --deep", "Include best-effort live Gateway probe checks."],
["openclaw security audit --fix", "Apply safe remediations and file-permission fixes."],
["openclaw security audit --json", "Output machine-readable JSON."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.openclaw.ai/cli/security")}\n`,
);
security

View File

@@ -18,7 +18,7 @@ export type { UpdateCommandOptions, UpdateStatusOptions, UpdateWizardOptions };
export function registerUpdateCli(program: Command) {
const update = program
.command("update")
.description("Update OpenClaw to the latest version")
.description("Update OpenClaw and inspect update channel status")
.option("--json", "Output result as JSON", false)
.option("--no-restart", "Skip restarting the gateway service after a successful update")
.option("--channel <stable|beta|dev>", "Persist update channel (git + npm)")