feat: improve doctor update flow

This commit is contained in:
Peter Steinberger
2026-01-21 05:23:22 +00:00
parent 810374d648
commit 6180603ef4
4 changed files with 49 additions and 15 deletions

View File

@@ -454,6 +454,10 @@ export function registerPluginsCli(program: Command) {
const targets = opts.all ? Object.keys(installs) : id ? [id] : [];
if (targets.length === 0) {
if (opts.all) {
defaultRuntime.log("No npm-installed plugins to update.");
return;
}
defaultRuntime.error("Provide a plugin id or use --all.");
process.exit(1);
}

View File

@@ -623,7 +623,8 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
process.env.CLAWDBOT_UPDATE_IN_PROGRESS = "1";
try {
const { doctorCommand } = await import("../commands/doctor.js");
await doctorCommand(defaultRuntime, { nonInteractive: true });
const interactiveDoctor = Boolean(process.stdin.isTTY) && !opts.json && opts.yes !== true;
await doctorCommand(defaultRuntime, { nonInteractive: !interactiveDoctor });
} catch (err) {
defaultRuntime.log(theme.warn(`Doctor failed: ${String(err)}`));
} finally {

View File

@@ -121,10 +121,14 @@ export async function loadAndMaybeMigrateDoctorConfig(params: {
options: DoctorOptions;
confirm: (p: { message: string; initialValue: boolean }) => Promise<boolean>;
}) {
void params.confirm;
const shouldRepair = params.options.repair === true || params.options.yes === true;
const snapshot = await readConfigFileSnapshot();
let cfg: ClawdbotConfig = snapshot.config ?? {};
const baseCfg = snapshot.config ?? {};
let cfg: ClawdbotConfig = baseCfg;
let candidate = structuredClone(baseCfg) as ClawdbotConfig;
let pendingChanges = false;
let shouldWriteConfig = false;
const fixHints: string[] = [];
if (snapshot.exists && !snapshot.valid && snapshot.legacyIssues.length === 0) {
note("Config invalid; doctor will run with best-effort config.", "Config");
}
@@ -139,52 +143,76 @@ export async function loadAndMaybeMigrateDoctorConfig(params: {
snapshot.legacyIssues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n"),
"Legacy config keys detected",
);
const { config: migrated, changes } = migrateLegacyConfig(snapshot.parsed);
if (changes.length > 0) {
note(changes.join("\n"), "Doctor changes");
}
if (migrated) {
candidate = migrated;
pendingChanges = pendingChanges || changes.length > 0;
}
if (shouldRepair) {
// Legacy migration (2026-01-02, commit: 16420e5b) — normalize per-provider allowlists; move WhatsApp gating into channels.whatsapp.allowFrom.
const { config: migrated, changes } = migrateLegacyConfig(snapshot.parsed);
if (changes.length > 0) note(changes.join("\n"), "Doctor changes");
if (migrated) cfg = migrated;
} else {
note(
fixHints.push(
`Run "${formatCliCommand("clawdbot doctor --fix")}" to apply legacy migrations.`,
"Doctor",
);
}
}
const normalized = normalizeLegacyConfigValues(cfg);
const normalized = normalizeLegacyConfigValues(candidate);
if (normalized.changes.length > 0) {
note(normalized.changes.join("\n"), "Doctor changes");
candidate = normalized.config;
pendingChanges = true;
if (shouldRepair) {
cfg = normalized.config;
} else {
note(`Run "${formatCliCommand("clawdbot doctor --fix")}" to apply these changes.`, "Doctor");
fixHints.push(`Run "${formatCliCommand("clawdbot doctor --fix")}" to apply these changes.`);
}
}
const autoEnable = applyPluginAutoEnable({ config: cfg, env: process.env });
const autoEnable = applyPluginAutoEnable({ config: candidate, env: process.env });
if (autoEnable.changes.length > 0) {
note(autoEnable.changes.join("\n"), "Doctor changes");
candidate = autoEnable.config;
pendingChanges = true;
if (shouldRepair) {
cfg = autoEnable.config;
} else {
note(`Run "${formatCliCommand("clawdbot doctor --fix")}" to apply these changes.`, "Doctor");
fixHints.push(`Run "${formatCliCommand("clawdbot doctor --fix")}" to apply these changes.`);
}
}
const unknown = stripUnknownConfigKeys(cfg);
const unknown = stripUnknownConfigKeys(candidate);
if (unknown.removed.length > 0) {
const lines = unknown.removed.map((path) => `- ${path}`).join("\n");
candidate = unknown.config;
pendingChanges = true;
if (shouldRepair) {
cfg = unknown.config;
note(lines, "Doctor changes");
} else {
note(lines, "Unknown config keys");
note('Run "clawdbot doctor --fix" to remove these keys.', "Doctor");
fixHints.push('Run "clawdbot doctor --fix" to remove these keys.');
}
}
if (!shouldRepair && pendingChanges) {
const shouldApply = await params.confirm({
message: "Apply recommended config repairs now?",
initialValue: true,
});
if (shouldApply) {
cfg = candidate;
shouldWriteConfig = true;
} else if (fixHints.length > 0) {
note(fixHints.join("\n"), "Doctor");
}
}
noteOpencodeProviderOverrides(cfg);
return { cfg, path: snapshot.path ?? CONFIG_PATH_CLAWDBOT };
return { cfg, path: snapshot.path ?? CONFIG_PATH_CLAWDBOT, shouldWriteConfig };
}

View File

@@ -250,7 +250,8 @@ export async function doctorCommand(
healthOk,
});
if (prompter.shouldRepair) {
const shouldWriteConfig = prompter.shouldRepair || configResult.shouldWriteConfig;
if (shouldWriteConfig) {
cfg = applyWizardMetadata(cfg, { command: "doctor", mode: resolveMode(cfg) });
await writeConfigFile(cfg);
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);