diff --git a/src/agents/context.ts b/src/agents/context.ts index b14aedd9e26..50e549877ea 100644 --- a/src/agents/context.ts +++ b/src/agents/context.ts @@ -3,6 +3,7 @@ import { loadConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js"; +import { consumeRootOptionToken, FLAG_TERMINATOR } from "../infra/cli-root-options.js"; import { resolveOpenClawAgentDir } from "./agent-paths.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; @@ -69,40 +70,17 @@ const MODEL_CACHE = new Map(); let loadPromise: Promise | null = null; let configuredWindowsPrimed = false; -function isValueToken(arg: string | undefined): boolean { - if (!arg || arg === "--") { - return false; - } - if (!arg.startsWith("-")) { - return true; - } - return /^-\d+(?:\.\d+)?$/.test(arg); -} - function getCommandPathFromArgv(argv: string[]): string[] { const args = argv.slice(2); const tokens: string[] = []; - let skipNextAsRootValue = false; for (let i = 0; i < args.length; i += 1) { const arg = args[i]; - if (!arg || arg === "--") { + if (!arg || arg === FLAG_TERMINATOR) { break; } - if (skipNextAsRootValue) { - skipNextAsRootValue = false; - continue; - } - if (arg === "--profile" || arg === "--log-level") { - const next = args[i + 1]; - skipNextAsRootValue = isValueToken(next); - continue; - } - if ( - arg === "--dev" || - arg === "--no-color" || - arg.startsWith("--profile=") || - arg.startsWith("--log-level=") - ) { + const consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; continue; } if (arg.startsWith("-")) { diff --git a/src/cli/argv.ts b/src/cli/argv.ts index ed5938bc76b..ca989dc4a4b 100644 --- a/src/cli/argv.ts +++ b/src/cli/argv.ts @@ -1,11 +1,13 @@ import { isBunRuntime, isNodeRuntime } from "../daemon/runtime-binary.js"; +import { + consumeRootOptionToken, + FLAG_TERMINATOR, + isValueToken, +} from "../infra/cli-root-options.js"; const HELP_FLAGS = new Set(["-h", "--help"]); const VERSION_FLAGS = new Set(["-V", "--version"]); const ROOT_VERSION_ALIAS_FLAG = "-v"; -const ROOT_BOOLEAN_FLAGS = new Set(["--dev", "--no-color"]); -const ROOT_VALUE_FLAGS = new Set(["--profile", "--log-level"]); -const FLAG_TERMINATOR = "--"; export function hasHelpOrVersion(argv: string[]): boolean { return ( @@ -13,19 +15,6 @@ export function hasHelpOrVersion(argv: string[]): boolean { ); } -function isValueToken(arg: string | undefined): boolean { - if (!arg) { - return false; - } - if (arg === FLAG_TERMINATOR) { - return false; - } - if (!arg.startsWith("-")) { - return true; - } - return /^-\d+(?:\.\d+)?$/.test(arg); -} - function parsePositiveInt(value: string): number | undefined { const parsed = Number.parseInt(value, 10); if (Number.isNaN(parsed) || parsed <= 0) { @@ -62,17 +51,9 @@ export function hasRootVersionAlias(argv: string[]): boolean { hasAlias = true; continue; } - if (ROOT_BOOLEAN_FLAGS.has(arg)) { - continue; - } - if (arg.startsWith("--profile=")) { - continue; - } - if (ROOT_VALUE_FLAGS.has(arg)) { - const next = args[i + 1]; - if (isValueToken(next)) { - i += 1; - } + const consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; continue; } if (arg.startsWith("-")) { @@ -109,17 +90,9 @@ function isRootInvocationForFlags( hasTarget = true; continue; } - if (ROOT_BOOLEAN_FLAGS.has(arg)) { - continue; - } - if (arg.startsWith("--profile=") || arg.startsWith("--log-level=")) { - continue; - } - if (ROOT_VALUE_FLAGS.has(arg)) { - const next = args[i + 1]; - if (isValueToken(next)) { - i += 1; - } + const consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; continue; } // Unknown flags and subcommand-scoped help/version should fall back to Commander. @@ -193,17 +166,9 @@ function getCommandPathInternal( break; } if (opts.skipRootOptions) { - if (arg.startsWith("--profile=") || arg.startsWith("--log-level=")) { - continue; - } - if (ROOT_BOOLEAN_FLAGS.has(arg)) { - continue; - } - if (ROOT_VALUE_FLAGS.has(arg)) { - const next = args[i + 1]; - if (isValueToken(next)) { - i += 1; - } + const consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; continue; } } diff --git a/src/cli/program/routes.ts b/src/cli/program/routes.ts index 366c51ae135..a2e13254044 100644 --- a/src/cli/program/routes.ts +++ b/src/cli/program/routes.ts @@ -1,3 +1,4 @@ +import { consumeRootOptionToken } from "../../infra/cli-root-options.js"; import { defaultRuntime } from "../../runtime.js"; import { getFlagValue, getPositiveIntFlagValue, getVerboseFlag, hasFlag } from "../argv.js"; @@ -99,16 +100,6 @@ const routeMemoryStatus: RouteSpec = { }, }; -function isValueToken(arg: string | undefined): boolean { - if (!arg || arg === "--") { - return false; - } - if (!arg.startsWith("-")) { - return true; - } - return /^-\d+(?:\.\d+)?$/.test(arg); -} - function getCommandPositionals(argv: string[]): string[] { const out: string[] = []; const args = argv.slice(2); @@ -119,17 +110,9 @@ function getCommandPositionals(argv: string[]): string[] { break; } if (!commandStarted) { - if (arg.startsWith("--profile=") || arg.startsWith("--log-level=")) { - continue; - } - if (arg === "--dev" || arg === "--no-color") { - continue; - } - if (arg === "--profile" || arg === "--log-level") { - const next = args[i + 1]; - if (isValueToken(next)) { - i += 1; - } + const consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; continue; } } diff --git a/src/infra/cli-root-options.test.ts b/src/infra/cli-root-options.test.ts new file mode 100644 index 00000000000..514548586f7 --- /dev/null +++ b/src/infra/cli-root-options.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { consumeRootOptionToken } from "./cli-root-options.js"; + +describe("consumeRootOptionToken", () => { + it("consumes boolean and inline root options", () => { + expect(consumeRootOptionToken(["--dev"], 0)).toBe(1); + expect(consumeRootOptionToken(["--profile=work"], 0)).toBe(1); + expect(consumeRootOptionToken(["--log-level=debug"], 0)).toBe(1); + }); + + it("consumes split root value option only when next token is a value", () => { + expect(consumeRootOptionToken(["--profile", "work"], 0)).toBe(2); + expect(consumeRootOptionToken(["--profile", "--no-color"], 0)).toBe(1); + expect(consumeRootOptionToken(["--profile", "--"], 0)).toBe(1); + }); +}); diff --git a/src/infra/cli-root-options.ts b/src/infra/cli-root-options.ts new file mode 100644 index 00000000000..9522e114966 --- /dev/null +++ b/src/infra/cli-root-options.ts @@ -0,0 +1,31 @@ +export const FLAG_TERMINATOR = "--"; + +const ROOT_BOOLEAN_FLAGS = new Set(["--dev", "--no-color"]); +const ROOT_VALUE_FLAGS = new Set(["--profile", "--log-level"]); + +export function isValueToken(arg: string | undefined): boolean { + if (!arg || arg === FLAG_TERMINATOR) { + return false; + } + if (!arg.startsWith("-")) { + return true; + } + return /^-\d+(?:\.\d+)?$/.test(arg); +} + +export function consumeRootOptionToken(args: ReadonlyArray, index: number): number { + const arg = args[index]; + if (!arg) { + return 0; + } + if (ROOT_BOOLEAN_FLAGS.has(arg)) { + return 1; + } + if (arg.startsWith("--profile=") || arg.startsWith("--log-level=")) { + return 1; + } + if (ROOT_VALUE_FLAGS.has(arg)) { + return isValueToken(args[index + 1]) ? 2 : 1; + } + return 0; +}