fix(status): show token previews

This commit is contained in:
Peter Steinberger
2026-01-11 01:11:46 +01:00
parent 57dafec0ec
commit 318f59ec3e
5 changed files with 60 additions and 8 deletions

View File

@@ -1,5 +1,10 @@
# Changelog # Changelog
## 2026.1.10-4
### Fixes
- CLI/Status: show token previews (first/last chars) in `clawdbot status` provider details; keep `status --all` redacted (hash+length).
## 2026.1.10-3 ## 2026.1.10-3
### Fixes ### Fixes

View File

@@ -1,6 +1,6 @@
{ {
"name": "clawdbot", "name": "clawdbot",
"version": "2026.1.10-3", "version": "2026.1.10-4",
"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",

View File

@@ -138,7 +138,7 @@ export async function statusAllCommand(
progress.setLabel("Scanning agents…"); progress.setLabel("Scanning agents…");
const agentStatus = await getAgentLocalStatuses(cfg); const agentStatus = await getAgentLocalStatuses(cfg);
progress.setLabel("Summarizing providers…"); progress.setLabel("Summarizing providers…");
const providers = await buildProvidersTable(cfg); const providers = await buildProvidersTable(cfg, { showSecrets: false });
const connectionDetailsForReport = (() => { const connectionDetailsForReport = (() => {
if (!remoteUrlMissing) return connection.message; if (!remoteUrlMissing) return connection.message;

View File

@@ -1,3 +1,4 @@
import crypto from "node:crypto";
import fs from "node:fs"; import fs from "node:fs";
import type { ClawdbotConfig } from "../../config/config.js"; import type { ClawdbotConfig } from "../../config/config.js";
import { import {
@@ -66,7 +67,28 @@ function existsSyncMaybe(p: string | undefined): boolean | null {
} }
} }
export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{ function sha256HexPrefix(value: string, len = 8): string {
return crypto.createHash("sha256").update(value).digest("hex").slice(0, len);
}
function formatTokenHint(
token: string,
opts: { showSecrets: boolean },
): string {
const t = token.trim();
if (!t) return "empty";
if (!opts.showSecrets)
return `sha256:${sha256HexPrefix(t)} · len ${t.length}`;
const head = t.slice(0, 4);
const tail = t.slice(-4);
if (t.length <= 10) return `${t} · len ${t.length}`;
return `${head}${tail} · len ${t.length}`;
}
export async function buildProvidersTable(
cfg: ClawdbotConfig,
opts?: { showSecrets?: boolean },
): Promise<{
rows: ProviderRow[]; rows: ProviderRow[];
details: Array<{ details: Array<{
title: string; title: string;
@@ -74,6 +96,7 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
rows: Array<Record<string, string>>; rows: Array<Record<string, string>>;
}>; }>;
}> { }> {
const showSecrets = opts?.showSecrets === true;
const rows: ProviderRow[] = []; const rows: ProviderRow[] = [];
const details: Array<{ const details: Array<{
title: string; title: string;
@@ -138,6 +161,10 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
const tgEnabledAccounts = tgAccounts.filter((a) => a.enabled); const tgEnabledAccounts = tgAccounts.filter((a) => a.enabled);
const tgTokenAccounts = tgEnabledAccounts.filter((a) => a.token?.trim()); const tgTokenAccounts = tgEnabledAccounts.filter((a) => a.token?.trim());
const tgSources = summarizeSources(tgTokenAccounts.map((a) => a.tokenSource)); const tgSources = summarizeSources(tgTokenAccounts.map((a) => a.tokenSource));
const tgSampleToken = tgTokenAccounts[0]?.token?.trim() || "";
const tgTokenHint = tgSampleToken
? formatTokenHint(tgSampleToken, { showSecrets })
: "";
const tgMissingFiles: string[] = []; const tgMissingFiles: string[] = [];
const tgGlobalTokenFileExists = existsSyncMaybe(cfg.telegram?.tokenFile); const tgGlobalTokenFileExists = existsSyncMaybe(cfg.telegram?.tokenFile);
if ( if (
@@ -170,7 +197,7 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
? tgMisconfigured ? tgMisconfigured
? `token file missing (${tgMissingFiles[0]})` ? `token file missing (${tgMissingFiles[0]})`
: tgTokenAccounts.length > 0 : tgTokenAccounts.length > 0
? `bot token ${tgSources.label} · accounts ${tgTokenAccounts.length}/${tgEnabledAccounts.length || 1}` ? `bot token ${tgSources.label}${tgTokenHint ? ` (${tgTokenHint})` : ""} · accounts ${tgTokenAccounts.length}/${tgEnabledAccounts.length || 1}`
: "no bot token (TELEGRAM_BOT_TOKEN / telegram.botToken)" : "no bot token (TELEGRAM_BOT_TOKEN / telegram.botToken)"
: "disabled", : "disabled",
}); });
@@ -183,13 +210,17 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
const dcEnabledAccounts = dcAccounts.filter((a) => a.enabled); const dcEnabledAccounts = dcAccounts.filter((a) => a.enabled);
const dcTokenAccounts = dcEnabledAccounts.filter((a) => a.token?.trim()); const dcTokenAccounts = dcEnabledAccounts.filter((a) => a.token?.trim());
const dcSources = summarizeSources(dcTokenAccounts.map((a) => a.tokenSource)); const dcSources = summarizeSources(dcTokenAccounts.map((a) => a.tokenSource));
const dcSampleToken = dcTokenAccounts[0]?.token?.trim() || "";
const dcTokenHint = dcSampleToken
? formatTokenHint(dcSampleToken, { showSecrets })
: "";
rows.push({ rows.push({
provider: "Discord", provider: "Discord",
enabled: dcEnabled, enabled: dcEnabled,
state: !dcEnabled ? "off" : dcTokenAccounts.length > 0 ? "ok" : "setup", state: !dcEnabled ? "off" : dcTokenAccounts.length > 0 ? "ok" : "setup",
detail: dcEnabled detail: dcEnabled
? dcTokenAccounts.length > 0 ? dcTokenAccounts.length > 0
? `bot token ${dcSources.label} · accounts ${dcTokenAccounts.length}/${dcEnabledAccounts.length || 1}` ? `bot token ${dcSources.label}${dcTokenHint ? ` (${dcTokenHint})` : ""} · accounts ${dcTokenAccounts.length}/${dcEnabledAccounts.length || 1}`
: "no bot token (DISCORD_BOT_TOKEN / discord.token)" : "no bot token (DISCORD_BOT_TOKEN / discord.token)"
: "disabled", : "disabled",
}); });
@@ -217,6 +248,15 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
const slAppSources = summarizeSources( const slAppSources = summarizeSources(
slReady.map((a) => a.appTokenSource ?? "none"), slReady.map((a) => a.appTokenSource ?? "none"),
); );
const slSample = slReady[0] ?? null;
const slBotHint =
slSample?.botToken?.trim() && slSample.botTokenSource !== "none"
? formatTokenHint(slSample.botToken, { showSecrets })
: "";
const slAppHint =
slSample?.appToken?.trim() && slSample.appTokenSource !== "none"
? formatTokenHint(slSample.appToken, { showSecrets })
: "";
rows.push({ rows.push({
provider: "Slack", provider: "Slack",
enabled: slEnabled, enabled: slEnabled,
@@ -231,7 +271,7 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
? slPartial.length > 0 ? slPartial.length > 0
? `partial tokens (need bot+app) · accounts ${slPartial.length}` ? `partial tokens (need bot+app) · accounts ${slPartial.length}`
: slReady.length > 0 : slReady.length > 0
? `tokens ok (bot ${slBotSources.label}, app ${slAppSources.label}) · accounts ${slReady.length}/${slEnabledAccounts.length || 1}` ? `tokens ok (bot ${slBotSources.label}${slBotHint ? ` ${slBotHint}` : ""}, app ${slAppSources.label}${slAppHint ? ` ${slAppHint}` : ""}) · accounts ${slReady.length}/${slEnabledAccounts.length || 1}`
: slHasAnyToken : slHasAnyToken
? "tokens incomplete (need bot+app)" ? "tokens incomplete (need bot+app)"
: "no tokens (SLACK_BOT_TOKEN + SLACK_APP_TOKEN)" : "no tokens (SLACK_BOT_TOKEN + SLACK_APP_TOKEN)"
@@ -298,6 +338,9 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
!msTenantId ? "tenantId" : null, !msTenantId ? "tenantId" : null,
].filter(Boolean) as string[]; ].filter(Boolean) as string[];
const msAnyPresent = Boolean(msAppId || msAppPassword || msTenantId); const msAnyPresent = Boolean(msAppId || msAppPassword || msTenantId);
const msPasswordHint = msAppPassword
? formatTokenHint(msAppPassword, { showSecrets })
: "";
rows.push({ rows.push({
provider: "MS Teams", provider: "MS Teams",
enabled: msEnabled, enabled: msEnabled,
@@ -310,7 +353,7 @@ export async function buildProvidersTable(cfg: ClawdbotConfig): Promise<{
: "setup", : "setup",
detail: msEnabled detail: msEnabled
? msCreds ? msCreds
? "credentials set" ? `credentials set${msPasswordHint ? ` (password ${msPasswordHint})` : ""}`
: msAnyPresent : msAnyPresent
? `credentials incomplete (missing ${msMissing.join(", ")})` ? `credentials incomplete (missing ${msMissing.join(", ")})`
: "no credentials (MSTEAMS_APP_ID / _PASSWORD / _TENANT_ID)" : "no credentials (MSTEAMS_APP_ID / _PASSWORD / _TENANT_ID)"

View File

@@ -578,7 +578,11 @@ export async function statusCommand(
progress.tick(); progress.tick();
progress.setLabel("Summarizing providers…"); progress.setLabel("Summarizing providers…");
const providers = await buildProvidersTable(cfg); const providers = await buildProvidersTable(cfg, {
// Show token previews in regular status; keep `status --all` redacted.
// Set `CLAWDBOT_SHOW_SECRETS=0` to force redaction.
showSecrets: process.env.CLAWDBOT_SHOW_SECRETS?.trim() !== "0",
});
progress.tick(); progress.tick();
progress.setLabel("Reading sessions…"); progress.setLabel("Reading sessions…");