From 3b26da4b820a246e1d0f06c93bb07d23f6eff781 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 15 Mar 2026 20:26:38 -0700 Subject: [PATCH] CLI: route gateway status before program registration --- src/cli/program/routes.test.ts | 72 ++++++++++++++++++++++++++++++++++ src/cli/program/routes.ts | 48 +++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/src/cli/program/routes.test.ts b/src/cli/program/routes.test.ts index 0eb92333c0a..896dcb6757a 100644 --- a/src/cli/program/routes.test.ts +++ b/src/cli/program/routes.test.ts @@ -5,6 +5,7 @@ const runConfigGetMock = vi.hoisted(() => vi.fn(async () => {})); const runConfigUnsetMock = vi.hoisted(() => vi.fn(async () => {})); const modelsListCommandMock = vi.hoisted(() => vi.fn(async () => {})); const modelsStatusCommandMock = vi.hoisted(() => vi.fn(async () => {})); +const gatewayStatusCommandMock = vi.hoisted(() => vi.fn(async () => {})); vi.mock("../config-cli.js", () => ({ runConfigGet: runConfigGetMock, @@ -16,6 +17,10 @@ vi.mock("../../commands/models.js", () => ({ modelsStatusCommand: modelsStatusCommandMock, })); +vi.mock("../../commands/gateway-status.js", () => ({ + gatewayStatusCommand: gatewayStatusCommandMock, +})); + describe("program routes", () => { beforeEach(() => { vi.clearAllMocks(); @@ -48,6 +53,73 @@ describe("program routes", () => { expect(shouldLoad(["node", "openclaw", "health", "--json"])).toBe(false); }); + it("matches gateway status route without plugin preload", () => { + const route = expectRoute(["gateway", "status"]); + expect(route?.loadPlugins).toBeUndefined(); + }); + + it("returns false for gateway status route when option values are missing", async () => { + await expectRunFalse(["gateway", "status"], ["node", "openclaw", "gateway", "status", "--url"]); + await expectRunFalse( + ["gateway", "status"], + ["node", "openclaw", "gateway", "status", "--token"], + ); + await expectRunFalse( + ["gateway", "status"], + ["node", "openclaw", "gateway", "status", "--password"], + ); + await expectRunFalse( + ["gateway", "status"], + ["node", "openclaw", "gateway", "status", "--timeout"], + ); + await expectRunFalse(["gateway", "status"], ["node", "openclaw", "gateway", "status", "--ssh"]); + await expectRunFalse( + ["gateway", "status"], + ["node", "openclaw", "gateway", "status", "--ssh-identity"], + ); + }); + + it("passes parsed gateway status flags through", async () => { + const route = expectRoute(["gateway", "status"]); + await expect( + route?.run([ + "node", + "openclaw", + "--profile", + "work", + "gateway", + "status", + "--url", + "ws://127.0.0.1:18789", + "--token", + "abc", + "--password", + "def", + "--timeout", + "5000", + "--ssh", + "user@host", + "--ssh-identity", + "~/.ssh/id_test", + "--ssh-auto", + "--json", + ]), + ).resolves.toBe(true); + expect(gatewayStatusCommandMock).toHaveBeenCalledWith( + { + url: "ws://127.0.0.1:18789", + token: "abc", + password: "def", + timeout: "5000", + json: true, + ssh: "user@host", + sshIdentity: "~/.ssh/id_test", + sshAuto: true, + }, + expect.any(Object), + ); + }); + it("returns false when status timeout flag value is missing", async () => { await expectRunFalse(["status"], ["node", "openclaw", "status", "--timeout"]); }); diff --git a/src/cli/program/routes.ts b/src/cli/program/routes.ts index 52e0d8f8446..353c9b8f11d 100644 --- a/src/cli/program/routes.ts +++ b/src/cli/program/routes.ts @@ -53,6 +53,53 @@ const routeStatus: RouteSpec = { }, }; +const routeGatewayStatus: RouteSpec = { + match: (path) => path[0] === "gateway" && path[1] === "status", + run: async (argv) => { + const url = getFlagValue(argv, "--url"); + if (url === null) { + return false; + } + const token = getFlagValue(argv, "--token"); + if (token === null) { + return false; + } + const password = getFlagValue(argv, "--password"); + if (password === null) { + return false; + } + const timeout = getFlagValue(argv, "--timeout"); + if (timeout === null) { + return false; + } + const ssh = getFlagValue(argv, "--ssh"); + if (ssh === null) { + return false; + } + const sshIdentity = getFlagValue(argv, "--ssh-identity"); + if (sshIdentity === null) { + return false; + } + const sshAuto = hasFlag(argv, "--ssh-auto"); + const json = hasFlag(argv, "--json"); + const { gatewayStatusCommand } = await import("../../commands/gateway-status.js"); + await gatewayStatusCommand( + { + url: url ?? undefined, + token: token ?? undefined, + password: password ?? undefined, + timeout: timeout ?? undefined, + json, + ssh: ssh ?? undefined, + sshIdentity: sshIdentity ?? undefined, + sshAuto, + }, + defaultRuntime, + ); + return true; + }, +}; + const routeSessions: RouteSpec = { // Fast-path only bare `sessions`; subcommands (e.g. `sessions cleanup`) // must fall through to Commander so nested handlers run. @@ -251,6 +298,7 @@ const routeModelsStatus: RouteSpec = { const routes: RouteSpec[] = [ routeHealth, routeStatus, + routeGatewayStatus, routeSessions, routeAgentsList, routeMemoryStatus,