import { confirm, isCancel } from "@clack/prompts" ;
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 { selectStyled } from "../../terminal/prompt-select-styled.js" ;
import { stylePromptMessage } from "../../terminal/prompt-style.js" ;
import { theme } from "../../terminal/theme.js" ;
import { pathExists } from "../../utils.js" ;
import {
isEmptyDir,
isGitCheckout,
parseTimeoutMsOrExit,
resolveGitInstallDir,
resolveUpdateRoot,
type UpdateWizardOptions,
} from "./shared.js" ;
import { updateCommand } from "./update-command.js" ;
export async function updateWizardCommand(opts: UpdateWizardOptions = {}): Promise<void > {
if (!process.stdin.isTTY) {
defaultRuntime.error(
"Update wizard requires a TTY. Use `openclaw update --channel <stable|beta|dev>` instead." ,
);
defaultRuntime.exit(1 );
return ;
}
const timeoutMs = parseTimeoutMsOrExit(opts.timeout);
if (timeoutMs === null ) {
return ;
}
const root = await resolveUpdateRoot();
const [updateStatus, configSnapshot] = await Promise.all([
checkUpdateStatus({
root,
timeoutMs: timeoutMs ?? 3500 ,
fetchGit: false ,
includeRegistry: false ,
}),
readConfigFileSnapshot(),
]);
const configChannel = configSnapshot.valid
? normalizeUpdateChannel(configSnapshot.config.update?.channel)
: null ;
const channelInfo = resolveEffectiveUpdateChannel({
configChannel,
installKind: updateStatus.installKind,
git: updateStatus.git
? { tag: updateStatus.git.tag, branch: updateStatus.git.branch }
: undefined,
});
const channelLabel = formatUpdateChannelLabel({
channel: channelInfo.channel,
source: channelInfo.source,
gitTag: updateStatus.git?.tag ?? null ,
gitBranch: updateStatus.git?.branch ?? null ,
});
const pickedChannel = await selectStyled({
message: "Update channel" ,
options: [
{
value: "keep" ,
label: `Keep current (${channelInfo.channel})`,
hint: channelLabel,
},
{
value: "stable" ,
label: "Stable" ,
hint: "Tagged releases (npm latest)" ,
},
{
value: "beta" ,
label: "Beta" ,
hint: "Prereleases (npm beta)" ,
},
{
value: "dev" ,
label: "Dev" ,
hint: "Git main" ,
},
],
initialValue: "keep" ,
});
if (isCancel(pickedChannel)) {
defaultRuntime.log(theme.muted("Update cancelled." ));
defaultRuntime.exit(0 );
return ;
}
const requestedChannel = pickedChannel === "keep" ? null : pickedChannel;
if (requestedChannel === "dev" && updateStatus.installKind !== "git" ) {
const gitDir = resolveGitInstallDir();
const hasGit = await isGitCheckout(gitDir);
if (!hasGit) {
const dirExists = await pathExists(gitDir);
if (dirExists) {
const empty = await isEmptyDir(gitDir);
if (!empty) {
defaultRuntime.error(
`OPENCLAW_GIT_DIR points at a non-git directory: ${gitDir}. Set OPENCLAW_GIT_DIR to an empty folder or an openclaw checkout.`,
);
defaultRuntime.exit(1 );
return ;
}
}
const ok = await confirm({
message: stylePromptMessage(
`Create a git checkout at ${gitDir}? (override via OPENCLAW_GIT_DIR)`,
),
initialValue: true ,
});
if (isCancel(ok) || !ok) {
defaultRuntime.log(theme.muted("Update cancelled." ));
defaultRuntime.exit(0 );
return ;
}
}
}
const restart = await confirm({
message: stylePromptMessage("Restart the gateway service after update?" ),
initialValue: true ,
});
if (isCancel(restart)) {
defaultRuntime.log(theme.muted("Update cancelled." ));
defaultRuntime.exit(0 );
return ;
}
try {
await updateCommand({
channel: requestedChannel ?? undefined,
restart,
timeout: opts.timeout,
});
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1 );
}
}
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland