diff --git a/docs/cli/tui.md b/docs/cli/tui.md index 56e272e99..2b1ce2095 100644 --- a/docs/cli/tui.md +++ b/docs/cli/tui.md @@ -9,6 +9,8 @@ read_when: Open the terminal UI connected to the Gateway. +Tip: If you pass `--url`, Moltbot treats this as client-only mode and won’t block on local config validation. + Related: - TUI guide: [TUI](/tui) @@ -19,4 +21,3 @@ openclaw tui openclaw tui --url ws://127.0.0.1:18789 --token openclaw tui --session main --deliver ``` - diff --git a/src/cli/program/preaction.ts b/src/cli/program/preaction.ts index 310b8faaa..076b97943 100644 --- a/src/cli/program/preaction.ts +++ b/src/cli/program/preaction.ts @@ -1,7 +1,7 @@ import type { Command } from "commander"; import { defaultRuntime } from "../../runtime.js"; import { emitCliBanner } from "../banner.js"; -import { getCommandPath, getVerboseFlag, hasHelpOrVersion } from "../argv.js"; +import { getCommandPath, getFlagValue, getVerboseFlag, hasHelpOrVersion } from "../argv.js"; import { ensureConfigReady } from "./config-guard.js"; import { ensurePluginRegistryLoaded } from "../plugin-registry.js"; import { isTruthyEnvValue } from "../../infra/env.js"; @@ -41,7 +41,12 @@ export function registerPreActionHooks(program: Command, programVersion: string) process.env.NODE_NO_WARNINGS ??= "1"; } if (commandPath[0] === "doctor") return; - await ensureConfigReady({ runtime: defaultRuntime, commandPath }); + // `tui --url` is client-only, so it should not require a valid local config (e.g. local plugins). + const url = commandPath[0] === "tui" ? getFlagValue(argv, "--url") : undefined; + const shouldBypassConfigGuardForTui = typeof url === "string" && url.trim().length > 0; + if (!shouldBypassConfigGuardForTui) { + await ensureConfigReady({ runtime: defaultRuntime, commandPath }); + } // Load plugins for commands that need channel access if (PLUGIN_REQUIRED_COMMANDS.has(commandPath[0])) { ensurePluginRegistryLoaded(); diff --git a/src/cli/program/preaction.tui-remote-bypass.test.ts b/src/cli/program/preaction.tui-remote-bypass.test.ts new file mode 100644 index 000000000..413cab1ae --- /dev/null +++ b/src/cli/program/preaction.tui-remote-bypass.test.ts @@ -0,0 +1,42 @@ +import { Command } from "commander"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +const ensureConfigReady = vi.fn(async () => undefined); +vi.mock("./config-guard.js", () => ({ ensureConfigReady })); + +const { registerPreActionHooks } = await import("./preaction.js"); + +describe("cli preAction config guard", () => { + const originalArgv = process.argv.slice(); + + afterEach(() => { + process.argv = originalArgv.slice(); + vi.clearAllMocks(); + }); + + it("bypasses config guard for tui --url (client mode)", async () => { + process.argv = ["node", "moltbot", "tui", "--url", "ws://example"]; + + const program = new Command(); + registerPreActionHooks(program, "test"); + const tuiAction = vi.fn(async () => undefined); + program.command("tui").option("--url ").action(tuiAction); + + await program.parseAsync(["tui", "--url", "ws://example"], { from: "user" }); + + expect(ensureConfigReady).not.toHaveBeenCalled(); + expect(tuiAction).toHaveBeenCalled(); + }); + + it("runs config guard for tui without --url", async () => { + process.argv = ["node", "moltbot", "tui"]; + + const program = new Command(); + registerPreActionHooks(program, "test"); + program.command("tui").action(async () => undefined); + + await program.parseAsync(["tui"], { from: "user" }); + + expect(ensureConfigReady).toHaveBeenCalled(); + }); +});