diff --git a/src/cli/json-output-mode.test.ts b/src/cli/json-output-mode.test.ts new file mode 100644 index 00000000000..b422109a488 --- /dev/null +++ b/src/cli/json-output-mode.test.ts @@ -0,0 +1,48 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { loggingState } from "../logging/state.js"; +import { hasJsonOutputFlag, withConsoleLogsRoutedToStderrForJson } from "./json-output-mode.js"; + +describe("json output mode", () => { + const originalForceStderr = loggingState.forceConsoleToStderr; + + beforeEach(() => { + loggingState.forceConsoleToStderr = false; + }); + + afterEach(() => { + loggingState.forceConsoleToStderr = originalForceStderr; + }); + + it("detects json output flags before argv terminators", () => { + expect(hasJsonOutputFlag(["node", "openclaw", "nodes", "list", "--json"])).toBe(true); + expect(hasJsonOutputFlag(["node", "openclaw", "nodes", "list", "--json=true"])).toBe(true); + expect(hasJsonOutputFlag(["node", "openclaw", "nodes", "--", "--json"])).toBe(false); + }); + + it("temporarily routes console logs to stderr while json output is being prepared", async () => { + const snapshots: boolean[] = []; + + await withConsoleLogsRoutedToStderrForJson( + ["node", "openclaw", "nodes", "list", "--json"], + async () => { + snapshots.push(loggingState.forceConsoleToStderr); + }, + ); + + expect(snapshots).toEqual([true]); + expect(loggingState.forceConsoleToStderr).toBe(false); + }); + + it("leaves existing stderr routing enabled after json output preparation", async () => { + loggingState.forceConsoleToStderr = true; + + await withConsoleLogsRoutedToStderrForJson( + ["node", "openclaw", "nodes", "list", "--json"], + async () => { + expect(loggingState.forceConsoleToStderr).toBe(true); + }, + ); + + expect(loggingState.forceConsoleToStderr).toBe(true); + }); +}); diff --git a/src/cli/nodes-cli/register.ts b/src/cli/nodes-cli/register.ts index 69004435401..c31eb635124 100644 --- a/src/cli/nodes-cli/register.ts +++ b/src/cli/nodes-cli/register.ts @@ -12,7 +12,7 @@ import { registerNodesPushCommand } from "./register.push.js"; import { registerNodesScreenCommands } from "./register.screen.js"; import { registerNodesStatusCommands } from "./register.status.js"; -export async function registerNodesCli(program: Command) { +export async function registerNodesCli(program: Command, argv: readonly string[] = process.argv) { const nodes = program .command("nodes") .description("Manage gateway-owned nodes (pairing, status, invoke, and media)") @@ -41,10 +41,12 @@ export async function registerNodesCli(program: Command) { registerNodesLocationCommands(nodes); const { registerPluginCliCommandsFromValidatedConfig } = await import("../../plugins/cli.js"); - await withConsoleLogsRoutedToStderrForJson(process.argv, () => - registerPluginCliCommandsFromValidatedConfig(program, undefined, undefined, { - mode: "lazy", - primary: "nodes", - }), + await withConsoleLogsRoutedToStderrForJson( + argv, + async () => + await registerPluginCliCommandsFromValidatedConfig(program, undefined, undefined, { + mode: "lazy", + primary: "nodes", + }), ); } diff --git a/src/cli/program/register.subclis-core.ts b/src/cli/program/register.subclis-core.ts index 072f1949048..5763022febc 100644 --- a/src/cli/program/register.subclis-core.ts +++ b/src/cli/program/register.subclis-core.ts @@ -127,11 +127,15 @@ const entrySpecs: readonly CommandGroupDescriptorSpec[] = [ loadModule: () => import("../exec-policy-cli.js"), exportName: "registerExecPolicyCli", }, - { - commandNames: ["nodes"], - loadModule: () => import("../nodes-cli.js"), - exportName: "registerNodesCli", + ]), + { + commandNames: ["nodes"], + register: async (program, argv) => { + const mod = await import("../nodes-cli.js"); + await mod.registerNodesCli(program, argv); }, + }, + ...defineImportedProgramCommandGroupSpecs([ { commandNames: ["devices"], loadModule: () => import("../devices-cli.js"), diff --git a/src/cli/program/register.subclis.test.ts b/src/cli/program/register.subclis.test.ts index 1d2c5833042..d3f59f5b1be 100644 --- a/src/cli/program/register.subclis.test.ts +++ b/src/cli/program/register.subclis.test.ts @@ -172,6 +172,12 @@ describe("registerSubCliCommands", () => { await program.parseAsync(["nodes", "list"], { from: "user" }); expect(registerNodesCli).toHaveBeenCalledTimes(1); + expect(registerNodesCli).toHaveBeenCalledWith(expect.any(Command), [ + "node", + "openclaw", + "nodes", + "list", + ]); expect(nodesAction).toHaveBeenCalledTimes(1); });