From 978a0a720e9bbc990c104aa2f1e542a30c6edae4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 12:27:55 +0100 Subject: [PATCH] refactor: dedupe cli lowercase helpers --- src/cli/command-secret-gateway.ts | 6 ++---- src/cli/cron-cli/register.cron-edit.ts | 10 ++++++---- src/cli/gateway-cli/dev.ts | 3 ++- src/cli/gateway-cli/run.ts | 7 +++++-- src/cli/nodes-cli/register.location.ts | 4 ++-- src/cli/nodes-cli/register.status.ts | 7 +++++-- src/cli/program/message/register.thread.ts | 3 ++- src/cli/prompt.ts | 3 ++- src/cli/run-main.ts | 9 +++++++-- 9 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/cli/command-secret-gateway.ts b/src/cli/command-secret-gateway.ts index 4c44cda6876..b61f1af7284 100644 --- a/src/cli/command-secret-gateway.ts +++ b/src/cli/command-secret-gateway.ts @@ -189,8 +189,7 @@ function classifyRuntimeWebTargetPathState(params: { return "inactive"; } - const configuredProvider = - typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : ""; + const configuredProvider = normalizeLowercaseStringOrEmpty(search?.provider); if (!configuredProvider) { return "active"; } @@ -250,8 +249,7 @@ function describeInactiveRuntimeWebTargetPath(params: { return "tools.web.search is disabled."; } - const configuredProvider = - typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : ""; + const configuredProvider = normalizeLowercaseStringOrEmpty(search?.provider); if (configuredProvider && configuredProvider !== match[1]) { return `tools.web.search.provider is "${configuredProvider}".`; } diff --git a/src/cli/cron-cli/register.cron-edit.ts b/src/cli/cron-cli/register.cron-edit.ts index 26c45c4c6b6..d6aad44bc25 100644 --- a/src/cli/cron-cli/register.cron-edit.ts +++ b/src/cli/cron-cli/register.cron-edit.ts @@ -3,7 +3,10 @@ import type { CronJob } from "../../cron/types.js"; import { danger } from "../../globals.js"; import { sanitizeAgentId } from "../../routing/session-key.js"; import { defaultRuntime } from "../../runtime.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { + normalizeOptionalLowercaseString, + normalizeOptionalString, +} from "../../shared/string-coerce.js"; import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js"; import { applyExistingCronSchedulePatch, @@ -279,8 +282,7 @@ export function registerCronEditCommand(cron: Command) { failureAlert.after = after; } if (hasFailureAlertChannel) { - const channel = String(opts.failureAlertChannel).trim().toLowerCase(); - failureAlert.channel = channel ? channel : undefined; + failureAlert.channel = normalizeOptionalLowercaseString(opts.failureAlertChannel); } if (hasFailureAlertTo) { const to = String(opts.failureAlertTo).trim(); @@ -294,7 +296,7 @@ export function registerCronEditCommand(cron: Command) { failureAlert.cooldownMs = cooldownMs; } if (hasFailureAlertMode) { - const mode = String(opts.failureAlertMode).trim().toLowerCase(); + const mode = normalizeOptionalLowercaseString(opts.failureAlertMode); if (mode !== "announce" && mode !== "webhook") { throw new Error("Invalid --failure-alert-mode (must be 'announce' or 'webhook')."); } diff --git a/src/cli/gateway-cli/dev.ts b/src/cli/gateway-cli/dev.ts index 9ab872a6c38..2c9b40dde26 100644 --- a/src/cli/gateway-cli/dev.ts +++ b/src/cli/gateway-cli/dev.ts @@ -6,6 +6,7 @@ import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js"; import { handleReset } from "../../commands/onboard-helpers.js"; import { createConfigIO, writeConfigFile } from "../../config/config.js"; import { defaultRuntime } from "../../runtime.js"; +import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import { resolveUserPath, shortenHomePath } from "../../utils.js"; const DEV_IDENTITY_NAME = "C3-PO"; @@ -32,7 +33,7 @@ async function loadDevTemplate(name: string, fallback: string): Promise const resolveDevWorkspaceDir = (env: NodeJS.ProcessEnv = process.env): string => { const baseDir = resolveDefaultAgentWorkspaceDir(env, os.homedir); - const profile = env.OPENCLAW_PROFILE?.trim().toLowerCase(); + const profile = normalizeOptionalLowercaseString(env.OPENCLAW_PROFILE); if (profile === "dev") { return baseDir; } diff --git a/src/cli/gateway-cli/run.ts b/src/cli/gateway-cli/run.ts index cab53eb534f..e0a86561be8 100644 --- a/src/cli/gateway-cli/run.ts +++ b/src/cli/gateway-cli/run.ts @@ -29,7 +29,10 @@ import { detectRespawnSupervisor } from "../../infra/supervisor-markers.js"; import { setConsoleSubsystemFilter, setConsoleTimestampPrefix } from "../../logging/console.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { defaultRuntime } from "../../runtime.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { + normalizeOptionalLowercaseString, + normalizeOptionalString, +} from "../../shared/string-coerce.js"; import { formatCliCommand } from "../command-format.js"; import { inheritOptionFromParent } from "../command-options.js"; import { forceFreePortAndWait, waitForPortBindable } from "../ports.js"; @@ -238,7 +241,7 @@ function isHealthyGatewayLockError(err: unknown): boolean { } async function runGatewayCommand(opts: GatewayRunOpts) { - const isDevProfile = process.env.OPENCLAW_PROFILE?.trim().toLowerCase() === "dev"; + const isDevProfile = normalizeOptionalLowercaseString(process.env.OPENCLAW_PROFILE) === "dev"; const devMode = Boolean(opts.dev) || isDevProfile; if (opts.reset && !devMode) { defaultRuntime.error("Use --reset with --dev."); diff --git a/src/cli/nodes-cli/register.location.ts b/src/cli/nodes-cli/register.location.ts index 557e57dc769..ccf8926e8a6 100644 --- a/src/cli/nodes-cli/register.location.ts +++ b/src/cli/nodes-cli/register.location.ts @@ -1,6 +1,7 @@ import type { Command } from "commander"; import { randomIdempotencyKey } from "../../gateway/call.js"; import { defaultRuntime } from "../../runtime.js"; +import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import { runNodesCommand } from "./cli-utils.js"; import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js"; import type { NodesRpcOpts } from "./types.js"; @@ -24,8 +25,7 @@ export function registerNodesLocationCommands(nodes: Command) { await runNodesCommand("location get", async () => { const nodeId = await resolveNodeId(opts, String(opts.node ?? "")); const maxAgeMs = opts.maxAge ? Number.parseInt(String(opts.maxAge), 10) : undefined; - const desiredAccuracyRaw = - typeof opts.accuracy === "string" ? opts.accuracy.trim().toLowerCase() : undefined; + const desiredAccuracyRaw = normalizeOptionalLowercaseString(opts.accuracy); const desiredAccuracy = desiredAccuracyRaw === "coarse" || desiredAccuracyRaw === "balanced" || diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 80de4794772..5139df03a67 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -2,7 +2,10 @@ import type { Command } from "commander"; import { formatErrorMessage } from "../../infra/errors.js"; import { formatTimeAgo } from "../../infra/format-time/format-relative.ts"; import { defaultRuntime } from "../../runtime.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { + normalizeOptionalLowercaseString, + normalizeOptionalString, +} from "../../shared/string-coerce.js"; import { getTerminalTableWidth, renderTable } from "../../terminal/table.js"; import { shortenHomeInString } from "../../utils.js"; import { parseDurationMs } from "../parse-duration.js"; @@ -38,7 +41,7 @@ function resolveNodeVersions(node: { if (!legacy) { return { core: undefined, ui: undefined }; } - const platform = node.platform?.trim().toLowerCase() ?? ""; + const platform = normalizeOptionalLowercaseString(node.platform) ?? ""; const headless = platform === "darwin" || platform === "linux" || platform === "win32" || platform === "windows"; return headless ? { core: legacy, ui: undefined } : { core: undefined, ui: legacy }; diff --git a/src/cli/program/message/register.thread.ts b/src/cli/program/message/register.thread.ts index 6fe35b4d3df..00fd91656a2 100644 --- a/src/cli/program/message/register.thread.ts +++ b/src/cli/program/message/register.thread.ts @@ -1,10 +1,11 @@ import type { Command } from "commander"; import { getChannelPlugin } from "../../../channels/plugins/index.js"; import type { ChannelMessageActionName } from "../../../channels/plugins/types.js"; +import { normalizeLowercaseStringOrEmpty } from "../../../shared/string-coerce.js"; import type { MessageCliHelpers } from "./helpers.js"; function resolveThreadCreateRequest(opts: Record) { - const channel = typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : ""; + const channel = normalizeLowercaseStringOrEmpty(opts.channel); if (channel) { const request = getChannelPlugin(channel)?.actions?.resolveCliActionRequest?.({ action: "thread-create", diff --git a/src/cli/prompt.ts b/src/cli/prompt.ts index 7b16e59c2c9..1e05c66f617 100644 --- a/src/cli/prompt.ts +++ b/src/cli/prompt.ts @@ -1,6 +1,7 @@ import { stdin as input, stdout as output } from "node:process"; import readline from "node:readline/promises"; import { isVerbose, isYes } from "../globals.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; export async function promptYesNo(question: string, defaultYes = false): Promise { // Simple Y/N prompt honoring global --yes and verbosity flags. @@ -12,7 +13,7 @@ export async function promptYesNo(question: string, defaultYes = false): Promise } const rl = readline.createInterface({ input, output }); const suffix = defaultYes ? " [Y/n] " : " [y/N] "; - const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase(); + const answer = normalizeLowercaseStringOrEmpty(await rl.question(`${question}${suffix}`)); rl.close(); if (!answer) { return defaultYes; diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 40e45dfd5a4..c327461d6b4 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -12,6 +12,10 @@ import { ensureOpenClawCliOnPath } from "../infra/path-env.js"; import { assertSupportedRuntime } from "../infra/runtime-guard.js"; import { enableConsoleCapture } from "../logging.js"; import { hasMemoryRuntime } from "../plugins/memory-state.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalLowercaseString, +} from "../shared/string-coerce.js"; import { resolveCliArgvInvocation } from "./argv-invocation.js"; import { shouldRegisterPrimaryCommandOnly, @@ -62,7 +66,7 @@ export function resolveMissingPluginCommandMessage( pluginId: string, config?: OpenClawConfig, ): string | null { - const normalizedPluginId = pluginId.trim().toLowerCase(); + const normalizedPluginId = normalizeLowercaseStringOrEmpty(pluginId); if (!normalizedPluginId) { return null; } @@ -70,7 +74,8 @@ export function resolveMissingPluginCommandMessage( Array.isArray(config?.plugins?.allow) && config.plugins.allow.length > 0 ? config.plugins.allow .filter((entry): entry is string => typeof entry === "string") - .map((entry) => entry.trim().toLowerCase()) + .map((entry) => normalizeOptionalLowercaseString(entry)) + .filter(Boolean) : []; if (allow.length > 0 && !allow.includes(normalizedPluginId)) { return (