fix(status): provider setup vs warn
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026.1.10-2
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- CLI/Status: provider table uses `SETUP` for missing credentials (not `WARN`); richer per-provider detail (token sources, missing keys).
|
||||||
|
|
||||||
## 2026.1.10-1
|
## 2026.1.10-1
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawdbot",
|
"name": "clawdbot",
|
||||||
"version": "2026.1.10-1",
|
"version": "2026.1.10-2",
|
||||||
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -360,11 +360,14 @@ export async function statusAllCommand(
|
|||||||
const providerRows = providers.rows.map((row) => ({
|
const providerRows = providers.rows.map((row) => ({
|
||||||
Provider: row.provider,
|
Provider: row.provider,
|
||||||
Enabled: row.enabled ? ok("ON") : muted("OFF"),
|
Enabled: row.enabled ? ok("ON") : muted("OFF"),
|
||||||
Configured: row.configured
|
State:
|
||||||
? ok("OK")
|
row.state === "ok"
|
||||||
: row.enabled
|
? ok("OK")
|
||||||
? warn("WARN")
|
: row.state === "warn"
|
||||||
: muted("OFF"),
|
? warn("WARN")
|
||||||
|
: row.state === "off"
|
||||||
|
? muted("OFF")
|
||||||
|
: theme.accentDim("SETUP"),
|
||||||
Detail: row.detail,
|
Detail: row.detail,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -373,7 +376,7 @@ export async function statusAllCommand(
|
|||||||
columns: [
|
columns: [
|
||||||
{ key: "Provider", header: "Provider", minWidth: 10 },
|
{ key: "Provider", header: "Provider", minWidth: 10 },
|
||||||
{ key: "Enabled", header: "Enabled", minWidth: 7 },
|
{ 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 },
|
{ key: "Detail", header: "Detail", flex: true, minWidth: 28 },
|
||||||
],
|
],
|
||||||
rows: providerRows,
|
rows: providerRows,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
import {
|
import {
|
||||||
listDiscordAccountIds,
|
listDiscordAccountIds,
|
||||||
@@ -35,10 +36,36 @@ import { formatAge } from "./format.js";
|
|||||||
export type ProviderRow = {
|
export type ProviderRow = {
|
||||||
provider: string;
|
provider: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
configured: boolean;
|
state: "ok" | "setup" | "warn" | "off";
|
||||||
detail: string;
|
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<{
|
export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
||||||
rows: ProviderRow[];
|
rows: ProviderRow[];
|
||||||
details: Array<{
|
details: Array<{
|
||||||
@@ -67,11 +94,11 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
|||||||
rows.push({
|
rows.push({
|
||||||
provider: "WhatsApp",
|
provider: "WhatsApp",
|
||||||
enabled: waEnabled,
|
enabled: waEnabled,
|
||||||
configured: waLinked,
|
state: !waEnabled ? "off" : waLinked ? "ok" : "setup",
|
||||||
detail: waEnabled
|
detail: waEnabled
|
||||||
? waLinked
|
? waLinked
|
||||||
? `linked${waSelf ? ` ${waSelf}` : ""}${waAuthAgeMs ? ` · auth ${formatAge(waAuthAgeMs)}` : ""} · accounts ${waAccounts.length || 1}`
|
? `linked${waSelf ? ` ${waSelf}` : ""}${waAuthAgeMs ? ` · auth ${formatAge(waAuthAgeMs)}` : ""} · accounts ${waAccounts.length || 1}`
|
||||||
: "not linked"
|
: "not linked (run clawdbot login)"
|
||||||
: "disabled",
|
: "disabled",
|
||||||
});
|
});
|
||||||
if (waLinked) {
|
if (waLinked) {
|
||||||
@@ -96,7 +123,7 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
|||||||
Account: account.name?.trim()
|
Account: account.name?.trim()
|
||||||
? `${account.accountId} (${account.name.trim()})`
|
? `${account.accountId} (${account.name.trim()})`
|
||||||
: account.accountId,
|
: account.accountId,
|
||||||
Status: account.enabled ? "OK" : "WARN",
|
Status: account.enabled ? "OK" : "OFF",
|
||||||
Notes: notes.join(" · "),
|
Notes: notes.join(" · "),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -108,15 +135,43 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
|||||||
const tgAccounts = listTelegramAccountIds(cfg).map((accountId) =>
|
const tgAccounts = listTelegramAccountIds(cfg).map((accountId) =>
|
||||||
resolveTelegramAccount({ cfg, 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({
|
rows.push({
|
||||||
provider: "Telegram",
|
provider: "Telegram",
|
||||||
enabled: tgEnabled,
|
enabled: tgEnabled,
|
||||||
configured: tgEnabled && tgConfigured,
|
state: !tgEnabled
|
||||||
|
? "off"
|
||||||
|
: tgMisconfigured
|
||||||
|
? "warn"
|
||||||
|
: tgTokenAccounts.length > 0
|
||||||
|
? "ok"
|
||||||
|
: "setup",
|
||||||
detail: tgEnabled
|
detail: tgEnabled
|
||||||
? tgConfigured
|
? tgMisconfigured
|
||||||
? `accounts ${tgAccounts.filter((a) => a.token?.trim()).length}`
|
? `token file missing (${tgMissingFiles[0]})`
|
||||||
: "not configured"
|
: tgTokenAccounts.length > 0
|
||||||
|
? `bot token ${tgSources.label} · accounts ${tgTokenAccounts.length}/${tgEnabledAccounts.length || 1}`
|
||||||
|
: "no bot token (TELEGRAM_BOT_TOKEN / telegram.botToken)"
|
||||||
: "disabled",
|
: "disabled",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,15 +180,17 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
|||||||
const dcAccounts = listDiscordAccountIds(cfg).map((accountId) =>
|
const dcAccounts = listDiscordAccountIds(cfg).map((accountId) =>
|
||||||
resolveDiscordAccount({ cfg, 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({
|
rows.push({
|
||||||
provider: "Discord",
|
provider: "Discord",
|
||||||
enabled: dcEnabled,
|
enabled: dcEnabled,
|
||||||
configured: dcEnabled && dcConfigured,
|
state: !dcEnabled ? "off" : dcTokenAccounts.length > 0 ? "ok" : "setup",
|
||||||
detail: dcEnabled
|
detail: dcEnabled
|
||||||
? dcConfigured
|
? dcTokenAccounts.length > 0
|
||||||
? `accounts ${dcAccounts.filter((a) => a.token?.trim()).length}`
|
? `bot token ${dcSources.label} · accounts ${dcTokenAccounts.length}/${dcEnabledAccounts.length || 1}`
|
||||||
: "not configured"
|
: "no bot token (DISCORD_BOT_TOKEN / discord.token)"
|
||||||
: "disabled",
|
: "disabled",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,17 +199,42 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
|||||||
const slAccounts = listSlackAccountIds(cfg).map((accountId) =>
|
const slAccounts = listSlackAccountIds(cfg).map((accountId) =>
|
||||||
resolveSlackAccount({ cfg, 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()),
|
(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({
|
rows.push({
|
||||||
provider: "Slack",
|
provider: "Slack",
|
||||||
enabled: slEnabled,
|
enabled: slEnabled,
|
||||||
configured: slEnabled && slConfigured,
|
state: !slEnabled
|
||||||
|
? "off"
|
||||||
|
: slPartial.length > 0
|
||||||
|
? "warn"
|
||||||
|
: slReady.length > 0
|
||||||
|
? "ok"
|
||||||
|
: "setup",
|
||||||
detail: slEnabled
|
detail: slEnabled
|
||||||
? slConfigured
|
? slPartial.length > 0
|
||||||
? `accounts ${slAccounts.filter((a) => a.botToken?.trim() && a.appToken?.trim()).length}`
|
? `partial tokens (need bot+app) · accounts ${slPartial.length}`
|
||||||
: "not configured"
|
: 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",
|
: "disabled",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,15 +243,20 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
|||||||
const siAccounts = listSignalAccountIds(cfg).map((accountId) =>
|
const siAccounts = listSignalAccountIds(cfg).map((accountId) =>
|
||||||
resolveSignalAccount({ cfg, 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({
|
rows.push({
|
||||||
provider: "Signal",
|
provider: "Signal",
|
||||||
enabled: siEnabled,
|
enabled: siEnabled,
|
||||||
configured: siEnabled && siConfigured,
|
state: !siEnabled
|
||||||
|
? "off"
|
||||||
|
: siConfiguredAccounts.length > 0
|
||||||
|
? "ok"
|
||||||
|
: "setup",
|
||||||
detail: siEnabled
|
detail: siEnabled
|
||||||
? siConfigured
|
? siConfiguredAccounts.length > 0
|
||||||
? `accounts ${siAccounts.filter((a) => a.configured).length}`
|
? `configured · accounts ${siConfiguredAccounts.length}/${siEnabledAccounts.length || 1}`
|
||||||
: "not configured"
|
: "default config (no overrides)"
|
||||||
: "disabled",
|
: "disabled",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,29 +265,55 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
|
|||||||
const imAccounts = listIMessageAccountIds(cfg).map((accountId) =>
|
const imAccounts = listIMessageAccountIds(cfg).map((accountId) =>
|
||||||
resolveIMessageAccount({ cfg, 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({
|
rows.push({
|
||||||
provider: "iMessage",
|
provider: "iMessage",
|
||||||
enabled: imEnabled,
|
enabled: imEnabled,
|
||||||
configured: imEnabled && imConfigured,
|
state: !imEnabled
|
||||||
|
? "off"
|
||||||
|
: imConfiguredAccounts.length > 0
|
||||||
|
? "ok"
|
||||||
|
: "setup",
|
||||||
detail: imEnabled
|
detail: imEnabled
|
||||||
? imConfigured
|
? imConfiguredAccounts.length > 0
|
||||||
? `accounts ${imAccounts.length}`
|
? `configured · accounts ${imConfiguredAccounts.length}/${imEnabledAccounts.length || 1}`
|
||||||
: "not configured"
|
: "default config (no overrides)"
|
||||||
: "disabled",
|
: "disabled",
|
||||||
});
|
});
|
||||||
|
|
||||||
// MS Teams
|
// MS Teams
|
||||||
const msEnabled = cfg.msteams?.enabled !== false;
|
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({
|
rows.push({
|
||||||
provider: "MS Teams",
|
provider: "MS Teams",
|
||||||
enabled: msEnabled,
|
enabled: msEnabled,
|
||||||
configured: msEnabled && msConfigured,
|
state: !msEnabled
|
||||||
|
? "off"
|
||||||
|
: msCreds
|
||||||
|
? "ok"
|
||||||
|
: msAnyPresent
|
||||||
|
? "warn"
|
||||||
|
: "setup",
|
||||||
detail: msEnabled
|
detail: msEnabled
|
||||||
? msConfigured
|
? msCreds
|
||||||
? "credentials present"
|
? "credentials set"
|
||||||
: "not configured"
|
: msAnyPresent
|
||||||
|
? `credentials incomplete (missing ${msMissing.join(", ")})`
|
||||||
|
: "no credentials (MSTEAMS_APP_ID / _PASSWORD / _TENANT_ID)"
|
||||||
: "disabled",
|
: "disabled",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -796,17 +796,20 @@ export async function statusCommand(
|
|||||||
columns: [
|
columns: [
|
||||||
{ key: "Provider", header: "Provider", minWidth: 10 },
|
{ key: "Provider", header: "Provider", minWidth: 10 },
|
||||||
{ key: "Enabled", header: "Enabled", minWidth: 7 },
|
{ 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 },
|
{ key: "Detail", header: "Detail", flex: true, minWidth: 24 },
|
||||||
],
|
],
|
||||||
rows: providers.rows.map((row) => ({
|
rows: providers.rows.map((row) => ({
|
||||||
Provider: row.provider,
|
Provider: row.provider,
|
||||||
Enabled: row.enabled ? ok("ON") : muted("OFF"),
|
Enabled: row.enabled ? ok("ON") : muted("OFF"),
|
||||||
Configured: row.configured
|
State:
|
||||||
? ok("OK")
|
row.state === "ok"
|
||||||
: row.enabled
|
? ok("OK")
|
||||||
? warn("WARN")
|
: row.state === "warn"
|
||||||
: muted("OFF"),
|
? warn("WARN")
|
||||||
|
: row.state === "off"
|
||||||
|
? muted("OFF")
|
||||||
|
: theme.accentDim("SETUP"),
|
||||||
Detail: row.detail,
|
Detail: row.detail,
|
||||||
})),
|
})),
|
||||||
}).trimEnd(),
|
}).trimEnd(),
|
||||||
|
|||||||
Reference in New Issue
Block a user