diff --git a/src/cli/program/register.subclis.test.ts b/src/cli/program/register.subclis.test.ts index c3c0c25df..0c2e8c5e9 100644 --- a/src/cli/program/register.subclis.test.ts +++ b/src/cli/program/register.subclis.test.ts @@ -18,7 +18,17 @@ const { nodesAction, registerNodesCli } = vi.hoisted(() => { return { nodesAction: action, registerNodesCli: register }; }); +const { gatewayRunAction, registerGatewayCli } = vi.hoisted(() => { + const action = vi.fn(); + const register = vi.fn((program: Command) => { + const gateway = program.command("gateway"); + gateway.command("run").option("--port ").action(action); + }); + return { gatewayRunAction: action, registerGatewayCli: register }; +}); + vi.mock("../acp-cli.js", () => ({ registerAcpCli })); +vi.mock("../gateway-cli.js", () => ({ registerGatewayCli })); vi.mock("../nodes-cli.js", () => ({ registerNodesCli })); const { registerSubCliByName, registerSubCliCommands } = await import("./register.subclis.js"); @@ -32,6 +42,8 @@ describe("registerSubCliCommands", () => { delete process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS; registerAcpCli.mockClear(); acpAction.mockClear(); + registerGatewayCli.mockClear(); + gatewayRunAction.mockClear(); registerNodesCli.mockClear(); nodesAction.mockClear(); }); @@ -79,6 +91,24 @@ describe("registerSubCliCommands", () => { expect(nodesAction).toHaveBeenCalledTimes(1); }); + it("preserves options for lazy subcommands", async () => { + process.argv = ["node", "openclaw", "gateway", "run", "--port", "18889"]; + const program = new Command(); + program.name("openclaw"); + registerSubCliCommands(program, process.argv); + + expect(program.commands.map((cmd) => cmd.name())).toEqual(["gateway"]); + + await program.parseAsync(process.argv); + + expect(registerGatewayCli).toHaveBeenCalledTimes(1); + expect(gatewayRunAction).toHaveBeenCalledTimes(1); + expect(gatewayRunAction).toHaveBeenCalledWith( + expect.objectContaining({ port: "18889" }), + expect.anything(), + ); + }); + it("replaces placeholder when registering a subcommand by name", async () => { process.argv = ["node", "openclaw", "acp", "--help"]; const program = new Command(); diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index 87eec67e1..4c488fd4a 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -246,7 +246,7 @@ export async function registerSubCliByName(program: Command, name: string): Prom return true; } -function registerLazyCommand(program: Command, entry: SubCliEntry) { +function registerLazyCommand(program: Command, entry: SubCliEntry, argvOverride?: string[]) { const placeholder = program.command(entry.name).description(entry.description); placeholder.allowUnknownOption(true); placeholder.allowExcessArguments(true); @@ -255,7 +255,7 @@ function registerLazyCommand(program: Command, entry: SubCliEntry) { await entry.register(program); const actionCommand = actionArgs.at(-1) as Command | undefined; const root = actionCommand?.parent ?? program; - const rawArgs = (root as Command & { rawArgs?: string[] }).rawArgs; + const rawArgs = (root as Command & { rawArgs?: string[] }).rawArgs ?? argvOverride; const actionArgsList = resolveActionArgs(actionCommand); const fallbackArgv = actionCommand?.name() ? [actionCommand.name(), ...actionArgsList] @@ -280,11 +280,11 @@ export function registerSubCliCommands(program: Command, argv: string[] = proces if (primary && shouldRegisterPrimaryOnly(argv)) { const entry = entries.find((candidate) => candidate.name === primary); if (entry) { - registerLazyCommand(program, entry); + registerLazyCommand(program, entry, argv); return; } } for (const candidate of entries) { - registerLazyCommand(program, candidate); + registerLazyCommand(program, candidate, argv); } }