fix(status): provider setup vs warn
This commit is contained in:
@@ -360,11 +360,14 @@ export async function statusAllCommand(
|
||||
const providerRows = providers.rows.map((row) => ({
|
||||
Provider: row.provider,
|
||||
Enabled: row.enabled ? ok("ON") : muted("OFF"),
|
||||
Configured: row.configured
|
||||
? ok("OK")
|
||||
: row.enabled
|
||||
? warn("WARN")
|
||||
: muted("OFF"),
|
||||
State:
|
||||
row.state === "ok"
|
||||
? ok("OK")
|
||||
: row.state === "warn"
|
||||
? warn("WARN")
|
||||
: row.state === "off"
|
||||
? muted("OFF")
|
||||
: theme.accentDim("SETUP"),
|
||||
Detail: row.detail,
|
||||
}));
|
||||
|
||||
@@ -373,7 +376,7 @@ export async function statusAllCommand(
|
||||
columns: [
|
||||
{ key: "Provider", header: "Provider", minWidth: 10 },
|
||||
{ key: "Enabled", header: "Enabled", minWidth: 7 },
|
||||
{ key: "Configured", header: "Configured", minWidth: 10 },
|
||||
{ key: "State", header: "State", minWidth: 8 },
|
||||
{ key: "Detail", header: "Detail", flex: true, minWidth: 28 },
|
||||
],
|
||||
rows: providerRows,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from "node:fs";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import {
|
||||
listDiscordAccountIds,
|
||||
@@ -35,10 +36,36 @@ import { formatAge } from "./format.js";
|
||||
export type ProviderRow = {
|
||||
provider: string;
|
||||
enabled: boolean;
|
||||
configured: boolean;
|
||||
state: "ok" | "setup" | "warn" | "off";
|
||||
detail: string;
|
||||
};
|
||||
|
||||
function summarizeSources(sources: Array<string | undefined>): {
|
||||
label: string;
|
||||
parts: string[];
|
||||
} {
|
||||
const counts = new Map<string, number>();
|
||||
for (const s of sources) {
|
||||
const key = s?.trim() ? s.trim() : "unknown";
|
||||
counts.set(key, (counts.get(key) ?? 0) + 1);
|
||||
}
|
||||
const parts = [...counts.entries()]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([key, n]) => `${key}${n > 1 ? `×${n}` : ""}`);
|
||||
const label = parts.length > 0 ? parts.join("+") : "unknown";
|
||||
return { label, parts };
|
||||
}
|
||||
|
||||
function existsSyncMaybe(p: string | undefined): boolean | null {
|
||||
const path = p?.trim() || "";
|
||||
if (!path) return null;
|
||||
try {
|
||||
return fs.existsSync(path);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
rows: ProviderRow[];
|
||||
details: Array<{
|
||||
@@ -67,11 +94,11 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
rows.push({
|
||||
provider: "WhatsApp",
|
||||
enabled: waEnabled,
|
||||
configured: waLinked,
|
||||
state: !waEnabled ? "off" : waLinked ? "ok" : "setup",
|
||||
detail: waEnabled
|
||||
? waLinked
|
||||
? `linked${waSelf ? ` ${waSelf}` : ""}${waAuthAgeMs ? ` · auth ${formatAge(waAuthAgeMs)}` : ""} · accounts ${waAccounts.length || 1}`
|
||||
: "not linked"
|
||||
: "not linked (run clawdbot login)"
|
||||
: "disabled",
|
||||
});
|
||||
if (waLinked) {
|
||||
@@ -96,7 +123,7 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
Account: account.name?.trim()
|
||||
? `${account.accountId} (${account.name.trim()})`
|
||||
: account.accountId,
|
||||
Status: account.enabled ? "OK" : "WARN",
|
||||
Status: account.enabled ? "OK" : "OFF",
|
||||
Notes: notes.join(" · "),
|
||||
};
|
||||
}),
|
||||
@@ -108,15 +135,43 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
const tgAccounts = listTelegramAccountIds(cfg).map((accountId) =>
|
||||
resolveTelegramAccount({ cfg, accountId }),
|
||||
);
|
||||
const tgConfigured = tgAccounts.some((a) => Boolean(a.token?.trim()));
|
||||
const tgEnabledAccounts = tgAccounts.filter((a) => a.enabled);
|
||||
const tgTokenAccounts = tgEnabledAccounts.filter((a) => a.token?.trim());
|
||||
const tgSources = summarizeSources(tgTokenAccounts.map((a) => a.tokenSource));
|
||||
const tgMissingFiles: string[] = [];
|
||||
const tgGlobalTokenFileExists = existsSyncMaybe(cfg.telegram?.tokenFile);
|
||||
if (
|
||||
tgEnabled &&
|
||||
cfg.telegram?.tokenFile?.trim() &&
|
||||
tgGlobalTokenFileExists === false
|
||||
) {
|
||||
tgMissingFiles.push("telegram.tokenFile");
|
||||
}
|
||||
for (const accountId of listTelegramAccountIds(cfg)) {
|
||||
const tokenFile =
|
||||
cfg.telegram?.accounts?.[accountId]?.tokenFile?.trim() || "";
|
||||
const ok = existsSyncMaybe(tokenFile);
|
||||
if (tgEnabled && tokenFile && ok === false) {
|
||||
tgMissingFiles.push(`telegram.accounts.${accountId}.tokenFile`);
|
||||
}
|
||||
}
|
||||
const tgMisconfigured = tgMissingFiles.length > 0;
|
||||
rows.push({
|
||||
provider: "Telegram",
|
||||
enabled: tgEnabled,
|
||||
configured: tgEnabled && tgConfigured,
|
||||
state: !tgEnabled
|
||||
? "off"
|
||||
: tgMisconfigured
|
||||
? "warn"
|
||||
: tgTokenAccounts.length > 0
|
||||
? "ok"
|
||||
: "setup",
|
||||
detail: tgEnabled
|
||||
? tgConfigured
|
||||
? `accounts ${tgAccounts.filter((a) => a.token?.trim()).length}`
|
||||
: "not configured"
|
||||
? tgMisconfigured
|
||||
? `token file missing (${tgMissingFiles[0]})`
|
||||
: tgTokenAccounts.length > 0
|
||||
? `bot token ${tgSources.label} · accounts ${tgTokenAccounts.length}/${tgEnabledAccounts.length || 1}`
|
||||
: "no bot token (TELEGRAM_BOT_TOKEN / telegram.botToken)"
|
||||
: "disabled",
|
||||
});
|
||||
|
||||
@@ -125,15 +180,17 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
const dcAccounts = listDiscordAccountIds(cfg).map((accountId) =>
|
||||
resolveDiscordAccount({ cfg, accountId }),
|
||||
);
|
||||
const dcConfigured = dcAccounts.some((a) => Boolean(a.token?.trim()));
|
||||
const dcEnabledAccounts = dcAccounts.filter((a) => a.enabled);
|
||||
const dcTokenAccounts = dcEnabledAccounts.filter((a) => a.token?.trim());
|
||||
const dcSources = summarizeSources(dcTokenAccounts.map((a) => a.tokenSource));
|
||||
rows.push({
|
||||
provider: "Discord",
|
||||
enabled: dcEnabled,
|
||||
configured: dcEnabled && dcConfigured,
|
||||
state: !dcEnabled ? "off" : dcTokenAccounts.length > 0 ? "ok" : "setup",
|
||||
detail: dcEnabled
|
||||
? dcConfigured
|
||||
? `accounts ${dcAccounts.filter((a) => a.token?.trim()).length}`
|
||||
: "not configured"
|
||||
? dcTokenAccounts.length > 0
|
||||
? `bot token ${dcSources.label} · accounts ${dcTokenAccounts.length}/${dcEnabledAccounts.length || 1}`
|
||||
: "no bot token (DISCORD_BOT_TOKEN / discord.token)"
|
||||
: "disabled",
|
||||
});
|
||||
|
||||
@@ -142,17 +199,42 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
const slAccounts = listSlackAccountIds(cfg).map((accountId) =>
|
||||
resolveSlackAccount({ cfg, accountId }),
|
||||
);
|
||||
const slConfigured = slAccounts.some(
|
||||
const slEnabledAccounts = slAccounts.filter((a) => a.enabled);
|
||||
const slReady = slEnabledAccounts.filter(
|
||||
(a) => Boolean(a.botToken?.trim()) && Boolean(a.appToken?.trim()),
|
||||
);
|
||||
const slPartial = slEnabledAccounts.filter(
|
||||
(a) =>
|
||||
(a.botToken?.trim() && !a.appToken?.trim()) ||
|
||||
(!a.botToken?.trim() && a.appToken?.trim()),
|
||||
);
|
||||
const slHasAnyToken = slEnabledAccounts.some(
|
||||
(a) => Boolean(a.botToken?.trim()) || Boolean(a.appToken?.trim()),
|
||||
);
|
||||
const slBotSources = summarizeSources(
|
||||
slReady.map((a) => a.botTokenSource ?? "none"),
|
||||
);
|
||||
const slAppSources = summarizeSources(
|
||||
slReady.map((a) => a.appTokenSource ?? "none"),
|
||||
);
|
||||
rows.push({
|
||||
provider: "Slack",
|
||||
enabled: slEnabled,
|
||||
configured: slEnabled && slConfigured,
|
||||
state: !slEnabled
|
||||
? "off"
|
||||
: slPartial.length > 0
|
||||
? "warn"
|
||||
: slReady.length > 0
|
||||
? "ok"
|
||||
: "setup",
|
||||
detail: slEnabled
|
||||
? slConfigured
|
||||
? `accounts ${slAccounts.filter((a) => a.botToken?.trim() && a.appToken?.trim()).length}`
|
||||
: "not configured"
|
||||
? slPartial.length > 0
|
||||
? `partial tokens (need bot+app) · accounts ${slPartial.length}`
|
||||
: slReady.length > 0
|
||||
? `tokens ok (bot ${slBotSources.label}, app ${slAppSources.label}) · accounts ${slReady.length}/${slEnabledAccounts.length || 1}`
|
||||
: slHasAnyToken
|
||||
? "tokens incomplete (need bot+app)"
|
||||
: "no tokens (SLACK_BOT_TOKEN + SLACK_APP_TOKEN)"
|
||||
: "disabled",
|
||||
});
|
||||
|
||||
@@ -161,15 +243,20 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
const siAccounts = listSignalAccountIds(cfg).map((accountId) =>
|
||||
resolveSignalAccount({ cfg, accountId }),
|
||||
);
|
||||
const siConfigured = siAccounts.some((a) => a.configured);
|
||||
const siEnabledAccounts = siAccounts.filter((a) => a.enabled);
|
||||
const siConfiguredAccounts = siEnabledAccounts.filter((a) => a.configured);
|
||||
rows.push({
|
||||
provider: "Signal",
|
||||
enabled: siEnabled,
|
||||
configured: siEnabled && siConfigured,
|
||||
state: !siEnabled
|
||||
? "off"
|
||||
: siConfiguredAccounts.length > 0
|
||||
? "ok"
|
||||
: "setup",
|
||||
detail: siEnabled
|
||||
? siConfigured
|
||||
? `accounts ${siAccounts.filter((a) => a.configured).length}`
|
||||
: "not configured"
|
||||
? siConfiguredAccounts.length > 0
|
||||
? `configured · accounts ${siConfiguredAccounts.length}/${siEnabledAccounts.length || 1}`
|
||||
: "default config (no overrides)"
|
||||
: "disabled",
|
||||
});
|
||||
|
||||
@@ -178,29 +265,55 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||
const imAccounts = listIMessageAccountIds(cfg).map((accountId) =>
|
||||
resolveIMessageAccount({ cfg, accountId }),
|
||||
);
|
||||
const imConfigured = imAccounts.some((a) => a.configured);
|
||||
const imEnabledAccounts = imAccounts.filter((a) => a.enabled);
|
||||
const imConfiguredAccounts = imEnabledAccounts.filter((a) => a.configured);
|
||||
rows.push({
|
||||
provider: "iMessage",
|
||||
enabled: imEnabled,
|
||||
configured: imEnabled && imConfigured,
|
||||
state: !imEnabled
|
||||
? "off"
|
||||
: imConfiguredAccounts.length > 0
|
||||
? "ok"
|
||||
: "setup",
|
||||
detail: imEnabled
|
||||
? imConfigured
|
||||
? `accounts ${imAccounts.length}`
|
||||
: "not configured"
|
||||
? imConfiguredAccounts.length > 0
|
||||
? `configured · accounts ${imConfiguredAccounts.length}/${imEnabledAccounts.length || 1}`
|
||||
: "default config (no overrides)"
|
||||
: "disabled",
|
||||
});
|
||||
|
||||
// MS Teams
|
||||
const msEnabled = cfg.msteams?.enabled !== false;
|
||||
const msConfigured = Boolean(resolveMSTeamsCredentials(cfg.msteams));
|
||||
const msCreds = resolveMSTeamsCredentials(cfg.msteams);
|
||||
const msAppId =
|
||||
cfg.msteams?.appId?.trim() || process.env.MSTEAMS_APP_ID?.trim();
|
||||
const msAppPassword =
|
||||
cfg.msteams?.appPassword?.trim() ||
|
||||
process.env.MSTEAMS_APP_PASSWORD?.trim();
|
||||
const msTenantId =
|
||||
cfg.msteams?.tenantId?.trim() || process.env.MSTEAMS_TENANT_ID?.trim();
|
||||
const msMissing = [
|
||||
!msAppId ? "appId" : null,
|
||||
!msAppPassword ? "appPassword" : null,
|
||||
!msTenantId ? "tenantId" : null,
|
||||
].filter(Boolean) as string[];
|
||||
const msAnyPresent = Boolean(msAppId || msAppPassword || msTenantId);
|
||||
rows.push({
|
||||
provider: "MS Teams",
|
||||
enabled: msEnabled,
|
||||
configured: msEnabled && msConfigured,
|
||||
state: !msEnabled
|
||||
? "off"
|
||||
: msCreds
|
||||
? "ok"
|
||||
: msAnyPresent
|
||||
? "warn"
|
||||
: "setup",
|
||||
detail: msEnabled
|
||||
? msConfigured
|
||||
? "credentials present"
|
||||
: "not configured"
|
||||
? msCreds
|
||||
? "credentials set"
|
||||
: msAnyPresent
|
||||
? `credentials incomplete (missing ${msMissing.join(", ")})`
|
||||
: "no credentials (MSTEAMS_APP_ID / _PASSWORD / _TENANT_ID)"
|
||||
: "disabled",
|
||||
});
|
||||
|
||||
|
||||
@@ -796,17 +796,20 @@ export async function statusCommand(
|
||||
columns: [
|
||||
{ key: "Provider", header: "Provider", minWidth: 10 },
|
||||
{ key: "Enabled", header: "Enabled", minWidth: 7 },
|
||||
{ key: "Configured", header: "Configured", minWidth: 10 },
|
||||
{ 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"),
|
||||
Configured: row.configured
|
||||
? ok("OK")
|
||||
: row.enabled
|
||||
? warn("WARN")
|
||||
: muted("OFF"),
|
||||
State:
|
||||
row.state === "ok"
|
||||
? ok("OK")
|
||||
: row.state === "warn"
|
||||
? warn("WARN")
|
||||
: row.state === "off"
|
||||
? muted("OFF")
|
||||
: theme.accentDim("SETUP"),
|
||||
Detail: row.detail,
|
||||
})),
|
||||
}).trimEnd(),
|
||||
|
||||
Reference in New Issue
Block a user