diff --git a/src/cli/program/core-command-descriptors.ts b/src/cli/program/core-command-descriptors.ts index e00c9ffbd89a..728471f939ac 100644 --- a/src/cli/program/core-command-descriptors.ts +++ b/src/cli/program/core-command-descriptors.ts @@ -8,17 +8,17 @@ export type CoreCliCommandDescriptor = NamedCommandDescriptor; const coreCliCommandCatalog = defineCommandDescriptorCatalog([ { name: "crestodian", - description: "Open the interactive setup and repair assistant", + description: "Open the ring-zero setup and repair helper", hasSubcommands: false, }, { name: "setup", - description: "Initialize local config and an agent workspace", + description: "Alias for openclaw onboard", hasSubcommands: false, }, { name: "onboard", - description: "Interactive onboarding for gateway, workspace, and skills", + description: "Guided setup for auth, models, Gateway, workspace, channels, and skills", hasSubcommands: false, }, { @@ -29,7 +29,7 @@ const coreCliCommandCatalog = defineCommandDescriptorCatalog([ { name: "config", description: - "Non-interactive config helpers (get/set/unset/file/validate). Default: starts guided setup.", + "Non-interactive config helpers (get/set/patch/unset/file/schema/validate). Run without subcommand for guided setup.", hasSubcommands: true, }, { @@ -44,7 +44,7 @@ const coreCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "doctor", - description: "Diagnose and repair config, Gateway, plugin, and channel problems", + description: "Health checks + quick fixes for the gateway and channels", hasSubcommands: false, }, { @@ -64,12 +64,12 @@ const coreCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "message", - description: "Send, read, and manage channel messages", + description: "Send, read, and manage messages and channel actions", hasSubcommands: true, }, { name: "mcp", - description: "Manage OpenClaw MCP config and channel bridge", + description: "Manage OpenClaw mcp.servers config and channel bridge", hasSubcommands: true, parentDefaultHelp: true, }, @@ -80,22 +80,22 @@ const coreCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "agent", - description: "Run one agent turn via the Gateway", + description: "Run an agent turn via the Gateway (use --local for embedded)", hasSubcommands: false, }, { name: "agents", - description: "Manage isolated agents (workspaces, auth, routing)", + description: "Manage isolated agents (workspaces + auth + routing)", hasSubcommands: true, }, { name: "status", - description: "Show Gateway, channel, model, and recent-session status", + description: "Show channel health and recent session recipients", hasSubcommands: false, }, { name: "health", - description: "Fetch detailed health from the running Gateway", + description: "Fetch health from the running gateway", hasSubcommands: false, }, { @@ -110,7 +110,7 @@ const coreCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "tasks", - description: "Inspect durable background tasks and flows", + description: "Inspect durable background tasks and TaskFlow state", hasSubcommands: true, }, ] as const satisfies ReadonlyArray); diff --git a/src/cli/program/root-command-descriptions.test.ts b/src/cli/program/root-command-descriptions.test.ts new file mode 100644 index 000000000000..ae7ef6c0b19c --- /dev/null +++ b/src/cli/program/root-command-descriptions.test.ts @@ -0,0 +1,64 @@ +// Root help renders catalog placeholders while command help and completion use +// registered Commander commands. Keep those user-facing descriptions aligned. +import { Command } from "commander"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + getCoreCliCommandDescriptors, + getCoreCliCommandNames, + registerCoreCliByName, +} from "./command-registry-core.js"; +import { createProgramContext } from "./context.js"; +import { getSubCliEntries, registerSubCliByName } from "./register.subclis.js"; + +describe("root command descriptions", () => { + beforeEach(() => { + vi.stubEnv("OPENCLAW_ENABLE_PRIVATE_QA_CLI", ""); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it("keeps catalog placeholders and registered commands in sync", async () => { + const program = new Command().name("openclaw"); + const ctx = createProgramContext(); + const argv = ["node", "openclaw", "completion"]; + + for (const name of getCoreCliCommandNames()) { + await registerCoreCliByName(program, ctx, name, argv); + } + for (const entry of getSubCliEntries()) { + await registerSubCliByName(program, entry.name, argv, { purpose: "completion" }); + } + + const registeredCommands = new Map(); + for (const command of program.commands) { + registeredCommands.set(command.name(), { command, registeredAsAlias: false }); + for (const alias of command.aliases()) { + registeredCommands.set(alias, { command, registeredAsAlias: true }); + } + } + + const descriptors = [...getCoreCliCommandDescriptors(), ...getSubCliEntries()]; + const missing: string[] = []; + const mismatches: string[] = []; + for (const descriptor of descriptors) { + const registered = registeredCommands.get(descriptor.name); + if (!registered) { + missing.push(descriptor.name); + continue; + } + if ( + !registered.registeredAsAlias && + registered.command.description() !== descriptor.description + ) { + mismatches.push( + `${descriptor.name}\n catalog: ${descriptor.description}\n registered: ${registered.command.description()}`, + ); + } + } + + expect(missing, "catalog entries with no registered command or alias").toEqual([]); + expect(mismatches, "root help vs registered command description drift").toEqual([]); + }); +}); diff --git a/src/cli/program/subcli-descriptors.ts b/src/cli/program/subcli-descriptors.ts index 5f094c1846e0..57648bd429e1 100644 --- a/src/cli/program/subcli-descriptors.ts +++ b/src/cli/program/subcli-descriptors.ts @@ -7,31 +7,31 @@ import { isPrivateQaCliEnabled } from "./private-qa-cli.js"; export type SubCliDescriptor = NamedCommandDescriptor; const subCliCommandCatalog = defineCommandDescriptorCatalog([ - { name: "acp", description: "Run and manage ACP-backed coding agents", hasSubcommands: true }, + { name: "acp", description: "Run an ACP bridge backed by the Gateway", hasSubcommands: true }, { name: "gateway", - description: "Run, inspect, and query the OpenClaw Gateway", + description: "Run, inspect, and query the WebSocket Gateway", hasSubcommands: true, }, { name: "daemon", - description: "Manage the Gateway service (legacy alias)", + description: "Manage the Gateway service (launchd/systemd/schtasks)", hasSubcommands: true, }, - { name: "logs", description: "Tail Gateway logs locally or via RPC", hasSubcommands: false }, + { name: "logs", description: "Tail gateway file logs via RPC", hasSubcommands: false }, { name: "system", - description: "System events, heartbeat, and presence", + description: "System tools (events, heartbeat, presence)", hasSubcommands: true, }, { name: "models", - description: "List, scan, and set model providers", + description: "Model discovery, scanning, and configuration", hasSubcommands: true, }, { name: "infer", - description: "Run provider-backed model, media, search, and embedding commands", + description: "Run provider-backed inference commands through a stable CLI surface", hasSubcommands: true, }, { @@ -57,12 +57,12 @@ const subCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "nodes", - description: "Pair nodes and run node-host commands through the Gateway", + description: "Manage gateway-owned nodes (pairing, status, invoke, and media)", hasSubcommands: true, }, { name: "devices", - description: "Device pairing + token management", + description: "Device pairing and auth tokens", hasSubcommands: true, parentDefaultHelp: true, }, @@ -73,12 +73,12 @@ const subCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "sandbox", - description: "Manage sandbox containers for agent isolation", + description: "Manage sandbox containers (Docker-based agent isolation)", hasSubcommands: true, }, { name: "worktrees", - description: "Manage isolated git worktrees for agent tasks", + description: "Create, inspect, restore, and clean up managed worktrees", hasSubcommands: true, parentDefaultHelp: true, }, @@ -104,7 +104,7 @@ const subCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "cron", - description: "Schedule and inspect Gateway background jobs", + description: "Manage cron jobs (via Gateway)", hasSubcommands: true, parentDefaultHelp: true, }, @@ -140,7 +140,7 @@ const subCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "qr", - description: "Generate mobile pairing QR/setup code", + description: "Generate a mobile pairing QR code and setup code", hasSubcommands: false, }, { @@ -155,13 +155,13 @@ const subCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "plugins", - description: "Install, enable, disable, and inspect plugins", + description: "Manage OpenClaw plugins and extensions", hasSubcommands: true, parentDefaultHelp: true, }, { name: "channels", - description: "Add, remove, login, and inspect messaging channels", + description: "Manage connected chat channels and accounts", hasSubcommands: true, parentDefaultHelp: true, }, @@ -172,17 +172,17 @@ const subCliCommandCatalog = defineCommandDescriptorCatalog([ }, { name: "security", - description: "Security tools and local config audits", + description: "Audit local config and state for common security foot-guns", hasSubcommands: true, }, { name: "secrets", - description: "Audit, apply, and reload SecretRef-backed credentials", + description: "Secrets runtime controls", hasSubcommands: true, }, { name: "skills", - description: "List, inspect, and install agent skills", + description: "List and inspect available skills", hasSubcommands: true, }, {