codex-sync 0.2.2

Sync and merge Codex conversations across computers, LAN, SSH, and offline storage
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use std::{
    fs,
    path::{Path, PathBuf},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
    pub node_name: String,
    pub listen: String,
    pub discovery_port: u16,
    pub sync_dir: PathBuf,
    pub shared_key: String,
    pub exclude: Vec<String>,
    pub auto_pull_seconds: u64,
}

impl Default for Config {
    fn default() -> Self {
        let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
        Self {
            node_name: hostname::get()
                .unwrap_or_default()
                .to_string_lossy()
                .into_owned(),
            listen: "0.0.0.0:8787".into(),
            discovery_port: 8788,
            sync_dir: home.join(".codex"),
            shared_key: "请修改为所有电脑相同的随机密钥".into(),
            exclude: vec![
                "auth.json".into(),
                "credentials.json".into(),
                "logs".into(),
                "cache".into(),
                "tmp".into(),
                "codex-sync-backups".into(),
                ".DS_Store".into(),
            ],
            auto_pull_seconds: 0,
        }
    }
}

impl Config {
    pub fn default_path() -> PathBuf {
        dirs::config_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join("codex-sync/config.toml")
    }

    pub fn init(path: &Path, force: bool) -> Result<()> {
        if path.exists() && !force {
            bail!("配置已存在;如需覆盖请添加 --force");
        }
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::write(path, toml::to_string_pretty(&Self::default())?)?;
        Ok(())
    }

    pub fn load(path: &Path) -> Result<Self> {
        let text = fs::read_to_string(path)
            .with_context(|| format!("无法读取 {},请先运行 codex-sync init", path.display()))?;
        let cfg: Self = toml::from_str(&text).context("配置格式错误")?;
        if cfg.shared_key.starts_with("请修改") {
            bail!("请先修改配置中的 shared_key");
        }
        Ok(cfg)
    }

    pub fn node_id(&self) -> String {
        blake3::hash(format!("{}:{}", self.node_name, self.shared_key).as_bytes()).to_hex()[..12]
            .to_string()
    }
}