import { formatUpdateAvailableHint, formatUpdateOneLiner, resolveUpdateAvailability, } from "../../commands/status.update.js"; import { readConfigFileSnapshot } from "../../config/config.js"; import { normalizeUpdateChannel, resolveUpdateChannelDisplay, } from "../../infra/update-channels.js"; import { checkUpdateStatus } from "../../infra/update-check.js"; import { defaultRuntime } from "../../runtime.js"; import { getTerminalTableWidth, renderTable } from "../../terminal/table.js"; import { theme } from "../../terminal/theme.js"; import { parseTimeoutMsOrExit, resolveUpdateRoot, type UpdateStatusOptions } from "./shared.js"; function formatGitStatusLine(params: { branch: string | null; tag: string | null; sha: string | null; }): string { const shortSha = params.sha ? params.sha.slice(0, 8) : null; const branch = params.branch && params.branch !== "HEAD" ? params.branch : null; const tag = params.tag; const parts = [ branch ?? (tag ? "detached" : "git"), tag ? `tag ${tag}` : null, shortSha ? `@ ${shortSha}` : null, ].filter(Boolean); return parts.join(" · "); } export async function updateStatusCommand(opts: UpdateStatusOptions): Promise { const timeoutMs = parseTimeoutMsOrExit(opts.timeout); if (timeoutMs === null) { return; } const root = await resolveUpdateRoot(); const configSnapshot = await readConfigFileSnapshot(); const configChannel = configSnapshot.valid ? normalizeUpdateChannel(configSnapshot.config.update?.channel) : null; const update = await checkUpdateStatus({ root, timeoutMs: timeoutMs ?? 3500, fetchGit: true, includeRegistry: true, }); const channelInfo = resolveUpdateChannelDisplay({ configChannel, installKind: update.installKind, gitTag: update.git?.tag ?? null, gitBranch: update.git?.branch ?? null, }); const channelLabel = channelInfo.label; const gitLabel = update.installKind === "git" ? formatGitStatusLine({ branch: update.git?.branch ?? null, tag: update.git?.tag ?? null, sha: update.git?.sha ?? null, }) : null; const updateAvailability = resolveUpdateAvailability(update); const updateLine = formatUpdateOneLiner(update).replace(/^Update:\s*/i, ""); if (opts.json) { defaultRuntime.log( JSON.stringify( { update, channel: { value: channelInfo.channel, source: channelInfo.source, label: channelLabel, config: configChannel, }, availability: updateAvailability, }, null, 2, ), ); return; } const tableWidth = getTerminalTableWidth(); const installLabel = update.installKind === "git" ? `git (${update.root ?? "unknown"})` : update.installKind === "package" ? update.packageManager : "unknown"; const rows = [ { Item: "Install", Value: installLabel }, { Item: "Channel", Value: channelLabel }, ...(gitLabel ? [{ Item: "Git", Value: gitLabel }] : []), { Item: "Update", Value: updateAvailability.available ? theme.warn(`available · ${updateLine}`) : updateLine, }, ]; defaultRuntime.log(theme.heading("OpenClaw update status")); defaultRuntime.log(""); defaultRuntime.log( renderTable({ width: tableWidth, columns: [ { key: "Item", header: "Item", minWidth: 10 }, { key: "Value", header: "Value", flex: true, minWidth: 24 }, ], rows, }).trimEnd(), ); defaultRuntime.log(""); const updateHint = formatUpdateAvailableHint(update); if (updateHint) { defaultRuntime.log(theme.warn(updateHint)); } }