use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result, bail};
use openmoat_hosts::Host;
use serde::{Deserialize, Serialize};
use crate::home::{Home, write_private};
const VERSION: u32 = 1;
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Recorded {
pub version: u32,
pub dirs: BTreeMap<String, PathBuf>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub files: BTreeMap<String, PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub claude_rules: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub backups: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mcp_wrapped: Vec<crate::mcp::wrap::Wrapped>,
}
impl Recorded {
pub fn load(home: &Home) -> Result<Self> {
let path = home.hosts_path();
if !path.exists() {
return Ok(Self::default());
}
let text =
fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?;
let recorded: Self = serde_json::from_str(&text)
.with_context(|| format!("parsing {}; run `moat init`", path.display()))?;
if recorded.version != VERSION {
bail!(
"{} has version {}; this build supports {VERSION}",
path.display(),
recorded.version
);
}
Ok(recorded)
}
pub fn save(&mut self, home: &Home) -> Result<()> {
self.version = VERSION;
let text = serde_json::to_string_pretty(self)? + "\n";
write_private(&home.hosts_path(), text.as_bytes())
}
pub fn owns(&self, path: &Path) -> bool {
self.backups.iter().any(|b| b == path)
}
pub fn dir(&self, host: Host) -> Option<PathBuf> {
self.dirs.get(host.id()).cloned()
}
pub fn file(&self, host: Host) -> Option<PathBuf> {
self.files.get(host.id()).cloned()
}
pub fn insert(&mut self, config: &super::HostConfig) {
let id = config.host.id().to_owned();
self.dirs.insert(id.clone(), config.dir().to_path_buf());
match config.openclaw_location() {
Some(location) if !location.config_is_default() => {
self.files.insert(id, location.config);
}
_ => {
self.files.remove(&id);
}
}
}
pub fn skipped(&self, host: Host) -> bool {
!self.dirs.is_empty() && self.dir(host).is_none()
}
}
#[cfg(test)]
mod tests;