From 833bbcd166757361278cd2b9f76f2d865d5f9b05 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 21 Jan 2026 04:01:23 +0000 Subject: [PATCH] fix: show subcommand help on --help --- src/cli/program/register.subclis.test.ts | 18 +++++++++++++++++- src/cli/program/register.subclis.ts | 13 ++++++++++--- src/cli/run-main.ts | 11 ++++++++++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/cli/program/register.subclis.test.ts b/src/cli/program/register.subclis.test.ts index dfd8b5852..32ba47356 100644 --- a/src/cli/program/register.subclis.test.ts +++ b/src/cli/program/register.subclis.test.ts @@ -21,7 +21,7 @@ const { nodesAction, registerNodesCli } = vi.hoisted(() => { vi.mock("../acp-cli.js", () => ({ registerAcpCli })); vi.mock("../nodes-cli.js", () => ({ registerNodesCli })); -const { registerSubCliCommands } = await import("./register.subclis.js"); +const { registerSubCliByName, registerSubCliCommands } = await import("./register.subclis.js"); describe("registerSubCliCommands", () => { const originalArgv = process.argv; @@ -78,4 +78,20 @@ describe("registerSubCliCommands", () => { expect(registerNodesCli).toHaveBeenCalledTimes(1); expect(nodesAction).toHaveBeenCalledTimes(1); }); + + it("replaces placeholder when registering a subcommand by name", async () => { + process.argv = ["node", "clawdbot", "acp", "--help"]; + const program = new Command(); + program.name("clawdbot"); + registerSubCliCommands(program, process.argv); + + await registerSubCliByName(program, "acp"); + + const names = program.commands.map((cmd) => cmd.name()); + expect(names.filter((name) => name === "acp")).toHaveLength(1); + + await program.parseAsync(["node", "clawdbot", "acp"], { from: "user" }); + expect(registerAcpCli).toHaveBeenCalledTimes(1); + expect(acpAction).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index 8b480b7f0..2f5e46921 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -19,9 +19,7 @@ const shouldRegisterPrimaryOnly = (argv: string[]) => { }; const shouldEagerRegisterSubcommands = (argv: string[]) => { - if (isTruthyEnvValue(process.env.CLAWDBOT_DISABLE_LAZY_SUBCOMMANDS)) return true; - if (hasHelpOrVersion(argv)) return true; - return false; + return isTruthyEnvValue(process.env.CLAWDBOT_DISABLE_LAZY_SUBCOMMANDS); }; const loadConfig = async (): Promise => { @@ -234,6 +232,15 @@ function removeCommand(program: Command, command: Command) { } } +export async function registerSubCliByName(program: Command, name: string): Promise { + const entry = entries.find((candidate) => candidate.name === name); + if (!entry) return false; + const existing = program.commands.find((cmd) => cmd.name() === entry.name); + if (existing) removeCommand(program, existing); + await entry.register(program); + return true; +} + function registerLazyCommand(program: Command, entry: SubCliEntry) { const placeholder = program.command(entry.name).description(entry.description); placeholder.allowUnknownOption(true); diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 84c6f3553..6d485130a 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -10,6 +10,7 @@ import { assertSupportedRuntime } from "../infra/runtime-guard.js"; import { formatUncaughtError } from "../infra/errors.js"; import { installUnhandledRejectionHandler } from "../infra/unhandled-rejections.js"; import { enableConsoleCapture } from "../logging.js"; +import { getPrimaryCommand, hasHelpOrVersion } from "./argv.js"; import { tryRouteCli } from "./route.js"; export function rewriteUpdateFlagArgv(argv: string[]): string[] { @@ -47,7 +48,15 @@ export async function runCli(argv: string[] = process.argv) { process.exit(1); }); - await program.parseAsync(rewriteUpdateFlagArgv(normalizedArgv)); + const parseArgv = rewriteUpdateFlagArgv(normalizedArgv); + if (hasHelpOrVersion(parseArgv)) { + const primary = getPrimaryCommand(parseArgv); + if (primary) { + const { registerSubCliByName } = await import("./program/register.subclis.js"); + await registerSubCliByName(program, primary); + } + } + await program.parseAsync(parseArgv); } function stripWindowsNodeExec(argv: string[]): string[] {