diff --git a/src/cli/channels-cli.ts b/src/cli/channels-cli.ts index e859bfc125..463bccac4e 100644 --- a/src/cli/channels-cli.ts +++ b/src/cli/channels-cli.ts @@ -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 ", + "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`, diff --git a/src/cli/config-cli.ts b/src/cli/config-cli.ts index 7ade5d0155..25de315692 100644 --- a/src/cli/config-cli.ts +++ b/src/cli/config-cli.ts @@ -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", () => diff --git a/src/cli/directory-cli.ts b/src/cli/directory-cli.ts index 93d5dd2550..b2d7d38742 100644 --- a/src/cli/directory-cli.ts +++ b/src/cli/directory-cli.ts @@ -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 ", + "List members for a specific group.", + ], + ])}\n\n${theme.muted("Docs:")} ${formatDocsLink( "/cli/directory", "docs.openclaw.ai/cli/directory", )}\n`, diff --git a/src/cli/gateway-cli/register.ts b/src/cli/gateway-cli/register.ts index 40c46053cb..34ef3273dd 100644 --- a/src/cli/gateway-cli/register.ts +++ b/src/cli/gateway-cli/register.ts @@ -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`, ), ); diff --git a/src/cli/memory-cli.ts b/src/cli/memory-cli.ts index 78d1615f74..c103f8b699 100644 --- a/src/cli/memory-cli.ts +++ b/src/cli/memory-cli.ts @@ -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 diff --git a/src/cli/node-cli/register.ts b/src/cli/node-cli/register.ts index 1ecdaa503c..b3cb08c79b 100644 --- a/src/cli/node-cli/register.ts +++ b/src/cli/node-cli/register.ts @@ -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 diff --git a/src/cli/nodes-cli/register.ts b/src/cli/nodes-cli/register.ts index 04e4391bff..2082996ca4 100644 --- a/src/cli/nodes-cli/register.ts +++ b/src/cli/nodes-cli/register.ts @@ -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 --raw "uname -a"', "Run a shell command on a node."], + ["openclaw nodes camera snap --node ", "Capture a photo from a node camera."], + ])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/nodes", "docs.openclaw.ai/cli/nodes")}\n`, ); registerNodesStatusCommands(nodes); diff --git a/src/cli/plugins-cli.ts b/src/cli/plugins-cli.ts index 5f897351c5..c7d884c68b 100644 --- a/src/cli/plugins-cli.ts +++ b/src/cli/plugins-cli.ts @@ -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", () => diff --git a/src/cli/program/command-registry.ts b/src/cli/program/command-registry.ts index 17b4a85944..a09ed87f11 100644 --- a/src/cli/program/command-registry.ts +++ b/src/cli/program/command-registry.ts @@ -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; }; @@ -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(); 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); diff --git a/src/cli/program/help.ts b/src/cli/program/help.ts index 2d92031542..2a922a61fb 100644 --- a/src/cli/program/help.ts +++ b/src/cli/program/help.ts @@ -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 --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)), }); diff --git a/src/cli/program/register.configure.ts b/src/cli/program/register.configure.ts index 4d85c6f6cc..e93fb2386e 100644 --- a/src/cli/program/register.configure.ts +++ b/src/cli/program/register.configure.ts @@ -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", () => diff --git a/src/cli/program/register.message.ts b/src/cli/program/register.message.ts index 5a1026b376..7ba0bace2e 100644 --- a/src/cli/program/register.message.ts +++ b/src/cli/program/register.message.ts @@ -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", () => diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index 6d0078979b..1fa981899b 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -9,6 +9,7 @@ type SubCliRegistrar = (program: Command) => Promise | void; type SubCliEntry = { name: string; description: string; + hasSubcommands: boolean; register: SubCliRegistrar; }; @@ -31,10 +32,14 @@ const loadConfig = async (): Promise => { 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); diff --git a/src/cli/security-cli.ts b/src/cli/security-cli.ts index bcaa9cf0be..f55f657f4c 100644 --- a/src/cli/security-cli.ts +++ b/src/cli/security-cli.ts @@ -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 diff --git a/src/cli/update-cli.ts b/src/cli/update-cli.ts index bc35b1a10b..6d7e965ea7 100644 --- a/src/cli/update-cli.ts +++ b/src/cli/update-cli.ts @@ -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 ", "Persist update channel (git + npm)")