refactor: share channel status account formatting

This commit is contained in:
Peter Steinberger
2026-04-19 05:33:19 +01:00
parent 3e081c5d21
commit 9a93ea9d7a
4 changed files with 111 additions and 154 deletions

View File

@@ -43,6 +43,37 @@ vi.mock("./channels/shared.js", () => ({
accountId: string;
name?: string;
}) => `${channel} ${accountId}`,
appendEnabledConfiguredLinkedBits: (bits: string[], account: Record<string, unknown>) => {
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<string, unknown>) => {
if (typeof account.mode === "string" && account.mode.length > 0) {
bits.push(`mode:${account.mode}`);
}
},
appendTokenSourceBits: (bits: string[], account: Record<string, unknown>) => {
if (account.tokenSource === "config") {
const unavailable = account.tokenStatus === "configured_unavailable" ? " (unavailable)" : "";
bits.push(`token:config${unavailable}`);
}
},
appendBaseUrlBit: (bits: string[], account: Record<string, unknown>) => {
if (typeof account.baseUrl === "string" && account.baseUrl) {
bits.push(`url:${account.baseUrl}`);
}
},
buildChannelAccountLine: (channel: string, account: Record<string, unknown>, bits: string[]) => {
const accountId = typeof account.accountId === "string" ? account.accountId : "default";
return `- ${channel} ${accountId}: ${bits.join(", ")}`;
},
}));
vi.mock("../channels/plugins/index.js", () => ({

View File

@@ -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<string, unknown>,
) {
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<string, unknown>) {
if (typeof account.mode === "string" && account.mode.length > 0) {
bits.push(`mode:${account.mode}`);
}
}
export function appendTokenSourceBits(bits: string[], account: Record<string, unknown>) {
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<string, unknown>) {
if (typeof account.baseUrl === "string" && account.baseUrl) {
bits.push(`url:${account.baseUrl}`);
}
}
export function buildChannelAccountLine(
provider: ChatChannel,
account: Record<string, unknown>,
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;
}

View File

@@ -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<string, unknown>) {
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<string, unknown>) {
if (typeof account.mode === "string" && account.mode.length > 0) {
bits.push(`mode:${account.mode}`);
}
}
function appendTokenSourceBits(bits: string[], account: Record<string, unknown>) {
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<string, unknown>) {
if (typeof account.baseUrl === "string" && account.baseUrl) {
bits.push(`url:${account.baseUrl}`);
}
}
function buildChannelAccountLine(
provider: ChatChannel,
account: Record<string, unknown>,
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,

View File

@@ -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<string, unknown>) {
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<string, unknown>) {
if (typeof account.mode === "string" && account.mode.length > 0) {
bits.push(`mode:${account.mode}`);
}
}
function appendTokenSourceBits(bits: string[], account: Record<string, unknown>) {
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<string, unknown>) {
if (typeof account.baseUrl === "string" && account.baseUrl) {
bits.push(`url:${account.baseUrl}`);
}
}
function buildChannelAccountLine(
provider: ChatChannel,
account: Record<string, unknown>,
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, unknown>): string[] {
const lines: string[] = [];
lines.push(theme.success("Gateway reachable."));