import { formatUpdateAvailableHint, formatUpdateOneLiner, resolveUpdateAvailability, } from "../../commands/status.update.js"; import { readConfigFileSnapshot } from "../../config/config.js"; import { formatUpdateChannelLabel, normalizeUpdateChannel, resolveEffectiveUpdateChannel, } from "../../infra/update-channels.js"; import { checkUpdateStatus } from "../../infra/update-check.js"; import { defaultRuntime } from "../../runtime.js"; import { renderTable } from "../../terminal/table.js"; import { theme } from "../../terminal/theme.js"; import { 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 = opts.timeout ? Number.parseInt(opts.timeout, 10) * 1000 : undefined; if (timeoutMs !== undefined && (Number.isNaN(timeoutMs) || timeoutMs <= 0)) { defaultRuntime.error("--timeout must be a positive integer (seconds)"); defaultRuntime.exit(1); 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 = resolveEffectiveUpdateChannel({ configChannel, installKind: update.installKind, git: update.git ? { tag: update.git.tag, branch: update.git.branch } : undefined, }); const channelLabel = formatUpdateChannelLabel({ channel: channelInfo.channel, source: channelInfo.source, gitTag: update.git?.tag ?? null, gitBranch: update.git?.branch ?? null, }); 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 = Math.max(60, (process.stdout.columns ?? 120) - 1); 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)); } }