import { getChannelPlugin } from "../channels/plugins/index.js" ;
import { formatChannelStatusState } from "../channels/plugins/status-state.js" ;
import { asNullableRecord } from "../shared/record-coerce.js" ;
import { colorize, isRich, theme } from "../terminal/theme.js" ;
import type { ChannelAccountHealthSummary, HealthSummary } from "./health.types.js" ;
const formatKv = (line: string, rich: boolean ) => {
const idx = line.indexOf(": " );
if (idx <= 0 ) {
return colorize(rich, theme.muted, line);
}
const key = line.slice(0 , idx);
const value = line.slice(idx + 2 );
const valueColor =
key === "Gateway target" || key === "Config"
? theme.command
: key === "Source"
? theme.muted
: theme.info;
return `${colorize(rich, theme.muted, `${key}:`)} ${colorize(rich, valueColor, value)}`;
};
export function formatHealthCheckFailure(err: unknown, opts: { rich?: boolean } = {}): string {
const rich = opts.rich ?? isRich();
const raw = String(err);
const message = err instanceof Error ? err.message : raw;
if (!rich) {
return `Health check failed: ${raw}`;
}
const lines = message
.split("\n" )
.map((l) => l.trimEnd())
.filter(Boolean );
const detailsIdx = lines.findIndex((l) => l.startsWith("Gateway target: " ));
const summaryLines = (detailsIdx >= 0 ? lines.slice(0 , detailsIdx) : lines)
.map((l) => l.trim())
.filter(Boolean );
const detailLines = detailsIdx >= 0 ? lines.slice(detailsIdx) : [];
const summary = summaryLines.length > 0 ? summaryLines.join(" " ) : message;
const header = colorize(rich, theme.error.bold, "Health check failed" );
const out: string[] = [`${header}: ${summary}`];
for (const line of detailLines) {
out.push(` ${formatKv(line, rich)}`);
}
return out.join("\n" );
}
const formatProbeLine = (probe: unknown, opts: { botUsernames?: string[] } = {}): string | null => {
const record = asNullableRecord(probe);
if (!record) {
return null ;
}
const ok = typeof record.ok === "boolean" ? record.ok : undefined;
if (ok === undefined) {
return null ;
}
const elapsedMs = typeof record.elapsedMs === "number" ? record.elapsedMs : null ;
const status = typeof record.status === "number" ? record.status : null ;
const error = typeof record.error === "string" ? record.error : null ;
const bot = asNullableRecord(record.bot);
const botUsername = bot && typeof bot.username === "string" ? bot.username : null ;
const webhook = asNullableRecord(record.webhook);
const webhookUrl = webhook && typeof webhook.url === "string" ? webhook.url : null ;
const usernames = new Set<string>();
if (botUsername) {
usernames.add(botUsername);
}
for (const extra of opts.botUsernames ?? []) {
if (extra) {
usernames.add(extra);
}
}
if (ok) {
let label = "ok" ;
if (usernames.size > 0 ) {
label += ` (@${Array.from(usernames).join(", @" )})`;
}
if (elapsedMs != null ) {
label += ` (${elapsedMs}ms)`;
}
if (webhookUrl) {
label += ` - webhook ${webhookUrl}`;
}
return label;
}
let label = `failed (${status ?? "unknown" })`;
if (error) {
label += ` - ${error}`;
}
return label;
};
const formatAccountProbeTiming = (summary: ChannelAccountHealthSummary): string | null => {
const probe = asNullableRecord(summary.probe);
if (!probe) {
return null ;
}
const elapsedMs = typeof probe.elapsedMs === "number" ? Math.round(probe.elapsedMs) : null ;
const ok = typeof probe.ok === "boolean" ? probe.ok : null ;
if (elapsedMs == null && ok !== true ) {
return null ;
}
const accountId = summary.accountId || "default" ;
const botRecord = asNullableRecord(probe.bot);
const botUsername =
botRecord && typeof botRecord.username === "string" ? botRecord.username : null ;
const handle = botUsername ? `@${botUsername}` : accountId;
const timing = elapsedMs != null ? `${elapsedMs}ms` : "ok" ;
return `${handle}:${accountId}:${timing}`;
};
const isProbeFailure = (summary: ChannelAccountHealthSummary): boolean => {
const probe = asNullableRecord(summary.probe);
if (!probe) {
return false ;
}
const ok = typeof probe.ok === "boolean" ? probe.ok : null ;
return ok === false ;
};
export const formatHealthChannelLines = (
summary: HealthSummary,
opts: {
accountMode?: "default" | "all" ;
accountIdsByChannel?: Record<string, string[] | undefined>;
} = {},
): string[] => {
const channels = summary.channels ?? {};
const channelOrder =
summary.channelOrder?.length > 0 ? summary.channelOrder : Object.keys(channels);
const accountMode = opts.accountMode ?? "default" ;
const lines: string[] = [];
for (const channelId of channelOrder) {
const channelSummary = channels[channelId];
if (!channelSummary) {
continue ;
}
const plugin = getChannelPlugin(channelId as never);
const label = summary.channelLabels?.[channelId] ?? plugin?.meta.label ?? channelId;
const accountSummaries = channelSummary.accounts ?? {};
const accountIds = opts.accountIdsByChannel?.[channelId];
const filteredSummaries =
accountIds && accountIds.length > 0
? accountIds
.map((accountId) => accountSummaries[accountId])
.filter((entry): entry is ChannelAccountHealthSummary => Boolean (entry))
: undefined;
const listSummaries =
accountMode === "all"
? Object.values(accountSummaries)
: (filteredSummaries ?? (channelSummary.accounts ? Object.values(accountSummaries) : []));
const baseSummary =
filteredSummaries && filteredSummaries.length > 0 ? filteredSummaries[0 ] : channelSummary;
const botUsernames = listSummaries
? listSummaries
.map((account) => {
const probeRecord = asNullableRecord(account.probe);
const bot = probeRecord ? asNullableRecord(probeRecord.bot) : null ;
return bot && typeof bot.username === "string" ? bot.username : null ;
})
.filter((value): value is string => Boolean (value))
: [];
const statusState =
typeof baseSummary.statusState === "string" ? baseSummary.statusState : null ;
if (statusState) {
if (statusState === "linked" ) {
const authAgeMs = typeof baseSummary.authAgeMs === "number" ? baseSummary.authAgeMs : null ;
const authLabel = authAgeMs != null ? ` (auth age ${Math.round(authAgeMs / 60000 )}m)` : "" ;
lines.push(`${label}: ${formatChannelStatusState(statusState)}${authLabel}`);
} else {
lines.push(`${label}: ${formatChannelStatusState(statusState)}`);
}
continue ;
}
const linked = typeof baseSummary.linked === "boolean" ? baseSummary.linked : null ;
if (linked !== null ) {
if (linked) {
const authAgeMs = typeof baseSummary.authAgeMs === "number" ? baseSummary.authAgeMs : null ;
const authLabel = authAgeMs != null ? ` (auth age ${Math.round(authAgeMs / 60000 )}m)` : "" ;
lines.push(`${label}: linked${authLabel}`);
} else {
lines.push(`${label}: not linked`);
}
continue ;
}
const configured = typeof baseSummary.configured === "boolean" ? baseSummary.configured : null ;
if (configured === false ) {
lines.push(`${label}: not configured`);
continue ;
}
const accountTimings =
accountMode === "all"
? listSummaries
.map((account) => formatAccountProbeTiming(account))
.filter((value): value is string => Boolean (value))
: [];
const failedSummary = listSummaries.find((summary) => isProbeFailure(summary));
if (failedSummary) {
const failureLine = formatProbeLine(failedSummary.probe, { botUsernames });
if (failureLine) {
lines.push(`${label}: ${failureLine}`);
continue ;
}
}
if (accountTimings.length > 0 ) {
lines.push(`${label}: ok (${accountTimings.join(", " )})`);
continue ;
}
const probeLine = formatProbeLine(baseSummary.probe, { botUsernames });
if (probeLine) {
lines.push(`${label}: ${probeLine}`);
continue ;
}
if (configured === true ) {
lines.push(`${label}: configured`);
continue ;
}
lines.push(`${label}: unknown`);
}
return lines;
};
Messung V0.5 in Prozent C=100 H=97 G=98
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland