mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
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:
committed by
GitHub
parent
05a83b9e97
commit
b25f334fa2
@@ -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`,
|
||||
|
||||
@@ -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",
|
||||
() =>
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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`,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
() =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)),
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
() =>
|
||||
|
||||
@@ -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",
|
||||
() =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)")
|
||||
|
||||
Reference in New Issue
Block a user