diff --git a/README.md b/README.md index 2ff170c..5a40131 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,12 @@ Goals: - Shows OpenClaw memory status when available - For `provider=openai`, runs a real minimal embeddings probe - For `provider=ollama`, checks reachability and whether `nomic-embed-text` is present -- `clawkeeper switch openai|ollama` - - Switches `agents.defaults.memorySearch.provider` - - Makes a timestamped backup of `~/.openclaw/openclaw.json` - - Restarts the gateway after switching +- `clawkeeper switch openai|ollama [--dry-run]` + - `--dry-run` previews the provider/config change without writing config or restarting the gateway + - Real switches update `agents.defaults.memorySearch.provider` + - 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]` - Prints recommended daily/weekly backup coverage - `--json` emits machine-readable include/exclude structure @@ -55,6 +57,8 @@ memops --help clawkeeper doctor clawkeeper switch ollama clawkeeper switch openai +clawkeeper switch ollama --dry-run +clawkeeper switch openai --dry-run clawkeeper backup-plan clawkeeper backup-plan --json clawkeeper verify-backup ~/openclaw-backup.tgz diff --git a/src/cli.ts b/src/cli.ts index c1b3bc2..93450d5 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -188,10 +188,13 @@ function backupPlan(opts: { json?: boolean } = {}) { 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 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') { set(cfg, [...path, 'provider'], 'ollama'); @@ -200,9 +203,37 @@ function switchProvider(to: 'openai' | 'ollama') { } else { set(cfg, [...path, 'provider'], 'openai'); // 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); @@ -221,7 +252,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') { console.log('clawkeeper'); console.log('Commands:'); 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 verify-backup '); console.log(' clawkeeper restore-guide'); @@ -275,9 +306,10 @@ else if (cmd === 'restore-apply') { } } else if (cmd === 'switch') { - const to = args[0]; - if (to !== 'openai' && to !== 'ollama') die('Usage: clawkeeper switch openai|ollama'); - switchProvider(to); + const to = args.find(arg => arg === 'openai' || arg === 'ollama'); + const dryRun = args.includes('--dry-run'); + if (to !== 'openai' && to !== 'ollama') die('Usage: clawkeeper switch openai|ollama [--dry-run]'); + switchProvider(to, { dryRun }); } else { die(`Unknown command: ${cmd}`); }