clync 0.3.0

Encrypted sync for Claude Code across machines
use anyhow::{Context, Result, bail};
use std::path::Path;

pub fn ensure_lfs_for_file(repo_path: &Path, rel_path: &str) -> Result<()> {
    check_lfs_installed()?;
    install_lfs_local(repo_path)?;

    let attr_path = repo_path.join(".gitattributes");
    let pattern = format!("{rel_path} filter=lfs diff=lfs merge=lfs -text");

    if attr_path.exists() {
        let content = std::fs::read_to_string(&attr_path)?;
        if content.contains(&pattern) {
            return Ok(());
        }
        let mut file = std::fs::OpenOptions::new().append(true).open(&attr_path)?;
        std::io::Write::write_all(&mut file, format!("{pattern}\n").as_bytes())?;
    } else {
        std::fs::write(&attr_path, format!("{pattern}\n"))?;
    }

    Ok(())
}

fn check_lfs_installed() -> Result<()> {
    let output = std::process::Command::new("git")
        .args(["lfs", "version"])
        .output();
    match output {
        Ok(o) if o.status.success() => Ok(()),
        _ => bail!(
            "git-lfs is not installed. Install it first:\n  \
             brew install git-lfs   # macOS\n  \
             apt install git-lfs    # Debian/Ubuntu"
        ),
    }
}

fn install_lfs_local(repo_path: &Path) -> Result<()> {
    let marker = repo_path.join(".git").join("lfs");
    if marker.exists() {
        return Ok(());
    }
    let status = std::process::Command::new("git")
        .args(["lfs", "install", "--local"])
        .current_dir(repo_path)
        .status()
        .context("git lfs install failed")?;
    if !status.success() {
        bail!("git lfs install --local failed");
    }
    Ok(())
}