diff --git a/src/auto-reply/reply/directive-handling.auth.ts b/src/auto-reply/reply/directive-handling.auth.ts index 480bf8a8207..e767bae979c 100644 --- a/src/auto-reply/reply/directive-handling.auth.ts +++ b/src/auto-reply/reply/directive-handling.auth.ts @@ -12,20 +12,10 @@ import { import { findNormalizedProviderValue, normalizeProviderId } from "../../agents/model-selection.js"; import type { OpenClawConfig } from "../../config/config.js"; import { shortenHomePath } from "../../utils.js"; +import { maskApiKey } from "../../utils/mask-api-key.js"; export type ModelAuthDetailMode = "compact" | "verbose"; -const maskApiKey = (value: string): string => { - const trimmed = value.trim(); - if (!trimmed) { - return "missing"; - } - if (trimmed.length <= 16) { - return trimmed; - } - return `${trimmed.slice(0, 8)}...${trimmed.slice(-8)}`; -}; - export const resolveAuthLabel = async ( provider: string, cfg: OpenClawConfig, diff --git a/src/commands/models/list.format.ts b/src/commands/models/list.format.ts index 5e8c0e245a2..e4f0815a970 100644 --- a/src/commands/models/list.format.ts +++ b/src/commands/models/list.format.ts @@ -1,4 +1,5 @@ import { colorize, isRich as isRichTerminal, theme } from "../../terminal/theme.js"; +export { maskApiKey } from "../../utils/mask-api-key.js"; export const isRich = (opts?: { json?: boolean; plain?: boolean }) => Boolean(isRichTerminal() && !opts?.json && !opts?.plain); @@ -55,14 +56,3 @@ export const truncate = (value: string, max: number) => { } return `${value.slice(0, max - 3)}...`; }; - -export const maskApiKey = (value: string): string => { - const trimmed = value.trim(); - if (!trimmed) { - return "missing"; - } - if (trimmed.length <= 16) { - return trimmed; - } - return `${trimmed.slice(0, 8)}...${trimmed.slice(-8)}`; -}; diff --git a/src/utils/mask-api-key.test.ts b/src/utils/mask-api-key.test.ts new file mode 100644 index 00000000000..f6981c9e10c --- /dev/null +++ b/src/utils/mask-api-key.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { maskApiKey } from "./mask-api-key.js"; + +describe("maskApiKey", () => { + it("returns missing for empty values", () => { + expect(maskApiKey("")).toBe("missing"); + expect(maskApiKey(" ")).toBe("missing"); + }); + + it("returns trimmed value when length is 16 chars or less", () => { + expect(maskApiKey(" abcdefghijklmnop ")).toBe("abcdefghijklmnop"); + expect(maskApiKey(" short ")).toBe("short"); + }); + + it("masks long values with first and last 8 chars", () => { + expect(maskApiKey("1234567890abcdefghijklmnop")).toBe("12345678...ijklmnop"); + }); +}); diff --git a/src/utils/mask-api-key.ts b/src/utils/mask-api-key.ts new file mode 100644 index 00000000000..f719ad53c23 --- /dev/null +++ b/src/utils/mask-api-key.ts @@ -0,0 +1,10 @@ +export const maskApiKey = (value: string): string => { + const trimmed = value.trim(); + if (!trimmed) { + return "missing"; + } + if (trimmed.length <= 16) { + return trimmed; + } + return `${trimmed.slice(0, 8)}...${trimmed.slice(-8)}`; +};