feat: add switch --dry-run preview mode

This commit is contained in:
2026-03-07 22:02:20 +10:00
parent e4a7652569
commit fefc10846d
2 changed files with 48 additions and 12 deletions
+8 -4
View File
@@ -14,10 +14,12 @@ Goals:
- Shows OpenClaw memory status when available - Shows OpenClaw memory status when available
- For `provider=openai`, runs a real minimal embeddings probe - For `provider=openai`, runs a real minimal embeddings probe
- For `provider=ollama`, checks reachability and whether `nomic-embed-text` is present - For `provider=ollama`, checks reachability and whether `nomic-embed-text` is present
- `clawkeeper switch openai|ollama` - `clawkeeper switch openai|ollama [--dry-run]`
- Switches `agents.defaults.memorySearch.provider` - `--dry-run` previews the provider/config change without writing config or restarting the gateway
- Makes a timestamped backup of `~/.openclaw/openclaw.json` - Real switches update `agents.defaults.memorySearch.provider`
- Restarts the gateway after switching - Switching to `ollama` removes `memorySearch.remote` from that section to avoid stale OpenAI config there
- Makes a timestamped backup of `~/.openclaw/openclaw.json` before any real write
- Restarts the gateway after a real config change
- `clawkeeper backup-plan [--json]` - `clawkeeper backup-plan [--json]`
- Prints recommended daily/weekly backup coverage - Prints recommended daily/weekly backup coverage
- `--json` emits machine-readable include/exclude structure - `--json` emits machine-readable include/exclude structure
@@ -55,6 +57,8 @@ memops --help
clawkeeper doctor clawkeeper doctor
clawkeeper switch ollama clawkeeper switch ollama
clawkeeper switch openai clawkeeper switch openai
clawkeeper switch ollama --dry-run
clawkeeper switch openai --dry-run
clawkeeper backup-plan clawkeeper backup-plan
clawkeeper backup-plan --json clawkeeper backup-plan --json
clawkeeper verify-backup ~/openclaw-backup.tgz clawkeeper verify-backup ~/openclaw-backup.tgz
+40 -8
View File
@@ -188,10 +188,13 @@ function backupPlan(opts: { json?: boolean } = {}) {
for (const p of plan.weeklyFull.include) console.log(`- ${p}`); for (const p of plan.weeklyFull.include) console.log(`- ${p}`);
} }
function switchProvider(to: 'openai' | 'ollama') { function switchProvider(to: 'openai' | 'ollama', opts: { dryRun?: boolean } = {}) {
const cfg = loadConfig(); const cfg = loadConfig();
const path = ['agents', 'defaults', 'memorySearch']; const path = ['agents', 'defaults', 'memorySearch'];
const ms = get(cfg, path) || {}; const msBefore = structuredClone(get(cfg, path) || {});
const from = msBefore?.provider;
const hadRemote = !!msBefore?.remote;
const hadOpenAIKey = !!msBefore?.remote?.apiKey;
if (to === 'ollama') { if (to === 'ollama') {
set(cfg, [...path, 'provider'], 'ollama'); set(cfg, [...path, 'provider'], 'ollama');
@@ -200,9 +203,37 @@ function switchProvider(to: 'openai' | 'ollama') {
} else { } else {
set(cfg, [...path, 'provider'], 'openai'); set(cfg, [...path, 'provider'], 'openai');
// keep existing remote.apiKey if present; do not prompt // keep existing remote.apiKey if present; do not prompt
if (!ms?.remote?.apiKey) { }
console.error('WARN: memorySearch.remote.apiKey is not set. Configure it in openclaw.json first.');
const msAfter = get(cfg, path) || {};
const removedRemote = hadRemote && !msAfter?.remote;
const changed = JSON.stringify(msBefore) !== JSON.stringify(msAfter);
if (opts.dryRun) {
console.log('Dry run: no changes applied.');
console.log(`config: ${OPENCLAW_CONFIG}`);
console.log(`memorySearch.provider: ${from} -> ${to}`);
if (to === 'ollama') {
console.log(`memorySearch.remote: ${removedRemote ? 'would be removed' : 'no change'}`);
} else {
console.log(`memorySearch.remote.apiKey.present: ${hadOpenAIKey}`);
if (!hadOpenAIKey) {
console.log('WARN: memorySearch.remote.apiKey is not set. A real switch to openai would still proceed, but memory search may remain broken until configured.');
}
} }
console.log(`config mutation: ${changed ? 'would update openclaw.json' : 'no-op (already matches requested provider)'}`);
console.log(`config backup: ${changed ? 'would create timestamped backup before write' : 'would skip (no write needed)'}`);
console.log(`gateway restart: ${changed ? 'would restart after config write' : 'would skip (no write needed)'}`);
return;
}
if (to === 'openai' && !hadOpenAIKey) {
console.error('WARN: memorySearch.remote.apiKey is not set. Configure it in openclaw.json first.');
}
if (!changed) {
console.log(`memorySearch.provider is already ${to}; no config changes made and gateway restart skipped.`);
return;
} }
saveConfig(cfg); saveConfig(cfg);
@@ -221,7 +252,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
console.log('clawkeeper'); console.log('clawkeeper');
console.log('Commands:'); console.log('Commands:');
console.log(' clawkeeper doctor'); console.log(' clawkeeper doctor');
console.log(' clawkeeper switch openai|ollama'); console.log(' clawkeeper switch openai|ollama [--dry-run]');
console.log(' clawkeeper backup-plan [--json]'); console.log(' clawkeeper backup-plan [--json]');
console.log(' clawkeeper verify-backup <path.tgz>'); console.log(' clawkeeper verify-backup <path.tgz>');
console.log(' clawkeeper restore-guide'); console.log(' clawkeeper restore-guide');
@@ -275,9 +306,10 @@ else if (cmd === 'restore-apply') {
} }
} }
else if (cmd === 'switch') { else if (cmd === 'switch') {
const to = args[0]; const to = args.find(arg => arg === 'openai' || arg === 'ollama');
if (to !== 'openai' && to !== 'ollama') die('Usage: clawkeeper switch openai|ollama'); const dryRun = args.includes('--dry-run');
switchProvider(to); if (to !== 'openai' && to !== 'ollama') die('Usage: clawkeeper switch openai|ollama [--dry-run]');
switchProvider(to, { dryRun });
} else { } else {
die(`Unknown command: ${cmd}`); die(`Unknown command: ${cmd}`);
} }