Skip to content

Commit 95b8bfb

Browse files
committed
refactor: replace OpenClaw auto-migration with detection notice
Remove silent auto-migration of OpenClaw config and replace with a startup notice linking to the migration guide when ~/.openclaw is detected.
1 parent fb24f22 commit 95b8bfb

4 files changed

Lines changed: 32 additions & 179 deletions

File tree

src/cli/chat.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ pub async fn run(args: ChatArgs, agent_id: &str) -> Result<()> {
183183
);
184184
println!("Type /help for commands, /quit to exit\n");
185185

186+
if let Some(notice) = crate::config::check_openclaw_detected() {
187+
println!("{}\n", notice);
188+
}
189+
186190
// Store agent_id for command handling
187191
let agent_id = agent_id.to_string();
188192

src/cli/daemon.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ pub fn daemonize_and_run(agent_id: &str) -> Result<()> {
9696
println!("\nUse 'localgpt daemon status' to check status");
9797
println!("Use 'localgpt daemon stop' to stop\n");
9898

99+
if let Some(notice) = crate::config::check_openclaw_detected() {
100+
println!("{}\n", notice);
101+
}
102+
99103
// Fork BEFORE starting Tokio
100104
// Use append mode to preserve previous logs within the same day
101105
let stdout = std::fs::OpenOptions::new()
@@ -296,6 +300,10 @@ async fn start_daemon(foreground: bool, agent_id: &str) -> Result<()> {
296300
agent_id
297301
);
298302

303+
if let Some(notice) = crate::config::check_openclaw_detected() {
304+
println!("{}\n", notice);
305+
}
306+
299307
// Write PID file for foreground mode
300308
fs::write(&pid_file, std::process::id().to_string())?;
301309

src/config/migrate.rs

Lines changed: 19 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,22 @@
1-
//! OpenClaw config migration support
1+
//! OpenClaw detection
22
//!
3-
//! Provides best-effort migration from OpenClaw's JSON5 config format to LocalGPT's TOML format.
4-
5-
use anyhow::Result;
6-
use serde::Deserialize;
7-
use std::fs;
8-
use std::path::PathBuf;
9-
use tracing::{debug, info, warn};
10-
11-
use super::{AnthropicConfig, ClaudeCliConfig, Config, OpenAIConfig};
12-
13-
/// OpenClaw config structure (partial - only fields we can migrate)
14-
#[derive(Debug, Deserialize)]
15-
#[serde(rename_all = "camelCase")]
16-
struct OpenClawConfig {
17-
#[serde(default)]
18-
agents: Option<OpenClawAgentsConfig>,
19-
20-
#[serde(default)]
21-
models: Option<OpenClawModelsConfig>,
22-
}
23-
24-
#[derive(Debug, Deserialize)]
25-
#[serde(rename_all = "camelCase")]
26-
struct OpenClawAgentsConfig {
27-
#[serde(default)]
28-
defaults: Option<OpenClawAgentDefaults>,
29-
}
30-
31-
#[derive(Debug, Deserialize)]
32-
#[serde(rename_all = "camelCase")]
33-
struct OpenClawAgentDefaults {
34-
#[serde(default)]
35-
workspace: Option<String>,
36-
37-
#[serde(default)]
38-
model: Option<String>,
39-
40-
#[serde(default)]
41-
context_window: Option<usize>,
42-
}
43-
44-
#[derive(Debug, Deserialize)]
45-
#[serde(rename_all = "camelCase")]
46-
struct OpenClawModelsConfig {
47-
#[serde(default)]
48-
openai: Option<OpenClawOpenAIConfig>,
49-
50-
#[serde(default)]
51-
anthropic: Option<OpenClawAnthropicConfig>,
52-
}
53-
54-
#[derive(Debug, Deserialize)]
55-
#[serde(rename_all = "camelCase")]
56-
struct OpenClawOpenAIConfig {
57-
#[serde(default)]
58-
api_key: Option<String>,
59-
}
60-
61-
#[derive(Debug, Deserialize)]
62-
#[serde(rename_all = "camelCase")]
63-
struct OpenClawAnthropicConfig {
64-
#[serde(default)]
65-
api_key: Option<String>,
66-
}
67-
68-
/// Path to OpenClaw's config file
69-
pub fn openclaw_config_path() -> Result<PathBuf> {
70-
let base = directories::BaseDirs::new()
71-
.ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
72-
73-
Ok(base.home_dir().join(".openclaw").join("config.json5"))
74-
}
75-
76-
/// Try to load and migrate OpenClaw config
77-
pub fn try_migrate_openclaw_config() -> Option<Config> {
78-
let path = match openclaw_config_path() {
79-
Ok(p) => p,
80-
Err(_) => return None,
81-
};
82-
83-
if !path.exists() {
84-
debug!("No OpenClaw config found at {:?}", path);
85-
return None;
3+
//! Checks for an existing OpenClaw installation and returns a notice
4+
//! directing users to the migration guide.
5+
6+
/// Check if an OpenClaw data directory exists at ~/.openclaw.
7+
///
8+
/// Returns a formatted notice string if detected, `None` otherwise.
9+
pub fn check_openclaw_detected() -> Option<String> {
10+
let base = directories::BaseDirs::new()?;
11+
let openclaw_dir = base.home_dir().join(".openclaw");
12+
13+
if openclaw_dir.exists() {
14+
Some(
15+
"Note: OpenClaw data detected at ~/.openclaw\n \
16+
Migration guide: https://localgpt.app/docs/openclaw-migration"
17+
.to_string(),
18+
)
19+
} else {
20+
None
8621
}
87-
88-
info!("Found OpenClaw config at {:?}, attempting migration", path);
89-
90-
let content = match fs::read_to_string(&path) {
91-
Ok(c) => c,
92-
Err(e) => {
93-
warn!("Failed to read OpenClaw config: {}", e);
94-
return None;
95-
}
96-
};
97-
98-
let openclaw_config: OpenClawConfig = match json5::from_str(&content) {
99-
Ok(c) => c,
100-
Err(e) => {
101-
warn!("Failed to parse OpenClaw config: {}", e);
102-
return None;
103-
}
104-
};
105-
106-
Some(convert_openclaw_config(openclaw_config))
107-
}
108-
109-
/// Convert OpenClaw config to LocalGPT config
110-
fn convert_openclaw_config(oc: OpenClawConfig) -> Config {
111-
let mut config = Config::default();
112-
113-
// Migrate agent defaults
114-
if let Some(agents) = oc.agents
115-
&& let Some(defaults) = agents.defaults
116-
{
117-
if let Some(workspace) = defaults.workspace {
118-
// Convert OpenClaw workspace path to LocalGPT XDG format
119-
let workspace = workspace.replace("~/.openclaw/", "~/.local/share/localgpt/");
120-
config.memory.workspace = workspace;
121-
}
122-
123-
if let Some(model) = defaults.model {
124-
config.agent.default_model = model;
125-
}
126-
127-
if let Some(context_window) = defaults.context_window {
128-
config.agent.context_window = context_window;
129-
}
130-
}
131-
132-
// Migrate model configs (API keys)
133-
if let Some(models) = oc.models {
134-
if let Some(openai) = models.openai
135-
&& let Some(api_key) = openai.api_key
136-
{
137-
config.providers.openai = Some(OpenAIConfig {
138-
api_key,
139-
base_url: "https://api.openai.com/v1".to_string(),
140-
});
141-
}
142-
143-
if let Some(anthropic) = models.anthropic
144-
&& let Some(api_key) = anthropic.api_key
145-
{
146-
config.providers.anthropic = Some(AnthropicConfig {
147-
api_key,
148-
base_url: "https://api.anthropic.com".to_string(),
149-
});
150-
}
151-
}
152-
153-
// Always enable Claude CLI as default (OpenClaw uses it too)
154-
if config.providers.claude_cli.is_none() {
155-
config.providers.claude_cli = Some(ClaudeCliConfig {
156-
command: "claude".to_string(),
157-
model: "opus".to_string(),
158-
});
159-
}
160-
161-
info!("Migrated OpenClaw config successfully");
162-
config
163-
}
164-
165-
/// Check if OpenClaw workspace exists and can be reused
166-
pub fn has_openclaw_workspace() -> bool {
167-
let base = match directories::BaseDirs::new() {
168-
Some(b) => b,
169-
None => return false,
170-
};
171-
172-
let workspace = base.home_dir().join(".openclaw").join("workspace");
173-
workspace.exists()
17422
}

src/config/mod.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod migrate;
22
mod schema;
33

4-
pub use migrate::{has_openclaw_workspace, openclaw_config_path, try_migrate_openclaw_config};
4+
pub use migrate::check_openclaw_detected;
55
pub use schema::*;
66

77
use anyhow::Result;
@@ -537,13 +537,6 @@ impl Config {
537537
let path = paths.config_file();
538538

539539
if !path.exists() {
540-
// Try to migrate from OpenClaw config
541-
if let Some(mut migrated) = try_migrate_openclaw_config() {
542-
migrated.paths = paths;
543-
// Save migrated config to disk
544-
migrated.save()?;
545-
return Ok(migrated);
546-
}
547540
// Create default config file on first run
548541
let config = Config {
549542
paths,

0 commit comments

Comments
 (0)