fix(status): improve diagnostics and output
This commit is contained in:
@@ -431,4 +431,38 @@ describe("providers command", () => {
|
||||
});
|
||||
expect(disconnected.join("\n")).toMatch(/disconnected/i);
|
||||
});
|
||||
|
||||
it("surfaces Signal runtime errors in providers status output", () => {
|
||||
const lines = formatGatewayProvidersStatusLines({
|
||||
signalAccounts: [
|
||||
{
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: false,
|
||||
lastError: "signal-cli unreachable",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(lines.join("\n")).toMatch(/Warnings:/);
|
||||
expect(lines.join("\n")).toMatch(/signal/i);
|
||||
expect(lines.join("\n")).toMatch(/Provider error/i);
|
||||
});
|
||||
|
||||
it("surfaces iMessage runtime errors in providers status output", () => {
|
||||
const lines = formatGatewayProvidersStatusLines({
|
||||
imessageAccounts: [
|
||||
{
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: false,
|
||||
lastError: "imsg permission denied",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(lines.join("\n")).toMatch(/Warnings:/);
|
||||
expect(lines.join("\n")).toMatch(/imessage/i);
|
||||
expect(lines.join("\n")).toMatch(/Provider error/i);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
||||
import { resolveGatewayLogPaths } from "../daemon/launchd.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
||||
import { normalizeControlUiBasePath } from "../gateway/control-ui.js";
|
||||
import { probeGateway } from "../gateway/probe.js";
|
||||
import { resolveClawdbotPackageRoot } from "../infra/clawdbot-root.js";
|
||||
import { resolveOsSummary } from "../infra/os-summary.js";
|
||||
@@ -18,10 +19,12 @@ import {
|
||||
readRestartSentinel,
|
||||
summarizeRestartSentinel,
|
||||
} from "../infra/restart-sentinel.js";
|
||||
import { readTailscaleStatusJson } from "../infra/tailscale.js";
|
||||
import {
|
||||
checkUpdateStatus,
|
||||
compareSemverStrings,
|
||||
} from "../infra/update-check.js";
|
||||
import { runExec } from "../process/exec.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { renderTable } from "../terminal/table.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
@@ -46,12 +49,54 @@ export async function statusAllCommand(
|
||||
opts?: { timeoutMs?: number },
|
||||
): Promise<void> {
|
||||
await withProgress(
|
||||
{ label: "Scanning status --all…", indeterminate: true },
|
||||
{ label: "Scanning status --all…", total: 11 },
|
||||
async (progress) => {
|
||||
progress.setLabel("Loading config…");
|
||||
const cfg = loadConfig();
|
||||
const osSummary = resolveOsSummary();
|
||||
const snap = await readConfigFileSnapshot().catch(() => null);
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Checking Tailscale…");
|
||||
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
||||
const tailscale = await (async () => {
|
||||
try {
|
||||
const parsed = await readTailscaleStatusJson(runExec, {
|
||||
timeoutMs: 1200,
|
||||
});
|
||||
const backendState =
|
||||
typeof parsed.BackendState === "string"
|
||||
? parsed.BackendState
|
||||
: null;
|
||||
const self =
|
||||
typeof parsed.Self === "object" && parsed.Self !== null
|
||||
? (parsed.Self as Record<string, unknown>)
|
||||
: null;
|
||||
const dnsNameRaw =
|
||||
self && typeof self.DNSName === "string" ? self.DNSName : null;
|
||||
const dnsName = dnsNameRaw ? dnsNameRaw.replace(/\.$/, "") : null;
|
||||
const ips =
|
||||
self && Array.isArray(self.TailscaleIPs)
|
||||
? (self.TailscaleIPs as unknown[])
|
||||
.filter((v) => typeof v === "string" && v.trim().length > 0)
|
||||
.map((v) => (v as string).trim())
|
||||
: [];
|
||||
return { ok: true as const, backendState, dnsName, ips, error: null };
|
||||
} catch (err) {
|
||||
return {
|
||||
ok: false as const,
|
||||
backendState: null,
|
||||
dnsName: null,
|
||||
ips: [] as string[],
|
||||
error: String(err),
|
||||
};
|
||||
}
|
||||
})();
|
||||
const tailscaleHttpsUrl =
|
||||
tailscaleMode !== "off" && tailscale.dnsName
|
||||
? `https://${tailscale.dnsName}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}`
|
||||
: null;
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Checking for updates…");
|
||||
const root = await resolveClawdbotPackageRoot({
|
||||
@@ -65,6 +110,7 @@ export async function statusAllCommand(
|
||||
fetchGit: true,
|
||||
includeRegistry: true,
|
||||
});
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Probing gateway…");
|
||||
const connection = buildGatewayConnectionDetails({ config: cfg });
|
||||
@@ -113,6 +159,7 @@ export async function statusAllCommand(
|
||||
const gatewaySelf = pickGatewaySelfPresence(
|
||||
gatewayProbe?.presence ?? null,
|
||||
);
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Checking daemon…");
|
||||
const daemon = await (async () => {
|
||||
@@ -137,11 +184,14 @@ export async function statusAllCommand(
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Scanning agents…");
|
||||
const agentStatus = await getAgentLocalStatuses(cfg);
|
||||
progress.tick();
|
||||
progress.setLabel("Summarizing providers…");
|
||||
const providers = await buildProvidersTable(cfg, { showSecrets: false });
|
||||
progress.tick();
|
||||
|
||||
const connectionDetailsForReport = (() => {
|
||||
if (!remoteUrlMissing) return connection.message;
|
||||
@@ -187,6 +237,7 @@ export async function statusAllCommand(
|
||||
const providerIssues = providersStatus
|
||||
? collectProvidersStatusIssues(providersStatus)
|
||||
: [];
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Checking local state…");
|
||||
const sentinel = await readRestartSentinel().catch(() => null);
|
||||
@@ -195,6 +246,7 @@ export async function statusAllCommand(
|
||||
);
|
||||
const port = resolveGatewayPort(cfg);
|
||||
const portUsage = await inspectPortUsage(port).catch(() => null);
|
||||
progress.tick();
|
||||
|
||||
const defaultWorkspace =
|
||||
agentStatus.agents.find((a) => a.id === agentStatus.defaultId)
|
||||
@@ -322,6 +374,15 @@ export async function statusAllCommand(
|
||||
dashboard
|
||||
? { Item: "Dashboard", Value: dashboard }
|
||||
: { Item: "Dashboard", Value: "disabled" },
|
||||
{
|
||||
Item: "Tailscale",
|
||||
Value:
|
||||
tailscaleMode === "off"
|
||||
? `off${tailscale.backendState ? ` · ${tailscale.backendState}` : ""}${tailscale.dnsName ? ` · ${tailscale.dnsName}` : ""}`
|
||||
: tailscale.dnsName && tailscaleHttpsUrl
|
||||
? `${tailscaleMode} · ${tailscale.backendState ?? "unknown"} · ${tailscale.dnsName} · ${tailscaleHttpsUrl}`
|
||||
: `${tailscaleMode} · ${tailscale.backendState ?? "unknown"} · magicdns unknown`,
|
||||
},
|
||||
{ Item: "Update", Value: updateLine },
|
||||
{
|
||||
Item: "Gateway",
|
||||
@@ -376,6 +437,46 @@ export async function statusAllCommand(
|
||||
: theme.accentDim("SETUP"),
|
||||
Detail: row.detail,
|
||||
}));
|
||||
const providerIssuesByProvider = (() => {
|
||||
const map = new Map<string, typeof providerIssues>();
|
||||
for (const issue of providerIssues) {
|
||||
const key = issue.provider;
|
||||
const list = map.get(key);
|
||||
if (list) list.push(issue);
|
||||
else map.set(key, [issue]);
|
||||
}
|
||||
return map;
|
||||
})();
|
||||
const providerKeyForLabel = (label: string) => {
|
||||
switch (label) {
|
||||
case "WhatsApp":
|
||||
return "whatsapp";
|
||||
case "Telegram":
|
||||
return "telegram";
|
||||
case "Discord":
|
||||
return "discord";
|
||||
case "Slack":
|
||||
return "slack";
|
||||
case "Signal":
|
||||
return "signal";
|
||||
case "iMessage":
|
||||
return "imessage";
|
||||
default:
|
||||
return label.toLowerCase();
|
||||
}
|
||||
};
|
||||
const providerRowsWithIssues = providerRows.map((row) => {
|
||||
const providerKey = providerKeyForLabel(row.Provider);
|
||||
const issues = providerIssuesByProvider.get(providerKey) ?? [];
|
||||
if (issues.length === 0) return row;
|
||||
const issue = issues[0];
|
||||
const suffix = ` · ${warn(`gateway: ${String(issue.message).slice(0, 90)}`)}`;
|
||||
return {
|
||||
...row,
|
||||
State: warn("WARN"),
|
||||
Detail: `${row.Detail}${suffix}`,
|
||||
};
|
||||
});
|
||||
|
||||
const providersTable = renderTable({
|
||||
width: tableWidth,
|
||||
@@ -385,7 +486,7 @@ export async function statusAllCommand(
|
||||
{ key: "State", header: "State", minWidth: 8 },
|
||||
{ key: "Detail", header: "Detail", flex: true, minWidth: 28 },
|
||||
],
|
||||
rows: providerRows,
|
||||
rows: providerRowsWithIssues,
|
||||
});
|
||||
|
||||
const agentRows = agentStatus.agents.map((a) => ({
|
||||
@@ -531,6 +632,31 @@ export async function statusAllCommand(
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const backend = tailscale.backendState ?? "unknown";
|
||||
const okBackend = backend === "Running";
|
||||
const hasDns = Boolean(tailscale.dnsName);
|
||||
const label =
|
||||
tailscaleMode === "off"
|
||||
? `Tailscale: off · ${backend}${tailscale.dnsName ? ` · ${tailscale.dnsName}` : ""}`
|
||||
: `Tailscale: ${tailscaleMode} · ${backend}${tailscale.dnsName ? ` · ${tailscale.dnsName}` : ""}`;
|
||||
emitCheck(
|
||||
label,
|
||||
okBackend && (tailscaleMode === "off" || hasDns) ? "ok" : "warn",
|
||||
);
|
||||
if (tailscale.error) {
|
||||
lines.push(` ${muted(`error: ${tailscale.error}`)}`);
|
||||
}
|
||||
if (tailscale.ips.length > 0) {
|
||||
lines.push(
|
||||
` ${muted(`ips: ${tailscale.ips.slice(0, 3).join(", ")}${tailscale.ips.length > 3 ? "…" : ""}`)}`,
|
||||
);
|
||||
}
|
||||
if (tailscaleHttpsUrl) {
|
||||
lines.push(` ${muted(`https: ${tailscaleHttpsUrl}`)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (skillStatus) {
|
||||
const eligible = skillStatus.skills.filter((s) => s.eligible).length;
|
||||
const missing = skillStatus.skills.filter(
|
||||
@@ -543,6 +669,7 @@ export async function statusAllCommand(
|
||||
);
|
||||
}
|
||||
|
||||
progress.setLabel("Reading logs…");
|
||||
const logPaths = (() => {
|
||||
try {
|
||||
return resolveGatewayLogPaths(process.env);
|
||||
@@ -575,6 +702,7 @@ export async function statusAllCommand(
|
||||
}
|
||||
}
|
||||
}
|
||||
progress.tick();
|
||||
|
||||
if (providersStatus) {
|
||||
emitCheck(
|
||||
@@ -623,6 +751,7 @@ export async function statusAllCommand(
|
||||
|
||||
progress.setLabel("Rendering…");
|
||||
runtime.log(lines.join("\n"));
|
||||
progress.tick();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -285,6 +285,8 @@ export async function buildProvidersTable(
|
||||
);
|
||||
const siEnabledAccounts = siAccounts.filter((a) => a.enabled);
|
||||
const siConfiguredAccounts = siEnabledAccounts.filter((a) => a.configured);
|
||||
const siSample = siConfiguredAccounts[0] ?? siEnabledAccounts[0] ?? null;
|
||||
const siBaseUrl = siSample?.baseUrl?.trim() ? siSample.baseUrl.trim() : "";
|
||||
rows.push({
|
||||
provider: "Signal",
|
||||
enabled: siEnabled,
|
||||
@@ -295,7 +297,7 @@ export async function buildProvidersTable(
|
||||
: "setup",
|
||||
detail: siEnabled
|
||||
? siConfiguredAccounts.length > 0
|
||||
? `configured · accounts ${siConfiguredAccounts.length}/${siEnabledAccounts.length || 1}`
|
||||
? `configured${siBaseUrl ? ` · baseUrl ${siBaseUrl}` : ""} · accounts ${siConfiguredAccounts.length}/${siEnabledAccounts.length || 1}`
|
||||
: "default config (no overrides)"
|
||||
: "disabled",
|
||||
});
|
||||
@@ -307,6 +309,9 @@ export async function buildProvidersTable(
|
||||
);
|
||||
const imEnabledAccounts = imAccounts.filter((a) => a.enabled);
|
||||
const imConfiguredAccounts = imEnabledAccounts.filter((a) => a.configured);
|
||||
const imSample = imEnabledAccounts[0] ?? null;
|
||||
const imCliPath = imSample?.config?.cliPath?.trim() || "";
|
||||
const imDbPath = imSample?.config?.dbPath?.trim() || "";
|
||||
rows.push({
|
||||
provider: "iMessage",
|
||||
enabled: imEnabled,
|
||||
@@ -317,7 +322,7 @@ export async function buildProvidersTable(
|
||||
: "setup",
|
||||
detail: imEnabled
|
||||
? imConfiguredAccounts.length > 0
|
||||
? `configured · accounts ${imConfiguredAccounts.length}/${imEnabledAccounts.length || 1}`
|
||||
? `configured${imCliPath ? ` · cliPath ${imCliPath}` : ""}${imDbPath ? ` · dbPath ${imDbPath}` : ""} · accounts ${imConfiguredAccounts.length}/${imEnabledAccounts.length || 1}`
|
||||
: "default config (no overrides)"
|
||||
: "disabled",
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ const mocks = vi.hoisted(() => ({
|
||||
presence: null,
|
||||
configSnapshot: null,
|
||||
}),
|
||||
callGateway: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
vi.mock("../config/sessions.js", () => ({
|
||||
@@ -47,6 +48,10 @@ vi.mock("../web/session.js", () => ({
|
||||
vi.mock("../gateway/probe.js", () => ({
|
||||
probeGateway: mocks.probeGateway,
|
||||
}));
|
||||
vi.mock("../gateway/call.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../gateway/call.js")>();
|
||||
return { ...actual, callGateway: mocks.callGateway };
|
||||
});
|
||||
vi.mock("../gateway/session-utils.js", () => ({
|
||||
listAgentsForGateway: () => ({
|
||||
defaultId: "main",
|
||||
@@ -175,4 +180,46 @@ describe("statusCommand", () => {
|
||||
else process.env.CLAWDBOT_GATEWAY_TOKEN = prevToken;
|
||||
}
|
||||
});
|
||||
|
||||
it("surfaces provider runtime errors from the gateway", async () => {
|
||||
mocks.probeGateway.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
url: "ws://127.0.0.1:18789",
|
||||
connectLatencyMs: 10,
|
||||
error: null,
|
||||
close: null,
|
||||
health: {},
|
||||
status: {},
|
||||
presence: [],
|
||||
configSnapshot: null,
|
||||
});
|
||||
mocks.callGateway.mockResolvedValueOnce({
|
||||
signalAccounts: [
|
||||
{
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: false,
|
||||
lastError: "signal-cli unreachable",
|
||||
},
|
||||
],
|
||||
imessageAccounts: [
|
||||
{
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: false,
|
||||
lastError: "imessage permission denied",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
(runtime.log as vi.Mock).mockClear();
|
||||
await statusCommand({}, runtime as never);
|
||||
const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0]));
|
||||
expect(logs.join("\n")).toMatch(/Signal/i);
|
||||
expect(logs.join("\n")).toMatch(/iMessage/i);
|
||||
expect(logs.join("\n")).toMatch(/gateway:/i);
|
||||
expect(logs.join("\n")).toMatch(/WARN/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "../config/sessions.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
||||
import { normalizeControlUiBasePath } from "../gateway/control-ui.js";
|
||||
import { probeGateway } from "../gateway/probe.js";
|
||||
import { listAgentsForGateway } from "../gateway/session-utils.js";
|
||||
import { info } from "../globals.js";
|
||||
@@ -29,12 +30,15 @@ import {
|
||||
formatUsageReportLines,
|
||||
loadProviderUsageSummary,
|
||||
} from "../infra/provider-usage.js";
|
||||
import { collectProvidersStatusIssues } from "../infra/providers-status-issues.js";
|
||||
import { peekSystemEvents } from "../infra/system-events.js";
|
||||
import { getTailnetHostname } from "../infra/tailscale.js";
|
||||
import {
|
||||
checkUpdateStatus,
|
||||
compareSemverStrings,
|
||||
type UpdateCheckResult,
|
||||
} from "../infra/update-check.js";
|
||||
import { runExec } from "../process/exec.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { renderTable } from "../terminal/table.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
@@ -533,7 +537,7 @@ export async function statusCommand(
|
||||
const scan = await withProgress(
|
||||
{
|
||||
label: "Scanning status…",
|
||||
total: 7,
|
||||
total: 9,
|
||||
enabled: opts.json !== true,
|
||||
},
|
||||
async (progress) => {
|
||||
@@ -542,6 +546,20 @@ export async function statusCommand(
|
||||
const osSummary = resolveOsSummary();
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Checking Tailscale…");
|
||||
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
||||
const tailscaleDns =
|
||||
tailscaleMode === "off"
|
||||
? null
|
||||
: await getTailnetHostname((cmd, args) =>
|
||||
runExec(cmd, args, { timeoutMs: 1200, maxBuffer: 200_000 }),
|
||||
).catch(() => null);
|
||||
const tailscaleHttpsUrl =
|
||||
tailscaleMode !== "off" && tailscaleDns
|
||||
? `https://${tailscaleDns}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}`
|
||||
: null;
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Checking for updates…");
|
||||
const updateTimeoutMs = opts.all ? 6500 : 2500;
|
||||
const update = await getUpdateCheckResult({
|
||||
@@ -580,6 +598,25 @@ export async function statusCommand(
|
||||
: null;
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Querying provider status…");
|
||||
const providersStatus = gatewayReachable
|
||||
? await callGateway<Record<string, unknown>>({
|
||||
method: "providers.status",
|
||||
params: {
|
||||
probe: false,
|
||||
timeoutMs: Math.min(8000, opts.timeoutMs ?? 10_000),
|
||||
},
|
||||
timeoutMs: Math.min(
|
||||
opts.all ? 5000 : 2500,
|
||||
opts.timeoutMs ?? 10_000,
|
||||
),
|
||||
}).catch(() => null)
|
||||
: null;
|
||||
const providerIssues = providersStatus
|
||||
? collectProvidersStatusIssues(providersStatus)
|
||||
: [];
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Summarizing providers…");
|
||||
const providers = await buildProvidersTable(cfg, {
|
||||
// Show token previews in regular status; keep `status --all` redacted.
|
||||
@@ -598,6 +635,9 @@ export async function statusCommand(
|
||||
return {
|
||||
cfg,
|
||||
osSummary,
|
||||
tailscaleMode,
|
||||
tailscaleDns,
|
||||
tailscaleHttpsUrl,
|
||||
update,
|
||||
gatewayConnection,
|
||||
remoteUrlMissing,
|
||||
@@ -605,6 +645,7 @@ export async function statusCommand(
|
||||
gatewayProbe,
|
||||
gatewayReachable,
|
||||
gatewaySelf,
|
||||
providerIssues,
|
||||
agentStatus,
|
||||
providers,
|
||||
summary,
|
||||
@@ -615,6 +656,9 @@ export async function statusCommand(
|
||||
const {
|
||||
cfg,
|
||||
osSummary,
|
||||
tailscaleMode,
|
||||
tailscaleDns,
|
||||
tailscaleHttpsUrl,
|
||||
update,
|
||||
gatewayConnection,
|
||||
remoteUrlMissing,
|
||||
@@ -622,6 +666,7 @@ export async function statusCommand(
|
||||
gatewayProbe,
|
||||
gatewayReachable,
|
||||
gatewaySelf,
|
||||
providerIssues,
|
||||
agentStatus,
|
||||
providers,
|
||||
summary,
|
||||
@@ -769,6 +814,15 @@ export async function statusCommand(
|
||||
const overviewRows = [
|
||||
{ Item: "Dashboard", Value: dashboard },
|
||||
{ Item: "OS", Value: `${osSummary.label} · node ${process.versions.node}` },
|
||||
{
|
||||
Item: "Tailscale",
|
||||
Value:
|
||||
tailscaleMode === "off"
|
||||
? muted("off")
|
||||
: tailscaleDns && tailscaleHttpsUrl
|
||||
? `${tailscaleMode} · ${tailscaleDns} · ${tailscaleHttpsUrl}`
|
||||
: warn(`${tailscaleMode} · magicdns unknown`),
|
||||
},
|
||||
{
|
||||
Item: "Update",
|
||||
Value: formatUpdateOneLiner(update).replace(/^Update:\s*/i, ""),
|
||||
@@ -801,6 +855,34 @@ export async function statusCommand(
|
||||
|
||||
runtime.log("");
|
||||
runtime.log(theme.heading("Providers"));
|
||||
const providerIssuesByProvider = (() => {
|
||||
const map = new Map<string, typeof providerIssues>();
|
||||
for (const issue of providerIssues) {
|
||||
const key = issue.provider;
|
||||
const list = map.get(key);
|
||||
if (list) list.push(issue);
|
||||
else map.set(key, [issue]);
|
||||
}
|
||||
return map;
|
||||
})();
|
||||
const providerKeyForLabel = (label: string) => {
|
||||
switch (label) {
|
||||
case "WhatsApp":
|
||||
return "whatsapp";
|
||||
case "Telegram":
|
||||
return "telegram";
|
||||
case "Discord":
|
||||
return "discord";
|
||||
case "Slack":
|
||||
return "slack";
|
||||
case "Signal":
|
||||
return "signal";
|
||||
case "iMessage":
|
||||
return "imessage";
|
||||
default:
|
||||
return label.toLowerCase();
|
||||
}
|
||||
};
|
||||
runtime.log(
|
||||
renderTable({
|
||||
width: tableWidth,
|
||||
@@ -810,19 +892,29 @@ export async function statusCommand(
|
||||
{ key: "State", header: "State", minWidth: 8 },
|
||||
{ key: "Detail", header: "Detail", flex: true, minWidth: 24 },
|
||||
],
|
||||
rows: providers.rows.map((row) => ({
|
||||
Provider: row.provider,
|
||||
Enabled: row.enabled ? ok("ON") : muted("OFF"),
|
||||
State:
|
||||
row.state === "ok"
|
||||
? ok("OK")
|
||||
: row.state === "warn"
|
||||
? warn("WARN")
|
||||
: row.state === "off"
|
||||
? muted("OFF")
|
||||
: theme.accentDim("SETUP"),
|
||||
Detail: row.detail,
|
||||
})),
|
||||
rows: providers.rows.map((row) => {
|
||||
const providerKey = providerKeyForLabel(row.provider);
|
||||
const issues = providerIssuesByProvider.get(providerKey) ?? [];
|
||||
const effectiveState =
|
||||
row.state === "off" ? "off" : issues.length > 0 ? "warn" : row.state;
|
||||
const issueSuffix =
|
||||
issues.length > 0
|
||||
? ` · ${warn(`gateway: ${shortenText(issues[0]?.message ?? "issue", 84)}`)}`
|
||||
: "";
|
||||
return {
|
||||
Provider: row.provider,
|
||||
Enabled: row.enabled ? ok("ON") : muted("OFF"),
|
||||
State:
|
||||
effectiveState === "ok"
|
||||
? ok("OK")
|
||||
: effectiveState === "warn"
|
||||
? warn("WARN")
|
||||
: effectiveState === "off"
|
||||
? muted("OFF")
|
||||
: theme.accentDim("SETUP"),
|
||||
Detail: `${row.detail}${issueSuffix}`,
|
||||
};
|
||||
}),
|
||||
}).trimEnd(),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user