eclaw 0.0.18

Personal AI assistant powered by Elph
use anyhow::Result;
use elph_agent::{InitProgress, ensure_dirs, try_block_on};
use elph_core::{BundledManifest, TrustStore, VersionFile};

use super::paths::Paths;

const INIT_STEPS: u64 = 3;
const APP_ID: &str = "eclaw";

/// Scaffold required directories and default files for a fresh Eclaw home.
pub async fn ensure(app_version: &str) -> Result<Paths> {
    let progress = InitProgress::new(INIT_STEPS).with_quiet_env("ECLAW_QUIET");
    progress.advance("Resolving home directories");
    let paths = Paths::resolve()?;
    run_init_steps(&paths, app_version, &progress).await?;
    progress.finish();
    Ok(paths)
}

/// Scaffold a specific home directory tree (useful in tests and custom setups).
#[allow(dead_code)]
pub async fn ensure_with_paths(paths: &Paths, app_version: &str) -> Result<()> {
    let progress = InitProgress::new(INIT_STEPS).with_quiet_env("ECLAW_QUIET");
    run_init_steps(paths, app_version, &progress).await?;
    progress.finish();
    Ok(())
}

/// Blocking wrapper for home initialization (dirs + config, no databases).
pub fn ensure_home_blocking(app_version: &str) -> Result<Paths> {
    try_block_on(ensure(app_version))?
}

async fn run_init_steps(paths: &Paths, app_version: &str, progress: &InitProgress) -> Result<()> {
    progress.advance("Creating directories");
    ensure_home_dirs(paths)?;

    progress.advance("Writing configuration");
    ensure_files(paths, app_version)?;

    Ok(())
}

fn ensure_home_dirs(paths: &Paths) -> Result<()> {
    ensure_dirs(&paths.required_dirs())
}

fn ensure_files(paths: &Paths, app_version: &str) -> Result<()> {
    super::settings::Settings::ensure(paths)?;
    TrustStore::ensure(paths)?;
    VersionFile::ensure(paths, app_version)?;
    BundledManifest::ensure(paths, APP_ID, app_version)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use elph_core::utils::path::AppPaths;

    #[tokio::test]
    async fn ensure_creates_eclaw_home() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let paths = Paths::from_dirs(tmp.path().join("config"), tmp.path().join("data"));

        ensure_with_paths(&paths, "0.0.10-test").await.expect("ensure home");

        assert!(paths.settings_path().exists());
        assert!(paths.trust_path().exists());
        assert!(paths.version_path().exists());
        assert!(paths.bundled_manifest_path().exists());

        crate::runtime::datastore::ensure(&paths)
            .await
            .expect("ensure datastore");
        assert!(paths.metadata_db_path().exists());
        assert!(paths.memory_db_path().exists());
        assert!(paths.bundled_dir().join("agents").is_dir());
        assert!(paths.bundled_dir().join("personas").is_dir());
        assert!(paths.bundled_dir().join("skills").is_dir());
        assert!(paths.bundled_dir().join("user-guide").is_dir());
        assert!(paths.prompts_dir().is_dir());
        assert!(paths.providers_dir().is_dir());
        assert!(paths.sessions_dir().is_dir());
        assert!(paths.skills_dir().is_dir());
        assert!(paths.worktrees_dir().is_dir());
        assert!(paths.attachments_dir().is_dir());
        assert!(paths.downloads_dir().is_dir());
        assert!(paths.logs_dir().is_dir());
        assert!(paths.vendor_dir().is_dir());
    }
}