From 50faccaba8ce5f53a71e92ddf89e28ccfaf8e74f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 25 Jan 2026 03:16:41 +0000 Subject: [PATCH] fix: register lazy subcommands before parse (#1683) (thanks @grrowl) --- CHANGELOG.md | 1 + src/cli/run-main.test.ts | 57 ++++++++++++++++++++-------------------- src/cli/run-main.ts | 2 +- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5707a46d..9fe3e26ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Docs: https://docs.clawd.bot - Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634) - Google Chat: tighten email allowlist matching, typing cleanup, media caps, and onboarding/docs/tests. (#1635) Thanks @iHildy. - Google Chat: normalize space targets without double `spaces/` prefix. +- CLI: register lazy subcommands before parse so non-help invocations work. (#1683) Thanks @grrowl. ## 2026.1.23-1 diff --git a/src/cli/run-main.test.ts b/src/cli/run-main.test.ts index 65bc2bf0b..213ff4100 100644 --- a/src/cli/run-main.test.ts +++ b/src/cli/run-main.test.ts @@ -1,37 +1,36 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; -import { rewriteUpdateFlagArgv } from "./run-main.js"; +const registerSubCliByName = vi.fn(async () => true); +const parseAsync = vi.fn(async () => undefined); +const buildProgram = vi.fn(() => ({ parseAsync })); -describe("rewriteUpdateFlagArgv", () => { - it("leaves argv unchanged when --update is absent", () => { - const argv = ["node", "entry.js", "status"]; - expect(rewriteUpdateFlagArgv(argv)).toBe(argv); +vi.mock("../infra/dotenv.js", () => ({ loadDotEnv: vi.fn() })); +vi.mock("../infra/env.js", () => ({ normalizeEnv: vi.fn() })); +vi.mock("../infra/path-env.js", () => ({ ensureClawdbotCliOnPath: vi.fn() })); +vi.mock("../infra/runtime-guard.js", () => ({ assertSupportedRuntime: vi.fn() })); +vi.mock("../infra/errors.js", () => ({ formatUncaughtError: vi.fn(() => "error") })); +vi.mock("../infra/unhandled-rejections.js", () => ({ installUnhandledRejectionHandler: vi.fn() })); +vi.mock("../logging.js", () => ({ enableConsoleCapture: vi.fn() })); +vi.mock("./program.js", () => ({ buildProgram })); +vi.mock("./program/register.subclis.js", () => ({ registerSubCliByName })); +vi.mock("./route.js", () => ({ tryRouteCli: vi.fn(async () => false) })); + +const { runCli } = await import("./run-main.js"); + +describe("runCli", () => { + afterEach(() => { + registerSubCliByName.mockClear(); + parseAsync.mockClear(); + buildProgram.mockClear(); }); - it("rewrites --update into the update command", () => { - expect(rewriteUpdateFlagArgv(["node", "entry.js", "--update"])).toEqual([ - "node", - "entry.js", - "update", - ]); - }); + it("registers the primary subcommand before parsing", async () => { + const argv = ["/usr/bin/node-22", "/opt/clawdbot/entry.js", "gateway", "--port", "18789"]; - it("preserves global flags that appear before --update", () => { - expect(rewriteUpdateFlagArgv(["node", "entry.js", "--profile", "p", "--update"])).toEqual([ - "node", - "entry.js", - "--profile", - "p", - "update", - ]); - }); + await runCli(argv); - it("keeps update options after the rewritten command", () => { - expect(rewriteUpdateFlagArgv(["node", "entry.js", "--update", "--json"])).toEqual([ - "node", - "entry.js", - "update", - "--json", - ]); + expect(registerSubCliByName).toHaveBeenCalledTimes(1); + expect(registerSubCliByName).toHaveBeenCalledWith(expect.any(Object), "gateway"); + expect(parseAsync).toHaveBeenCalledWith(argv); }); }); diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 97a2a1756..b24e5b456 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -11,7 +11,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 { getPrimaryCommand } from "./argv.js"; import { tryRouteCli } from "./route.js"; export function rewriteUpdateFlagArgv(argv: string[]): string[] {