Skip to main content

apollo/
claw_adapter.rs

1//! Claw adapter — migrate from OpenClaw to aclaw
2//! Maps SOUL.md, AGENTS.md config to aclaw Config
3//! Enables existing Claw workflows to run on aclaw
4
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7
8/// Map SOUL.md personality
9#[derive(Debug, Serialize, Deserialize, Clone)]
10pub struct Soul {
11    pub name: String,
12    pub vibe: String,
13}
14
15/// Map USER.md context
16#[derive(Debug, Serialize, Deserialize, Clone)]
17pub struct User {
18    pub name: String,
19    pub location: String,
20    pub languages: Vec<String>,
21    pub interests: Vec<String>,
22}
23
24/// Map AGENTS.md workspace
25#[derive(Debug, Serialize, Deserialize, Clone)]
26pub struct AgentsConfig {
27    pub soul: Soul,
28    pub user: User,
29    pub workspace: String,
30    pub memory_dir: String,
31    pub projects: Vec<ProjectRef>,
32}
33
34#[derive(Debug, Serialize, Deserialize, Clone)]
35pub struct ProjectRef {
36    pub name: String,
37    pub path: String,
38}
39
40impl AgentsConfig {
41    /// Load from SOUL.md + USER.md + AGENTS.md
42    pub fn load(workspace: &str) -> anyhow::Result<Self> {
43        let workspace_path = Path::new(workspace);
44
45        // Read SOUL.md
46        let soul_content =
47            std::fs::read_to_string(workspace_path.join("SOUL.md")).unwrap_or_default();
48        let soul = Soul {
49            name: extract_field(&soul_content, "Name"),
50            vibe: extract_field(&soul_content, "Vibe"),
51        };
52
53        // Read USER.md
54        let user_content =
55            std::fs::read_to_string(workspace_path.join("USER.md")).unwrap_or_default();
56        let user = User {
57            name: extract_field(&user_content, "Name"),
58            location: extract_field(&user_content, "Location"),
59            languages: vec![],
60            interests: vec![],
61        };
62
63        Ok(Self {
64            soul,
65            user,
66            workspace: workspace.to_string(),
67            memory_dir: workspace_path.join("memory").to_string_lossy().to_string(),
68            projects: vec![],
69        })
70    }
71
72    /// Convert to aclaw system prompt
73    pub fn to_system_prompt(&self) -> String {
74        format!(
75            "You are {}. {}\n\nWorking for: {}\nLocation: {}\n\nStyle: {}",
76            self.soul.name, self.soul.vibe, self.user.name, self.user.location, self.soul.vibe
77        )
78    }
79
80    /// Get claw configuration
81    pub fn claw_config(&self) -> String {
82        format!(
83            r#"{{
84  "claw": {{
85    "name": "{}",
86    "vibe": "{}",
87    "user": {{
88      "name": "{}",
89      "location": "{}"
90    }},
91    "workspace": "{}",
92    "memory_dir": "{}"
93  }}
94}}"#,
95            self.soul.name,
96            self.soul.vibe,
97            self.user.name,
98            self.user.location,
99            self.workspace,
100            self.memory_dir
101        )
102    }
103}
104
105fn extract_field(content: &str, field: &str) -> String {
106    let bold_key = format!("**{}:**", field);
107    let plain_key = format!("{}:", field);
108    for line in content.lines() {
109        let after = [bold_key.as_str(), plain_key.as_str()]
110            .iter()
111            .find_map(|key| line.find(key).map(|i| &line[i + key.len()..]));
112        if let Some(value) = after {
113            return value.trim().to_string();
114        }
115    }
116    String::new()
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_extract_field() {
125        let content = r#"**Name:** Claw
126**Vibe:** Smartass best friend who knows everything."#;
127
128        assert_eq!(extract_field(content, "Name"), "Claw");
129        assert_eq!(
130            extract_field(content, "Vibe"),
131            "Smartass best friend who knows everything."
132        );
133    }
134
135    #[test]
136    fn test_system_prompt() {
137        let config = AgentsConfig {
138            soul: Soul {
139                name: "Claw".to_string(),
140                vibe: "Smartass helper".to_string(),
141            },
142            user: User {
143                name: "Max".to_string(),
144                location: "Melbourne".to_string(),
145                languages: vec![],
146                interests: vec![],
147            },
148            workspace: "/tmp".to_string(),
149            memory_dir: "/tmp/memory".to_string(),
150            projects: vec![],
151        };
152
153        let prompt = config.to_system_prompt();
154        assert!(prompt.contains("Claw"));
155        assert!(prompt.contains("Max"));
156        assert!(prompt.contains("Melbourne"));
157    }
158}