From 9a93ea9d7aee2832d96fe6769322fc8be46d7e2f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 19 Apr 2026 05:33:19 +0100 Subject: [PATCH] refactor: share channel status account formatting --- .../channels.status.command-flow.test.ts | 31 ++++++ src/commands/channels/shared.ts | 67 +++++++++++++ src/commands/channels/status-config-format.ts | 96 ++----------------- src/commands/channels/status.ts | 71 +------------- 4 files changed, 111 insertions(+), 154 deletions(-) diff --git a/src/commands/channels.status.command-flow.test.ts b/src/commands/channels.status.command-flow.test.ts index 9ab49eea586..f342da4c349 100644 --- a/src/commands/channels.status.command-flow.test.ts +++ b/src/commands/channels.status.command-flow.test.ts @@ -43,6 +43,37 @@ vi.mock("./channels/shared.js", () => ({ accountId: string; name?: string; }) => `${channel} ${accountId}`, + appendEnabledConfiguredLinkedBits: (bits: string[], account: Record) => { + if (typeof account.enabled === "boolean") { + bits.push(account.enabled ? "enabled" : "disabled"); + } + if (account.configured === true) { + bits.push("configured"); + if (Object.values(account).includes("configured_unavailable")) { + bits.push("secret unavailable in this command path"); + } + } + }, + appendModeBit: (bits: string[], account: Record) => { + if (typeof account.mode === "string" && account.mode.length > 0) { + bits.push(`mode:${account.mode}`); + } + }, + appendTokenSourceBits: (bits: string[], account: Record) => { + if (account.tokenSource === "config") { + const unavailable = account.tokenStatus === "configured_unavailable" ? " (unavailable)" : ""; + bits.push(`token:config${unavailable}`); + } + }, + appendBaseUrlBit: (bits: string[], account: Record) => { + if (typeof account.baseUrl === "string" && account.baseUrl) { + bits.push(`url:${account.baseUrl}`); + } + }, + buildChannelAccountLine: (channel: string, account: Record, bits: string[]) => { + const accountId = typeof account.accountId === "string" ? account.accountId : "default"; + return `- ${channel} ${accountId}: ${bits.join(", ")}`; + }, })); vi.mock("../channels/plugins/index.js", () => ({ diff --git a/src/commands/channels/shared.ts b/src/commands/channels/shared.ts index 2cea2deb6a6..318ee1ad62a 100644 --- a/src/commands/channels/shared.ts +++ b/src/commands/channels/shared.ts @@ -1,3 +1,4 @@ +import { hasConfiguredUnavailableCredentialStatus } from "../../channels/account-snapshot-fields.js"; import { type ChannelId, getChannelPlugin } from "../../channels/plugins/index.js"; import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js"; import type { CommandSecretResolutionMode } from "../../cli/command-secret-gateway.js"; @@ -66,6 +67,72 @@ export function formatChannelAccountLabel(params: { return `${styledChannel} ${styledAccount}`; } +export function appendEnabledConfiguredLinkedBits( + bits: string[], + account: Record, +) { + if (typeof account.enabled === "boolean") { + bits.push(account.enabled ? "enabled" : "disabled"); + } + if (typeof account.configured === "boolean") { + if (account.configured) { + bits.push("configured"); + if (hasConfiguredUnavailableCredentialStatus(account)) { + bits.push("secret unavailable in this command path"); + } + } else { + bits.push("not configured"); + } + } + if (typeof account.linked === "boolean") { + bits.push(account.linked ? "linked" : "not linked"); + } +} + +export function appendModeBit(bits: string[], account: Record) { + if (typeof account.mode === "string" && account.mode.length > 0) { + bits.push(`mode:${account.mode}`); + } +} + +export function appendTokenSourceBits(bits: string[], account: Record) { + const appendSourceBit = (label: string, sourceKey: string, statusKey: string) => { + const source = account[sourceKey]; + if (typeof source !== "string" || !source || source === "none") { + return; + } + const status = account[statusKey]; + const unavailable = status === "configured_unavailable" ? " (unavailable)" : ""; + bits.push(`${label}:${source}${unavailable}`); + }; + + appendSourceBit("token", "tokenSource", "tokenStatus"); + appendSourceBit("bot", "botTokenSource", "botTokenStatus"); + appendSourceBit("app", "appTokenSource", "appTokenStatus"); + appendSourceBit("signing", "signingSecretSource", "signingSecretStatus"); +} + +export function appendBaseUrlBit(bits: string[], account: Record) { + if (typeof account.baseUrl === "string" && account.baseUrl) { + bits.push(`url:${account.baseUrl}`); + } +} + +export function buildChannelAccountLine( + provider: ChatChannel, + account: Record, + bits: string[], +): string { + const accountId = typeof account.accountId === "string" ? account.accountId : DEFAULT_ACCOUNT_ID; + const name = typeof account.name === "string" ? account.name : undefined; + const labelText = formatChannelAccountLabel({ + channel: provider, + accountId, + name, + }); + return `- ${labelText}: ${bits.join(", ")}`; +} + export function shouldUseWizard(params?: { hasFlags?: boolean }) { return params?.hasFlags === false; } diff --git a/src/commands/channels/status-config-format.ts b/src/commands/channels/status-config-format.ts index d89a590b5c7..6b4910711cf 100644 --- a/src/commands/channels/status-config-format.ts +++ b/src/commands/channels/status-config-format.ts @@ -9,96 +9,16 @@ import { } from "../../channels/plugins/status.js"; import type { ChannelAccountSnapshot } from "../../channels/plugins/types.public.js"; import type { OpenClawConfig } from "../../config/config.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { formatDocsLink } from "../../terminal/links.js"; import { theme } from "../../terminal/theme.js"; - -type ChatChannel = string; - -function formatAccountLabel(params: { accountId: string; name?: string }) { - const base = params.accountId || "default"; - if (params.name?.trim()) { - return `${base} (${params.name.trim()})`; - } - return base; -} - -function formatChannelAccountLabel(params: { - channel: ChatChannel; - accountId: string; - name?: string; -}): string { - const channelText = - listChannelPlugins().find((plugin) => plugin.id === params.channel)?.meta.label ?? - params.channel; - return `${channelText} ${formatAccountLabel({ - accountId: params.accountId, - name: params.name, - })}`; -} - -function appendEnabledConfiguredLinkedBits(bits: string[], account: Record) { - if (typeof account.enabled === "boolean") { - bits.push(account.enabled ? "enabled" : "disabled"); - } - if (typeof account.configured === "boolean") { - if (account.configured) { - bits.push("configured"); - if (hasConfiguredUnavailableCredentialStatus(account)) { - bits.push("secret unavailable in this command path"); - } - } else { - bits.push("not configured"); - } - } - if (typeof account.linked === "boolean") { - bits.push(account.linked ? "linked" : "not linked"); - } -} - -function appendModeBit(bits: string[], account: Record) { - if (typeof account.mode === "string" && account.mode.length > 0) { - bits.push(`mode:${account.mode}`); - } -} - -function appendTokenSourceBits(bits: string[], account: Record) { - const appendSourceBit = (label: string, sourceKey: string, statusKey: string) => { - const source = account[sourceKey]; - if (typeof source !== "string" || !source || source === "none") { - return; - } - const status = account[statusKey]; - const unavailable = status === "configured_unavailable" ? " (unavailable)" : ""; - bits.push(`${label}:${source}${unavailable}`); - }; - - appendSourceBit("token", "tokenSource", "tokenStatus"); - appendSourceBit("bot", "botTokenSource", "botTokenStatus"); - appendSourceBit("app", "appTokenSource", "appTokenStatus"); - appendSourceBit("signing", "signingSecretSource", "signingSecretStatus"); -} - -function appendBaseUrlBit(bits: string[], account: Record) { - if (typeof account.baseUrl === "string" && account.baseUrl) { - bits.push(`url:${account.baseUrl}`); - } -} - -function buildChannelAccountLine( - provider: ChatChannel, - account: Record, - bits: string[], -): string { - const accountId = typeof account.accountId === "string" ? account.accountId : "default"; - const name = normalizeOptionalString(account.name) ?? ""; - const labelText = formatChannelAccountLabel({ - channel: provider, - accountId, - name: name || undefined, - }); - return `- ${labelText}: ${bits.join(", ")}`; -} +import { + appendBaseUrlBit, + appendEnabledConfiguredLinkedBits, + appendModeBit, + appendTokenSourceBits, + buildChannelAccountLine, + type ChatChannel, +} from "./shared.js"; export async function formatConfigChannelsStatusLines( cfg: OpenClawConfig, diff --git a/src/commands/channels/status.ts b/src/commands/channels/status.ts index 4c280471234..dae73b3e0b0 100644 --- a/src/commands/channels/status.ts +++ b/src/commands/channels/status.ts @@ -1,4 +1,3 @@ -import { hasConfiguredUnavailableCredentialStatus } from "../../channels/account-snapshot-fields.js"; import { listChannelPlugins } from "../../channels/plugins/index.js"; import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js"; import { formatCliCommand } from "../../cli/command-format.js"; @@ -9,12 +8,15 @@ import { callGateway } from "../../gateway/call.js"; import { collectChannelStatusIssues } from "../../infra/channels-status-issues.js"; import { formatTimeAgo } from "../../infra/format-time/format-relative.ts"; import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { formatDocsLink } from "../../terminal/links.js"; import { theme } from "../../terminal/theme.js"; import { + appendBaseUrlBit, + appendEnabledConfiguredLinkedBits, + appendModeBit, + appendTokenSourceBits, + buildChannelAccountLine, type ChatChannel, - formatChannelAccountLabel, requireValidConfigSnapshot, } from "./shared.js"; import { formatConfigChannelsStatusLines } from "./status-config-format.js"; @@ -27,69 +29,6 @@ export type ChannelsStatusOptions = { timeout?: string; }; -function appendEnabledConfiguredLinkedBits(bits: string[], account: Record) { - if (typeof account.enabled === "boolean") { - bits.push(account.enabled ? "enabled" : "disabled"); - } - if (typeof account.configured === "boolean") { - if (account.configured) { - bits.push("configured"); - if (hasConfiguredUnavailableCredentialStatus(account)) { - bits.push("secret unavailable in this command path"); - } - } else { - bits.push("not configured"); - } - } - if (typeof account.linked === "boolean") { - bits.push(account.linked ? "linked" : "not linked"); - } -} - -function appendModeBit(bits: string[], account: Record) { - if (typeof account.mode === "string" && account.mode.length > 0) { - bits.push(`mode:${account.mode}`); - } -} - -function appendTokenSourceBits(bits: string[], account: Record) { - const appendSourceBit = (label: string, sourceKey: string, statusKey: string) => { - const source = account[sourceKey]; - if (typeof source !== "string" || !source || source === "none") { - return; - } - const status = account[statusKey]; - const unavailable = status === "configured_unavailable" ? " (unavailable)" : ""; - bits.push(`${label}:${source}${unavailable}`); - }; - - appendSourceBit("token", "tokenSource", "tokenStatus"); - appendSourceBit("bot", "botTokenSource", "botTokenStatus"); - appendSourceBit("app", "appTokenSource", "appTokenStatus"); - appendSourceBit("signing", "signingSecretSource", "signingSecretStatus"); -} - -function appendBaseUrlBit(bits: string[], account: Record) { - if (typeof account.baseUrl === "string" && account.baseUrl) { - bits.push(`url:${account.baseUrl}`); - } -} - -function buildChannelAccountLine( - provider: ChatChannel, - account: Record, - bits: string[], -): string { - const accountId = typeof account.accountId === "string" ? account.accountId : "default"; - const name = normalizeOptionalString(account.name) ?? ""; - const labelText = formatChannelAccountLabel({ - channel: provider, - accountId, - name: name || undefined, - }); - return `- ${labelText}: ${bits.join(", ")}`; -} - export function formatGatewayChannelsStatusLines(payload: Record): string[] { const lines: string[] = []; lines.push(theme.success("Gateway reachable."));