import fs from "node:fs/promises"; import path from "node:path"; import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; import { lookupContextTokens } from "../agents/context.js"; import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER, } from "../agents/defaults.js"; import { resolveConfiguredModelRef } from "../agents/model-selection.js"; import { withProgress } from "../cli/progress.js"; import { loadConfig, resolveGatewayPort } from "../config/config.js"; import { loadSessionStore, resolveMainSessionKey, resolveStorePath, type SessionEntry, } from "../config/sessions.js"; import { resolveGatewayService } from "../daemon/service.js"; import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js"; import { probeGateway } from "../gateway/probe.js"; import { listAgentsForGateway } from "../gateway/session-utils.js"; import { info } from "../globals.js"; import { resolveClawdbotPackageRoot } from "../infra/clawdbot-root.js"; import { resolveOsSummary } from "../infra/os-summary.js"; import { buildProviderSummary } from "../infra/provider-summary.js"; import { formatUsageReportLines, loadProviderUsageSummary, } from "../infra/provider-usage.js"; import { peekSystemEvents } from "../infra/system-events.js"; import { checkUpdateStatus, compareSemverStrings, type UpdateCheckResult, } from "../infra/update-check.js"; import type { RuntimeEnv } from "../runtime.js"; import { renderTable } from "../terminal/table.js"; import { theme } from "../terminal/theme.js"; import { VERSION } from "../version.js"; import { resolveWhatsAppAccount } from "../web/accounts.js"; import { resolveHeartbeatSeconds } from "../web/reconnect.js"; import { getWebAuthAgeMs, webAuthExists } from "../web/session.js"; import type { HealthSummary } from "./health.js"; import { resolveControlUiLinks } from "./onboard-helpers.js"; import { formatGatewayAuthUsed } from "./status-all/format.js"; import { buildProvidersTable } from "./status-all/providers.js"; import { statusAllCommand } from "./status-all.js"; export type SessionStatus = { key: string; kind: "direct" | "group" | "global" | "unknown"; sessionId?: string; updatedAt: number | null; age: number | null; thinkingLevel?: string; verboseLevel?: string; reasoningLevel?: string; elevatedLevel?: string; systemSent?: boolean; abortedLastRun?: boolean; inputTokens?: number; outputTokens?: number; totalTokens: number | null; remainingTokens: number | null; percentUsed: number | null; model: string | null; contextTokens: number | null; flags: string[]; }; export type StatusSummary = { web: { linked: boolean; authAgeMs: number | null }; heartbeatSeconds: number; providerSummary: string[]; queuedSystemEvents: string[]; sessions: { path: string; count: number; defaults: { model: string | null; contextTokens: number | null }; recent: SessionStatus[]; }; }; export async function getStatusSummary(): Promise { const cfg = loadConfig(); const account = resolveWhatsAppAccount({ cfg }); const linked = await webAuthExists(account.authDir); const authAgeMs = getWebAuthAgeMs(account.authDir); const heartbeatSeconds = resolveHeartbeatSeconds(cfg, undefined); const providerSummary = await buildProviderSummary(cfg, { colorize: true, includeAllowFrom: true, }); const mainSessionKey = resolveMainSessionKey(cfg); const queuedSystemEvents = peekSystemEvents(mainSessionKey); const resolved = resolveConfiguredModelRef({ cfg, defaultProvider: DEFAULT_PROVIDER, defaultModel: DEFAULT_MODEL, }); const configModel = resolved.model ?? DEFAULT_MODEL; const configContextTokens = cfg.agents?.defaults?.contextTokens ?? lookupContextTokens(configModel) ?? DEFAULT_CONTEXT_TOKENS; const storePath = resolveStorePath(cfg.session?.store); const store = loadSessionStore(storePath); const now = Date.now(); const sessions = Object.entries(store) .filter(([key]) => key !== "global" && key !== "unknown") .map(([key, entry]) => { const updatedAt = entry?.updatedAt ?? null; const age = updatedAt ? now - updatedAt : null; const model = entry?.model ?? configModel ?? null; const contextTokens = entry?.contextTokens ?? lookupContextTokens(model) ?? configContextTokens ?? null; const input = entry?.inputTokens ?? 0; const output = entry?.outputTokens ?? 0; const total = entry?.totalTokens ?? input + output; const remaining = contextTokens != null ? Math.max(0, contextTokens - total) : null; const pct = contextTokens && contextTokens > 0 ? Math.min(999, Math.round((total / contextTokens) * 100)) : null; return { key, kind: classifyKey(key, entry), sessionId: entry?.sessionId, updatedAt, age, thinkingLevel: entry?.thinkingLevel, verboseLevel: entry?.verboseLevel, reasoningLevel: entry?.reasoningLevel, elevatedLevel: entry?.elevatedLevel, systemSent: entry?.systemSent, abortedLastRun: entry?.abortedLastRun, inputTokens: entry?.inputTokens, outputTokens: entry?.outputTokens, totalTokens: total ?? null, remainingTokens: remaining, percentUsed: pct, model, contextTokens, flags: buildFlags(entry), } satisfies SessionStatus; }) .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)); const recent = sessions.slice(0, 5); return { web: { linked, authAgeMs }, heartbeatSeconds, providerSummary, queuedSystemEvents, sessions: { path: storePath, count: sessions.length, defaults: { model: configModel ?? null, contextTokens: configContextTokens ?? null, }, recent, }, }; } const formatKTokens = (value: number) => `${(value / 1000).toFixed(value >= 10_000 ? 0 : 1)}k`; const formatAge = (ms: number | null | undefined) => { if (!ms || ms < 0) return "unknown"; const minutes = Math.round(ms / 60_000); if (minutes < 1) return "just now"; if (minutes < 60) return `${minutes}m ago`; const hours = Math.round(minutes / 60); if (hours < 48) return `${hours}h ago`; const days = Math.round(hours / 24); return `${days}d ago`; }; const formatDuration = (ms: number | null | undefined) => { if (ms == null || !Number.isFinite(ms)) return "unknown"; if (ms < 1000) return `${Math.round(ms)}ms`; return `${(ms / 1000).toFixed(1)}s`; }; const shortenText = (value: string, maxLen: number) => { const chars = Array.from(value); if (chars.length <= maxLen) return value; return `${chars.slice(0, Math.max(0, maxLen - 1)).join("")}…`; }; const formatTokensCompact = ( sess: Pick, ) => { const used = sess.totalTokens ?? 0; const ctx = sess.contextTokens; if (!ctx) return `${formatKTokens(used)} used`; const pctLabel = sess.percentUsed != null ? `${sess.percentUsed}%` : "?%"; return `${formatKTokens(used)}/${formatKTokens(ctx)} (${pctLabel})`; }; const classifyKey = ( key: string, entry?: SessionEntry, ): SessionStatus["kind"] => { if (key === "global") return "global"; if (key === "unknown") return "unknown"; if (entry?.chatType === "group" || entry?.chatType === "room") return "group"; if ( key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:") ) { return "group"; } return "direct"; }; const formatDaemonRuntimeShort = (runtime?: { status?: string; pid?: number; state?: string; detail?: string; missingUnit?: boolean; }) => { if (!runtime) return null; const status = runtime.status ?? "unknown"; const details: string[] = []; if (runtime.pid) details.push(`pid ${runtime.pid}`); if (runtime.state && runtime.state.toLowerCase() !== status) { details.push(`state ${runtime.state}`); } const detail = runtime.detail?.replace(/\s+/g, " ").trim() || ""; const noisyLaunchctlDetail = runtime.missingUnit === true && detail.toLowerCase().includes("could not find service"); if (detail && !noisyLaunchctlDetail) details.push(detail); return details.length > 0 ? `${status} (${details.join(", ")})` : status; }; async function getDaemonStatusSummary(): Promise<{ label: string; installed: boolean | null; loadedText: string; runtimeShort: string | null; }> { try { const service = resolveGatewayService(); const [loaded, runtime, command] = await Promise.all([ service .isLoaded({ profile: process.env.CLAWDBOT_PROFILE }) .catch(() => false), service.readRuntime(process.env).catch(() => undefined), service.readCommand(process.env).catch(() => null), ]); const installed = command != null; const loadedText = loaded ? service.loadedText : service.notLoadedText; const runtimeShort = formatDaemonRuntimeShort(runtime); return { label: service.label, installed, loadedText, runtimeShort }; } catch { return { label: "Daemon", installed: null, loadedText: "unknown", runtimeShort: null, }; } } type AgentLocalStatus = { id: string; name?: string; workspaceDir: string | null; bootstrapPending: boolean | null; sessionsPath: string; sessionsCount: number; lastUpdatedAt: number | null; lastActiveAgeMs: number | null; }; async function fileExists(p: string): Promise { try { await fs.access(p); return true; } catch { return false; } } async function getAgentLocalStatuses(): Promise<{ defaultId: string; agents: AgentLocalStatus[]; totalSessions: number; bootstrapPendingCount: number; }> { const cfg = loadConfig(); const agentList = listAgentsForGateway(cfg); const now = Date.now(); const statuses: AgentLocalStatus[] = []; for (const agent of agentList.agents) { const agentId = agent.id; const workspaceDir = (() => { try { return resolveAgentWorkspaceDir(cfg, agentId); } catch { return null; } })(); const bootstrapPath = workspaceDir != null ? path.join(workspaceDir, "BOOTSTRAP.md") : null; const bootstrapPending = bootstrapPath != null ? await fileExists(bootstrapPath) : null; const sessionsPath = resolveStorePath(cfg.session?.store, { agentId }); const store = (() => { try { return loadSessionStore(sessionsPath); } catch { return {}; } })(); const sessions = Object.entries(store) .filter(([key]) => key !== "global" && key !== "unknown") .map(([, entry]) => entry); const sessionsCount = sessions.length; const lastUpdatedAt = sessions.reduce( (max, e) => Math.max(max, e?.updatedAt ?? 0), 0, ); const resolvedLastUpdatedAt = lastUpdatedAt > 0 ? lastUpdatedAt : null; const lastActiveAgeMs = resolvedLastUpdatedAt ? now - resolvedLastUpdatedAt : null; statuses.push({ id: agentId, name: agent.name, workspaceDir, bootstrapPending, sessionsPath, sessionsCount, lastUpdatedAt: resolvedLastUpdatedAt, lastActiveAgeMs, }); } const totalSessions = statuses.reduce((sum, s) => sum + s.sessionsCount, 0); const bootstrapPendingCount = statuses.reduce( (sum, s) => sum + (s.bootstrapPending ? 1 : 0), 0, ); return { defaultId: agentList.defaultId, agents: statuses, totalSessions, bootstrapPendingCount, }; } function resolveGatewayProbeAuth(cfg: ReturnType): { token?: string; password?: string; } { const isRemoteMode = cfg.gateway?.mode === "remote"; const remote = isRemoteMode ? cfg.gateway?.remote : undefined; const authToken = cfg.gateway?.auth?.token; const authPassword = cfg.gateway?.auth?.password; const token = isRemoteMode ? typeof remote?.token === "string" && remote.token.trim().length > 0 ? remote.token.trim() : undefined : process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() || (typeof authToken === "string" && authToken.trim().length > 0 ? authToken.trim() : undefined); const password = process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() || (isRemoteMode ? typeof remote?.password === "string" && remote.password.trim().length > 0 ? remote.password.trim() : undefined : typeof authPassword === "string" && authPassword.trim().length > 0 ? authPassword.trim() : undefined); return { token, password }; } function pickGatewaySelfPresence(presence: unknown): { host?: string; ip?: string; version?: string; platform?: string; } | null { if (!Array.isArray(presence)) return null; const entries = presence as Array>; const self = entries.find((e) => e.mode === "gateway" && e.reason === "self") ?? null; if (!self) return null; return { host: typeof self.host === "string" ? self.host : undefined, ip: typeof self.ip === "string" ? self.ip : undefined, version: typeof self.version === "string" ? self.version : undefined, platform: typeof self.platform === "string" ? self.platform : undefined, }; } async function getUpdateCheckResult(params: { timeoutMs: number; fetchGit: boolean; includeRegistry: boolean; }): Promise { const root = await resolveClawdbotPackageRoot({ moduleUrl: import.meta.url, argv1: process.argv[1], cwd: process.cwd(), }); return await checkUpdateStatus({ root, timeoutMs: params.timeoutMs, fetchGit: params.fetchGit, includeRegistry: params.includeRegistry, }); } function formatUpdateOneLiner(update: UpdateCheckResult): string { const parts: string[] = []; if (update.installKind === "git" && update.git) { const branch = update.git.branch ? `git ${update.git.branch}` : "git"; parts.push(branch); if (update.git.upstream) parts.push(`↔ ${update.git.upstream}`); if (update.git.dirty === true) parts.push("dirty"); if (update.git.behind != null && update.git.ahead != null) { if (update.git.behind === 0 && update.git.ahead === 0) { parts.push("up to date"); } else if (update.git.behind > 0 && update.git.ahead === 0) { parts.push(`behind ${update.git.behind}`); } else if (update.git.behind === 0 && update.git.ahead > 0) { parts.push(`ahead ${update.git.ahead}`); } else if (update.git.behind > 0 && update.git.ahead > 0) { parts.push( `diverged (ahead ${update.git.ahead}, behind ${update.git.behind})`, ); } } if (update.git.fetchOk === false) parts.push("fetch failed"); if (update.registry?.latestVersion) { const cmp = compareSemverStrings(VERSION, update.registry.latestVersion); if (cmp === 0) parts.push(`npm latest ${update.registry.latestVersion}`); else if (cmp != null && cmp < 0) parts.push(`npm update ${update.registry.latestVersion}`); else parts.push(`npm latest ${update.registry.latestVersion} (local newer)`); } else if (update.registry?.error) { parts.push("npm latest unknown"); } } else { parts.push( update.packageManager !== "unknown" ? update.packageManager : "pkg", ); if (update.registry?.latestVersion) { const cmp = compareSemverStrings(VERSION, update.registry.latestVersion); if (cmp === 0) parts.push(`npm latest ${update.registry.latestVersion}`); else if (cmp != null && cmp < 0) { parts.push(`npm update ${update.registry.latestVersion}`); } else { parts.push(`npm latest ${update.registry.latestVersion} (local newer)`); } } else if (update.registry?.error) { parts.push("npm latest unknown"); } } if (update.deps) { if (update.deps.status === "ok") parts.push("deps ok"); if (update.deps.status === "missing") parts.push("deps missing"); if (update.deps.status === "stale") parts.push("deps stale"); } return `Update: ${parts.join(" · ")}`; } const buildFlags = (entry: SessionEntry): string[] => { const flags: string[] = []; const think = entry?.thinkingLevel; if (typeof think === "string" && think.length > 0) flags.push(`think:${think}`); const verbose = entry?.verboseLevel; if (typeof verbose === "string" && verbose.length > 0) flags.push(`verbose:${verbose}`); const reasoning = entry?.reasoningLevel; if (typeof reasoning === "string" && reasoning.length > 0) flags.push(`reasoning:${reasoning}`); const elevated = entry?.elevatedLevel; if (typeof elevated === "string" && elevated.length > 0) flags.push(`elevated:${elevated}`); if (entry?.systemSent) flags.push("system"); if (entry?.abortedLastRun) flags.push("aborted"); const sessionId = entry?.sessionId as unknown; if (typeof sessionId === "string" && sessionId.length > 0) flags.push(`id:${sessionId}`); return flags; }; export async function statusCommand( opts: { json?: boolean; deep?: boolean; usage?: boolean; timeoutMs?: number; verbose?: boolean; all?: boolean; }, runtime: RuntimeEnv, ) { if (opts.all && !opts.json) { await statusAllCommand(runtime, { timeoutMs: opts.timeoutMs }); return; } const scan = await withProgress( { label: "Scanning status…", total: 7, enabled: opts.json !== true, }, async (progress) => { progress.setLabel("Loading config…"); const cfg = loadConfig(); const osSummary = resolveOsSummary(); progress.tick(); progress.setLabel("Checking for updates…"); const updateTimeoutMs = opts.all ? 6500 : 2500; const update = await getUpdateCheckResult({ timeoutMs: updateTimeoutMs, fetchGit: true, includeRegistry: true, }); progress.tick(); progress.setLabel("Resolving agents…"); const agentStatus = await getAgentLocalStatuses(); progress.tick(); progress.setLabel("Probing gateway…"); const gatewayConnection = buildGatewayConnectionDetails(); const isRemoteMode = cfg.gateway?.mode === "remote"; const remoteUrlRaw = typeof cfg.gateway?.remote?.url === "string" ? cfg.gateway.remote.url : ""; const remoteUrlMissing = isRemoteMode && !remoteUrlRaw.trim(); const gatewayMode = isRemoteMode ? "remote" : "local"; const gatewayProbe = remoteUrlMissing ? null : await probeGateway({ url: gatewayConnection.url, auth: resolveGatewayProbeAuth(cfg), timeoutMs: Math.min( opts.all ? 5000 : 2500, opts.timeoutMs ?? 10_000, ), }).catch(() => null); const gatewayReachable = gatewayProbe?.ok === true; const gatewaySelf = gatewayProbe?.presence ? pickGatewaySelfPresence(gatewayProbe.presence) : null; progress.tick(); progress.setLabel("Summarizing providers…"); 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.setLabel("Reading sessions…"); const summary = await getStatusSummary(); progress.tick(); progress.setLabel("Rendering…"); progress.tick(); return { cfg, osSummary, update, gatewayConnection, remoteUrlMissing, gatewayMode, gatewayProbe, gatewayReachable, gatewaySelf, agentStatus, providers, summary, }; }, ); const { cfg, osSummary, update, gatewayConnection, remoteUrlMissing, gatewayMode, gatewayProbe, gatewayReachable, gatewaySelf, agentStatus, providers, summary, } = scan; const usage = opts.usage ? await withProgress( { label: "Fetching usage snapshot…", indeterminate: true, enabled: opts.json !== true, }, async () => await loadProviderUsageSummary({ timeoutMs: opts.timeoutMs }), ) : undefined; const health: HealthSummary | undefined = opts.deep ? await withProgress( { label: "Checking gateway health…", indeterminate: true, enabled: opts.json !== true, }, async () => await callGateway({ method: "health", timeoutMs: opts.timeoutMs, }), ) : undefined; if (opts.json) { runtime.log( JSON.stringify( { ...summary, os: osSummary, update, gateway: { mode: gatewayMode, url: gatewayConnection.url, urlSource: gatewayConnection.urlSource, misconfigured: remoteUrlMissing, reachable: gatewayReachable, connectLatencyMs: gatewayProbe?.connectLatencyMs ?? null, self: gatewaySelf, error: gatewayProbe?.error ?? null, }, agents: agentStatus, ...(health || usage ? { health, usage } : {}), }, null, 2, ), ); return; } const rich = true; const muted = (value: string) => (rich ? theme.muted(value) : value); const ok = (value: string) => (rich ? theme.success(value) : value); const warn = (value: string) => (rich ? theme.warn(value) : value); if (opts.verbose) { const details = buildGatewayConnectionDetails(); runtime.log(info("Gateway connection:")); for (const line of details.message.split("\n")) runtime.log(` ${line}`); runtime.log(""); } const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1); const dashboard = (() => { const controlUiEnabled = cfg.gateway?.controlUi?.enabled ?? true; if (!controlUiEnabled) return "disabled"; const links = resolveControlUiLinks({ port: resolveGatewayPort(cfg), bind: cfg.gateway?.bind, basePath: cfg.gateway?.controlUi?.basePath, }); return links.httpUrl; })(); const gatewayValue = (() => { const target = remoteUrlMissing ? `fallback ${gatewayConnection.url}` : `${gatewayConnection.url}${gatewayConnection.urlSource ? ` (${gatewayConnection.urlSource})` : ""}`; const reach = remoteUrlMissing ? warn("misconfigured (remote.url missing)") : gatewayReachable ? ok(`reachable ${formatDuration(gatewayProbe?.connectLatencyMs)}`) : warn( gatewayProbe?.error ? `unreachable (${gatewayProbe.error})` : "unreachable", ); const auth = gatewayReachable && !remoteUrlMissing ? ` · auth ${formatGatewayAuthUsed(resolveGatewayProbeAuth(cfg))}` : ""; const self = gatewaySelf?.host || gatewaySelf?.version || gatewaySelf?.platform ? [ gatewaySelf?.host ? gatewaySelf.host : null, gatewaySelf?.ip ? `(${gatewaySelf.ip})` : null, gatewaySelf?.version ? `app ${gatewaySelf.version}` : null, gatewaySelf?.platform ? gatewaySelf.platform : null, ] .filter(Boolean) .join(" ") : null; const suffix = self ? ` · ${self}` : ""; return `${gatewayMode} · ${target} · ${reach}${auth}${suffix}`; })(); const agentsValue = (() => { const pending = agentStatus.bootstrapPendingCount > 0 ? `${agentStatus.bootstrapPendingCount} bootstrapping` : "no bootstraps"; const def = agentStatus.agents.find((a) => a.id === agentStatus.defaultId); const defActive = def?.lastActiveAgeMs != null ? formatAge(def.lastActiveAgeMs) : "unknown"; const defSuffix = def ? ` · default ${def.id} active ${defActive}` : ""; return `${agentStatus.agents.length} · ${pending} · sessions ${agentStatus.totalSessions}${defSuffix}`; })(); const daemon = await getDaemonStatusSummary(); const daemonValue = (() => { if (daemon.installed === false) return `${daemon.label} not installed`; const installedPrefix = daemon.installed === true ? "installed · " : ""; return `${daemon.label} ${installedPrefix}${daemon.loadedText}${daemon.runtimeShort ? ` · ${daemon.runtimeShort}` : ""}`; })(); const defaults = summary.sessions.defaults; const defaultCtx = defaults.contextTokens ? ` (${formatKTokens(defaults.contextTokens)} ctx)` : ""; const eventsValue = summary.queuedSystemEvents.length > 0 ? `${summary.queuedSystemEvents.length} queued` : "none"; const probesValue = health ? ok("enabled") : muted("skipped (use --deep)"); const overviewRows = [ { Item: "Dashboard", Value: dashboard }, { Item: "OS", Value: `${osSummary.label} · node ${process.versions.node}` }, { Item: "Update", Value: formatUpdateOneLiner(update).replace(/^Update:\s*/i, ""), }, { Item: "Gateway", Value: gatewayValue }, { Item: "Daemon", Value: daemonValue }, { Item: "Agents", Value: agentsValue }, { Item: "Probes", Value: probesValue }, { Item: "Events", Value: eventsValue }, { Item: "Heartbeat", Value: `${summary.heartbeatSeconds}s` }, { Item: "Sessions", Value: `${summary.sessions.count} active · default ${defaults.model ?? "unknown"}${defaultCtx} · store ${summary.sessions.path}`, }, ]; runtime.log(theme.heading("Clawdbot status")); runtime.log(""); runtime.log(theme.heading("Overview")); runtime.log( renderTable({ width: tableWidth, columns: [ { key: "Item", header: "Item", minWidth: 12 }, { key: "Value", header: "Value", flex: true, minWidth: 32 }, ], rows: overviewRows, }).trimEnd(), ); runtime.log(""); runtime.log(theme.heading("Providers")); runtime.log( renderTable({ width: tableWidth, columns: [ { key: "Provider", header: "Provider", minWidth: 10 }, { key: "Enabled", header: "Enabled", minWidth: 7 }, { 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, })), }).trimEnd(), ); runtime.log(""); runtime.log(theme.heading("Sessions")); runtime.log( renderTable({ width: tableWidth, columns: [ { key: "Key", header: "Key", minWidth: 20, flex: true }, { key: "Kind", header: "Kind", minWidth: 6 }, { key: "Age", header: "Age", minWidth: 9 }, { key: "Model", header: "Model", minWidth: 14 }, { key: "Tokens", header: "Tokens", minWidth: 16 }, ], rows: summary.sessions.recent.length > 0 ? summary.sessions.recent.map((sess) => ({ Key: shortenText(sess.key, 32), Kind: sess.kind, Age: sess.updatedAt ? formatAge(sess.age) : "no activity", Model: sess.model ?? "unknown", Tokens: formatTokensCompact(sess), })) : [ { Key: muted("no sessions yet"), Kind: "", Age: "", Model: "", Tokens: "", }, ], }).trimEnd(), ); if (summary.queuedSystemEvents.length > 0) { runtime.log(""); runtime.log(theme.heading("System events")); runtime.log( renderTable({ width: tableWidth, columns: [{ key: "Event", header: "Event", flex: true, minWidth: 24 }], rows: summary.queuedSystemEvents.slice(0, 5).map((event) => ({ Event: event, })), }).trimEnd(), ); if (summary.queuedSystemEvents.length > 5) { runtime.log(muted(`… +${summary.queuedSystemEvents.length - 5} more`)); } } if (health) { runtime.log(""); runtime.log(theme.heading("Health")); const rows: Array> = []; rows.push({ Provider: "Gateway", Status: ok("reachable"), Detail: `${health.durationMs}ms`, }); rows.push({ Provider: "Telegram", Status: health.telegram.configured ? health.telegram.probe?.ok ? ok("OK") : warn("WARN") : muted("OFF"), Detail: health.telegram.configured ? health.telegram.probe?.ok ? `@${health.telegram.probe.bot?.username ?? "unknown"} · ${health.telegram.probe.elapsedMs}ms` : (health.telegram.probe?.error ?? "probe failed") : "not configured", }); rows.push({ Provider: "Discord", Status: health.discord.configured ? health.discord.probe?.ok ? ok("OK") : warn("WARN") : muted("OFF"), Detail: health.discord.configured ? health.discord.probe?.ok ? `@${health.discord.probe.bot?.username ?? "unknown"} · ${health.discord.probe.elapsedMs}ms` : (health.discord.probe?.error ?? "probe failed") : "not configured", }); runtime.log( renderTable({ width: tableWidth, columns: [ { key: "Provider", header: "Provider", minWidth: 10 }, { key: "Status", header: "Status", minWidth: 8 }, { key: "Detail", header: "Detail", flex: true, minWidth: 28 }, ], rows, }).trimEnd(), ); } if (usage) { runtime.log(""); runtime.log(theme.heading("Usage")); for (const line of formatUsageReportLines(usage)) { runtime.log(line); } } runtime.log(""); runtime.log("FAQ: https://docs.clawd.bot/faq"); runtime.log("Troubleshooting: https://docs.clawd.bot/troubleshooting"); runtime.log( "More: clawdbot status --all · clawdbot status --deep · clawdbot gateway status · clawdbot providers status --probe · clawdbot daemon status · clawdbot logs --follow", ); }